From 99a6b2fbd2c14899c0e587af74a1a544a158793d Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:10:17 -0400 Subject: [PATCH 01/78] Degen mode analytics, copy adjustment (#5979) --- src/__swaps__/screens/Swap/hooks/useSwapSettings.ts | 7 ++++++- .../screens/Swap/providers/swap-provider.tsx | 11 +++++++---- src/analytics/event.ts | 5 +++-- src/languages/en_US.json | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/__swaps__/screens/Swap/hooks/useSwapSettings.ts b/src/__swaps__/screens/Swap/hooks/useSwapSettings.ts index a5bcb03b7ec..8c03a4facbf 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapSettings.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapSettings.ts @@ -1,4 +1,5 @@ import { slippageStep } from '@/__swaps__/screens/Swap/constants'; +import { analyticsV2 } from '@/analytics'; import { swapsStore } from '@/state/swaps/swapsStore'; import { runOnJS, SharedValue, useSharedValue } from 'react-native-reanimated'; @@ -8,7 +9,11 @@ export const useSwapSettings = ({ debouncedFetchQuote, slippage }: { debouncedFe const setSlippage = swapsStore(state => state.setSlippage); const setFlashbots = swapsStore(state => state.setFlashbots); - const setDegenMode = swapsStore(state => state.setDegenMode); + + const setDegenMode = (value: boolean) => { + swapsStore.getState().setDegenMode(value); + analyticsV2.track(analyticsV2.event.swapsToggledDegenMode, { enabled: value }); + }; const onToggleFlashbots = () => { 'worklet'; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 11942053ebf..22569e89d96 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -202,6 +202,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const connectedToHardhat = !!providerUrl && isHardHat(providerUrl); const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; + const isDegenModeEnabled = swapsStore.getState().degenMode; const slippage = swapsStore.getState().slippage; const selectedGas = getSelectedGas(parameters.chainId); @@ -216,7 +217,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { screen: Screens.SWAPS, operation: TimeToSignOperation.KeychainRead, metadata: { - degenMode: swapsStore.getState().degenMode, + degenMode: isDegenModeEnabled, }, })({ address: parameters.quote.from, @@ -226,7 +227,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { screen: Screens.SWAPS, operation: TimeToSignOperation.Authentication, metadata: { - degenMode: swapsStore.getState().degenMode, + degenMode: isDegenModeEnabled, }, }, }); @@ -261,7 +262,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { screen: Screens.SWAPS, operation: TimeToSignOperation.SignTransaction, metadata: { - degenMode: swapsStore.getState().degenMode, + degenMode: isDegenModeEnabled, }, })(wallet, type, { ...parameters, @@ -286,6 +287,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { errorMessage, inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + degenMode: isDegenModeEnabled, }); if (errorMessage !== 'handled') { @@ -324,7 +326,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { operation: TimeToSignOperation.SheetDismissal, endOfOperation: true, metadata: { - degenMode: swapsStore.getState().degenMode, + degenMode: isDegenModeEnabled, }, })(Routes.PROFILE_SCREEN, {}); @@ -338,6 +340,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { bridge: isBridge, inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + degenMode: isDegenModeEnabled, }); } catch (error) { isSwapping.value = false; diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 5b3183eab96..7c8a797b545 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -139,7 +139,7 @@ export const event = { swapsSearchedForToken: 'swaps.searched_for_token', swapsChangedChainId: 'swaps.changed_chain_id', swapsFlippedAssets: 'swaps.flipped_assets', - swapsToggledFlashbots: 'swaps.toggled_flashbots', + swapsToggledDegenMode: 'swaps.toggled_degen_mode', swapsReceivedQuote: 'swaps.received_quote', swapsSubmitted: 'swaps.submitted', swapsFailed: 'swaps.failed', @@ -162,6 +162,7 @@ type SwapEventParameters = { selectedGas: GasSettings; selectedGasSpeed: GasSpeed; slippage: string; + degenMode: boolean; }; type SwapsEventFailedParameters = { @@ -542,7 +543,7 @@ export type EventProperties = { previousOutputAsset: ParsedSearchAsset | ExtendedAnimatedAssetWithColors | null; }; - [event.swapsToggledFlashbots]: { + [event.swapsToggledDegenMode]: { enabled: boolean; }; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index c65a3427fc5..39be923db06 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Auto", "degen_mode": "Degen Mode", - "degen_mode_description": "Skip the review sheet to swap faster", + "degen_mode_description": "Skip the review step to swap faster", "maximum_sold": "Maximum Sold", "minimum_received": "Minimum Received", "preferred_network": "Preferred Network", From a38f633809a7fd1533349174d33db5c8dcf9ede3 Mon Sep 17 00:00:00 2001 From: brdy <41711440+BrodyHughes@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:32:38 -0500 Subject: [PATCH 02/78] Brody/swap v2 e2e (#5915) * init * test file * clean up * matthew review fixes * lint * oops!!! * type casting * rename * . * lint * oops hardhat stuff removed * reverts * simplify hardhat check * Update src/__swaps__/screens/Swap/components/FadeMask.tsx Co-authored-by: Matthew Wall * Apply suggestions from code review IS_TESTING > IS_TEST Co-authored-by: Matthew Wall * fix lint * okay actually fix lint --------- Co-authored-by: Matthew Wall --- e2e/3_homeScreen.spec.ts | 14 +- e2e/9_swaps.spec.ts | 143 ++++++++++++++++++ e2e/helpers.ts | 13 +- package.json | 7 +- .../screens/Swap/components/CoinRow.tsx | 6 +- .../Swap/components/EstimatedSwapGasFee.tsx | 3 +- .../screens/Swap/components/FadeMask.tsx | 42 +++-- .../Swap/components/SwapActionButton.tsx | 5 +- .../Swap/components/SwapBackground.tsx | 11 +- .../Swap/components/SwapInputAsset.tsx | 11 +- .../components/TokenList/TokenToBuyList.tsx | 5 + src/__swaps__/screens/Swap/constants.ts | 31 ++-- .../screens/Swap/hooks/useAssetsToSell.ts | 8 +- .../Swap/hooks/useSwapInputsController.ts | 24 ++- src/__swaps__/types/chains.ts | 4 + src/components/activity-list/ActivityList.js | 1 + src/components/animations/animationConfigs.ts | 46 +++--- .../FastComponents/FastBalanceCoinRow.tsx | 2 +- src/config/experimental.ts | 2 +- src/references/chain-assets.json | 4 +- src/references/index.ts | 29 ++-- src/state/assets/userAssets.ts | 8 +- src/state/sync/UserAssetsSync.tsx | 7 +- 23 files changed, 326 insertions(+), 100 deletions(-) create mode 100644 e2e/9_swaps.spec.ts diff --git a/e2e/3_homeScreen.spec.ts b/e2e/3_homeScreen.spec.ts index c090519ded0..94fea2546f0 100644 --- a/e2e/3_homeScreen.spec.ts +++ b/e2e/3_homeScreen.spec.ts @@ -5,8 +5,9 @@ import { checkIfExists, checkIfExistsByText, swipe, - waitAndTap, afterAllcleanApp, + tap, + delayTime, } from './helpers'; const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth'; @@ -41,19 +42,20 @@ describe('Home Screen', () => { }); it('tapping "Swap" opens the swap screen', async () => { - await waitAndTap('swap-button'); + await tap('swap-button'); + await delayTime('long'); await checkIfExists('swap-screen'); - await swipe('swap-screen', 'down', 'slow'); + await swipe('swap-screen', 'down', 'fast'); }); it('tapping "Send" opens the send screen', async () => { - await waitAndTap('send-button'); + await tap('send-button'); await checkIfVisible('send-asset-form-field'); - await swipe('send-asset-form-field', 'down'); + await swipe('send-asset-form-field', 'down', 'fast'); }); it('tapping "Copy" shows copy address toast', async () => { - await waitAndTap('receive-button'); + await tap('receive-button'); await checkIfVisible('address-copied-toast'); }); }); diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts new file mode 100644 index 00000000000..9755763f4c8 --- /dev/null +++ b/e2e/9_swaps.spec.ts @@ -0,0 +1,143 @@ +/* + * // Other tests to consider: + * - Flip assets + * - exchange button onPress + * - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged + * - swap execution + * - token search (both from userAssets and output token list) + * - custom gas panel + * - flashbots + * - slippage + * - explainer sheets + * - switching wallets inside of swap screen + */ + +import { + importWalletFlow, + sendETHtoTestWallet, + checkIfVisible, + beforeAllcleanApp, + afterAllcleanApp, + fetchElementAttributes, + tap, + tapByText, + delayTime, +} from './helpers'; + +import { expect } from '@jest/globals'; +import { WALLET_VARS } from './testVariables'; + +describe('Swap Sheet Interaction Flow', () => { + beforeAll(async () => { + await beforeAllcleanApp({ hardhat: true }); + }); + afterAll(async () => { + await afterAllcleanApp({ hardhat: true }); + }); + + it('Import a wallet and go to welcome', async () => { + await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK); + }); + + it('Should send ETH to test wallet', async () => { + // send 20 eth + await sendETHtoTestWallet(); + }); + + it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => { + await tap('dev-button-hardhat'); + await checkIfVisible('testnet-toast-Hardhat'); + + // validate it has the expected funds of 20 eth + const attributes = await fetchElementAttributes('fast-coin-info'); + expect(attributes.label).toContain('Ethereum'); + expect(attributes.label).toContain('20'); + }); + + it('Should open swap screen with 50% inputAmount for inputAsset', async () => { + await device.disableSynchronization(); + await tap('swap-button'); + await delayTime('long'); + await tap('token-to-buy-dai-1'); + const swapInput = await fetchElementAttributes('swap-asset-input'); + + // expect inputAsset === .5 * eth balance + expect(swapInput.label).toContain('ETH'); + expect(swapInput.label).toContain('10'); + }); + + it('Should be able to go to review and execute a swap', async () => { + await tapByText('Review'); + const reviewActionElements = await fetchElementAttributes('swap-action-button'); + expect(reviewActionElements.elements[0].label).toContain('ETH'); + expect(reviewActionElements.elements[1].label).toContain('DAI'); + expect(reviewActionElements.elements[2].label).toContain('Tap to Swap'); + + /* + * + * Everything from this point fails. Un-comment out the following line to see behavior. + * Currently some spots have chainId 1 and chainId 1337 for various things. I suspect + * there is some issue with one of these things. Log the variables in getNonceAndPerformSwap + * to see the behavior. + * + * To run this test: + * + * yarn clean:ios && yarn fast && yarn start:clean + * yarn detox:ios:build && yarn detox test -c ios.sim.debug 8_swaps.spec.ts + * + */ + + // await tapByText('Tap to Swap'); + }); + + it.skip('Should be able to verify swap is happening', async () => { + // await delayTime('very-long'); + // const activityListElements = await fetchElementAttributes('wallet-activity-list'); + // expect(activityListElements.label).toContain('ETH'); + // expect(activityListElements.label).toContain('DAI'); + // await tapByText('Swapping'); + // await delayTime('long'); + // const transactionSheet = await checkIfVisible('transaction-details-sheet'); + // expect(transactionSheet).toBeTruthy(); + }); + + it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should open swap screen from asset chart with that asset selected', async () => { + /** + * tap any user asset (store const uniqueId here) + * wait for Swap header to be visible + * expect inputAsset.uniqueId === const uniqueId ^^ + */ + }); + + it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should not be able to type in output amount if cross-chain quote', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * select different chain in output list chain selector + * select any asset in output token list + * focus output amount + * attempt to type any number in the SwapNumberPad + * attempt to remove a character as well + * + * ^^ expect both of those to not change the outputAmount + */ + }); +}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 55344f6052c..8daa9e7d5fa 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -4,8 +4,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { expect, device, element, by, waitFor } from 'detox'; import { parseEther } from '@ethersproject/units'; +import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox'; -const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608'; +const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F'; const DEFAULT_TIMEOUT = 20_000; const android = device.getPlatform() === 'android'; @@ -70,6 +71,16 @@ export async function tap(elementId: string | RegExp) { } } +interface CustomElementAttributes { + elements: Array; +} + +type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes; + +export const fetchElementAttributes = async (testId: string): Promise => { + return (await element(by.id(testId)).getAttributes()) as ElementAttributes; +}; + export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) { await delayTime('medium'); try { diff --git a/package.json b/package.json index 6cf1f8c2b24..87776938218 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,10 @@ "nuke": "./scripts/nuke.sh", "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", - "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1", - "detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests", - "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1", + "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", + "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", + "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", + "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1", "ds:install": "cd src/design-system/docs && yarn install", "ds": "cd src/design-system/docs && yarn dev", "fast": "yarn install && yarn setup && yarn install-pods-fast", diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 9d619ef0b96..a8a4126de90 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -52,6 +52,7 @@ interface InputCoinRowProps { onPress: (asset: ParsedSearchAsset | null) => void; output?: false | undefined; uniqueId: string; + testID?: string; } type PartialAsset = Pick; @@ -62,11 +63,12 @@ interface OutputCoinRowProps extends PartialAsset { output: true; nativePriceChange?: string; isTrending?: boolean; + testID?: string; } type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { +export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -116,7 +118,7 @@ export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniq if (!address || !chainId) return null; return ( - + diff --git a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx index 5f510346f37..9a0de33268e 100644 --- a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx +++ b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx @@ -9,6 +9,7 @@ import { pulsingConfig, sliderConfig } from '../constants'; import { GasSettings } from '../hooks/useCustomGas'; import { useSwapEstimatedGasFee } from '../hooks/useEstimatedGasFee'; import { useSwapContext } from '../providers/swap-provider'; +import { SpringConfig } from 'react-native-reanimated/lib/typescript/animation/springUtils'; type EstimatedSwapGasFeeProps = { gasSettings?: GasSettings } & Partial< Pick @@ -48,7 +49,7 @@ const GasFeeText = memo(function GasFeeText({ color: withTiming(isLoading.value ? zeroAmountColor : textColor, TIMING_CONFIGS.slowFadeConfig), opacity: isLoading.value ? withRepeat(withSequence(withTiming(0.5, pulsingConfig), withTiming(1, pulsingConfig)), -1, true) - : withSpring(1, sliderConfig), + : withSpring(1, sliderConfig as SpringConfig), })); return ( diff --git a/src/__swaps__/screens/Swap/components/FadeMask.tsx b/src/__swaps__/screens/Swap/components/FadeMask.tsx index 7c2928c0435..3d90924f9af 100644 --- a/src/__swaps__/screens/Swap/components/FadeMask.tsx +++ b/src/__swaps__/screens/Swap/components/FadeMask.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { Box, Columns, Column, globalColors } from '@/design-system'; import LinearGradient from 'react-native-linear-gradient'; +import { IS_TEST } from '@/env'; +import { View } from 'react-native'; export const FadeMask = ({ fadeEdgeInset = 6, @@ -22,14 +24,18 @@ export const FadeMask = ({ - + {IS_TEST ? ( + + ) : ( + + )} ) : null} @@ -39,14 +45,18 @@ export const FadeMask = ({ {!side || side === 'right' ? ( <> - + {IS_TEST ? ( + + ) : ( + + )} diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index e407de019ab..a35c9db0591 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -34,6 +34,7 @@ function SwapButton({ disabled, opacity, children, + testID, }: { asset: DerivedValue; borderRadius?: number; @@ -47,6 +48,7 @@ function SwapButton({ disabled?: DerivedValue; opacity?: DerivedValue; children?: React.ReactNode; + testID?: string; }) { const { isDarkMode } = useColorMode(); const fallbackColor = useForegroundColor('label'); @@ -110,6 +112,7 @@ function SwapButton({ return ( {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {holdProgress && } diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 8fc56cf4f0e..594b33f61bc 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -1,11 +1,11 @@ import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia'; import React from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { useDerivedValue, withTiming } from 'react-native-reanimated'; import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useColorMode } from '@/design-system'; -import { IS_ANDROID } from '@/env'; +import { IS_ANDROID, IS_TEST } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swaps__/utils/swaps'; import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -18,12 +18,15 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig ); }); + const animatedBottomColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig @@ -34,6 +37,10 @@ export const SwapBackground = () => { return [animatedTopColor.value, animatedBottomColor.value]; }); + if (IS_TEST) { + return ; + } + return ( diff --git a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx index 915e657472c..5d5a40a51f2 100644 --- a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx @@ -72,7 +72,14 @@ function SwapInputAmount() { }} > } style={styles.inputTextMask}> - + {SwapInputController.formattedInputAmount} @@ -123,7 +130,7 @@ export function SwapInputAsset() { return ( - + diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index 589e4d082c1..8efda04120d 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -130,6 +130,10 @@ export const TokenToBuyList = () => { if (isLoading) return null; + const getFormattedTestId = (name: string, chainId: ChainId) => { + return `token-to-buy-${name}-${chainId}`.toLowerCase().replace(/\s+/g, '-'); + }; + return ( { } return ( { + if (!IS_TEST) return config; + return { ...config, duration: 0, damping: 0 }; +}; + +export const buttonPressConfig = disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }); +export const caretConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }); +export const fadeConfig = disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const pulsingConfig = disableForTestingEnvironment({ duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }); +export const sliderConfig = disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }); +export const slowFadeConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const snappySpringConfig = disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }); +export const snappierSpringConfig = disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }); +export const springConfig = disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }); // // /---- END animation configs ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts index 13f6d54e02d..d5c6e57f2f8 100644 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts @@ -10,6 +10,7 @@ import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; import { useAccountSettings, useDebounce } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { getCachedProviderForNetwork, isHardHat } from '@/handlers/web3'; const sortBy = (by: UserAssetFilter) => { switch (by) { @@ -21,7 +22,11 @@ const sortBy = (by: UserAssetFilter) => { }; export const useAssetsToSell = () => { - const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); + const { accountAddress: currentAddress, nativeCurrency: currentCurrency, network: currentNetwork } = useAccountSettings(); + + const provider = getCachedProviderForNetwork(currentNetwork); + const providerUrl = provider?.connection?.url ?? ''; + const connectedToHardhat = isHardHat(providerUrl); const filter = userAssetsStore(state => state.filter); const searchQuery = userAssetsStore(state => state.inputSearchQuery); @@ -32,6 +37,7 @@ export const useAssetsToSell = () => { { address: currentAddress as Address, currency: currentCurrency, + testnetMode: connectedToHardhat, }, { select: data => diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 266caea1063..1f86c00061f 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,5 +1,14 @@ import { useCallback } from 'react'; -import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; +import { + SharedValue, + WithSpringConfig, + runOnJS, + runOnUI, + useAnimatedReaction, + useDerivedValue, + useSharedValue, + withSpring, +} from 'react-native-reanimated'; import { useDebouncedCallback } from 'use-debounce'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; @@ -314,13 +323,13 @@ export function useSwapInputsController({ // Handle updating the slider position if the quote was output based if (originalQuoteParams.lastTypedInput === 'outputAmount' || originalQuoteParams.lastTypedInput === 'outputNativeValue') { if (!inputAmount || inputAmount === 0) { - sliderXPosition.value = withSpring(0, snappySpringConfig); + sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); } else { const inputBalance = internalSelectedInputAsset.value?.maxSwappableAmount || '0'; const updatedSliderPosition = greaterThanWorklet(inputBalance, 0) ? clamp(Number(divWorklet(inputAmount, inputBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH) : 0; - sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); + sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig as WithSpringConfig); } } @@ -595,7 +604,7 @@ export function useSwapInputsController({ [inputKey]: hasDecimal ? inputKeyValue : 0, })); - if (updateSlider) sliderXPosition.value = withSpring(0, snappySpringConfig); + if (updateSlider) sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); }; const debouncedFetchQuote = useDebouncedCallback( @@ -651,7 +660,7 @@ export function useSwapInputsController({ } if (didInputAssetChange) { - sliderXPosition.value = withSpring(SLIDER_WIDTH / 2, snappySpringConfig); + sliderXPosition.value = withSpring(SLIDER_WIDTH / 2, snappySpringConfig as WithSpringConfig); } const { inputAmount, inputNativeValue } = getInputValuesForSliderPositionWorklet({ @@ -817,14 +826,14 @@ export function useSwapInputsController({ const inputAssetBalance = internalSelectedInputAsset.value?.maxSwappableAmount || '0'; if (equalWorklet(inputAssetBalance, 0)) { - sliderXPosition.value = withSpring(0, snappySpringConfig); + sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); } else { const updatedSliderPosition = clamp( Number(divWorklet(current.values.inputAmount, inputAssetBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH ); - sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); + sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig as WithSpringConfig); } runOnJS(debouncedFetchQuote)(); @@ -855,7 +864,6 @@ export function useSwapInputsController({ } } ); - return { debouncedFetchQuote, formattedInputAmount, diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts index c61c29adbdd..feaea2a5c11 100644 --- a/src/__swaps__/types/chains.ts +++ b/src/__swaps__/types/chains.ts @@ -100,6 +100,7 @@ export enum ChainName { celo = 'celo', degen = 'degen', gnosis = 'gnosis', + goerli = 'goerli', linea = 'linea', manta = 'manta', optimism = 'optimism', @@ -169,6 +170,7 @@ export const chainNameToIdMapping: { [ChainName.celo]: ChainId.celo, [ChainName.degen]: ChainId.degen, [ChainName.gnosis]: ChainId.gnosis, + [ChainName.goerli]: chain.goerli.id, [ChainName.linea]: ChainId.linea, [ChainName.manta]: ChainId.manta, [ChainName.optimism]: ChainId.optimism, @@ -208,6 +210,7 @@ export const chainIdToNameMapping: { [ChainId.celo]: ChainName.celo, [ChainId.degen]: ChainName.degen, [ChainId.gnosis]: ChainName.gnosis, + [chain.goerli.id]: ChainName.goerli, [ChainId.linea]: ChainName.linea, [ChainId.manta]: ChainName.manta, [ChainId.optimism]: ChainName.optimism, @@ -249,6 +252,7 @@ export const ChainNameDisplay = { [ChainId.scroll]: chain.scroll.name, [ChainId.zora]: 'Zora', [ChainId.mainnet]: 'Ethereum', + [chain.goerli.id]: 'Goerli', [ChainId.hardhat]: 'Hardhat', [ChainId.hardhatOptimism]: chainHardhatOptimism.name, [ChainId.sepolia]: chain.sepolia.name, diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index 4367a80af1a..98df923fbd2 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -125,6 +125,7 @@ const ActivityList = ({ nativeCurrency, pendingTransactionsCount, }} + testID={'wallet-activity-list'} getItemLayout={getItemLayout} initialNumToRender={12} keyExtractor={keyExtractor} diff --git a/src/components/animations/animationConfigs.ts b/src/components/animations/animationConfigs.ts index b3505c762d9..b91ea55120f 100644 --- a/src/components/animations/animationConfigs.ts +++ b/src/components/animations/animationConfigs.ts @@ -1,40 +1,50 @@ +import { IS_TEST } from '@/env'; import { Easing, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'; function createSpringConfigs>(configs: T): T { return configs; } + function createTimingConfigs>(configs: T): T { return configs; } +// Wrapper function for spring configs +const disableSpringForTesting = (config: WithSpringConfig): WithSpringConfig => { + if (!IS_TEST) return config; + return { ...config, damping: undefined, stiffness: undefined, duration: undefined }; +}; + +// Wrapper function for timing configs +const disableTimingForTesting = (config: WithTimingConfig): WithTimingConfig => { + if (!IS_TEST) return config; + return { ...config, duration: 0 }; +}; + // /---- 🍎 Spring Animations 🍎 ----/ // -// const springAnimations = createSpringConfigs({ - browserTabTransition: { dampingRatio: 0.82, duration: 800 }, - keyboardConfig: { damping: 500, mass: 3, stiffness: 1000 }, - sliderConfig: { damping: 40, mass: 1.25, stiffness: 450 }, - slowSpring: { damping: 500, mass: 3, stiffness: 800 }, - snappierSpringConfig: { damping: 42, mass: 0.8, stiffness: 800 }, - snappySpringConfig: { damping: 100, mass: 0.8, stiffness: 275 }, - springConfig: { damping: 100, mass: 1.2, stiffness: 750 }, + browserTabTransition: disableSpringForTesting({ dampingRatio: 0.82, duration: 800 }), + keyboardConfig: disableSpringForTesting({ damping: 500, mass: 3, stiffness: 1000 }), + sliderConfig: disableSpringForTesting({ damping: 40, mass: 1.25, stiffness: 450 }), + slowSpring: disableSpringForTesting({ damping: 500, mass: 3, stiffness: 800 }), + snappierSpringConfig: disableSpringForTesting({ damping: 42, mass: 0.8, stiffness: 800 }), + snappySpringConfig: disableSpringForTesting({ damping: 100, mass: 0.8, stiffness: 275 }), + springConfig: disableSpringForTesting({ damping: 100, mass: 1.2, stiffness: 750 }), }); export const SPRING_CONFIGS: Record = springAnimations; -// // /---- END ----/ // // /---- ⏱️ Timing Animations ⏱️ ----/ // -// const timingAnimations = createTimingConfigs({ - buttonPressConfig: { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }, - fadeConfig: { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - fastFadeConfig: { duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowFadeConfig: { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowerFadeConfig: { duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowestFadeConfig: { duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - tabPressConfig: { duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + buttonPressConfig: disableTimingForTesting({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), + fadeConfig: disableTimingForTesting({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + fastFadeConfig: disableTimingForTesting({ duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowFadeConfig: disableTimingForTesting({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowerFadeConfig: disableTimingForTesting({ duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowestFadeConfig: disableTimingForTesting({ duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + tabPressConfig: disableTimingForTesting({ duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }), }); export const TIMING_CONFIGS: Record = timingAnimations; -// // /---- END ----/ // diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 942fc40a041..08d6b42844e 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -91,7 +91,7 @@ const MemoizedBalanceCoinRow = React.memo( const valueColor = nativeDisplay ? theme.colors.dark : theme.colors.blueGreyLight; return ( - + diff --git a/src/config/experimental.ts b/src/config/experimental.ts index 77c87bd58a4..c9d422f38c1 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -61,7 +61,7 @@ export const defaultConfig: Record = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: false }, + [SWAPS_V2]: { settings: true, value: !!IS_TEST }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, }; diff --git a/src/references/chain-assets.json b/src/references/chain-assets.json index 44548600043..8e14b5ac633 100644 --- a/src/references/chain-assets.json +++ b/src/references/chain-assets.json @@ -9,7 +9,7 @@ }, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Ether", + "name": "Goerli", "network": "goerli", "price": { "changed_at": 1582568575, @@ -31,7 +31,7 @@ }, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Ether", + "name": "Ethereum", "network": "mainnet", "price": { "changed_at": 1582568575, diff --git a/src/references/index.ts b/src/references/index.ts index b4b7cc6686a..a692c06120e 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -229,21 +229,10 @@ export const SUPPORTED_MAINNET_CHAINS: Chain[] = [mainnet, polygon, optimism, ar name: ChainNameDisplay[chain.id], })); -export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => - [ - // In default order of appearance - mainnet, - base, - optimism, - arbitrum, - polygon, - zora, - blast, - degen, - avalanche, - bsc, +export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => { + const mainnetChains: Chain[] = [mainnet, base, optimism, arbitrum, polygon, zora, blast, degen, avalanche, bsc]; - // Testnets + const testnetChains: Chain[] = [ goerli, holesky, sepolia, @@ -255,12 +244,12 @@ export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolea zoraSepolia, avalancheFuji, bscTestnet, - ].reduce((chainList, chain) => { - if (testnetMode || !chain.testnet) { - chainList.push({ ...chain, name: ChainNameDisplay[chain.id] }); - } - return chainList; - }, [] as Chain[]); + ]; + + const allChains = mainnetChains.concat(testnetMode ? testnetChains : []); + + return allChains.map(chain => ({ ...chain, name: ChainNameDisplay[chain.id] ?? chain.name })); +}; export const SUPPORTED_CHAIN_IDS = ({ testnetMode = false }: { testnetMode?: boolean }): ChainId[] => SUPPORTED_CHAINS({ testnetMode }).map(chain => chain.id); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index ecb5330ed39..3c150c4e6a3 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,10 +1,13 @@ +import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; +import { getIsHardhatConnected } from '@/handlers/web3'; +import { ethereumUtils } from '@/utils'; +import { NetworkTypes } from '@/helpers'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; import { SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; -import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; import { swapsStore } from '../swaps/swapsStore'; const SEARCH_CACHE_MAX_ENTRIES = 50; @@ -179,7 +182,6 @@ export const userAssetsStore = createRainbowStore( return filteredIds; } }, - getHighestValueAsset: (usePreferredNetwork = true) => { const preferredNetwork = usePreferredNetwork ? swapsStore.getState().preferredNetwork : undefined; const assets = get().userAssets; diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 636b937c0d0..4589f941881 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -1,4 +1,3 @@ -import { memo } from 'react'; import { Address } from 'viem'; import { useAccountSettings } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; @@ -6,9 +5,10 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; -export const UserAssetsSync = memo(function UserAssetsSync() { +export const UserAssetsSync = function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const userAssetsWalletAddress = userAssetsStore(state => state.associatedWalletAddress); @@ -18,6 +18,7 @@ export const UserAssetsSync = memo(function UserAssetsSync() { { address: currentAddress as Address, currency: currentCurrency, + testnetMode: getIsHardhatConnected(), }, { enabled: !isSwapsOpen || userAssetsWalletAddress !== currentAddress, @@ -41,4 +42,4 @@ export const UserAssetsSync = memo(function UserAssetsSync() { ); return null; -}); +}; From 04c3ffd26dde1b475fda8958b0f441e13a53e2fd Mon Sep 17 00:00:00 2001 From: brdy <41711440+BrodyHughes@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:02:51 -0500 Subject: [PATCH 03/78] Revert CI script changes (#5961) * init * oop * update logging * test * undo * Revert "rerun only failed tests (#5878)" This reverts commit 497d27aed6148b5fb8f6d29121df79f277ad1509. --- scripts/run-retry-tests.sh | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/scripts/run-retry-tests.sh b/scripts/run-retry-tests.sh index 76fa3380926..b3584fbe9cd 100755 --- a/scripts/run-retry-tests.sh +++ b/scripts/run-retry-tests.sh @@ -2,39 +2,17 @@ max_retries="$1" count=0 -failed_tests="" - -run_tests() { - if [ -z "$failed_tests" ]; then - ./node_modules/.bin/detox test -c ios.sim.release --maxWorkers 2 -- --forceExit --bail 1 - else - ./node_modules/.bin/detox test -c ios.sim.release --maxWorkers 2 -f "$failed_tests" -- --forceExit --bail 1 - fi -} until (( count >= max_retries )) do - if [ $count -eq 0 ]; then - run_tests | tee output.log - else - run_tests - fi - - ret_val=$? - if [ $ret_val -eq 0 ]; then - echo "All tests passed." - exit 0 - fi - - if [ $count -eq 0 ]; then - failed_tests=$(grep -oP '(?<=at )[^ ]+\.spec\.ts(?=:)' output.log | sort -u | tr '\n' '|' | sed 's/|$//') - rm output.log - fi - - ((count++)) - echo "Test failed, attempt $count/$max_retries..." - echo "Rerunning failed tests: $failed_tests" + ./node_modules/.bin/detox test -c ios.sim.release --maxWorkers 2 -- --forceExit --bail 1 + ret_val=$? + if [ $ret_val -eq 0 ]; then + exit 0 + fi + ((count++)) + echo "Test failed, attempt $count/$max_retries..." done echo "Tests failed after $max_retries attempts." -exit 1 \ No newline at end of file +exit 1 From 1877bc7bf753af2345b2df425b9c62dba67891c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:03:26 -0400 Subject: [PATCH 04/78] Bump fast-xml-parser from 4.4.0 to 4.4.1 (#5965) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 4.4.0 to 4.4.1. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v4.4.0...v4.4.1) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 97d09486309..d39d92e225c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13410,13 +13410,13 @@ __metadata: linkType: hard "fast-xml-parser@npm:^4.0.12, fast-xml-parser@npm:^4.2.4": - version: 4.4.0 - resolution: "fast-xml-parser@npm:4.4.0" + version: 4.4.1 + resolution: "fast-xml-parser@npm:4.4.1" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 10c0/ce32fad713471a40bea67959894168f297a5dd0aba64b89a2abc71a4fec0b1ae1d49c2dd8d8719ca8beeedf477824358c8a486b360b9f3ef12abc2e355d11318 + checksum: 10c0/7f334841fe41bfb0bf5d920904ccad09cefc4b5e61eaf4c225bf1e1bb69ee77ef2147d8942f783ee8249e154d1ca8a858e10bda78a5d78b8bed3f48dcee9bf33 languageName: node linkType: hard From 7e4d24667e7adc977cdc5dc7abd596c2a5735b0d Mon Sep 17 00:00:00 2001 From: Daniel Sinclair <4412473+DanielSinclair@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:03:37 -0400 Subject: [PATCH 05/78] chore: i18n updates (#5980) --- src/languages/ar_AR.json | 104 ++++++++++++++++++++++++++++++++++-- src/languages/es_419.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/fr_FR.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/hi_IN.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/id_ID.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/ja_JP.json | 104 ++++++++++++++++++++++++++++++++++-- src/languages/ko_KR.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/pt_BR.json | 108 +++++++++++++++++++++++++++++++++++--- src/languages/ru_RU.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/th_TH.json | 106 ++++++++++++++++++++++++++++++++++--- src/languages/tr_TR.json | 108 +++++++++++++++++++++++++++++++++++--- src/languages/zh_CN.json | 106 ++++++++++++++++++++++++++++++++++--- 12 files changed, 1200 insertions(+), 72 deletions(-) diff --git a/src/languages/ar_AR.json b/src/languages/ar_AR.json index d44b744c54b..529ecaacb6a 100644 --- a/src/languages/ar_AR.json +++ b/src/languages/ar_AR.json @@ -24,6 +24,7 @@ "tab_showcase": "عرض", "tab_points": "النقاط", "tab_positions": "المواقف", + "tab_rewards": "المكافآت", "tab_transactions": "المعاملات", "tab_transactions_tooltip": "المعاملات وتحويلات الرموز", "tab_uniquetokens": "الرموز المميزة", @@ -431,6 +432,23 @@ "description": "يمكنك أيضًا الضغط بطول على عنوان\nالموجود أعلاه لنسخه." } }, + "check_identifier": { + "title": "المصادقة مطلوبة", + "description": "يبدو أنك قد قمت بتبديل الأجهزة أو إعادة تثبيت Rainbow. يرجى المصادقة حتى نتمكن من التحقق من أن بيانات محفظتك لا تزال بحالة جيدة.", + "action": "مصادقة", + "dismiss": "رفض", + "error_alert": { + "title": "خطأ في التحقق من المحفظة", + "message": "واجهنا خطأ أثناء التحقق من بيانات محفظتك. يرجى المحاولة مرة أخرى. إذا استمرت المشكلة، يرجى الاتصال بالدعم.", + "contact_support": "اتصل بالدعم", + "cancel": "إلغاء" + }, + "failure_alert": { + "title": "بيانات المحفظة غير صالحة", + "message": "تعذر علينا التحقق من سلامة بيانات محفظتك. يرجى استعادة محفظتك والمحاولة مرة أخرى. إذا استمرت المشكلة، يرجى الاتصال بالدعم.", + "action": "استمر" + } + }, "cloud": { "backup_success": "تم نسخ محفظتك بنجاح!" }, @@ -541,7 +559,8 @@ "from_divider": "من", "to_divider": "إلى", "view_on": "اعرض على %{blockExplorerName}", - "view_on_etherscan": "عرض على Etherscan" + "view_on_etherscan": "عرض على Etherscan", + "copy_contract_address": "نسخ عنوان العقد" }, "all_networks": "جميع الشبكات", "filter_by_network": "الفلترة حسب الشبكة", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "انتظار طويل", - "description": "تقريبًا %{time}" + "description": "تقريبًا %{time}", + "description_prefix": "تقريباً." }, "unknown_price": { "title": "القيمة السوقية غير معروفة", @@ -587,6 +607,7 @@ "small_market": "سوق صغير", "small_market_try_smaller_amount": "سوق صغير - حاول مبلغ أصغر", "you_are_losing": "أنت تخسر %{priceImpact}", + "you_are_losing_prefix": "أنت تخسر", "no_data": "القيمة السوقية غير معروفة", "label": "خسارة محتملة", "no_data_subtitle": "إذا قررت المتابعة، تأكد من أنك راض بالمبلغ المعروض" @@ -687,6 +708,7 @@ "flashbots_protect": "حماية فلاشبوتس", "losing": "خاسر", "on": "على", + "off": "إيقاف", "price_impact": "تأثير السعر", "price_row_per_token": "لكل", "slippage_message": "هذا سوق صغير، لذا أنت تحصل على سعر سيء. حاول التداول بكمية أقل!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} لـ %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} لـ %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "أوتو", + "degen_mode": "وضع ديجين", + "degen_mode_description": "تجاوز ورقة المراجعة للتبديل بشكل أسرع", + "maximum_sold": "الحد الأقصى المباع", + "minimum_received": "الحد الأدنى المستلم", + "preferred_network": "الشبكة المفضلة", + "rainbow_fee": "تضمين رسوم الرينبو", + "settings": "الإعدادات" + }, "token_index": { "get_token": "احصل على %{assetSymbol}", "makeup_of_token": "مكونات 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "عاجل", "custom": "مخصص" }, + "unable_to_determine_selected_gas": "غير قادر على تحديد سعر الغاز المحدد", "network_fee": "رسوم الشبكة المقدرة", "current_base_fee": "الرسوم الأساسية الحالية", "max_base_fee": "أقصى رسوم أساسية", "miner_tip": "تلميح المعدن", + "gas_price": "سعر الغاز", "max_transaction_fee": "الحد الأقصى لرسوم المعاملة", "warning_separator": "·", "lower_than_suggested": "منخفض · قد يتعثر", "higher_than_suggested": "مرتفع · دفع زائد", + "likely_to_fail": "منخفض · من المحتمل أن يفشل", "max_base_fee_too_low_error": "منخفض · من المحتمل أن يفشل", "tip_too_low_error": "منخفض · من المحتمل أن يفشل", "alert_message_higher_miner_tip_needed": "يُنصَح بتعيين بدل أعلى للمعدن لتجنب المشكلات.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "احصل على نقاطك", + "title_rewards_line_1": "أهلا بك في", + "title_rewards_line_2": "مكافآت Rainbow", "subtitle": "النقاط متوفرة الآن. اكتشف كم نقطة حصلت عليها.", + "subtitle_rewards": "احصل على مكافآت لاستخدام Rainbow. افعل أشياء على السلسلة، احصل على النقاط، واكسب ETH.", "get_started": "ابدأ الآن", "use_referral_code": "استخدم كود الإحالة" }, "referral": { "title": "أدخل رمز الإحالة الخاص بك", - "subtitle": "أدخل كود الإحالة أدناه لتطالب بمكافأتك الإضافية.", + "subtitle": "أدخل رمز الإحالة أدناه للمطالبة بمكافأة النقاط الخاصة بك.", "back": "رجوع", "error": "حدث خطأ ما. الرجاء إعادة تشغيل التطبيق والمحاولة مرة أخرى.", "get_started": "ابدأ الآن", @@ -1378,6 +1416,7 @@ "share_link": "شارك الرابط", "next_drop": "الإسقاط التالي", "your_rank": "رتبتك", + "no_change": "لا تغيير", "of_x": "من %{totalUsers}", "referrals": "الإحالات", "streak": "سلسلة النجاح", @@ -1395,12 +1434,40 @@ "today": "اليوم", "error": "عذرًا!\nالرجاء السحب للتحديث.", "points": "النقاط", + "points_capitalized": "النقاط", "referral_code_copied": "تم نسخ رمز الإحالة", "earn_points_for_referring": "اربح نقاط عندما يقوم من تحيلهم بتبديل 100 دولار من خلال Rainbow، بالإضافة إلى كسب 10٪ من النقاط التي يكسبها من تحيلهم", "already_claimed_points": "عذرًا! لقد قمت بالفعل بالمطالبة بنقاطك.", "now": "الآن", "unranked": "غير مصنف", - "points_to_rank": "750 نقطة للتصنيف" + "points_to_rank": "750 نقطة للتصنيف", + "ranking": "وضع %{rank}", + "available_to_claim": "متاح للمطالبة", + "claim": "مطالبة %{value}", + "choose_claim_network": "اختر شبكة المطالبة", + "claim_on_network": "المطالبة على %{network}", + "claimed_on_network": "تمت المطالبة على %{network}", + "claiming_on_network": "المطالبة على %{network}", + "error_claiming": "فشل المطالبة", + "free_to_claim": "مجاني للمطالبة", + "has_bridge_fee": "لديه رسوم الجسر", + "claim_rewards": "المطالبة بالمكافآت", + "earned_last_week": "تم الكسب الأسبوع الماضي", + "top_10_earner": "أحد أفضل 10 الكاسبين", + "zero_points": "0 نقاط", + "no_weekly_rank": "لا يوجد ترتيب أسبوعي", + "none": "لا شيء", + "my_earnings": "أرباحي", + "claimed_earnings": "الأرباح المكتسبة", + "current_value": "القيمة الحالية", + "rainbow_users_have_earned": "لقد حصل مستخدمو Rainbow على", + "earn_eth_rewards": "اكسب مكافآت ETH", + "rewards_explainer": "كل أسبوع، سيحصل الكاسبين في أعلى النقاط الأسبوعية على جزء من عائدات Rainbow's على السلسلة. اجمع النقاط عن طريق التبديل أو التعامل على Rainbow، أو استخدام dapps، أو إحالة الأصدقاء.", + "my_points": "نقاطي", + "refer_friends": "إحالة الأصدقاء", + "try_again": "حاول مرة أخرى", + "bridge_error": "نجاح جزئي", + "bridge_error_explainer": "تم بنجاح المطالبة بمكافآتك على Optimism، ولكن فشل النقل إلى %{network}. إذا كنت ترغب، يمكنك جسر مكافآتك يدويًا باستخدام ميزة التبديل." }, "console": { "claim_bonus_paragraph": "سوف تحصل على 100 نقطة إضافية بمجرد استبدال ما قيمته 100 دولار على الأقل من خلال Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "طلبات التسجيل كثيرة جدا، يرجى المحاولة مرة أخرى لاحقا" }, "swap": { + "actions": { + "hold_to_swap": "اضغط مع الاستمرار للتبديل", + "save": "حفظ", + "enter_amount": "أدخل كمية", + "review": "مراجعة", + "fetching_prices": "جلب البيانات", + "swapping": "تبديل", + "select_token": "اختر رمزًا", + "insufficient_funds": "أموال غير كافية", + "insufficient": "غير كافٍ", + "quote_error": "خطأ في عرض السعر" + }, "aggregators": { "rainbow": "رينبو" }, + "bridging": "جسر", + "swapping": "تبديل", + "max": "الحد الأقصى", + "select": "اختر", + "token_to_get": "رمز للحصول عليه", + "token_to_swap": "رمز للتبديل", + "no_balance": "لا يوجد رصيد", + "find_a_token_to_buy": "البحث عن رمز للشراء", + "search_your_tokens": "ابحث عن رموزك", + "error_executing_swap": "خطأ في تنفيذ التبديل", + "flashbots_protection": "حماية Flashbots", "tokens_input": { "tokens": "رموز", "sort": "الترتيب", @@ -1945,6 +2035,7 @@ }, "loading": "جار التحميل...", "modal_types": { + "bridge": "جسر", "confirm": "تأكيد", "deposit": "إيداع", "receive": "استلم", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "الأخير", "favorites": "المفضلة", "bridge": "جسر", "verified": "تم التحقق منه", @@ -2849,6 +2941,8 @@ "forward": "إلى الأمام", "back": "رجوع" } - } + }, + "copy": "نسخ", + "paste": "لصق" } } diff --git a/src/languages/es_419.json b/src/languages/es_419.json index 16fa3b30f05..43216bd6eb9 100644 --- a/src/languages/es_419.json +++ b/src/languages/es_419.json @@ -24,6 +24,7 @@ "tab_showcase": "Escaparate", "tab_points": "Puntos", "tab_positions": "Posiciones", + "tab_rewards": "Recompensas", "tab_transactions": "Transacciones", "tab_transactions_tooltip": "Transacciones y Transferencias de Tokens", "tab_uniquetokens": "Tokens Únicos", @@ -431,6 +432,23 @@ "description": "También puedes mantener presionada tu dirección\nde arriba para copiarla." } }, + "check_identifier": { + "title": "Autenticación Necesaria", + "description": "Parece que has cambiado de dispositivo o reinstalado Rainbow. Por favor, autentícate para que podamos validar que los datos de tu llavero aún están en buen estado.", + "action": "Autenticar", + "dismiss": "Descartar", + "error_alert": { + "title": "Error al Validar el Llavero", + "message": "Encontramos un error al validar los datos de tu llavero. Por favor, intenta nuevamente. Si el problema persiste, contacta al soporte.", + "contact_support": "Contactar Soporte", + "cancel": "Cancelar" + }, + "failure_alert": { + "title": "Datos de Llavero Inválidos", + "message": "No pudimos verificar la integridad de los datos de tu llavero. Por favor, restaura tu monedero e intenta nuevamente. Si el problema persiste, contacta al soporte.", + "action": "Continuar" + } + }, "cloud": { "backup_success": "¡Tu billetera se ha respaldado exitosamente!" }, @@ -541,7 +559,8 @@ "from_divider": "desde", "to_divider": "a", "view_on": "Ver en %{blockExplorerName}", - "view_on_etherscan": "Ver en Etherscan" + "view_on_etherscan": "Ver en Etherscan", + "copy_contract_address": "Copiar dirección del contrato" }, "all_networks": "Todas las Redes", "filter_by_network": "Filtrar por red", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Larga espera", - "description": "Aprox. %{time}" + "description": "Aprox. %{time}", + "description_prefix": "Aprox." }, "unknown_price": { "title": "Valor de Mercado Desconocido", @@ -587,6 +607,7 @@ "small_market": "Mercado pequeño", "small_market_try_smaller_amount": "Mercado pequeño — prueba con una cantidad menor", "you_are_losing": "Estás perdiendo %{priceImpact}", + "you_are_losing_prefix": "Estás perdiendo", "no_data": "Valor de Mercado Desconocido", "label": "Posible pérdida", "no_data_subtitle": "Si decides continuar, asegúrate de estar satisfecho con la cantidad indicada." @@ -687,6 +708,7 @@ "flashbots_protect": "Protección de Flashbots", "losing": "Perdiendo", "on": "Encendido", + "off": "Desactivado", "price_impact": "Impacto de precio", "price_row_per_token": "por", "slippage_message": "Este es un mercado pequeño, por lo que estás obteniendo un mal precio. ¡Intenta hacer un intercambio más pequeño!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} para %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} para %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Automático", + "degen_mode": "Modo Degen", + "degen_mode_description": "Omitir la hoja de revisión para intercambiar más rápido", + "maximum_sold": "Máximo Vendido", + "minimum_received": "Mínimo Recibido", + "preferred_network": "Red preferida", + "rainbow_fee": "Tarifa Rainbow incluida", + "settings": "Configuraciones" + }, "token_index": { "get_token": "Obtener %{assetSymbol}", "makeup_of_token": "Composición de 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "Urgente", "custom": "Personalizado" }, - "network_fee": "Comisión estimada de red", + "unable_to_determine_selected_gas": "No se puede determinar el gas seleccionado", + "network_fee": "Tarifa Est. de Red", "current_base_fee": "Tarifa base actual", "max_base_fee": "Tarifa base máxima", "miner_tip": "Propina al minero", + "gas_price": "Precio del Gas", "max_transaction_fee": "Comisión máxima de transacción", "warning_separator": "·", "lower_than_suggested": "Baja · puede quedarse atascada", "higher_than_suggested": "Alta · pago excesivo", + "likely_to_fail": "Baja · probablemente fallará", "max_base_fee_too_low_error": "Baja · probablemente fallará", "tip_too_low_error": "Baja · probablemente fallará", "alert_message_higher_miner_tip_needed": "Se recomienda establecer una propina más alta para los mineros para evitar problemas.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Reclama tus puntos", + "title_rewards_line_1": "Bienvenido a", + "title_rewards_line_2": "Recompensas de Rainbow", "subtitle": "Los puntos están aquí. Descubre cuántos has recibido.", + "subtitle_rewards": "Obtén recompensas por usar Rainbow. Haz cosas en la cadena, gana puntos, gana ETH.", "get_started": "Comenzar", "use_referral_code": "Usar Código de Referencia" }, "referral": { "title": "Ingresa tu código de referido", - "subtitle": "Ingresa un código de referido abajo para reclamar tu recompensa adicional.", + "subtitle": "Ingresa un código de referencia abajo para reclamar tu bono de puntos.", "back": "Atrás", "error": "Algo salió mal. Por favor reinicia la app e intenta de nuevo.", "get_started": "Comenzar", @@ -1378,6 +1416,7 @@ "share_link": "Compartir enlace", "next_drop": "Próxima entrega", "your_rank": "Tu Rango", + "no_change": "Sin cambios", "of_x": "De %{totalUsers}", "referrals": "Referidos", "streak": "Racha", @@ -1395,12 +1434,40 @@ "today": "Hoy", "error": "¡Ups!\nPor favor, desliza para actualizar.", "points": "puntos", + "points_capitalized": "Puntos", "referral_code_copied": "Código de referencia copiado", "earn_points_for_referring": "Gana puntos cuando tus referidos intercambien $100 a través de Rainbow, además gana el 10% de los puntos que tus referidos ganan", "already_claimed_points": "¡Lo siento! Ya has reclamado tus puntos.", "now": "Ahora", "unranked": "Sin clasificar", - "points_to_rank": "750 puntos para clasificar" + "points_to_rank": "750 puntos para clasificar", + "ranking": "Colocado #%{rank}", + "available_to_claim": "Disponible para Reclamar", + "claim": "Reclamar %{value}", + "choose_claim_network": "Elegir Red de Reclamo", + "claim_on_network": "Reclamar en %{network}", + "claimed_on_network": "Reclamado en %{network}", + "claiming_on_network": "Reclamando en %{network}", + "error_claiming": "Reclamo fallido", + "free_to_claim": "Libre para Reclamar", + "has_bridge_fee": "Tiene Tarifa de Puente", + "claim_rewards": "Reclamar Recompensas", + "earned_last_week": "Ganado la Semana Pasada", + "top_10_earner": "Top 10 Ganador", + "zero_points": "0 Puntos", + "no_weekly_rank": "Sin Rango Semanal", + "none": "Ninguno", + "my_earnings": "Mis Ganancias", + "claimed_earnings": "Ganancias Reclamadas", + "current_value": "Valor Actual", + "rainbow_users_have_earned": "Los usuarios de Rainbow han ganado", + "earn_eth_rewards": "Gana Recompensas en ETH", + "rewards_explainer": "Cada semana, los principales ganadores de puntos semanales recibirán una porción de los ingresos en cadena de Rainbow. Recoge puntos intercambiando o transaccionando en Rainbow, usando dapps o refiriendo a amigos.", + "my_points": "Mis Puntos", + "refer_friends": "Referir Amigos", + "try_again": "Intenta de Nuevo", + "bridge_error": "Éxito parcial", + "bridge_error_explainer": "Tus recompensas fueron reclamadas con éxito en Optimism, pero la transferencia a %{network} falló. Si lo deseas, puedes enviar tus recompensas manualmente usando la función de Swap." }, "console": { "claim_bonus_paragraph": "Recibirás 100 puntos adicionales una vez que cambies al menos $100 a través de Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Demasiadas solicitudes de registro, por favor intentalo nuevamente más tarde" }, "swap": { + "actions": { + "hold_to_swap": "Mantener para Intercambiar", + "save": "Guardar", + "enter_amount": "Ingresa una cantidad", + "review": "Revisar", + "fetching_prices": "Obteniendo", + "swapping": "Intercambiando", + "select_token": "Seleccionar token", + "insufficient_funds": "Fondos Insuficientes", + "insufficient": "Insuficiente", + "quote_error": "Error en la cotización" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "Puentificando", + "swapping": "Intercambiando", + "max": "Máx.", + "select": "Seleccionar", + "token_to_get": "Token a recibir", + "token_to_swap": "Token a intercambiar", + "no_balance": "Sin Balance", + "find_a_token_to_buy": "Encuentra un token para comprar", + "search_your_tokens": "Busca tus tokens", + "error_executing_swap": "Error ejecutando intercambio", + "flashbots_protection": "Protección Flashbots", "tokens_input": { "tokens": "Tokens", "sort": "Ordenar", @@ -1945,6 +2035,7 @@ }, "loading": "Cargando...", "modal_types": { + "bridge": "Puente", "confirm": "Confirmar", "deposit": "Depósito", "receive": "Recibir", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "Reciente", "favorites": "Favoritos", "bridge": "Puente", "verified": "Verificado", @@ -2849,6 +2941,8 @@ "forward": "Adelante", "back": "Atrás" } - } + }, + "copy": "Copiar", + "paste": "Pegar" } } diff --git a/src/languages/fr_FR.json b/src/languages/fr_FR.json index fce07821053..8866fc35723 100644 --- a/src/languages/fr_FR.json +++ b/src/languages/fr_FR.json @@ -24,6 +24,7 @@ "tab_showcase": "Showcase", "tab_points": "Points", "tab_positions": "Positions", + "tab_rewards": "Récompenses", "tab_transactions": "Transactions", "tab_transactions_tooltip": "Transactions et transferts de jetons", "tab_uniquetokens": "Jetons uniques", @@ -431,6 +432,23 @@ "description": "Vous pouvez également appuyer longuement sur votre adresse\nci-dessus pour la copier." } }, + "check_identifier": { + "title": "Authentification Requise", + "description": "Il semble que vous ayez changé d'appareil ou réinstallé Rainbow. Veuillez vous authentifier afin que nous puissions vérifier que les données de votre trousseau de clés sont toujours en bon état.", + "action": "Authentifier", + "dismiss": "Rejeter", + "error_alert": { + "title": "Erreur de Validation du Trousseau", + "message": "Nous avons rencontré une erreur en validant les données de votre trousseau de clés. Veuillez réessayer. Si le problème persiste, veuillez contacter le support.", + "contact_support": "Contacter le Support", + "cancel": "Annuler" + }, + "failure_alert": { + "title": "Données du Trousseau Invalides", + "message": "Nous n'avons pas pu vérifier l'intégrité des données de votre trousseau de clés. Veuillez restaurer votre portefeuille et réessayer. Si le problème persiste, veuillez contacter le support.", + "action": "Continuer" + } + }, "cloud": { "backup_success": "Votre portefeuille a été sauvegardé avec succès!" }, @@ -541,7 +559,8 @@ "from_divider": "de", "to_divider": "à", "view_on": "Voir sur %{blockExplorerName}", - "view_on_etherscan": "Voir sur Etherscan" + "view_on_etherscan": "Voir sur Etherscan", + "copy_contract_address": "Copier l'adresse du contrat" }, "all_networks": "Tous les réseaux", "filter_by_network": "Filtrer par réseau", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Attente longue", - "description": "Env. %{time}" + "description": "Env. %{time}", + "description_prefix": "Env." }, "unknown_price": { "title": "Valeur de marché inconnue", @@ -587,6 +607,7 @@ "small_market": "Petit marché", "small_market_try_smaller_amount": "Petit marché — essayez une plus petite quantité", "you_are_losing": "Vous perdez %{priceImpact}", + "you_are_losing_prefix": "Vous perdez", "no_data": "Valeur de marché inconnue", "label": "Perte possible", "no_data_subtitle": "Si vous décidez de continuer, assurez-vous d'être satisfait de la quantité indiquée" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots protègent", "losing": "Perdre", "on": "Sur", + "off": "Arrêt", "price_impact": "Price impact :)", "price_row_per_token": "par", "slippage_message": "C'est un petit marché, donc vous obtenez un mauvais prix. Essayez un échange plus petit!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} pour %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} pour %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Auto", + "degen_mode": "Mode Degen", + "degen_mode_description": "Passez la feuille de révision pour un échange plus rapide", + "maximum_sold": "Vendu au maximum", + "minimum_received": "Montant minimum reçu", + "preferred_network": "Réseau Préféré", + "rainbow_fee": "Frais Rainbow inclus", + "settings": "Paramètres" + }, "token_index": { "get_token": "Obtenir %{assetSymbol}", "makeup_of_token": "Composition de 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "Urgent", "custom": "Personnalisé" }, - "network_fee": "Frais de réseau est.", + "unable_to_determine_selected_gas": "Impossible de déterminer le gaz sélectionné", + "network_fee": "Frais de réseau estimés", "current_base_fee": "Frais de base actuels", "max_base_fee": "Frais de base max", "miner_tip": "Pourboire du mineur", + "gas_price": "Prix du gaz", "max_transaction_fee": "Frais de transaction max", "warning_separator": "·", "lower_than_suggested": "Faible · peut se coincer", "higher_than_suggested": "Élevé · surpayer", + "likely_to_fail": "Faible · susceptible d'échouer", "max_base_fee_too_low_error": "Faible · susceptible d'échouer", "tip_too_low_error": "Faible · susceptible d'échouer", "alert_message_higher_miner_tip_needed": "Il est recommandé de fixer un pourboire de mineur plus élevé pour éviter les problèmes.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Réclamez vos points", + "title_rewards_line_1": "Bienvenue chez", + "title_rewards_line_2": "Rainbow Rewards", "subtitle": "Les points sont là. Découvrez combien vous en avez reçu.", + "subtitle_rewards": "Soyez récompensé pour utiliser Rainbow. Faites des actions onchain, gagnez des points, gagnez de l'ETH.", "get_started": "Commencer", "use_referral_code": "Utiliser un code de parrainage" }, "referral": { "title": "Entrez votre code de parrainage", - "subtitle": "Entrez un code de parrainage ci-dessous pour réclamer votre récompense bonus.", + "subtitle": "Entrez un code de parrainage ci-dessous pour réclamer votre bonus de points.", "back": "Retour", "error": "Une erreur s’est produite. Veuillez redémarrer l'application et réessayer.", "get_started": "Commencer", @@ -1378,6 +1416,7 @@ "share_link": "Partager le lien", "next_drop": "Prochaine Distribution", "your_rank": "Votre Classement", + "no_change": "Pas de changement", "of_x": "De %{totalUsers}", "referrals": "Parrainages", "streak": "Série", @@ -1395,12 +1434,40 @@ "today": "Aujourd'hui :)", "error": "Oups !\nVeuillez tirer pour rafraîchir.", "points": "points", + "points_capitalized": "Points", "referral_code_copied": "Code de parrainage copié", "earn_points_for_referring": "Gagnez des points lorsque vos filleuls échangent 100 $ à travers Rainbow, plus gagnez 10 % des points que vos filleuls gagnent", "already_claimed_points": "Désolé ! Vous avez déjà réclamé vos points.", "now": "Maintenant", "unranked": "Non classé", - "points_to_rank": "750 points pour être classé" + "points_to_rank": "750 points pour être classé", + "ranking": "Classé #%{rank}", + "available_to_claim": "Disponible à Réclamer", + "claim": "Réclamer %{value}", + "choose_claim_network": "Choisir Réseau de Réclamation", + "claim_on_network": "Réclamer sur %{network}", + "claimed_on_network": "Réclamé sur %{network}", + "claiming_on_network": "Réclamation sur %{network}", + "error_claiming": "Échec de réclamation", + "free_to_claim": "Réclamer gratuitement", + "has_bridge_fee": "Frais de Bridge", + "claim_rewards": "Réclamer des Récompenses", + "earned_last_week": "Gagné la Semaine Dernière", + "top_10_earner": "Top 10 Gagnants", + "zero_points": "0 Points", + "no_weekly_rank": "Pas de Classement Hebdomadaire", + "none": "Aucun", + "my_earnings": "Mes Gains", + "claimed_earnings": "Gains Réclamés", + "current_value": "Valeur actuelle", + "rainbow_users_have_earned": "Les utilisateurs de Rainbow ont gagné", + "earn_eth_rewards": "Gagner des Récompenses en ETH", + "rewards_explainer": "Chaque semaine, les meilleurs gagnants de points hebdomadaires recevront une partie des revenus de Rainbow sur la chaîne. Collectez des points en échangeant ou en effectuant des transactions sur Rainbow, en utilisant des dapps ou en parrainant des amis.", + "my_points": "Mes Points", + "refer_friends": "Parrainer des Amis", + "try_again": "Réessayer", + "bridge_error": "Succès Partiel", + "bridge_error_explainer": "Vos récompenses ont été revendiquées avec succès sur Optimism, mais le transfert vers %{network} a échoué. Si vous le souhaitez, vous pouvez transférer vos récompenses manuellement en utilisant la fonction Swap." }, "console": { "claim_bonus_paragraph": "Vous recevrez 100 points supplémentaires une fois que vous aurez échangé au moins 100 $ via Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Nous avons détecté trop de demandes d’inscription, veuillez réessayer plus tard" }, "swap": { + "actions": { + "hold_to_swap": "Maintenir pour échanger", + "save": "Sauvegarder", + "enter_amount": "Entrez un Montant", + "review": "Revoir", + "fetching_prices": "Récupération", + "swapping": "Échange en cours", + "select_token": "Sélectionner un Jeton", + "insufficient_funds": "Fonds insuffisants", + "insufficient": "Insuffisant", + "quote_error": "Erreur de Devis" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "Crée un pont en cours", + "swapping": "Échange en cours", + "max": "Max", + "select": "Sélectionner", + "token_to_get": "Jeton à Obtenir", + "token_to_swap": "Jeton à Échanger", + "no_balance": "Pas de solde", + "find_a_token_to_buy": "Trouvez un jeton à acheter", + "search_your_tokens": "Recherchez vos jetons", + "error_executing_swap": "Erreur lors de l'exécution de l'échange", + "flashbots_protection": "Protection Flashbots", "tokens_input": { "tokens": "Jetons", "sort": "Trier", @@ -1945,6 +2035,7 @@ }, "loading": "Chargement...", "modal_types": { + "bridge": "Pont", "confirm": "Confirmer", "deposit": "Dépôt", "receive": "Recevoir", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "Récent", "favorites": "Favoris", "bridge": "Pont", "verified": "Vérifié", @@ -2849,6 +2941,8 @@ "forward": "Avancer", "back": "Retour" } - } + }, + "copy": "Copier", + "paste": "Coller" } } diff --git a/src/languages/hi_IN.json b/src/languages/hi_IN.json index c7d92dfb61c..69f9d44673b 100644 --- a/src/languages/hi_IN.json +++ b/src/languages/hi_IN.json @@ -24,6 +24,7 @@ "tab_showcase": "शोकेस", "tab_points": "अंक", "tab_positions": "पोजीशन्स", + "tab_rewards": "इनाम", "tab_transactions": "लेन-देन", "tab_transactions_tooltip": "लेन-देन और टोकन स्थानांतरण", "tab_uniquetokens": "अद्वितीय टोकन्स", @@ -431,6 +432,23 @@ "description": "आप यह भी कर सकते हैं เพื่อ किसी अपने\nपते को कॉपी करने के लिए दबाएं।" } }, + "check_identifier": { + "title": "प्रमाणीकरण आवश्यक", + "description": "ऐसा लगता है कि आपने डिवाइस बदल लिया है या रेनबो को पुनः स्थापित कर दिया है। कृपया प्रमाणीकरण करें ताकि हम आपके कीचेन डेटा को सत्यापित कर सकें कि यह अभी भी एक स्वस्थ स्थिति में है।", + "action": "प्रमाणित करें", + "dismiss": "खारिज करें", + "error_alert": { + "title": "कीचेन सत्यापन में त्रुटि", + "message": "आपके कीचेन डेटा को सत्यापित करते समय हमें एक त्रुटि का सामना करना पड़ा। कृपया पुनः प्रयास करें। यदि समस्या बनी रहती है, तो कृपया समर्थन से संपर्क करें।", + "contact_support": "समर्थन से संपर्क करें", + "cancel": "रद्द करें" + }, + "failure_alert": { + "title": "अमान्य कीचेन डेटा", + "message": "हम आपके कीचेन डेटा की अखंडता की पुष्टि करने में असमर्थ थे। कृपया अपना वॉलेट बहाल करें और पुनः प्रयास करें। यदि समस्या बनी रहती है, तो कृपया समर्थन से संपर्क करें।", + "action": "जारी रखें" + } + }, "cloud": { "backup_success": "आपका वॉलेट सफलतापूर्वक बैकअप कर दिया गया है!" }, @@ -541,7 +559,8 @@ "from_divider": "से", "to_divider": "करने के लिए", "view_on": "देखें %{blockExplorerName}पर", - "view_on_etherscan": "Etherscan पर देखें" + "view_on_etherscan": "Etherscan पर देखें", + "copy_contract_address": "कॉंट्रैक्ट एड्रेस कॉपी करें" }, "all_networks": "सभी नेटवर्क", "filter_by_network": "नेटवर्क के आधार पर फ़िल्टर करें", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "लंबा इंतजार", - "description": "अनुमानित %{time}" + "description": "अनुमानित %{time}", + "description_prefix": "लगभग।" }, "unknown_price": { "title": "बाजार मूल्य अज्ञात", @@ -587,6 +607,7 @@ "small_market": "लघु बाजार", "small_market_try_smaller_amount": "छोटा बाजार — एक छोटी राशि आज़माएँ", "you_are_losing": "आप %{priceImpact} खो रहे हैं", + "you_are_losing_prefix": "आप खो रहे हैं", "no_data": "बाजार मूल्य अज्ञात", "label": "संभावित हानि", "no_data_subtitle": "यदि आप जारी रखने का निर्णय लेते हैं, तो सुनिश्चित करें कि आप उद्धृत राशि से संतुष्ट हैं" @@ -687,6 +708,7 @@ "flashbots_protect": "फ्लैशबॉट्स सुरक्षित", "losing": "हानि हो रही है", "on": "पर", + "off": "बंद", "price_impact": "मूल्य प्रभाव", "price_row_per_token": "प्रति", "slippage_message": "यह एक छोटा बाजार है, इसलिए आपको खराब मूल्य मिल रहा है। एक छोटा काम करके देखें!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} के लिए %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} के लिए %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "ऑटो", + "degen_mode": "डिजन मोड", + "degen_mode_description": "तेज स्वैप करने के लिए समीक्षा शीट को छोड़ें", + "maximum_sold": "अधिकतम बेचा गया", + "minimum_received": "न्यूनतम प्राप्त", + "preferred_network": "पसंदीदा नेटवर्क", + "rainbow_fee": "रेनबो शुल्क शामिल", + "settings": "सेटिंग्स" + }, "token_index": { "get_token": "प्राप्त करें %{assetSymbol}", "makeup_of_token": "1 %{assetSymbol}का मेकअप", @@ -1030,14 +1062,17 @@ "urgent": "अत्यावश्यक", "custom": "कस्टम" }, + "unable_to_determine_selected_gas": "चयनित गैस का निर्धारण करने में असमर्थ", "network_fee": "अनुमानित नेटवर्क शुल्क", "current_base_fee": "वर्तमान बेस फीस", "max_base_fee": "अधिकतम मूल शुल्क", "miner_tip": "खनकर्मी का टिप", + "gas_price": "गैस की कीमत", "max_transaction_fee": "अधिकतम लेन-देन शुल्क", "warning_separator": "·", "lower_than_suggested": "कम · अटक सकता है", "higher_than_suggested": "अधिक · अधिक भुगतान कर रहे हैं", + "likely_to_fail": "कम · असफल होने की संभावना", "max_base_fee_too_low_error": "कम · असफल होने की संभावना", "tip_too_low_error": "कम · असफल होने की संभावना", "alert_message_higher_miner_tip_needed": "समस्याओं से बचने के लिए अधिक माइनर टिप की सेटिंग करने की सिफारिश की जाती है।", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "अपने अंक दावा करें", + "title_rewards_line_1": "में आपका स्वागत है", + "title_rewards_line_2": "रेनबो रिवार्ड्स", "subtitle": "अंक यहाँ हैं। जानिए आपको कितने अंक मिले हैं।", + "subtitle_rewards": "रेनबो का उपयोग करने के लिए पुरस्कृत होइए। ब्लॉकचेन पर कार्य करें, अंक प्राप्त करें, ETH अर्जित करें।", "get_started": "शुरुआत करें", "use_referral_code": "रेफरल कोड का प्रयोग करें" }, "referral": { "title": "अपना रेफरल कोड दर्ज करें", - "subtitle": "अपने बोनस रिवार्ड का दावा करने के लिए नीचे एक रेफरल कोड दर्ज करें।", + "subtitle": "अंक बोनस का दावा करने के लिए नीचे एक रेफरल कोड दर्ज करें।", "back": "वापस", "error": "कुछ गलत हो गया। कृपया ऐप को पुनः आरंभ करें और फिर से प्रयास करें।", "get_started": "शुरुआत करें", @@ -1378,6 +1416,7 @@ "share_link": "लिंक साझा करें", "next_drop": "अगला ड्रॉप", "your_rank": "आपका रैंक", + "no_change": "कोई बदलाव नहीं", "of_x": "का %{totalUsers}", "referrals": "रेफरल्स", "streak": "स्ट्रीक", @@ -1395,12 +1434,40 @@ "today": "आज", "error": "उफ़!\nकृपया ताज़ा करने के लिए खींचें।", "points": "अंक", + "points_capitalized": "अंक", "referral_code_copied": "रेफरल कोड कॉपी किया गया", "earn_points_for_referring": "जब आपके रेफरल Rainbow के माध्यम से $100 का आदान-प्रदान करते हैं तो अंक अर्जित करें, साथ ही अपने रेफरल्स द्वारा अर्जित अंकों का 10% भी कमाएं", "already_claimed_points": "क्षमा करें! आप पहले ही अपने अंक दावा कर चुके हैं।", "now": "अब", "unranked": "अश्रेणीकृत", - "points_to_rank": "रैंक हेतु 750 अंक आवश्यक" + "points_to_rank": "रैंक हेतु 750 अंक आवश्यक", + "ranking": "स्थान #%{rank}", + "available_to_claim": "दावा करने के लिए उपलब्ध", + "claim": "%{value} का दावा करें", + "choose_claim_network": "दावा नेटवर्क चुनें", + "claim_on_network": "%{network} पर दावा करें", + "claimed_on_network": "%{network} पर दावा किया गया", + "claiming_on_network": "%{network} पर दावा हो रहा है", + "error_claiming": "दावा विफल", + "free_to_claim": "दावा करने के लिए मुफ्त", + "has_bridge_fee": "ब्रिज शुल्क है", + "claim_rewards": "पुरस्कार दावा करें", + "earned_last_week": "पिछले सप्ताह अर्जित", + "top_10_earner": "शीर्ष 10 कमाने वाला", + "zero_points": "0 अंक", + "no_weekly_rank": "कोई साप्ताहिक रैंक नहीं", + "none": "कोई नहीं", + "my_earnings": "मेरी कमाई", + "claimed_earnings": "दावा की गई कमाई", + "current_value": "वर्तमान मूल्य", + "rainbow_users_have_earned": "रेनबो उपयोगकर्ताओं ने कमाए हैं", + "earn_eth_rewards": "ETH पुरस्कार अर्जित करें", + "rewards_explainer": "हर सप्ताह, शीर्ष साप्ताहिक अंक अर्जित करने वालों को रेनबो की ऑनचेन राजस्व का एक हिस्सा प्राप्त होगा। रेनबो में स्वैप या लेनदेन करके, dapps का उपयोग करके, या दोस्तों को रेफर करके अंक अर्जित करें।", + "my_points": "मेरे अंक", + "refer_friends": "दोस्तों को रेफर करें", + "try_again": "फिर से प्रयास करें", + "bridge_error": "आंशिक सफलता", + "bridge_error_explainer": "आपके पुरस्कारों को सफलतापूर्वक Optimism पर दावा किया गया है, लेकिन %{network} पर स्थानांतरण विफल हो गया। यदि आप चाहें, तो आप स्वैप फीचर का उपयोग करके अपने पुरस्कारों को मैन्युअल रूप से पुल कर सकते हैं।" }, "console": { "claim_bonus_paragraph": "आपको Rainbow के माध्यम से कम से कम $100 बदलने पर अतिरिक्त 100 अंक मिलेंगे", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "बहुत सारे साइनअप अनुरोध, कृपया बाद में पुनः प्रयास करें" }, "swap": { + "actions": { + "hold_to_swap": "बदलने के लिए होल्ड करें", + "save": "सहेजें", + "enter_amount": "राशि दर्ज करें", + "review": "समीक्षा करें", + "fetching_prices": "प्राप्त किया जा रहा है", + "swapping": "स्वापिंग", + "select_token": "टोकन चुनें", + "insufficient_funds": "अपर्याप्त धन", + "insufficient": "पर्याप्त", + "quote_error": "कोट त्रुटि" + }, "aggregators": { "rainbow": "रेनबो" }, + "bridging": "ब्रिजिंग", + "swapping": "स्वापिंग", + "max": "अधिकतम", + "select": "चुनें", + "token_to_get": "प्राप्त करने के लिए टोकन", + "token_to_swap": "स्वैप करने के लिए टोकन", + "no_balance": "कोई बैलेंस नहीं", + "find_a_token_to_buy": "खरीदने के लिए टोकन खोजें", + "search_your_tokens": "अपने टोकन खोजें", + "error_executing_swap": "स्वैप निष्पादित करते समय त्रुटि", + "flashbots_protection": "फ़्लैशबॉट्स सुरक्षा", "tokens_input": { "tokens": "Tokens", "sort": "Sort", @@ -1945,6 +2035,7 @@ }, "loading": "लोड हो रहा है...", "modal_types": { + "bridge": "ब्रिज", "confirm": "पुष्टि करें", "deposit": "जमा", "receive": "प्राप्त करें", @@ -1976,11 +2067,12 @@ }, "token_search": { "section_header": { + "recent": "हाल का", "favorites": "पसंदीदा", "bridge": "ब्रिज", "verified": "सत्यापित", "unverified": "असत्यापित", - "on_other_networks": "अन्य नेटवर्क पर" + "on_other_networks": "अन्य नेटवर्कों पर" } }, "transactions": { @@ -2849,6 +2941,8 @@ "forward": "आगे", "back": "वापस" } - } + }, + "copy": "कॉपी", + "paste": "चिपकाएं" } } diff --git a/src/languages/id_ID.json b/src/languages/id_ID.json index 5d4434b2abd..312d0d854ac 100644 --- a/src/languages/id_ID.json +++ b/src/languages/id_ID.json @@ -24,6 +24,7 @@ "tab_showcase": "Pameran", "tab_points": "Poin", "tab_positions": "Posisi", + "tab_rewards": "Hadiah", "tab_transactions": "Transaksi", "tab_transactions_tooltip": "Transaksi dan Transfer Token", "tab_uniquetokens": "Token Unik", @@ -431,6 +432,23 @@ "description": "Anda juga bisa menekan lama alamat\ndi atas untuk menyalinnya." } }, + "check_identifier": { + "title": "Otentikasi Diperlukan", + "description": "Sepertinya Anda mungkin telah mengganti perangkat atau menginstal ulang Rainbow. Harap otentikasi sehingga kami dapat memvalidasi data keychain Anda masih dalam kondisi sehat.", + "action": "Otentikasi", + "dismiss": "Tutup", + "error_alert": { + "title": "Kesalahan Memvalidasi Keychain", + "message": "Kami mengalami kesalahan saat memvalidasi data keychain Anda. Harap coba lagi. Jika masalah berlanjut, harap hubungi dukungan.", + "contact_support": "Hubungi Dukungan", + "cancel": "Batal" + }, + "failure_alert": { + "title": "Data Keychain Tidak Valid", + "message": "Kami tidak dapat memverifikasi integritas data keychain Anda. Harap pulihkan dompet Anda dan coba lagi. Jika masalah berlanjut, harap hubungi dukungan.", + "action": "Lanjutkan" + } + }, "cloud": { "backup_success": "Dompet Anda telah berhasil dicadangkan!" }, @@ -541,7 +559,8 @@ "from_divider": "dari", "to_divider": "ke", "view_on": "Tampilkan di %{blockExplorerName}", - "view_on_etherscan": "Tampilkan di Etherscan" + "view_on_etherscan": "Tampilkan di Etherscan", + "copy_contract_address": "Salin Alamat Kontrak" }, "all_networks": "Semua Jaringan", "filter_by_network": "Filter berdasarkan Jaringan", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Menunggu lama", - "description": "Perkiraan %{time}" + "description": "Perkiraan %{time}", + "description_prefix": "Kira-kira." }, "unknown_price": { "title": "Nilai Pasar Tidak Diketahui", @@ -587,6 +607,7 @@ "small_market": "Pasar Kecil", "small_market_try_smaller_amount": "Pasar kecil — coba jumlah yang lebih kecil", "you_are_losing": "Anda kehilangan %{priceImpact}", + "you_are_losing_prefix": "Anda kehilangan", "no_data": "Nilai Pasar Tidak Diketahui", "label": "Kemungkinan kerugian", "no_data_subtitle": "Jika Anda memutuskan untuk melanjutkan, pastikan Anda puas dengan jumlah yang dikutip" @@ -687,6 +708,7 @@ "flashbots_protect": "Pelindung Flashbots", "losing": "Kalah", "on": "On", + "off": "Mati", "price_impact": "Dampak harga", "price_row_per_token": "per", "slippage_message": "Ini adalah pasar kecil, jadi Anda mendapatkan harga yang buruk. Coba perdagangan yang lebih kecil!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} untuk %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} untuk %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Otomatis", + "degen_mode": "Mode Degen", + "degen_mode_description": "Lewati lembar tinjauan untuk swap lebih cepat", + "maximum_sold": "Maksimum Dijual", + "minimum_received": "Minimum yang diterima", + "preferred_network": "Jaringan yang Disukai", + "rainbow_fee": "Termasuk biaya Rainbow", + "settings": "Pengaturan" + }, "token_index": { "get_token": "Dapatkan %{assetSymbol}", "makeup_of_token": "Komposisi dari 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "Mendesak", "custom": "Kustom" }, - "network_fee": "Biaya jaringan Est.", + "unable_to_determine_selected_gas": "Tidak dapat menentukan gas yang dipilih", + "network_fee": "Perk. Biaya Jaringan", "current_base_fee": "Biaya dasar saat ini", "max_base_fee": "Biaya dasar maksimum", "miner_tip": "Tip penambang", + "gas_price": "Harga Gas", "max_transaction_fee": "Biaya transaksi maksimum", "warning_separator": "·", "lower_than_suggested": "Rendah · mungkin terjebak", "higher_than_suggested": "Tinggi · membayar lebih", + "likely_to_fail": "Rendah · cenderung gagal", "max_base_fee_too_low_error": "Rendah · cenderung gagal", "tip_too_low_error": "Rendah · cenderung gagal", "alert_message_higher_miner_tip_needed": "Mengatur tip penambang yang lebih tinggi direkomendasikan untuk menghindari masalah.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Klaim poin Anda", + "title_rewards_line_1": "Selamat datang di", + "title_rewards_line_2": "Rainbow Rewards", "subtitle": "Poin telah ada. Cari tahu berapa banyak yang telah Anda dapatkan.", + "subtitle_rewards": "Dapatkan hadiah karena menggunakan Rainbow. Lakukan aktivitas onchain, dapatkan poin, dan dapatkan ETH.", "get_started": "Mulai", "use_referral_code": "Gunakan Kode Referral" }, "referral": { "title": "Masukkan kode rujukan Anda", - "subtitle": "Masukkan kode referral di bawah untuk mengklaim hadiah bonus Anda.", + "subtitle": "Masukkan kode referensi di bawah untuk mengklaim bonus poin Anda.", "back": "Kembali", "error": "Terjadi kesalahan. Silakan memulai ulang aplikasi dan coba lagi.", "get_started": "Mulai", @@ -1378,6 +1416,7 @@ "share_link": "Bagikan Tautan", "next_drop": "Pengeluaran Berikutnya", "your_rank": "Peringkat Anda", + "no_change": "Tidak Ada Perubahan", "of_x": "Dari %{totalUsers}", "referrals": "Referal", "streak": "Streak", @@ -1395,12 +1434,40 @@ "today": "Hari Ini", "error": "Ups!\nSilakan tarik untuk memuat ulang.", "points": "poin", + "points_capitalized": "Poin", "referral_code_copied": "Kode rujukan telah disalin", "earn_points_for_referring": "Dapatkan poin ketika rujukanmu menukar $100 melalui Rainbow, plus dapatkan 10% dari poin yang diperoleh rujukanmu", "already_claimed_points": "Maaf! Kamu sudah mengklaim poinmu.", "now": "Sekarang", "unranked": "Tidak Terperingkat", - "points_to_rank": "750 poin untuk naik peringkat" + "points_to_rank": "750 poin untuk naik peringkat", + "ranking": "Ditempatkan #%{rank}", + "available_to_claim": "Tersedia untuk Diklaim", + "claim": "Klaim %{value}", + "choose_claim_network": "Pilih Jaringan Klaim", + "claim_on_network": "Klaim di %{network}", + "claimed_on_network": "Sudah Diklaim di %{network}", + "claiming_on_network": "Mengklaim di %{network}", + "error_claiming": "Klaim Gagal", + "free_to_claim": "Gratis untuk Diklaim", + "has_bridge_fee": "Ada Biaya Jembatan", + "claim_rewards": "Klaim Hadiah", + "earned_last_week": "Diperoleh Minggu Lalu", + "top_10_earner": "Top 10 Pendapatan", + "zero_points": "0 Poin", + "no_weekly_rank": "Tidak Ada Peringkat Mingguan", + "none": "Tidak ada", + "my_earnings": "Pendapatan Saya", + "claimed_earnings": "Pendapatan yang Diklaim", + "current_value": "Nilai Saat Ini", + "rainbow_users_have_earned": "Pengguna Rainbow telah mendapatkan", + "earn_eth_rewards": "Dapatkan Hadiah ETH", + "rewards_explainer": "Setiap minggu, penghasil poin mingguan tertinggi akan menerima sebagian dari pendapatan onchain Rainbow. Kumpulkan poin dengan menukar atau bertransaksi di Rainbow, menggunakan dapps, atau merujuk teman.", + "my_points": "Poin Saya", + "refer_friends": "Rujukkan Teman", + "try_again": "Coba Lagi", + "bridge_error": "Keberhasilan Sebagian", + "bridge_error_explainer": "Hadiah Anda berhasil diklaim di Optimism, tetapi transfer ke %{network} gagal. Jika Anda mau, Anda bisa menjembatani hadiah Anda secara manual menggunakan fitur Swap." }, "console": { "claim_bonus_paragraph": "Anda akan mendapatkan tambahan 100 poin setelah Anda menukar minimal $100 melalui Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Terlalu banyak permintaan pendaftaran, silakan coba lagi nanti" }, "swap": { + "actions": { + "hold_to_swap": "Tahan untuk Tukar", + "save": "Simpan", + "enter_amount": "Masukkan Jumlah", + "review": "Tinjau", + "fetching_prices": "Mengambil Data", + "swapping": "Menukar", + "select_token": "Pilih Token", + "insufficient_funds": "Dana Tidak Cukup", + "insufficient": "Kurangnya", + "quote_error": "Kesalahan Kutipan" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "Membuat Jembatan", + "swapping": "Menukar", + "max": "Maks", + "select": "Pilih", + "token_to_get": "Token untuk Dapatkan", + "token_to_swap": "Token untuk Tukar", + "no_balance": "Tidak Ada Saldo", + "find_a_token_to_buy": "Cari token untuk beli", + "search_your_tokens": "Cari token Anda", + "error_executing_swap": "Kesalahan saat melakukan swap", + "flashbots_protection": "Perlindungan Flashbots", "tokens_input": { "tokens": "Token", "sort": "Urutkan", @@ -1945,6 +2035,7 @@ }, "loading": "Memuat...", "modal_types": { + "bridge": "Jembatan", "confirm": "Konfirmasi", "deposit": "Deposit", "receive": "Terima", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "Baru-baru ini", "favorites": "Favorit", "bridge": "Jembatan", "verified": "Terverifikasi", @@ -2849,6 +2941,8 @@ "forward": "Maju", "back": "Kembali" } - } + }, + "copy": "Salin", + "paste": "Tempel" } } diff --git a/src/languages/ja_JP.json b/src/languages/ja_JP.json index 88a5059c883..a3a1f552f82 100644 --- a/src/languages/ja_JP.json +++ b/src/languages/ja_JP.json @@ -24,6 +24,7 @@ "tab_showcase": "ショーケース", "tab_points": "ポイント", "tab_positions": "ポジション", + "tab_rewards": "報酬", "tab_transactions": "取引", "tab_transactions_tooltip": "取引とトークンの送金", "tab_uniquetokens": "ユニークトークン", @@ -431,6 +432,23 @@ "description": "また、上記の\nアドレスを長押ししてコピーすることもできます" } }, + "check_identifier": { + "title": "認証が必要です", + "description": "デバイスを変更するか、またはRainbowを再インストールした可能性があります。キー チェーンデータが健全な状態にあることを確認するために、認証を行ってください。", + "action": "認証", + "dismiss": "閉じる", + "error_alert": { + "title": "キーチェーンの検証エラー", + "message": "キーチェーンデータの検証中にエラーが発生しました。もう一度試してください。問題が解決しない場合は、サポートに連絡してください。", + "contact_support": "サポートに連絡", + "cancel": "キャンセル" + }, + "failure_alert": { + "title": "無効なキーチェーンデータ", + "message": "キーチェーンデータの整合性を確認できませんでした。ウォレットをリストアしてから再試行してください。問題が解決しない場合は、サポートに連絡してください。", + "action": "続ける" + } + }, "cloud": { "backup_success": "ウォレットは正常にバックアップされました!" }, @@ -541,7 +559,8 @@ "from_divider": "送信元", "to_divider": "to", "view_on": "%{blockExplorerName}で表示する", - "view_on_etherscan": "Etherscanで見る" + "view_on_etherscan": "Etherscanで見る", + "copy_contract_address": "契約アドレスをコピー" }, "all_networks": "全てのネットワーク", "filter_by_network": "ネットワークでフィルタリング", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "長い待ち時間", - "description": "Approx. %{time}" + "description": "Approx. %{time}", + "description_prefix": "約" }, "unknown_price": { "title": "市場価値不明", @@ -587,6 +607,7 @@ "small_market": "小さな市場", "small_market_try_smaller_amount": "小さな市場 — より小さい金額を試してください", "you_are_losing": "価格インパクト %{priceImpact}", + "you_are_losing_prefix": "損失が発生しています", "no_data": "市場価値不明", "label": "可能な損失", "no_data_subtitle": "続行する場合は、提示された金額に満足していることを確認してください。" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots Protect", "losing": "損失中", "on": "オン", + "off": "オフ", "price_impact": "価格へのインパクト", "price_row_per_token": "あたり", "slippage_message": "これは小さな市場なので、あなたは悪い価格を取得しています。 より小さな取引を試してみてください!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} につき %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} につき %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "自動", + "degen_mode": "デゲンモード", + "degen_mode_description": "レビューページをスキップして速くスワップする", + "maximum_sold": "最大売却数量", + "minimum_received": "受け取る最小額", + "preferred_network": "優先ネットワーク", + "rainbow_fee": "Rainbow手数料込み", + "settings": "設定" + }, "token_index": { "get_token": "%{assetSymbol}を入手", "makeup_of_token": "1 %{assetSymbol}の構成", @@ -1030,14 +1062,17 @@ "urgent": "緊急", "custom": "カスタム" }, + "unable_to_determine_selected_gas": "選択されたガスを判断できません", "network_fee": "推定ネットワーク手数料", "current_base_fee": "現在の基本手数料", "max_base_fee": "最大ベース手数料", "miner_tip": "マイナーチップ", + "gas_price": "ガス価格", "max_transaction_fee": "最大取引手数料", "warning_separator": "·", "lower_than_suggested": "低い · スタックする可能性あり", "higher_than_suggested": "高い · 過剰支払い", + "likely_to_fail": "低い · 失敗する可能性が高い", "max_base_fee_too_low_error": "低い · 失敗する可能性が高い", "tip_too_low_error": "低い · 失敗する可能性が高い", "alert_message_higher_miner_tip_needed": "問題を回避するために、より高いマイナーチップの設定が推奨されます。", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "ポイントを請求する", + "title_rewards_line_1": "ようこそ", + "title_rewards_line_2": "Rainbow報酬", "subtitle": "ポイントがここに。あなたがいくつ獲得したかを確認してください。", + "subtitle_rewards": "Rainbowを使用することで報酬を獲得できます。オンチェーンで操作を行い、ポイントを獲得し、ETHを得ることができます。", "get_started": "始める", "use_referral_code": "紹介コードを使用する" }, "referral": { "title": "あなたの紹介コードを入力してください", - "subtitle": "以下に紹介コードを入力して、ボーナス報酬を請求してください。", + "subtitle": "下に紹介コードを入力して、ポイントボーナスを獲得してください。", "back": "戻る", "error": "何か問題が発生しました。アプリを再起動して、もう一度お試しください。", "get_started": "始める", @@ -1378,6 +1416,7 @@ "share_link": "リンクを共有する", "next_drop": "次のドロップ", "your_rank": "あなたのランク", + "no_change": "変更なし", "of_x": "%{totalUsers}個中の", "referrals": "紹介", "streak": "ストリーク", @@ -1395,12 +1434,40 @@ "today": "今日", "error": "おっと!\nリフレッシュするには引っ張ってください。", "points": "ポイント", + "points_capitalized": "ポイント", "referral_code_copied": "紹介コードをコピーしました", "earn_points_for_referring": "あなたの紹介者がRainbowを通じて$100を交換するとポイントが獲得でき、さらに紹介者が獲得するポイントの10%を獲得できます", "already_claimed_points": "申し訳ありません! ポイントは既に請求済みです。", "now": "今", "unranked": "ランク外", - "points_to_rank": "ランクには750ポイントが必要です" + "points_to_rank": "ランクには750ポイントが必要です", + "ranking": "%{rank}位にランクイン", + "available_to_claim": "請求可能", + "claim": "%{value}を請求", + "choose_claim_network": "請求ネットワークを選択", + "claim_on_network": "%{network}で請求", + "claimed_on_network": "%{network}で請求済み", + "claiming_on_network": "%{network}で請求中", + "error_claiming": "主張失敗", + "free_to_claim": "無料で請求", + "has_bridge_fee": "ブリッジ手数料あり", + "claim_rewards": "報酬を請求", + "earned_last_week": "先週獲得", + "top_10_earner": "トップ10獲得者", + "zero_points": "0ポイント", + "no_weekly_rank": "週間ランクなし", + "none": "なし", + "my_earnings": "私の収益", + "claimed_earnings": "請求された収益", + "current_value": "現在の値", + "rainbow_users_have_earned": "Rainbowユーザーが稼いだ", + "earn_eth_rewards": "ETH報酬を稼ぐ", + "rewards_explainer": "毎週、トップ週次ポイント獲得者はRainbowのオンチェーン収益の一部を受け取ります。 Rainbowでのスワップまたはトランザクション、dappsの使用、または友達を紹介してポイントを集めましょう。", + "my_points": "私のポイント", + "refer_friends": "友達を紹介", + "try_again": "再試行", + "bridge_error": "部分的な成功", + "bridge_error_explainer": "報酬はOptimismで正常に受け取られましたが、%{network}への転送に失敗しました。必要に応じて、Swap機能を使用して手動で報酬をブリッジすることができます。" }, "console": { "claim_bonus_paragraph": "Rainbowで少なくとも$100をスワップすると、追加で100ポイントがもらえます", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "登録リクエストが多すぎるため、後でもう一度お試しください" }, "swap": { + "actions": { + "hold_to_swap": "スワップを保持", + "save": "保存する", + "enter_amount": "金額を入力してください", + "review": "レビュー", + "fetching_prices": "取得中", + "swapping": "スワップ中", + "select_token": "トークンを選択", + "insufficient_funds": "資金が不足しています", + "insufficient": "不足しています", + "quote_error": "見積りエラー" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "ブリッジング中", + "swapping": "スワップ中", + "max": "最大", + "select": "選択", + "token_to_get": "取得するトークン", + "token_to_swap": "交換するトークン", + "no_balance": "残高なし", + "find_a_token_to_buy": "購入するトークンを探す", + "search_your_tokens": "トークンを検索", + "error_executing_swap": "スワップの実行エラー", + "flashbots_protection": "Flashbots保護", "tokens_input": { "tokens": "トークン", "sort": "ソート", @@ -1945,6 +2035,7 @@ }, "loading": "読み込み中...", "modal_types": { + "bridge": "ブリッジ", "confirm": "確認", "deposit": "預金", "receive": "受け取る", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "最近の", "favorites": "お気に入り", "bridge": "ブリッジ", "verified": "確認済み", @@ -2849,6 +2941,8 @@ "forward": "進む", "back": "戻る" } - } + }, + "copy": "Copy", + "paste": "貼り付け" } } diff --git a/src/languages/ko_KR.json b/src/languages/ko_KR.json index e4fe3f57044..ee720cf8789 100644 --- a/src/languages/ko_KR.json +++ b/src/languages/ko_KR.json @@ -24,6 +24,7 @@ "tab_showcase": "쇼케이스", "tab_points": "포인트", "tab_positions": "포지션", + "tab_rewards": "보상", "tab_transactions": "거래", "tab_transactions_tooltip": "거래 및 토큰 이전", "tab_uniquetokens": "고유한 토큰", @@ -431,6 +432,23 @@ "description": "\n주소를 길게 눌러 복사할 수도 있습니다." } }, + "check_identifier": { + "title": "인증 필요", + "description": "기기를 변경하거나 레인보우를 재설치한 것 같습니다. 키체인 데이터의 건강 상태를 확인할 수 있도록 인증해 주시기 바랍니다.", + "action": "인증", + "dismiss": "거부", + "error_alert": { + "title": "키체인 검증 오류", + "message": "키체인 데이터를 검증하는 중에 오류가 발생했습니다. 다시 시도해 주세요. 문제가 계속되면 지원팀에 문의해 주세요.", + "contact_support": "지원팀에 문의", + "cancel": "취소" + }, + "failure_alert": { + "title": "키체인 데이터 무효", + "message": "키체인 데이터의 무결성을 확인할 수 없었습니다. 지갑을 복원하고 다시 시도해 주세요. 문제가 계속되면 지원팀에 문의해 주세요.", + "action": "계속" + } + }, "cloud": { "backup_success": "당신의 지갑이 성공적으로 백업되었습니다!" }, @@ -541,7 +559,8 @@ "from_divider": "from", "to_divider": "에게", "view_on": "%{blockExplorerName}에서 보기", - "view_on_etherscan": "Etherscan에서 보기" + "view_on_etherscan": "Etherscan에서 보기", + "copy_contract_address": "계약 주소 복사" }, "all_networks": "모든 네트워크", "filter_by_network": "네트워크별 필터링", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "긴 대기 시간", - "description": "약 %{time}" + "description": "약 %{time}", + "description_prefix": "약." }, "unknown_price": { "title": "시장 가치 불명", @@ -587,6 +607,7 @@ "small_market": "소형 시장", "small_market_try_smaller_amount": "수요가 작은 시장 — 더 작은 금액을 시도해 보세요", "you_are_losing": "당신은 %{priceImpact}를 잃었습니다", + "you_are_losing_prefix": "손실 중", "no_data": "시장 가치 불명", "label": "가능한 손실", "no_data_subtitle": "계속하기로 결정하시면, 인용된 금액에 만족하는지 확인하세요" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots 보호", "losing": "손실", "on": "켜기", + "off": "끄기", "price_impact": "가격 영향", "price_row_per_token": "당", "slippage_message": "이것은 작은 시장이므로 나쁜 가격을 얻고 있습니다. 더 작은 거래를 시도해보세요!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} 에 대해 %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} 에 대해 %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "자동", + "degen_mode": "디겐 모드", + "degen_mode_description": "검토 시트를 건너뛰어 더 빠르게 스왑", + "maximum_sold": "최대 판매", + "minimum_received": "최소 수령량", + "preferred_network": "선호 네트워크", + "rainbow_fee": "포함된 레인보우 비용", + "settings": "설정" + }, "token_index": { "get_token": "%{assetSymbol}얻기", "makeup_of_token": "1 %{assetSymbol}의 구성", @@ -1030,14 +1062,17 @@ "urgent": "긴급", "custom": "사용자 정의" }, - "network_fee": "예상 네트워크 수수료", + "unable_to_determine_selected_gas": "선택된 가스를 확인할 수 없음", + "network_fee": "추정 네트워크 수수료", "current_base_fee": "현재 기본 수수료", "max_base_fee": "최대 기본료", "miner_tip": "마이너 팁", + "gas_price": "가스 가격", "max_transaction_fee": "최대 거래 수수료", "warning_separator": "·", "lower_than_suggested": "낮음 · 특이사항 가능성", "higher_than_suggested": "높음 · 과도한 지불", + "likely_to_fail": "낮음 · 실패 가능성 큼", "max_base_fee_too_low_error": "낮음 · 실패 가능성 큼", "tip_too_low_error": "낮음 · 실패 가능성 큼", "alert_message_higher_miner_tip_needed": "문제를 피하기 위해 더 높은 광부 팁 설정이 권장됩니다.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "포인트를 청구하세요", + "title_rewards_line_1": "환영합니다", + "title_rewards_line_2": "레인보우 리워드", "subtitle": "포인트가 도착했습니다. 얼마나 많은 포인트를 받았는지 확인하세요.", + "subtitle_rewards": "레인보우 사용으로 리워드를 받으세요. 온체인에서 활동하고, 포인트를 얻고, ETH를 벌 수 있습니다.", "get_started": "시작하기", "use_referral_code": "추천 코드 사용" }, "referral": { "title": "추천 코드를 입력하세요", - "subtitle": "아래에 추천 코드를 입력하여 보너스 보상을 받으세요.", + "subtitle": "포인트 보너스를 받으려면 아래에 추천 코드를 입력하세요.", "back": "뒤로", "error": "문제가 발생했습니다. 앱을 다시 시작하고 다시 시도해 주세요.", "get_started": "시작하기", @@ -1378,6 +1416,7 @@ "share_link": "링크 공유하기", "next_drop": "다음 드롭", "your_rank": "당신의 순위", + "no_change": "변경 없음", "of_x": "%{totalUsers}의", "referrals": "추천인", "streak": "연속", @@ -1395,12 +1434,40 @@ "today": "오늘", "error": "이런!\n새로고침해 주세요.", "points": "포인트", + "points_capitalized": "포인트", "referral_code_copied": "추천 코드가 복사되었습니다", "earn_points_for_referring": "당신의 추천인이 Rainbow를 통해 $100를 교환할 때 포인트를 적립하고, 추천인이 적립한 포인트의 10%를 추가로 적립하세요", "already_claimed_points": "죄송합니다! 이미 포인트를 청구하셨습니다.", "now": "지금", "unranked": "순위 없음", - "points_to_rank": "등급까지 750포인트" + "points_to_rank": "등급까지 750포인트", + "ranking": "%{rank}위에 배치됨", + "available_to_claim": "클레임 가능", + "claim": "%{value} 클레임", + "choose_claim_network": "클레임 네트워크 선택", + "claim_on_network": "%{network}에서 클레임하기", + "claimed_on_network": "%{network}에서 클레임됨", + "claiming_on_network": "%{network}에서 클레임 중", + "error_claiming": "청구 실패", + "free_to_claim": "무료 클레임 가능", + "has_bridge_fee": "브릿지 수수료 있음", + "claim_rewards": "리워드 클레임", + "earned_last_week": "지난주 적립", + "top_10_earner": "상위 10위 적립자", + "zero_points": "0 포인트", + "no_weekly_rank": "주간 순위 없음", + "none": "없음", + "my_earnings": "나의 수익", + "claimed_earnings": "클레임된 수익", + "current_value": "현재 값", + "rainbow_users_have_earned": "레인보우 사용자가 벌어들인 수익", + "earn_eth_rewards": "ETH 리워드 적립", + "rewards_explainer": "매주 상위 주간 포인트 적립자는 레인보우의 온체인 수익의 일부를 받습니다. 레인보우에서 스왑하거나 거래를 하고, Dapp을 사용하거나 친구를 추천하여 포인트를 모으세요.", + "my_points": "나의 포인트", + "refer_friends": "친구 추천", + "try_again": "다시 시도하기", + "bridge_error": "부분 성공", + "bridge_error_explainer": "귀하의 보상은 Optimism에서 성공적으로 수령되었지만, %{network}으로의 전송이 실패했습니다. 원하시면 Swap 기능을 사용하여 보상을 수동으로 브리지할 수 있습니다." }, "console": { "claim_bonus_paragraph": "Rainbow에서 최소 $100를 스왑하면 추가로 100점을 받게 됩니다", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "가입 요청이 너무 많습니다, 나중에 다시 시도해주세요" }, "swap": { + "actions": { + "hold_to_swap": "스왑하기 위해 누르고 있기", + "save": "저장", + "enter_amount": "금액 입력", + "review": "리뷰", + "fetching_prices": "가져오는 중", + "swapping": "교환 중", + "select_token": "토큰 선택", + "insufficient_funds": "자금이 부족합니다", + "insufficient": "부족한", + "quote_error": "견적 오류" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "브릿징 중", + "swapping": "교환 중", + "max": "최대", + "select": "선택", + "token_to_get": "받을 토큰", + "token_to_swap": "스왑할 토큰", + "no_balance": "잔액 없음", + "find_a_token_to_buy": "구매할 토큰 찾기", + "search_your_tokens": "토큰 검색", + "error_executing_swap": "스왑 실행 오류", + "flashbots_protection": "Flashbots 보호", "tokens_input": { "tokens": "토큰", "sort": "정렬", @@ -1945,6 +2035,7 @@ }, "loading": "로딩 중...", "modal_types": { + "bridge": "브릿지", "confirm": "확인", "deposit": "예금", "receive": "받기", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "최근", "favorites": "즐겨찾기", "bridge": "브릿지", "verified": "인증됨", @@ -2849,6 +2941,8 @@ "forward": "앞으로", "back": "뒤로" } - } + }, + "copy": "복사", + "paste": "붙여넣기" } } diff --git a/src/languages/pt_BR.json b/src/languages/pt_BR.json index bef4cd22a2a..37c17e65acc 100644 --- a/src/languages/pt_BR.json +++ b/src/languages/pt_BR.json @@ -24,6 +24,7 @@ "tab_showcase": "Vitrine", "tab_points": "Pontos", "tab_positions": "Posições", + "tab_rewards": "Recompensas", "tab_transactions": "Transações", "tab_transactions_tooltip": "Transações e Transferências de Token", "tab_uniquetokens": "Tokens Únicos", @@ -431,6 +432,23 @@ "description": "Você também pode pressionar longamente o endereço\nacima para copiá-lo." } }, + "check_identifier": { + "title": "Autenticação Necessária", + "description": "Parece que você pode ter trocado de dispositivo ou reinstalado o Rainbow. Por favor, autentique-se para que possamos validar se seus dados da chave mestra ainda estão em bom estado.", + "action": "Autenticar", + "dismiss": "Dispensar", + "error_alert": { + "title": "Erro ao Validar a Chave Mestra", + "message": "Encontramos um erro ao validar seus dados da chave mestra. Por favor, tente novamente. Se o problema persistir, entre em contato com o suporte.", + "contact_support": "Contactar Suporte", + "cancel": "Cancelar" + }, + "failure_alert": { + "title": "Dados Inválidos da Chave Mestra", + "message": "Não conseguimos verificar a integridade dos seus dados da chave mestra. Por favor, restaure sua carteira e tente novamente. Se o problema persistir, entre em contato com o suporte.", + "action": "Continuar" + } + }, "cloud": { "backup_success": "Sua carteira foi salva com sucesso!" }, @@ -541,7 +559,8 @@ "from_divider": "de", "to_divider": "para", "view_on": "Visualizar em %{blockExplorerName}", - "view_on_etherscan": "Visualizar no Etherscan" + "view_on_etherscan": "Visualizar no Etherscan", + "copy_contract_address": "Copiar Endereço do Contrato" }, "all_networks": "Todas as redes", "filter_by_network": "Filtrar por rede", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Tempo de espera prolongado", - "description": "Aprox. %{time}" + "description": "Aprox. %{time}", + "description_prefix": "Aprox." }, "unknown_price": { "title": "Valor de mercado desconhecido", @@ -587,6 +607,7 @@ "small_market": "Mercado Pequeno", "small_market_try_smaller_amount": "Pequeno mercado — tente um valor menor", "you_are_losing": "Você está perdendo %{priceImpact}", + "you_are_losing_prefix": "Você está perdendo", "no_data": "Valor de mercado desconhecido", "label": "Possível perda", "no_data_subtitle": "Se decidir continuar, certifique-se de que está satisfeito com o valor cotado" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots Proteger", "losing": "Perdendo", "on": "Ligado", + "off": "Desativado", "price_impact": "Impacto no preço", "price_row_per_token": "por", "slippage_message": "Este é um mercado pequeno, então você está obtendo um preço ruim. Tente uma negociação menor!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} para %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} para %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Automático", + "degen_mode": "Modo Degen", + "degen_mode_description": "Pule a revisão para trocar mais rápido", + "maximum_sold": "Máximo Vendido", + "minimum_received": "Valor Mínimo Recebido", + "preferred_network": "Rede Preferida", + "rainbow_fee": "Taxa Rainbow incluída", + "settings": "Configurações" + }, "token_index": { "get_token": "Obter %{assetSymbol}", "makeup_of_token": "Composição de 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "Urgente", "custom": "Personalizado" }, - "network_fee": "Taxa estimada da rede", + "unable_to_determine_selected_gas": "Incapaz de determinar o gás selecionado", + "network_fee": "Est. Taxa de Rede", "current_base_fee": "Taxa de base atual", "max_base_fee": "Taxa base máxima", "miner_tip": "Gorjeta para o minerador", + "gas_price": "Preço do Gás", "max_transaction_fee": "Taxa máxima da transação", "warning_separator": "·", "lower_than_suggested": "Baixa · pode ficar preso", "higher_than_suggested": "Alta · pagando demais", + "likely_to_fail": "Baixa · provavelmente falhará", "max_base_fee_too_low_error": "Baixa · provavelmente falhará", "tip_too_low_error": "Baixa · provavelmente falhará", "alert_message_higher_miner_tip_needed": "É recomendado definir uma dica de minerador maior para evitar problemas.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Resgate seus pontos", + "title_rewards_line_1": "Bem-vindo a", + "title_rewards_line_2": "Recompensas Rainbow", "subtitle": "Pontos estão aqui. Descubra quantos você recebeu.", + "subtitle_rewards": "Seja recompensado por usar o Rainbow. Faça coisas onchain, ganhe pontos, ganhe ETH.", "get_started": "Comece Agora", "use_referral_code": "Utilize o Código de Indicação" }, "referral": { "title": "Insira seu código de indicação", - "subtitle": "Insira um código de indicação abaixo para resgatar sua recompensa bônus.", + "subtitle": "Insira um código de indicação abaixo para reivindicar seu bônus de pontos.", "back": "Voltar", "error": "Algo deu errado. Por favor, reinicie o aplicativo e tente novamente.", "get_started": "Comece Agora", @@ -1378,6 +1416,7 @@ "share_link": "Compartilhar Link", "next_drop": "Próxima Disponibilização", "your_rank": "Sua Classificação", + "no_change": "Sem Alterações", "of_x": "De %{totalUsers}", "referrals": "Indicações", "streak": "Sequência", @@ -1395,12 +1434,40 @@ "today": "Hoje", "error": "Ops!\nPor favor, puxe para atualizar.", "points": "pontos", + "points_capitalized": "Pontos", "referral_code_copied": "Código de referência copiado", "earn_points_for_referring": "Ganhe pontos quando seus indicados trocarem $100 através da Rainbow, além de ganhar 10% dos pontos que seus indicados acumularem", "already_claimed_points": "Desculpe! Você já resgatou seus pontos.", "now": "Agora", "unranked": "Sem Classificação", - "points_to_rank": "750 pontos para classificar" + "points_to_rank": "750 pontos para classificar", + "ranking": "Colocado em #%{rank}", + "available_to_claim": "Disponível para Reivindicação", + "claim": "Reivindicar %{value}", + "choose_claim_network": "Escolha a Rede para Reivindicação", + "claim_on_network": "Reivindicar na %{network}", + "claimed_on_network": "Reivindicado na %{network}", + "claiming_on_network": "Reivindicando na %{network}", + "error_claiming": "Reivindicação falhou", + "free_to_claim": "Livre para Reivindicar", + "has_bridge_fee": "Tem Taxa de Ponte", + "claim_rewards": "Reivindicar Recompensas", + "earned_last_week": "Ganho na Última Semana", + "top_10_earner": "Top 10 Ganhadores", + "zero_points": "0 Pontos", + "no_weekly_rank": "Sem Classificação Semanal", + "none": "Nenhum", + "my_earnings": "Meus Ganhos", + "claimed_earnings": "Ganhos Reivindicados", + "current_value": "Valor atual", + "rainbow_users_have_earned": "Os usuários do Rainbow ganharam", + "earn_eth_rewards": "Ganhar Recompensas em ETH", + "rewards_explainer": "Toda semana, os maiores ganhadores de pontos semanais receberão uma parte da receita onchain do Rainbow. Colete pontos trocando ou transacionando no Rainbow, usando dapps ou indicando amigos.", + "my_points": "Meus Pontos", + "refer_friends": "Indicar Amigos", + "try_again": "Tente Novamente", + "bridge_error": "Sucesso Parcial", + "bridge_error_explainer": "Suas recompensas foram reivindicadas com sucesso na Optimism, mas a transferência para %{network} falhou. Se desejar, você pode transferir suas recompensas manualmente usando o recurso Swap." }, "console": { "claim_bonus_paragraph": "Você receberá 100 pontos adicionais uma vez que trocar pelo menos $100 através do Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Muitas solicitações de inscrição, por favor, tente novamente mais tarde" }, "swap": { + "actions": { + "hold_to_swap": "Segure para Trocar", + "save": "Salvar", + "enter_amount": "Insira um valor", + "review": "Revisão", + "fetching_prices": "Buscando", + "swapping": "Trocando", + "select_token": "Selecionar Token", + "insufficient_funds": "Fundos insuficientes", + "insufficient": "Insuficiente", + "quote_error": "Erro na Cotação" + }, "aggregators": { "rainbow": "Arco-íris" }, + "bridging": "Conectando", + "swapping": "Trocando", + "max": "Máx", + "select": "Selecionar", + "token_to_get": "Token a Receber", + "token_to_swap": "Token a Trocar", + "no_balance": "Sem Saldo", + "find_a_token_to_buy": "Encontrar um token para comprar", + "search_your_tokens": "Buscar seus tokens", + "error_executing_swap": "Erro ao executar a troca", + "flashbots_protection": "Proteção Flashbots", "tokens_input": { "tokens": "Tokens", "sort": "Ordenar", @@ -1945,6 +2035,7 @@ }, "loading": "Carregando...", "modal_types": { + "bridge": "Ponte", "confirm": "Confirmar", "deposit": "Depósito", "receive": "Receber", @@ -1976,11 +2067,12 @@ }, "token_search": { "section_header": { + "recent": "Recente", "favorites": "Favoritos", "bridge": "Ponte", "verified": "Verificado", "unverified": "Não verificado", - "on_other_networks": "Em outras redes" + "on_other_networks": "Em Outras Redes" } }, "transactions": { @@ -2849,6 +2941,8 @@ "forward": "Avançar", "back": "Voltar" } - } + }, + "copy": "Copiar", + "paste": "Colar" } } diff --git a/src/languages/ru_RU.json b/src/languages/ru_RU.json index 230315220d8..7bbc404f7f8 100644 --- a/src/languages/ru_RU.json +++ b/src/languages/ru_RU.json @@ -24,6 +24,7 @@ "tab_showcase": "Витрина", "tab_points": "Очки", "tab_positions": "Позиции", + "tab_rewards": "Награды", "tab_transactions": "Транзакции", "tab_transactions_tooltip": "Транзакции и передачи токенов", "tab_uniquetokens": "Уникальные токены", @@ -431,6 +432,23 @@ "description": "Вы также можете долго нажать на ваш адрес\nвыше, чтобы скопировать его." } }, + "check_identifier": { + "title": "Требуется аутентификация", + "description": "Похоже, вы сменили устройство или переустановили Rainbow. Пожалуйста, аутентифицируйтесь, чтобы мы могли убедиться, что ваши данные в связке ключей находятся в хорошем состоянии.", + "action": "Аутентифицироваться", + "dismiss": "Отклонить", + "error_alert": { + "title": "Ошибка при проверке связки ключей", + "message": "Во время проверки данных вашей связки ключей произошла ошибка. Пожалуйста, попробуйте снова. Если ошибка повторяется, обратитесь в службу поддержки.", + "contact_support": "Обратитесь в службу поддержки", + "cancel": "Отменить" + }, + "failure_alert": { + "title": "Неверные данные связки ключей", + "message": "Мы не смогли проверить целостность данных вашей связки ключей. Пожалуйста, восстановите свой кошелек и попробуйте снова. Если ошибка повторяется, обратитесь в службу поддержки.", + "action": "Продолжить" + } + }, "cloud": { "backup_success": "Ваш кошелек успешно создан резервная копия!" }, @@ -541,7 +559,8 @@ "from_divider": "отправитель", "to_divider": "кому", "view_on": "Посмотреть на %{blockExplorerName}", - "view_on_etherscan": "Посмотреть на Etherscan" + "view_on_etherscan": "Посмотреть на Etherscan", + "copy_contract_address": "Скопировать адрес контракта" }, "all_networks": "Все сети", "filter_by_network": "Фильтр по сети", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Длительное ожидание", - "description": "Примерно %{time}" + "description": "Примерно %{time}", + "description_prefix": "Примерно" }, "unknown_price": { "title": "Рыночная стоимость неизвестна", @@ -587,6 +607,7 @@ "small_market": "Маленький рынок", "small_market_try_smaller_amount": "Небольшой рынок — попробуйте меньшую сумму", "you_are_losing": "Вы теряете %{priceImpact}", + "you_are_losing_prefix": "Вы теряете", "no_data": "Рыночная стоимость неизвестна", "label": "Возможная потеря", "no_data_subtitle": "Если вы решите продолжить, убедитесь, что вас устраивает указанное количество" @@ -687,6 +708,7 @@ "flashbots_protect": "Защита от Flashbots", "losing": "Убыток", "on": "Включено", + "off": "Выкл.", "price_impact": "Влияние на цену", "price_row_per_token": "за", "slippage_message": "Это небольшой рынок, поэтому вы получаете плохую цену. Попробуйте сделать меньшую сделку!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} по %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} по %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Авто", + "degen_mode": "Режим Degen", + "degen_mode_description": "Пропустите лист проверки для ускорения обмена", + "maximum_sold": "Максимум продано", + "minimum_received": "Минимальная получаемая", + "preferred_network": "Предпочитаемая сеть", + "rainbow_fee": "Включена комиссия Rainbow", + "settings": "Настройки" + }, "token_index": { "get_token": "Получите %{assetSymbol}", "makeup_of_token": "Makeup из 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "Срочный", "custom": "Пользовательский" }, - "network_fee": "Ожидаемая сетевая комиссия", + "unable_to_determine_selected_gas": "Не удалось определить выбранный газ", + "network_fee": "Оценочная сетевая комиссия", "current_base_fee": "Текущая базовая комиссия", "max_base_fee": "Максимальная базовая комиссия", "miner_tip": "Чаевые для майнера", + "gas_price": "Цена газа", "max_transaction_fee": "Максимальная комиссия за транзакцию", "warning_separator": "·", "lower_than_suggested": "Низкая · может застрять", "higher_than_suggested": "Высокая · переплата", + "likely_to_fail": "Низкая · вероятность сбоя", "max_base_fee_too_low_error": "Низкая · вероятность сбоя", "tip_too_low_error": "Низкая · вероятность сбоя", "alert_message_higher_miner_tip_needed": "Рекомендуется установить более высокий сбор майнера, чтобы избежать проблем.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Заберите свои баллы", + "title_rewards_line_1": "Добро пожаловать", + "title_rewards_line_2": "Rainbow Rewards", "subtitle": "Баллы у вас. Узнайте, сколько вам начислено.", + "subtitle_rewards": "Получите награды за использование Rainbow. Выполняйте действия onchain, получайте баллы, зарабатывайте ETH.", "get_started": "Начать", "use_referral_code": "Используйте реферальный код" }, "referral": { "title": "Введите ваш реферальный код", - "subtitle": "Введите реферальный код ниже, чтобы получить ваш бонусный приз.", + "subtitle": "Введите реферальный код ниже, чтобы получить бонусные баллы.", "back": "Назад", "error": "Что-то пошло не так. Пожалуйста, перезапустите приложение и попробуйте снова.", "get_started": "Начать", @@ -1378,6 +1416,7 @@ "share_link": "Поделиться ссылкой", "next_drop": "Следующее начисление", "your_rank": "Ваш ранг", + "no_change": "Без изменений", "of_x": "Из %{totalUsers}", "referrals": "Рефералы", "streak": "Серия", @@ -1395,12 +1434,40 @@ "today": "Сегодня", "error": "Упс!\nПожалуйста, потяните вниз для обновления.", "points": "баллы", + "points_capitalized": "Очки", "referral_code_copied": "Реферальный код скопирован", "earn_points_for_referring": "Получайте баллы, когда ваши рефералы меняют $100 через Rainbow, плюс зарабатывайте 10% от баллов, которые получают ваши рефералы", "already_claimed_points": "Извините! Вы уже забрали свои баллы.", "now": "Сейчас", "unranked": "Без ранга", - "points_to_rank": "750 баллов до ранга" + "points_to_rank": "750 баллов до ранга", + "ranking": "Место #%{rank}", + "available_to_claim": "Доступно для получения", + "claim": "Получить %{value}", + "choose_claim_network": "Выбрать сеть для получения", + "claim_on_network": "Получить в сети %{network}", + "claimed_on_network": "Получено в сети %{network}", + "claiming_on_network": "Получение в сети %{network}", + "error_claiming": "Получение не удалось", + "free_to_claim": "Бесплатно для получения", + "has_bridge_fee": "Имеется комиссия за мост", + "claim_rewards": "Получение наград", + "earned_last_week": "Заработано на прошлой неделе", + "top_10_earner": "Топ 10 заработавших", + "zero_points": "0 баллов", + "no_weekly_rank": "Нет недельного рейтинга", + "none": "Нет", + "my_earnings": "Мои заработки", + "claimed_earnings": "Полученные заработки", + "current_value": "Текущая стоимость", + "rainbow_users_have_earned": "Пользователи Rainbow заработали", + "earn_eth_rewards": "Зарабатывайте ETH награды", + "rewards_explainer": "Каждую неделю лучшие зарабатыватели баллов получат часть дохода Rainbow в сети. Собирайте баллы, обменяя или проводя транзакции в Rainbow, используя dapps или приглашая друзей.", + "my_points": "Мои баллы", + "refer_friends": "Пригласить друзей", + "try_again": "Попробуйте снова", + "bridge_error": "Частичный успех", + "bridge_error_explainer": "Ваши награды были успешно получены на Optimism, но перевод на %{network} не удался. Если хотите, вы можете вручную перевести ваши награды, используя функцию обмена." }, "console": { "claim_bonus_paragraph": "Вы получите дополнительные 100 баллов, когда обменяете хотя бы $100 через Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Слишком много запросов на регистрацию, пожалуйста, попробуйте позже" }, "swap": { + "actions": { + "hold_to_swap": "Удерживайте для обмена", + "save": "Сохранить", + "enter_amount": "Введите сумму", + "review": "Просмотр", + "fetching_prices": "Получение данных", + "swapping": "Смена", + "select_token": "Выберите токен", + "insufficient_funds": "Недостаточно средств", + "insufficient": "Недостаточно", + "quote_error": "Ошибка котировки" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "Перевод", + "swapping": "Смена", + "max": "Макс.", + "select": "Выбрать", + "token_to_get": "Токен для получения", + "token_to_swap": "Токен для обмена", + "no_balance": "Нет Баланса", + "find_a_token_to_buy": "Найдите токен для покупки", + "search_your_tokens": "Поиск ваших токенов", + "error_executing_swap": "Ошибка при выполнении обмена", + "flashbots_protection": "Защита Flashbots", "tokens_input": { "tokens": "Токены", "sort": "Сортировать", @@ -1945,6 +2035,7 @@ }, "loading": "Загрузка...", "modal_types": { + "bridge": "Мост", "confirm": "Подтвердить", "deposit": "Депозит", "receive": "Получить", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "Последние", "favorites": "Избранное", "bridge": "Мост", "verified": "Проверенные", @@ -2849,6 +2941,8 @@ "forward": "Вперед", "back": "Назад" } - } + }, + "copy": "Копировать", + "paste": "Вставить" } } diff --git a/src/languages/th_TH.json b/src/languages/th_TH.json index aa5c55d13d5..9c82f0bfb88 100644 --- a/src/languages/th_TH.json +++ b/src/languages/th_TH.json @@ -24,6 +24,7 @@ "tab_showcase": "โชว์เคส", "tab_points": "แต้ม", "tab_positions": "ตำแหน่ง", + "tab_rewards": "รางวัล", "tab_transactions": "การทำธุรกรรม", "tab_transactions_tooltip": "การทำธุรกรรมและการโอนโทเค็น", "tab_uniquetokens": "โทเค็นที่ไม่ซ้ำกัน", @@ -431,6 +432,23 @@ "description": "คุณยังสามารถกดค้างที่อยู่\nข้างต้นเพื่อคัดลอก" } }, + "check_identifier": { + "title": "ต้องการการตรวจสอบตัวตน", + "description": "ดูเหมือนว่าคุณอาจเปลี่ยนอุปกรณ์หรือทำการติดตั้ง Rainbow ใหม่ กรุณาตรวจสอบตัวตนเพื่อให้เราสามารถตรวจสอบข้อมูลใน keychain ของคุณยังคงอยู่ในสถานะที่ดี", + "action": "ตรวจสอบตัวตน", + "dismiss": "ยกเลิก", + "error_alert": { + "title": "ข้อผิดพลาดในการตรวจสอบ keychain", + "message": "เราพบข้อผิดพลาดขณะตรวจสอบข้อมูล keychain ของคุณ กรุณาลองอีกครั้ง หากปัญหายังมีอยู่ กรุณาติดต่อฝ่ายสนับสนุน", + "contact_support": "ติดต่อฝ่ายสนับสนุน", + "cancel": "ยกเลิก" + }, + "failure_alert": { + "title": "ข้อมูล keychain ไม่ถูกต้อง", + "message": "เราไม่สามารถตรวจสอบความสมบูรณ์ของข้อมูล keychain ของคุณ กรุณากู้คืนวอลเล็ตของคุณและลองใหม่อีกครั้ง หากปัญหายังคงมีอยู่ กรุณาติดต่อฝ่ายสนับสนุน", + "action": "ดำเนินการต่อ" + } + }, "cloud": { "backup_success": "กระเป๋าเงินของคุณได้รับการสำรองข้อมูลเรียบร้อยแล้ว!" }, @@ -541,7 +559,8 @@ "from_divider": "จาก", "to_divider": "ไปยัง", "view_on": "ดูบน %{blockExplorerName}", - "view_on_etherscan": "ดูบน Etherscan" + "view_on_etherscan": "ดูบน Etherscan", + "copy_contract_address": "คัดลอกที่อยู่สัญญา" }, "all_networks": "ทุกเครือข่าย", "filter_by_network": "กรองตามเครือข่าย", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "รอนาน", - "description": "ประมาณ %{time}" + "description": "ประมาณ %{time}", + "description_prefix": "ประมาณ" }, "unknown_price": { "title": "ไม่ทราบมูลค่าตลาด", @@ -587,6 +607,7 @@ "small_market": "ตลาดเล็ก", "small_market_try_smaller_amount": "ตลาดเล็ก - ลองปริมาณที่น้อยกว่า", "you_are_losing": "คุณสูญเสีย %{priceImpact}", + "you_are_losing_prefix": "คุณกำลังสูญเสีย", "no_data": "ไม่ทราบมูลค่าตลาด", "label": "เสี่ยงที่จะสูญเสีย", "no_data_subtitle": "หากคุณตัดสินใจดำเนินการต่อ โปรดแน่ใจว่าคุณพอใจกับจำนวนที่ได้รับการเสนอราคา" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots ป้องกัน", "losing": "ขาดทุน", "on": "เปิด", + "off": "ปิด", "price_impact": "ผลกระทบของราคา", "price_row_per_token": "ต่อ", "slippage_message": "นี้เป็นตลาดขนาดเล็ก, ดังนั้นคุณกำลังได้รับราคาที่ไม่ดี ลองเทรดขนาดเล็กลงดู!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} สำหรับ %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} สำหรับ %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "อัตโนมัติ", + "degen_mode": "โหมดดีเจ็น", + "degen_mode_description": "ข้ามหน้าตรวจสอบเพื่อแลกเปลี่ยนเร็วขึ้น", + "maximum_sold": "ขายสูงสุด", + "minimum_received": "รับต่ำสุด", + "preferred_network": "เครือข่ายที่คุณต้องการ", + "rainbow_fee": "รวมค่าธรรมเนียม Rainbow", + "settings": "ตั้งค่า" + }, "token_index": { "get_token": "รับ %{assetSymbol}", "makeup_of_token": "ส่วนประกอบของ 1 %{assetSymbol}", @@ -1030,14 +1062,17 @@ "urgent": "ด่วน", "custom": "กำหนดเอง" }, - "network_fee": "ค่าธรรมเนียมเครือข่ายโดยประมาณ", + "unable_to_determine_selected_gas": "ไม่สามารถระบุก๊าซที่เลือกได้", + "network_fee": "ค่าธรรมเนียมเครือข่าย (ประเมิน)", "current_base_fee": "ค่าฐานปัจจุบัน", "max_base_fee": "ค่าฐานสูงสุด", "miner_tip": "ค่าเทิปของนักขุด", + "gas_price": "ราคาก๊าซ", "max_transaction_fee": "ค่าธรรมเนียมการทำธุรกรรมสูงสุด", "warning_separator": "·", "lower_than_suggested": "ต่ำ · อาจติดขัด", "higher_than_suggested": "สูง · จ่ายเกิน", + "likely_to_fail": "ต่ำ · มีโอกาสล้มเหลว", "max_base_fee_too_low_error": "ต่ำ · มีโอกาสล้มเหลว", "tip_too_low_error": "ต่ำ · มีโอกาสล้มเหลว", "alert_message_higher_miner_tip_needed": "แนะนำให้ตั้งค่าค่าธรรมเนียมทิปคนขุดที่สูงขึ้นเพื่อหลีกเลี่ยงปัญหา", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "เรียกร้องคะแนนของคุณ", + "title_rewards_line_1": "ยินดีต้อนรับสู่", + "title_rewards_line_2": "Rainbow Rewards", "subtitle": "คะแนนของคุณมาแล้ว หากดูว่าคุณได้รับกี่คะแนน", + "subtitle_rewards": "รับรางวัลจากการใช้ Rainbow ทำสิ่งต่างๆ บนเชน รับคะแนน และรับ ETH", "get_started": "เริ่มทำงาน", "use_referral_code": "ใช้รหัสอ้างอิง" }, "referral": { "title": "กรอกรหัสอ้างอิงของคุณ", - "subtitle": "กรอกรหัสอ้างอิงด้านล่างเพื่อเรียกร้องรางวัลโบนัสของคุณ", + "subtitle": "ป้อนรหัสอ้างอิงด้านล่างเพื่อรับโบนัสคะแนนของคุณ", "back": "ย้อนกลับ", "error": "เกิดข้อผิดพลาดบางอย่าง กรุณาเริ่มต้นแอปใหม่และลองใหม่อีกครั้ง", "get_started": "เริ่มทำงาน", @@ -1378,6 +1416,7 @@ "share_link": "แชร์ลิงค์", "next_drop": "รางวัลถัดไป", "your_rank": "อันดับของคุณ", + "no_change": "ไม่มีการเปลี่ยนแปลง", "of_x": "จาก %{totalUsers}", "referrals": "การอ้างอิง", "streak": "แนวโน้ม", @@ -1395,12 +1434,40 @@ "today": "วันนี้", "error": "อุ๊ปส์!\nกรุณาดึงเพื่อรีเฟรช.", "points": "คะแนน", + "points_capitalized": "แต้ม", "referral_code_copied": "คัดลอกโค้ดการอ้างอิงเรียบร้อย", "earn_points_for_referring": "รับคะแนนเมื่อผู้ที่คุณอ้างอิงแลกเปลี่ยนเงินผ่าน Rainbow มูลค่า $100 และรับ 10% ของคะแนนที่ผู้ที่คุณอ้างอิงได้รับ", "already_claimed_points": "ขออภัย! คุณได้รับคะแนนนี้แล้ว.", "now": "ตอนนี้", "unranked": "ไม่มีอันดับ", - "points_to_rank": "ต้องการคะแนนอีก 750 คะแนนเพื่อจัดอันดับ" + "points_to_rank": "ต้องการคะแนนอีก 750 คะแนนเพื่อจัดอันดับ", + "ranking": "อันดับที่ %{rank}", + "available_to_claim": "พร้อมสำหรับการรับ", + "claim": "รับ %{value}", + "choose_claim_network": "เลือกเครือข่ายการรับ", + "claim_on_network": "รับบน %{network}", + "claimed_on_network": "รับแล้วบน %{network}", + "claiming_on_network": "กำลังรับบน %{network}", + "error_claiming": "การเรียกร้องล้มเหลว", + "free_to_claim": "ฟรีสำหรับการรับ", + "has_bridge_fee": "มีค่าธรรมเนียมการโอน", + "claim_rewards": "รับรางวัล", + "earned_last_week": "ได้รับเมื่อสัปดาห์ที่แล้ว", + "top_10_earner": "ผู้ทำรายได้สูงสุด 10 อันดับแรก", + "zero_points": "0 คะแนน", + "no_weekly_rank": "ไม่มีอันดับประจําสัปดาห์", + "none": "ไม่มี", + "my_earnings": "รายได้ของฉัน", + "claimed_earnings": "รายได้ที่รับแล้ว", + "current_value": "มูลค่าปัจจุบัน", + "rainbow_users_have_earned": "ผู้ใช้ Rainbow ได้รับ", + "earn_eth_rewards": "รับรางวัล ETH", + "rewards_explainer": "ทุกสัปดาห์ ผู้ที่ได้รับคะแนนประจำสัปดาห์สูงสุดจะได้รับส่วนแบ่งจากรายได้ของ Rainbow บนเชน สะสมคะแนนโดยการแลกเปลี่ยนหรือทำธุรกรรมใน Rainbow ใช้ dapps หรือแนะนำเพื่อน", + "my_points": "คะแนนของฉัน", + "refer_friends": "แนะนำเพื่อน", + "try_again": "ลองใหม่", + "bridge_error": "สำเร็จบางส่วน", + "bridge_error_explainer": "ของรางวัลของคุณถูกเรียกไปเรียบร้อยบน Optimism อย่างไรก็ตาม การโอนไปยัง %{network} ล้มเหลว หากคุณต้องการ คุณสามารถบริดจ์ของรางวัลด้วยตนเองโดยใช้ฟีเจอร์แลกเปลี่ยน" }, "console": { "claim_bonus_paragraph": "คุณจะได้รับแต้มเพิ่มอีก 100 แต้ม เมื่อคุณทำการแลกเปลี่ยนเงินอย่างน้อย $100 ผ่าน Rainbow", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "คำขอสมัครเยอะเกินไป, โปรดลองอีกครั้งในภายหลัง" }, "swap": { + "actions": { + "hold_to_swap": "กดแลกเปลี่ยน", + "save": "บันทึก", + "enter_amount": "ป้อนจำนวน", + "review": "รีวิว", + "fetching_prices": "กำลังดึงข้อมูล", + "swapping": "กำลังแลกเปลี่ยน", + "select_token": "เลือกโทเคน", + "insufficient_funds": "เงินทุนไม่เพียงพอ", + "insufficient": "ไม่เพียงพอ", + "quote_error": "ข้อผิดพลาดของราคาที่เสนอ" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "กำลังสร้างสะพาน", + "swapping": "กำลังแลกเปลี่ยน", + "max": "สูงสุด", + "select": "เลือก", + "token_to_get": "โทเคนที่จะได้รับ", + "token_to_swap": "โทเคนที่จะแลกเปลี่ยน", + "no_balance": "ไม่มีคงเหลือ", + "find_a_token_to_buy": "ค้นหาโทเคนที่จะซื้อ", + "search_your_tokens": "ค้นหาโทเค็นของคุณ", + "error_executing_swap": "ข้อผิดพลาดในการดำเนินการแลกเปลี่ยน", + "flashbots_protection": "การป้องกัน Flashbots", "tokens_input": { "tokens": "โทเคน", "sort": "เรียงลำดับ", @@ -1945,6 +2035,7 @@ }, "loading": "กำลังโหลด...", "modal_types": { + "bridge": "สะพาน", "confirm": "ยืนยัน", "deposit": "ฝาก", "receive": "รับ", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "ล่าสุด", "favorites": "รายการโปรด", "bridge": "สะพาน", "verified": "ได้รับการตรวจสอบ", @@ -2849,6 +2941,8 @@ "forward": "ส่งต่อ", "back": "ย้อนกลับ" } - } + }, + "copy": "คัดลอก", + "paste": "วาง" } } diff --git a/src/languages/tr_TR.json b/src/languages/tr_TR.json index 7451833c410..0ccec26d073 100644 --- a/src/languages/tr_TR.json +++ b/src/languages/tr_TR.json @@ -24,6 +24,7 @@ "tab_showcase": "Vitrin", "tab_points": "Noktalar", "tab_positions": "Pozisyonlar", + "tab_rewards": "Ödüller", "tab_transactions": "İşlemler", "tab_transactions_tooltip": "İşlemler ve Token Transferleri", "tab_uniquetokens": "Eşsiz Tokenler", @@ -431,6 +432,23 @@ "description": "Ayrıca yukarıdaki\nadresinizi kopyalamak için uzun basabilirsiniz." } }, + "check_identifier": { + "title": "Kimlik Doğrulama Gerekiyor", + "description": "Cihaz değiştirmiş veya Rainbow'u yeniden yüklemiş olabilirsiniz. Lütfen kimlik doğrulaması yapın, böylece anahtar zincirinizi sağlıklı bir durumda olduğunu doğrulayabiliriz.", + "action": "Kimlik Doğrula", + "dismiss": "İptal Et", + "error_alert": { + "title": "Anahtar Zinciri Doğrulama Hatası", + "message": "Anahtar zinciri verilerinizi doğrularken bir hata ile karşılaştık. Lütfen tekrar deneyin. Sorun devam ederse, lütfen destek ile iletişime geçin.", + "contact_support": "Desteğe Ulaşın", + "cancel": "İptal" + }, + "failure_alert": { + "title": "Geçersiz Anahtar Zinciri Verisi", + "message": "Anahtar zinciri verilerinizin bütünlüğünü doğrulamak mümkün olmadı. Lütfen cüzdanınızı geri yükleyin ve tekrar deneyin. Sorun devam ederse, lütfen destek ile iletişime geçin.", + "action": "Devam Et" + } + }, "cloud": { "backup_success": "Cüzdanınız başarıyla yedeklendi!" }, @@ -541,7 +559,8 @@ "from_divider": "gönderen", "to_divider": "kime", "view_on": "%{blockExplorerName}'da Görüntüle", - "view_on_etherscan": "Etherscan'da Görüntüle" + "view_on_etherscan": "Etherscan'da Görüntüle", + "copy_contract_address": "Kontrat Adresini Kopyala" }, "all_networks": "Tüm Ağlar", "filter_by_network": "Ağa Göre Filtrele", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "Uzun bekleme", - "description": "Yaklaşık %{time}" + "description": "Yaklaşık %{time}", + "description_prefix": "Yaklaşık." }, "unknown_price": { "title": "Piyasa Değeri Bilinmiyor", @@ -587,6 +607,7 @@ "small_market": "Küçük Pazar", "small_market_try_smaller_amount": "Küçük piyasa — daha küçük bir miktar deneyin", "you_are_losing": "%{priceImpact} kaybediyorsun", + "you_are_losing_prefix": "Kaybediyorsunuz", "no_data": "Piyasa Değeri Bilinmiyor", "label": "Olası kayıp", "no_data_subtitle": "Devam etmeye karar verirseniz, belirtilen miktardan memnun olduğunuzdan emin olun" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots Koruma", "losing": "Kaybediyorum", "on": "Açık", + "off": "Kapalı", "price_impact": "Fiyat etkisi", "price_row_per_token": "her", "slippage_message": "Bu küçük bir piyasa, bu yüzden kötü bir fiyat alıyorsunuz. Daha küçük bir işlem deneyin!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} için %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} için %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "Oto", + "degen_mode": "Degen Modu", + "degen_mode_description": "Hızlı Takas İçin İnceleme Sayfasını Atlayın", + "maximum_sold": "Azami Satılan", + "minimum_received": "Asgari Alınan", + "preferred_network": "Tercih Edilen Ağ", + "rainbow_fee": "Dahil Olan Rainbow Ücreti", + "settings": "Ayarlar" + }, "token_index": { "get_token": "Al %{assetSymbol}", "makeup_of_token": "1 %{assetSymbol}oluşum", @@ -1030,14 +1062,17 @@ "urgent": "Acil", "custom": "Özel" }, - "network_fee": "Tahmini ağ ücreti", + "unable_to_determine_selected_gas": "Seçilen gazı belirlemek mümkün değil", + "network_fee": "Tahmini Ağ Ücreti", "current_base_fee": "Geçerli temel ücret", "max_base_fee": "Mak. taban ücreti", "miner_tip": "Madenci bahşişi", + "gas_price": "Gas Fiyatı", "max_transaction_fee": "Maksimum işlem ücreti", "warning_separator": "·", "lower_than_suggested": "Düşük · takılabilir", "higher_than_suggested": "Yüksek · fazla ödeme", + "likely_to_fail": "Düşük · başarısız olması muhtemel", "max_base_fee_too_low_error": "Düşük · başarısız olması muhtemel", "tip_too_low_error": "Düşük · başarısız olması muhtemel", "alert_message_higher_miner_tip_needed": "Sorunları önlemek için daha yüksek bir madenci bahşişi ayarlamak önerilir.", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "Puanlarınızı Talep Edin", + "title_rewards_line_1": "Hoşgeldiniz", + "title_rewards_line_2": "Rainbow Ödülleri", "subtitle": "Puanların burada. Ne kadar puan kazandığınızı öğrenin.", + "subtitle_rewards": "Rainbow'u kullanarak ödüller kazanın. Zincir üstü işlemleri yapın, puan kazanın, ETH kazanın.", "get_started": "Başlayın", "use_referral_code": "Yönlendirme Kodu Kullan" }, "referral": { "title": "Referans kodunuzu girin", - "subtitle": "Bonus ödülünüzü talep etmek için aşağıya bir yönlendirme kodu giriniz.", + "subtitle": "Puan bonusunuzu almak için aşağıya bir referans kodu girin.", "back": "Geri", "error": "Bir şeyler ters gitti. Lütfen uygulamayı yeniden başlatın ve tekrar deneyin.", "get_started": "Başlayın", @@ -1378,6 +1416,7 @@ "share_link": "Linki Paylaş", "next_drop": "Sonraki Dağıtım", "your_rank": "Sıralamanız", + "no_change": "Değişiklik Yok", "of_x": "%{totalUsers}adet", "referrals": "Referanslar", "streak": "Serilik", @@ -1395,12 +1434,40 @@ "today": "Bugün", "error": "Hata!\nYenilemek için aşağı çekin.", "points": "puanlar", + "points_capitalized": "Noktalar", "referral_code_copied": "Referans kodu kopyalandı", "earn_points_for_referring": "Rainbow üzerinden takas yapan tavsiyeleriniz için puan kazanın, ayrıca tavsiyelerinizin kazandığı puanların %10'unu kazanın", "already_claimed_points": "Üzgünüz! Puanlarınızı zaten talep ettiniz.", "now": "Şimdi", "unranked": "Sıralamasız", - "points_to_rank": "Rütbeye ulaşmak için 750 puan" + "points_to_rank": "Rütbeye ulaşmak için 750 puan", + "ranking": "%{rank} numarasıyla yerleştirildi", + "available_to_claim": "Talep Edilebilir", + "claim": "%{value} Talep Et", + "choose_claim_network": "Talep Ağı Seçin", + "claim_on_network": "%{network} ağına talep et", + "claimed_on_network": "%{network} ağına talep edildi", + "claiming_on_network": "Talep ediliyor %{network} ağına", + "error_claiming": "Talep Başarısız", + "free_to_claim": "Talep Etmek Ücretsiz", + "has_bridge_fee": "Köprü Ücreti Var", + "claim_rewards": "Ödülleri Talep Et", + "earned_last_week": "Geçen Hafta Kazanılanlar", + "top_10_earner": "İlk 10 Kazanan", + "zero_points": "0 Puan", + "no_weekly_rank": "Haftalık Sıralama Yok", + "none": "Hiçbiri", + "my_earnings": "Kazanımlarım", + "claimed_earnings": "Talep Edilen Kazançlar", + "current_value": "Güncel Değer", + "rainbow_users_have_earned": "Rainbow kullanıcıları kazandı", + "earn_eth_rewards": "ETH Ödülleri Kazanın", + "rewards_explainer": "Her hafta, en çok puan kazanan haftalık kazananlar Rainbow’un zincir üstü gelirinin bir kısmını alacaklar. Puan toplamak için Rainbow'da takas yapın veya işlem yapın, dapp'leri kullanın veya arkadaşlarınızı davet edin.", + "my_points": "Puanlarım", + "refer_friends": "Arkadaşları Davet Et", + "try_again": "Tekrar Deneyin", + "bridge_error": "Kısmi Başarı", + "bridge_error_explainer": "Ödülleriniz Optimism üzerinde başarıyla alındı, ancak %{network} ağına transfer başarısız oldu. İsterseniz, Swap özelliğini kullanarak ödüllerinizi manuel olarak aktarabilirsiniz." }, "console": { "claim_bonus_paragraph": "Rainbow üzerinden en az 100 dolarlık takas yapmanız durumunda ekstra 100 puan alacaksınız", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "Çok fazla kayıt talebi oldu, lütfen daha sonra tekrar deneyin" }, "swap": { + "actions": { + "hold_to_swap": "Değiştirmek için Tut", + "save": "Kaydet", + "enter_amount": "Miktar Girin", + "review": "İncele", + "fetching_prices": "Alınıyor", + "swapping": "Takas Yapılıyor", + "select_token": "Token Seçin", + "insufficient_funds": "Yetersiz Bakiye", + "insufficient": "Yetersiz", + "quote_error": "Teklif Hatası" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "Köprüleme", + "swapping": "Takas Yapılıyor", + "max": "Maks", + "select": "Seç", + "token_to_get": "Alınacak Token", + "token_to_swap": "Takas Edilecek Token", + "no_balance": "Bakiye Yok", + "find_a_token_to_buy": "Satın Almak İçin Bir Token Bulun", + "search_your_tokens": "Tokenlarınızı Arayın", + "error_executing_swap": "Takas Yürütme Hatası", + "flashbots_protection": "Flashbots Koruması", "tokens_input": { "tokens": "Tokenler", "sort": "Sırala", @@ -1945,6 +2035,7 @@ }, "loading": "Yükleniyor...", "modal_types": { + "bridge": "Köprü", "confirm": "Onayla", "deposit": "Mevduat", "receive": "Al", @@ -1976,11 +2067,12 @@ }, "token_search": { "section_header": { + "recent": "Son", "favorites": "Favoriler", "bridge": "Köprü", "verified": "Doğrulanmış", "unverified": "Doğrulanmamış", - "on_other_networks": "Diğer ağlarda" + "on_other_networks": "Diğer Ağlarda" } }, "transactions": { @@ -2849,6 +2941,8 @@ "forward": "İleri", "back": "Geri" } - } + }, + "copy": "Kopyala", + "paste": "Yapıştır" } } diff --git a/src/languages/zh_CN.json b/src/languages/zh_CN.json index 9844b6847b4..a347d97c12e 100644 --- a/src/languages/zh_CN.json +++ b/src/languages/zh_CN.json @@ -24,6 +24,7 @@ "tab_showcase": "展示", "tab_points": "积分", "tab_positions": "持仓", + "tab_rewards": "奖励", "tab_transactions": "交易", "tab_transactions_tooltip": "交易和代币转账", "tab_uniquetokens": "唯一代币", @@ -431,6 +432,23 @@ "description": "您也可以长按上面的\n地址来复制。" } }, + "check_identifier": { + "title": "需要认证", + "description": "看起来您可能已经更换了设备或重新安装了彩虹钱包。请进行认证,以便我们可以验证您的密钥链数据是否仍处于健康状态。", + "action": "认证", + "dismiss": "关闭", + "error_alert": { + "title": "密钥链验证错误", + "message": "我们在验证您的密钥链数据时遇到了错误。请再试一次。如果问题仍然存在,请联系支持人员。", + "contact_support": "联系支持", + "cancel": "取消" + }, + "failure_alert": { + "title": "无效的密钥链数据", + "message": "我们无法验证您的密钥链数据的完整性。请恢复您的钱包并重试。如果问题仍然存在,请联系支持人员。", + "action": "继续" + } + }, "cloud": { "backup_success": "您的钱包已成功备份!" }, @@ -541,7 +559,8 @@ "from_divider": "来自", "to_divider": "至", "view_on": "在 %{blockExplorerName}查看", - "view_on_etherscan": "在 Etherscan 上查看" + "view_on_etherscan": "在 Etherscan 上查看", + "copy_contract_address": "复制合约地址" }, "all_networks": "所有网络", "filter_by_network": "按网络筛选", @@ -577,7 +596,8 @@ "price_impact": { "long_wait": { "title": "等待时间较长", - "description": "大约%{time}" + "description": "大约%{time}", + "description_prefix": "约" }, "unknown_price": { "title": "市场价值未知", @@ -587,6 +607,7 @@ "small_market": "小市场", "small_market_try_smaller_amount": "市场较小—尝试较小的金额", "you_are_losing": "您失去了%{priceImpact}", + "you_are_losing_prefix": "您正在损失", "no_data": "市场价值未知", "label": "可能的损失", "no_data_subtitle": "如果您决定继续,请确保您对所引用的金额感到满意" @@ -687,6 +708,7 @@ "flashbots_protect": "Flashbots保护", "losing": "正在损失", "on": "开", + "off": "关闭", "price_impact": "价格影响", "price_row_per_token": "每", "slippage_message": "这是一个小市场,所以您的价格不好。尝试较小的交易!", @@ -724,6 +746,16 @@ "input_exchange_rate": "1 %{inputSymbol} 兑换至 %{executionRate} %{outputSymbol}", "output_exchange_rate": "1 %{outputSymbol} 兑换至 %{executionRate} %{inputSymbol}" }, + "swap_details_v2": { + "automatic": "自动", + "degen_mode": "Degen 模式", + "degen_mode_description": "跳过审核表以更快地交换", + "maximum_sold": "最大出售量", + "minimum_received": "最低接收金额", + "preferred_network": "首选网络", + "rainbow_fee": "包括彩虹费", + "settings": "设置" + }, "token_index": { "get_token": "获取 %{assetSymbol}", "makeup_of_token": "1 %{assetSymbol}的构成", @@ -1030,14 +1062,17 @@ "urgent": "紧急", "custom": "自定义" }, - "network_fee": "预计网络费", + "unable_to_determine_selected_gas": "无法确定选择的Gas", + "network_fee": "估计网络费用", "current_base_fee": "当前基本费用", "max_base_fee": "最大基础费用", "miner_tip": "矿工小费", + "gas_price": "Gas价格", "max_transaction_fee": "最大交易费用", "warning_separator": "·", "lower_than_suggested": "低·可能会卡住", "higher_than_suggested": "高·过度支付", + "likely_to_fail": "低·可能会失败", "max_base_fee_too_low_error": "低·可能会失败", "tip_too_low_error": "低·可能会失败", "alert_message_higher_miner_tip_needed": "建议设置更高的矿工小费以避免问题。", @@ -1357,13 +1392,16 @@ }, "claim": { "title": "领取你的积分", + "title_rewards_line_1": "欢迎来到", + "title_rewards_line_2": "彩虹奖励", "subtitle": "积分就在这里。查看你获得了多少积分。", + "subtitle_rewards": "使用彩虹钱包获取奖励。在链上做事,获取积分,赚取ETH。", "get_started": "开始", "use_referral_code": "使用推荐码" }, "referral": { "title": "输入你的推荐码", - "subtitle": "在下方输入推荐码以领取你的奖励红利。", + "subtitle": "在下面输入推荐代码以领取您的积分奖励。", "back": "返回", "error": "出现了一些问题。请重启应用并重试。", "get_started": "开始", @@ -1378,6 +1416,7 @@ "share_link": "分享链接", "next_drop": "下一次发布", "your_rank": "你的排名", + "no_change": "无变化", "of_x": "共 %{totalUsers}个", "referrals": "推荐人", "streak": "连续登录", @@ -1395,12 +1434,40 @@ "today": "今天", "error": "糟糕!\n请下拉刷新。", "points": "积分", + "points_capitalized": "积分", "referral_code_copied": "推荐码已复制", "earn_points_for_referring": "当您的推荐人通过Rainbow换取100美元时,您可以获得积分,此外您还可以获得推荐人所赚积分的10%", "already_claimed_points": "抱歉!您已经领取了您的积分。", "now": "现在", "unranked": "未排名", - "points_to_rank": "距离排名还需750积分" + "points_to_rank": "距离排名还需750积分", + "ranking": "排名第%{rank}位", + "available_to_claim": "可领取", + "claim": "领取%{value}", + "choose_claim_network": "选择领取网络", + "claim_on_network": "在%{network}上领取", + "claimed_on_network": "已在%{network}上领取", + "claiming_on_network": "正在%{network}上领取", + "error_claiming": "认领失败", + "free_to_claim": "免费领取", + "has_bridge_fee": "有桥接费用", + "claim_rewards": "领取奖励", + "earned_last_week": "上周赚取", + "top_10_earner": "前10名收入者", + "zero_points": "0分", + "no_weekly_rank": "无每周排名", + "none": "无", + "my_earnings": "我的收入", + "claimed_earnings": "已领取收入", + "current_value": "当前价值", + "rainbow_users_have_earned": "彩虹钱包用户已经赚取", + "earn_eth_rewards": "赚取ETH奖励", + "rewards_explainer": "每周,排名前列的积分赚取者将获得彩虹钱包链上收入的一部分。通过在彩虹钱包中换币或交易、使用dapp或推荐朋友来收集积分。", + "my_points": "我的积分", + "refer_friends": "推荐朋友", + "try_again": "再试一次", + "bridge_error": "部分成功", + "bridge_error_explainer": "您的奖励已成功在 Optimism 上领取,但转移到 %{network} 失败。如果您愿意,可以使用交换功能手动桥接您的奖励。" }, "console": { "claim_bonus_paragraph": "一旦您通过Rainbow兑换至少$100,您将额外获得100积分", @@ -1916,9 +1983,32 @@ "too_many_signup_request": "过多的注册请求,请稍后再试" }, "swap": { + "actions": { + "hold_to_swap": "按住以交换", + "save": "保存", + "enter_amount": "输入金额", + "review": "审阅", + "fetching_prices": "获取中", + "swapping": "交换中", + "select_token": "选择代币", + "insufficient_funds": "资金不足", + "insufficient": "不足", + "quote_error": "报价错误" + }, "aggregators": { "rainbow": "Rainbow" }, + "bridging": "桥接中", + "swapping": "交换中", + "max": "最大", + "select": "选择", + "token_to_get": "获取的代币", + "token_to_swap": "交换的代币", + "no_balance": "无余额", + "find_a_token_to_buy": "找一个要购买的代币", + "search_your_tokens": "搜索你的代币", + "error_executing_swap": "执行交换时出错", + "flashbots_protection": "Flashbots 保护", "tokens_input": { "tokens": "代币", "sort": "排序", @@ -1945,6 +2035,7 @@ }, "loading": "载入中...", "modal_types": { + "bridge": "桥接", "confirm": "确认", "deposit": "存款", "receive": "接收", @@ -1976,6 +2067,7 @@ }, "token_search": { "section_header": { + "recent": "最近", "favorites": "收藏夹", "bridge": "桥接", "verified": "已验证", @@ -2849,6 +2941,8 @@ "forward": "前进", "back": "返回" } - } + }, + "copy": "复制", + "paste": "粘贴" } } From fdcdd09dcf416da82e50be95035fa91a960d7a41 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 6 Aug 2024 13:18:15 -0400 Subject: [PATCH 06/78] Revert "Brody/swap v2 e2e (#5915)" (#5987) This reverts commit a38f633809a7fd1533349174d33db5c8dcf9ede3. --- e2e/3_homeScreen.spec.ts | 14 +- e2e/9_swaps.spec.ts | 143 ------------------ e2e/helpers.ts | 13 +- package.json | 7 +- .../screens/Swap/components/CoinRow.tsx | 6 +- .../Swap/components/EstimatedSwapGasFee.tsx | 3 +- .../screens/Swap/components/FadeMask.tsx | 42 ++--- .../Swap/components/SwapActionButton.tsx | 5 +- .../Swap/components/SwapBackground.tsx | 11 +- .../Swap/components/SwapInputAsset.tsx | 11 +- .../components/TokenList/TokenToBuyList.tsx | 5 - src/__swaps__/screens/Swap/constants.ts | 31 ++-- .../screens/Swap/hooks/useAssetsToSell.ts | 8 +- .../Swap/hooks/useSwapInputsController.ts | 24 +-- src/__swaps__/types/chains.ts | 4 - src/components/activity-list/ActivityList.js | 1 - src/components/animations/animationConfigs.ts | 46 +++--- .../FastComponents/FastBalanceCoinRow.tsx | 2 +- src/config/experimental.ts | 2 +- src/references/chain-assets.json | 4 +- src/references/index.ts | 29 ++-- src/state/assets/userAssets.ts | 8 +- src/state/sync/UserAssetsSync.tsx | 7 +- 23 files changed, 100 insertions(+), 326 deletions(-) delete mode 100644 e2e/9_swaps.spec.ts diff --git a/e2e/3_homeScreen.spec.ts b/e2e/3_homeScreen.spec.ts index 94fea2546f0..c090519ded0 100644 --- a/e2e/3_homeScreen.spec.ts +++ b/e2e/3_homeScreen.spec.ts @@ -5,9 +5,8 @@ import { checkIfExists, checkIfExistsByText, swipe, + waitAndTap, afterAllcleanApp, - tap, - delayTime, } from './helpers'; const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth'; @@ -42,20 +41,19 @@ describe('Home Screen', () => { }); it('tapping "Swap" opens the swap screen', async () => { - await tap('swap-button'); - await delayTime('long'); + await waitAndTap('swap-button'); await checkIfExists('swap-screen'); - await swipe('swap-screen', 'down', 'fast'); + await swipe('swap-screen', 'down', 'slow'); }); it('tapping "Send" opens the send screen', async () => { - await tap('send-button'); + await waitAndTap('send-button'); await checkIfVisible('send-asset-form-field'); - await swipe('send-asset-form-field', 'down', 'fast'); + await swipe('send-asset-form-field', 'down'); }); it('tapping "Copy" shows copy address toast', async () => { - await tap('receive-button'); + await waitAndTap('receive-button'); await checkIfVisible('address-copied-toast'); }); }); diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts deleted file mode 100644 index 9755763f4c8..00000000000 --- a/e2e/9_swaps.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * // Other tests to consider: - * - Flip assets - * - exchange button onPress - * - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged - * - swap execution - * - token search (both from userAssets and output token list) - * - custom gas panel - * - flashbots - * - slippage - * - explainer sheets - * - switching wallets inside of swap screen - */ - -import { - importWalletFlow, - sendETHtoTestWallet, - checkIfVisible, - beforeAllcleanApp, - afterAllcleanApp, - fetchElementAttributes, - tap, - tapByText, - delayTime, -} from './helpers'; - -import { expect } from '@jest/globals'; -import { WALLET_VARS } from './testVariables'; - -describe('Swap Sheet Interaction Flow', () => { - beforeAll(async () => { - await beforeAllcleanApp({ hardhat: true }); - }); - afterAll(async () => { - await afterAllcleanApp({ hardhat: true }); - }); - - it('Import a wallet and go to welcome', async () => { - await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK); - }); - - it('Should send ETH to test wallet', async () => { - // send 20 eth - await sendETHtoTestWallet(); - }); - - it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => { - await tap('dev-button-hardhat'); - await checkIfVisible('testnet-toast-Hardhat'); - - // validate it has the expected funds of 20 eth - const attributes = await fetchElementAttributes('fast-coin-info'); - expect(attributes.label).toContain('Ethereum'); - expect(attributes.label).toContain('20'); - }); - - it('Should open swap screen with 50% inputAmount for inputAsset', async () => { - await device.disableSynchronization(); - await tap('swap-button'); - await delayTime('long'); - await tap('token-to-buy-dai-1'); - const swapInput = await fetchElementAttributes('swap-asset-input'); - - // expect inputAsset === .5 * eth balance - expect(swapInput.label).toContain('ETH'); - expect(swapInput.label).toContain('10'); - }); - - it('Should be able to go to review and execute a swap', async () => { - await tapByText('Review'); - const reviewActionElements = await fetchElementAttributes('swap-action-button'); - expect(reviewActionElements.elements[0].label).toContain('ETH'); - expect(reviewActionElements.elements[1].label).toContain('DAI'); - expect(reviewActionElements.elements[2].label).toContain('Tap to Swap'); - - /* - * - * Everything from this point fails. Un-comment out the following line to see behavior. - * Currently some spots have chainId 1 and chainId 1337 for various things. I suspect - * there is some issue with one of these things. Log the variables in getNonceAndPerformSwap - * to see the behavior. - * - * To run this test: - * - * yarn clean:ios && yarn fast && yarn start:clean - * yarn detox:ios:build && yarn detox test -c ios.sim.debug 8_swaps.spec.ts - * - */ - - // await tapByText('Tap to Swap'); - }); - - it.skip('Should be able to verify swap is happening', async () => { - // await delayTime('very-long'); - // const activityListElements = await fetchElementAttributes('wallet-activity-list'); - // expect(activityListElements.label).toContain('ETH'); - // expect(activityListElements.label).toContain('DAI'); - // await tapByText('Swapping'); - // await delayTime('long'); - // const transactionSheet = await checkIfVisible('transaction-details-sheet'); - // expect(transactionSheet).toBeTruthy(); - }); - - it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * grab highest user asset balance from userAssetsStore - * expect inputAsset.uniqueId === highest user asset uniqueId - */ - }); - - it.skip('Should open swap screen from asset chart with that asset selected', async () => { - /** - * tap any user asset (store const uniqueId here) - * wait for Swap header to be visible - * expect inputAsset.uniqueId === const uniqueId ^^ - */ - }); - - it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * grab highest user asset balance from userAssetsStore - * expect inputAsset.uniqueId === highest user asset uniqueId - */ - }); - - it.skip('Should not be able to type in output amount if cross-chain quote', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * select different chain in output list chain selector - * select any asset in output token list - * focus output amount - * attempt to type any number in the SwapNumberPad - * attempt to remove a character as well - * - * ^^ expect both of those to not change the outputAmount - */ - }); -}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 8daa9e7d5fa..55344f6052c 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -4,9 +4,8 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { expect, device, element, by, waitFor } from 'detox'; import { parseEther } from '@ethersproject/units'; -import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox'; -const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F'; +const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608'; const DEFAULT_TIMEOUT = 20_000; const android = device.getPlatform() === 'android'; @@ -71,16 +70,6 @@ export async function tap(elementId: string | RegExp) { } } -interface CustomElementAttributes { - elements: Array; -} - -type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes; - -export const fetchElementAttributes = async (testId: string): Promise => { - return (await element(by.id(testId)).getAttributes()) as ElementAttributes; -}; - export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) { await delayTime('medium'); try { diff --git a/package.json b/package.json index 87776938218..6cf1f8c2b24 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,9 @@ "nuke": "./scripts/nuke.sh", "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", - "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", - "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", - "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", - "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1", + "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1", + "detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests", + "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1", "ds:install": "cd src/design-system/docs && yarn install", "ds": "cd src/design-system/docs && yarn dev", "fast": "yarn install && yarn setup && yarn install-pods-fast", diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index a8a4126de90..9d619ef0b96 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -52,7 +52,6 @@ interface InputCoinRowProps { onPress: (asset: ParsedSearchAsset | null) => void; output?: false | undefined; uniqueId: string; - testID?: string; } type PartialAsset = Pick; @@ -63,12 +62,11 @@ interface OutputCoinRowProps extends PartialAsset { output: true; nativePriceChange?: string; isTrending?: boolean; - testID?: string; } type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) { +export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -118,7 +116,7 @@ export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniq if (!address || !chainId) return null; return ( - + diff --git a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx index 9a0de33268e..5f510346f37 100644 --- a/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx +++ b/src/__swaps__/screens/Swap/components/EstimatedSwapGasFee.tsx @@ -9,7 +9,6 @@ import { pulsingConfig, sliderConfig } from '../constants'; import { GasSettings } from '../hooks/useCustomGas'; import { useSwapEstimatedGasFee } from '../hooks/useEstimatedGasFee'; import { useSwapContext } from '../providers/swap-provider'; -import { SpringConfig } from 'react-native-reanimated/lib/typescript/animation/springUtils'; type EstimatedSwapGasFeeProps = { gasSettings?: GasSettings } & Partial< Pick @@ -49,7 +48,7 @@ const GasFeeText = memo(function GasFeeText({ color: withTiming(isLoading.value ? zeroAmountColor : textColor, TIMING_CONFIGS.slowFadeConfig), opacity: isLoading.value ? withRepeat(withSequence(withTiming(0.5, pulsingConfig), withTiming(1, pulsingConfig)), -1, true) - : withSpring(1, sliderConfig as SpringConfig), + : withSpring(1, sliderConfig), })); return ( diff --git a/src/__swaps__/screens/Swap/components/FadeMask.tsx b/src/__swaps__/screens/Swap/components/FadeMask.tsx index 3d90924f9af..7c2928c0435 100644 --- a/src/__swaps__/screens/Swap/components/FadeMask.tsx +++ b/src/__swaps__/screens/Swap/components/FadeMask.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Box, Columns, Column, globalColors } from '@/design-system'; import LinearGradient from 'react-native-linear-gradient'; -import { IS_TEST } from '@/env'; -import { View } from 'react-native'; export const FadeMask = ({ fadeEdgeInset = 6, @@ -24,18 +22,14 @@ export const FadeMask = ({ - {IS_TEST ? ( - - ) : ( - - )} + ) : null} @@ -45,18 +39,14 @@ export const FadeMask = ({ {!side || side === 'right' ? ( <> - {IS_TEST ? ( - - ) : ( - - )} + diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index a35c9db0591..e407de019ab 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -34,7 +34,6 @@ function SwapButton({ disabled, opacity, children, - testID, }: { asset: DerivedValue; borderRadius?: number; @@ -48,7 +47,6 @@ function SwapButton({ disabled?: DerivedValue; opacity?: DerivedValue; children?: React.ReactNode; - testID?: string; }) { const { isDarkMode } = useColorMode(); const fallbackColor = useForegroundColor('label'); @@ -112,7 +110,6 @@ function SwapButton({ return ( {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {holdProgress && } diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 594b33f61bc..8fc56cf4f0e 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -1,11 +1,11 @@ import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia'; import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet } from 'react-native'; import { useDerivedValue, withTiming } from 'react-native-reanimated'; import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useColorMode } from '@/design-system'; -import { IS_ANDROID, IS_TEST } from '@/env'; +import { IS_ANDROID } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swaps__/utils/swaps'; import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -18,15 +18,12 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig ); }); - const animatedBottomColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig @@ -37,10 +34,6 @@ export const SwapBackground = () => { return [animatedTopColor.value, animatedBottomColor.value]; }); - if (IS_TEST) { - return ; - } - return ( diff --git a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx index 5d5a40a51f2..915e657472c 100644 --- a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx @@ -72,14 +72,7 @@ function SwapInputAmount() { }} > } style={styles.inputTextMask}> - + {SwapInputController.formattedInputAmount} @@ -130,7 +123,7 @@ export function SwapInputAsset() { return ( - + diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index 8efda04120d..589e4d082c1 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -130,10 +130,6 @@ export const TokenToBuyList = () => { if (isLoading) return null; - const getFormattedTestId = (name: string, chainId: ChainId) => { - return `token-to-buy-${name}-${chainId}`.toLowerCase().replace(/\s+/g, '-'); - }; - return ( { } return ( { - if (!IS_TEST) return config; - return { ...config, duration: 0, damping: 0 }; -}; - -export const buttonPressConfig = disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }); -export const caretConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }); -export const fadeConfig = disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }); -export const pulsingConfig = disableForTestingEnvironment({ duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }); -export const sliderConfig = disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }); -export const slowFadeConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }); -export const snappySpringConfig = disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }); -export const snappierSpringConfig = disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }); -export const springConfig = disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }); +export const buttonPressConfig = { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }; +export const caretConfig = { duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }; +export const fadeConfig = { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }; +export const pulsingConfig = { duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }; +export const sliderConfig = { damping: 40, mass: 1.25, stiffness: 450 }; +export const slowFadeConfig = { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }; +export const snappySpringConfig = { damping: 100, mass: 0.8, stiffness: 275 }; +export const snappierSpringConfig = { damping: 42, mass: 0.8, stiffness: 800 }; +export const springConfig = { damping: 100, mass: 1.2, stiffness: 750 }; // // /---- END animation configs ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts index d5c6e57f2f8..13f6d54e02d 100644 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts @@ -10,7 +10,6 @@ import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; import { useAccountSettings, useDebounce } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { getCachedProviderForNetwork, isHardHat } from '@/handlers/web3'; const sortBy = (by: UserAssetFilter) => { switch (by) { @@ -22,11 +21,7 @@ const sortBy = (by: UserAssetFilter) => { }; export const useAssetsToSell = () => { - const { accountAddress: currentAddress, nativeCurrency: currentCurrency, network: currentNetwork } = useAccountSettings(); - - const provider = getCachedProviderForNetwork(currentNetwork); - const providerUrl = provider?.connection?.url ?? ''; - const connectedToHardhat = isHardHat(providerUrl); + const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const filter = userAssetsStore(state => state.filter); const searchQuery = userAssetsStore(state => state.inputSearchQuery); @@ -37,7 +32,6 @@ export const useAssetsToSell = () => { { address: currentAddress as Address, currency: currentCurrency, - testnetMode: connectedToHardhat, }, { select: data => diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 1f86c00061f..266caea1063 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,14 +1,5 @@ import { useCallback } from 'react'; -import { - SharedValue, - WithSpringConfig, - runOnJS, - runOnUI, - useAnimatedReaction, - useDerivedValue, - useSharedValue, - withSpring, -} from 'react-native-reanimated'; +import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; import { useDebouncedCallback } from 'use-debounce'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; @@ -323,13 +314,13 @@ export function useSwapInputsController({ // Handle updating the slider position if the quote was output based if (originalQuoteParams.lastTypedInput === 'outputAmount' || originalQuoteParams.lastTypedInput === 'outputNativeValue') { if (!inputAmount || inputAmount === 0) { - sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); + sliderXPosition.value = withSpring(0, snappySpringConfig); } else { const inputBalance = internalSelectedInputAsset.value?.maxSwappableAmount || '0'; const updatedSliderPosition = greaterThanWorklet(inputBalance, 0) ? clamp(Number(divWorklet(inputAmount, inputBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH) : 0; - sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig as WithSpringConfig); + sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); } } @@ -604,7 +595,7 @@ export function useSwapInputsController({ [inputKey]: hasDecimal ? inputKeyValue : 0, })); - if (updateSlider) sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); + if (updateSlider) sliderXPosition.value = withSpring(0, snappySpringConfig); }; const debouncedFetchQuote = useDebouncedCallback( @@ -660,7 +651,7 @@ export function useSwapInputsController({ } if (didInputAssetChange) { - sliderXPosition.value = withSpring(SLIDER_WIDTH / 2, snappySpringConfig as WithSpringConfig); + sliderXPosition.value = withSpring(SLIDER_WIDTH / 2, snappySpringConfig); } const { inputAmount, inputNativeValue } = getInputValuesForSliderPositionWorklet({ @@ -826,14 +817,14 @@ export function useSwapInputsController({ const inputAssetBalance = internalSelectedInputAsset.value?.maxSwappableAmount || '0'; if (equalWorklet(inputAssetBalance, 0)) { - sliderXPosition.value = withSpring(0, snappySpringConfig as WithSpringConfig); + sliderXPosition.value = withSpring(0, snappySpringConfig); } else { const updatedSliderPosition = clamp( Number(divWorklet(current.values.inputAmount, inputAssetBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH ); - sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig as WithSpringConfig); + sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); } runOnJS(debouncedFetchQuote)(); @@ -864,6 +855,7 @@ export function useSwapInputsController({ } } ); + return { debouncedFetchQuote, formattedInputAmount, diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts index feaea2a5c11..c61c29adbdd 100644 --- a/src/__swaps__/types/chains.ts +++ b/src/__swaps__/types/chains.ts @@ -100,7 +100,6 @@ export enum ChainName { celo = 'celo', degen = 'degen', gnosis = 'gnosis', - goerli = 'goerli', linea = 'linea', manta = 'manta', optimism = 'optimism', @@ -170,7 +169,6 @@ export const chainNameToIdMapping: { [ChainName.celo]: ChainId.celo, [ChainName.degen]: ChainId.degen, [ChainName.gnosis]: ChainId.gnosis, - [ChainName.goerli]: chain.goerli.id, [ChainName.linea]: ChainId.linea, [ChainName.manta]: ChainId.manta, [ChainName.optimism]: ChainId.optimism, @@ -210,7 +208,6 @@ export const chainIdToNameMapping: { [ChainId.celo]: ChainName.celo, [ChainId.degen]: ChainName.degen, [ChainId.gnosis]: ChainName.gnosis, - [chain.goerli.id]: ChainName.goerli, [ChainId.linea]: ChainName.linea, [ChainId.manta]: ChainName.manta, [ChainId.optimism]: ChainName.optimism, @@ -252,7 +249,6 @@ export const ChainNameDisplay = { [ChainId.scroll]: chain.scroll.name, [ChainId.zora]: 'Zora', [ChainId.mainnet]: 'Ethereum', - [chain.goerli.id]: 'Goerli', [ChainId.hardhat]: 'Hardhat', [ChainId.hardhatOptimism]: chainHardhatOptimism.name, [ChainId.sepolia]: chain.sepolia.name, diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index 98df923fbd2..4367a80af1a 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -125,7 +125,6 @@ const ActivityList = ({ nativeCurrency, pendingTransactionsCount, }} - testID={'wallet-activity-list'} getItemLayout={getItemLayout} initialNumToRender={12} keyExtractor={keyExtractor} diff --git a/src/components/animations/animationConfigs.ts b/src/components/animations/animationConfigs.ts index b91ea55120f..b3505c762d9 100644 --- a/src/components/animations/animationConfigs.ts +++ b/src/components/animations/animationConfigs.ts @@ -1,50 +1,40 @@ -import { IS_TEST } from '@/env'; import { Easing, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'; function createSpringConfigs>(configs: T): T { return configs; } - function createTimingConfigs>(configs: T): T { return configs; } -// Wrapper function for spring configs -const disableSpringForTesting = (config: WithSpringConfig): WithSpringConfig => { - if (!IS_TEST) return config; - return { ...config, damping: undefined, stiffness: undefined, duration: undefined }; -}; - -// Wrapper function for timing configs -const disableTimingForTesting = (config: WithTimingConfig): WithTimingConfig => { - if (!IS_TEST) return config; - return { ...config, duration: 0 }; -}; - // /---- 🍎 Spring Animations 🍎 ----/ // +// const springAnimations = createSpringConfigs({ - browserTabTransition: disableSpringForTesting({ dampingRatio: 0.82, duration: 800 }), - keyboardConfig: disableSpringForTesting({ damping: 500, mass: 3, stiffness: 1000 }), - sliderConfig: disableSpringForTesting({ damping: 40, mass: 1.25, stiffness: 450 }), - slowSpring: disableSpringForTesting({ damping: 500, mass: 3, stiffness: 800 }), - snappierSpringConfig: disableSpringForTesting({ damping: 42, mass: 0.8, stiffness: 800 }), - snappySpringConfig: disableSpringForTesting({ damping: 100, mass: 0.8, stiffness: 275 }), - springConfig: disableSpringForTesting({ damping: 100, mass: 1.2, stiffness: 750 }), + browserTabTransition: { dampingRatio: 0.82, duration: 800 }, + keyboardConfig: { damping: 500, mass: 3, stiffness: 1000 }, + sliderConfig: { damping: 40, mass: 1.25, stiffness: 450 }, + slowSpring: { damping: 500, mass: 3, stiffness: 800 }, + snappierSpringConfig: { damping: 42, mass: 0.8, stiffness: 800 }, + snappySpringConfig: { damping: 100, mass: 0.8, stiffness: 275 }, + springConfig: { damping: 100, mass: 1.2, stiffness: 750 }, }); export const SPRING_CONFIGS: Record = springAnimations; +// // /---- END ----/ // // /---- ⏱️ Timing Animations ⏱️ ----/ // +// const timingAnimations = createTimingConfigs({ - buttonPressConfig: disableTimingForTesting({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), - fadeConfig: disableTimingForTesting({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - fastFadeConfig: disableTimingForTesting({ duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowFadeConfig: disableTimingForTesting({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowerFadeConfig: disableTimingForTesting({ duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowestFadeConfig: disableTimingForTesting({ duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - tabPressConfig: disableTimingForTesting({ duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + buttonPressConfig: { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }, + fadeConfig: { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + fastFadeConfig: { duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowFadeConfig: { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowerFadeConfig: { duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowestFadeConfig: { duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + tabPressConfig: { duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }, }); export const TIMING_CONFIGS: Record = timingAnimations; +// // /---- END ----/ // diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 08d6b42844e..942fc40a041 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -91,7 +91,7 @@ const MemoizedBalanceCoinRow = React.memo( const valueColor = nativeDisplay ? theme.colors.dark : theme.colors.blueGreyLight; return ( - + diff --git a/src/config/experimental.ts b/src/config/experimental.ts index c9d422f38c1..77c87bd58a4 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -61,7 +61,7 @@ export const defaultConfig: Record = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: !!IS_TEST }, + [SWAPS_V2]: { settings: true, value: false }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, }; diff --git a/src/references/chain-assets.json b/src/references/chain-assets.json index 8e14b5ac633..44548600043 100644 --- a/src/references/chain-assets.json +++ b/src/references/chain-assets.json @@ -9,7 +9,7 @@ }, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Goerli", + "name": "Ether", "network": "goerli", "price": { "changed_at": 1582568575, @@ -31,7 +31,7 @@ }, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Ethereum", + "name": "Ether", "network": "mainnet", "price": { "changed_at": 1582568575, diff --git a/src/references/index.ts b/src/references/index.ts index a692c06120e..b4b7cc6686a 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -229,10 +229,21 @@ export const SUPPORTED_MAINNET_CHAINS: Chain[] = [mainnet, polygon, optimism, ar name: ChainNameDisplay[chain.id], })); -export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => { - const mainnetChains: Chain[] = [mainnet, base, optimism, arbitrum, polygon, zora, blast, degen, avalanche, bsc]; +export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => + [ + // In default order of appearance + mainnet, + base, + optimism, + arbitrum, + polygon, + zora, + blast, + degen, + avalanche, + bsc, - const testnetChains: Chain[] = [ + // Testnets goerli, holesky, sepolia, @@ -244,12 +255,12 @@ export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolea zoraSepolia, avalancheFuji, bscTestnet, - ]; - - const allChains = mainnetChains.concat(testnetMode ? testnetChains : []); - - return allChains.map(chain => ({ ...chain, name: ChainNameDisplay[chain.id] ?? chain.name })); -}; + ].reduce((chainList, chain) => { + if (testnetMode || !chain.testnet) { + chainList.push({ ...chain, name: ChainNameDisplay[chain.id] }); + } + return chainList; + }, [] as Chain[]); export const SUPPORTED_CHAIN_IDS = ({ testnetMode = false }: { testnetMode?: boolean }): ChainId[] => SUPPORTED_CHAINS({ testnetMode }).map(chain => chain.id); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 3c150c4e6a3..ecb5330ed39 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,13 +1,10 @@ -import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; -import { getIsHardhatConnected } from '@/handlers/web3'; -import { ethereumUtils } from '@/utils'; -import { NetworkTypes } from '@/helpers'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; import { SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; +import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; import { swapsStore } from '../swaps/swapsStore'; const SEARCH_CACHE_MAX_ENTRIES = 50; @@ -182,6 +179,7 @@ export const userAssetsStore = createRainbowStore( return filteredIds; } }, + getHighestValueAsset: (usePreferredNetwork = true) => { const preferredNetwork = usePreferredNetwork ? swapsStore.getState().preferredNetwork : undefined; const assets = get().userAssets; diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 4589f941881..636b937c0d0 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { Address } from 'viem'; import { useAccountSettings } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; @@ -5,10 +6,9 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; -import { getIsHardhatConnected } from '@/handlers/web3'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; -export const UserAssetsSync = function UserAssetsSync() { +export const UserAssetsSync = memo(function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const userAssetsWalletAddress = userAssetsStore(state => state.associatedWalletAddress); @@ -18,7 +18,6 @@ export const UserAssetsSync = function UserAssetsSync() { { address: currentAddress as Address, currency: currentCurrency, - testnetMode: getIsHardhatConnected(), }, { enabled: !isSwapsOpen || userAssetsWalletAddress !== currentAddress, @@ -42,4 +41,4 @@ export const UserAssetsSync = function UserAssetsSync() { ); return null; -}; +}); From 0d09b104aa7d3d5c5cdf841eb1b927dbddf9840a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 6 Aug 2024 13:57:14 -0400 Subject: [PATCH 07/78] Convert App.js => App.tsx (#5792) * . * undo tsconfig change and create routes.tsx with platform selector * chore: merge * yep * fix * rm authorize * change initial route context initial value * wrap in usecallbacks --- src/{App.js => App.tsx} | 261 ++++++++++++----------------- src/entities/index.ts | 1 + src/entities/transactions/index.ts | 1 + src/graphql/config.js | 2 +- src/navigation/Routes.tsx | 6 + src/navigation/initialRoute.js | 2 - src/navigation/initialRoute.ts | 6 + src/redux/appState.ts | 2 +- src/storage/schema.ts | 2 +- src/theme/ThemeContext.tsx | 2 +- 10 files changed, 124 insertions(+), 161 deletions(-) rename src/{App.js => App.tsx} (62%) create mode 100644 src/navigation/Routes.tsx delete mode 100644 src/navigation/initialRoute.js create mode 100644 src/navigation/initialRoute.ts diff --git a/src/App.js b/src/App.tsx similarity index 62% rename from src/App.js rename to src/App.tsx index 07948f7af95..b32cb85b7ea 100644 --- a/src/App.js +++ b/src/App.tsx @@ -1,7 +1,8 @@ import './languages'; import * as Sentry from '@sentry/react-native'; -import React, { Component } from 'react'; -import { AppRegistry, AppState, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; +import branch from 'react-native-branch'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; @@ -14,18 +15,13 @@ import { OfflineToast } from './components/toasts'; import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug'; import monitorNetwork from './debugging/network'; import { Playground } from './design-system/playground/Playground'; -import { TransactionType } from './entities'; -import appEvents from './handlers/appEvents'; import handleDeeplink from './handlers/deeplinks'; import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; -import { getIsHardhatConnected, isL2Network } from './handlers/web3'; import RainbowContextWrapper from './helpers/RainbowContext'; import isTestFlight from './helpers/isTestFlight'; -import networkTypes from './helpers/networkTypes'; import * as keychain from '@/model/keychain'; import { loadAddress } from './model/wallet'; import { Navigation } from './navigation'; -// eslint-disable-next-line import/no-unresolved import RoutesComponent from './navigation/Routes'; import { PerformanceContextMap } from './performance/PerformanceContextMap'; import { PerformanceTracking } from './performance/tracking'; @@ -33,13 +29,11 @@ import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetr import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query'; import store from './redux/store'; import { walletConnectLoadState } from './redux/walletconnect'; -import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { MainThemeProvider } from './theme/ThemeContext'; -import { ethereumUtils } from './utils'; import { branchListener } from './utils/branch'; import { addressKey } from './utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; -import { InitialRouteContext } from '@/navigation/initialRoute'; +import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute'; import Routes from '@/navigation/routesNames'; import { Portal } from '@/react-native-cool-modals/Portal'; import { NotificationsHandler } from '@/notifications/NotificationsHandler'; @@ -50,11 +44,13 @@ import * as ls from '@/storage'; import { migrate } from '@/migrations'; import { initListeners as initWalletConnectListeners } from '@/walletConnect'; import { saveFCMToken } from '@/notifications/tokens'; -import branch from 'react-native-branch'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { initializeRemoteConfig } from '@/model/remoteConfig'; +import { NavigationContainerRef } from '@react-navigation/native'; +import { RootStackParamList } from './navigation/types'; +import { Address } from 'viem'; import { IS_DEV } from './env'; import { checkIdentifierOnLaunch } from './model/backup'; @@ -67,84 +63,47 @@ enableScreens(); const containerStyle = { flex: 1 }; -class OldApp extends Component { - state = { - appState: AppState.currentState, - initialRoute: null, - eventSubscription: null, - }; - - /** - * There's a race condition in Branch's RN SDK. From a cold start, Branch - * doesn't always handle an initial URL, so we need to check for it here and - * then pass it to Branch to do its thing. - * - * @see https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/issues/673#issuecomment-1220974483 - */ - async setupDeeplinking() { +interface AppProps { + walletReady: boolean; +} + +function App({ walletReady }: AppProps) { + const [appState, setAppState] = useState(AppState.currentState); + const [initialRoute, setInitialRoute] = useState(null); + const eventSubscription = useRef | null>(null); + const branchListenerRef = useRef | null>(null); + const navigatorRef = useRef | null>(null); + + const setupDeeplinking = useCallback(async () => { const initialUrl = await Linking.getInitialURL(); - // main Branch handler - this.branchListener = await branchListener(url => { + branchListenerRef.current = await branchListener(url => { logger.debug(`Branch: listener called`, {}, logger.DebugContext.deeplinks); - try { - handleDeeplink(url, this.state.initialRoute); - } catch (e) { - logger.error(new RainbowError('Error opening deeplink'), { - message: e.message, - url, - }); + handleDeeplink(url, initialRoute); + } catch (error) { + if (error instanceof Error) { + logger.error(new RainbowError('Error opening deeplink'), { + message: error.message, + url, + }); + } else { + logger.error(new RainbowError('Error opening deeplink'), { + message: 'Unknown error', + url, + }); + } } }); - // if we have an initial URL, pass it to Branch if (initialUrl) { logger.debug(`App: has initial URL, opening with Branch`, { initialUrl }); branch.openURL(initialUrl); } - } - - async componentDidMount() { - if (!__DEV__ && isTestFlight) { - logger.info(`Test flight usage - ${isTestFlight}`); - } - - this.identifyFlow(); - const eventSub = AppState?.addEventListener('change', this?.handleAppStateChange); - this.setState({ eventSubscription: eventSub }); - appEvents.on('transactionConfirmed', this.handleTransactionConfirmed); - - const p1 = analyticsV2.initializeRudderstack(); - const p2 = this.setupDeeplinking(); - const p3 = saveFCMToken(); - await Promise.all([p1, p2, p3]); - - /** - * Needs to be called AFTER FCM token is loaded - */ - initWalletConnectListeners(); - - PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); - analyticsV2.track(analyticsV2.event.applicationDidMount); - } - - componentDidUpdate(prevProps) { - if (!prevProps.walletReady && this.props.walletReady) { - // Everything we need to do after the wallet is ready goes here - logger.info('✅ Wallet ready!'); - runWalletBackupStatusChecks(); - } - } - - componentWillUnmount() { - this.state.eventSubscription.remove(); - this.branchListener(); - } + }, [initialRoute]); - identifyFlow = async () => { + const identifyFlow = useCallback(async () => { const address = await loadAddress(); - if (address) { setTimeout(() => { InteractionManager.runAfterInteractions(() => { @@ -152,94 +111,84 @@ class OldApp extends Component { }); }, 10_000); - checkIdentifierOnLaunch(); + InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); } - const initialRoute = address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN; - this.setState({ initialRoute }); - PerformanceContextMap.set('initialRoute', initialRoute); - }; - - handleAppStateChange = async nextAppState => { - // Restore WC connectors when going from BG => FG - if (this.state.appState === 'background' && nextAppState === 'active') { - store.dispatch(walletConnectLoadState()); - } - this.setState({ appState: nextAppState }); + setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + }, []); - analyticsV2.track(analyticsV2.event.appStateChange, { - category: 'app state', - label: nextAppState, - }); - }; + const handleAppStateChange = useCallback( + (nextAppState: AppStateStatus) => { + if (appState === 'background' && nextAppState === 'active') { + store.dispatch(walletConnectLoadState()); + } + setAppState(nextAppState); + analyticsV2.track(analyticsV2.event.appStateChange, { + category: 'app state', + label: nextAppState, + }); + }, + [appState] + ); - handleNavigatorRef = navigatorRef => { - this.navigatorRef = navigatorRef; - Navigation.setTopLevelNavigator(navigatorRef); - }; + const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { + navigatorRef.current = ref; + Navigation.setTopLevelNavigator(ref); + }, []); - handleTransactionConfirmed = tx => { - const network = tx.chainId ? ethereumUtils.getNetworkFromChainId(tx.chainId) : tx.network || networkTypes.mainnet; - const isL2 = isL2Network(network); + useEffect(() => { + if (!__DEV__ && isTestFlight) { + logger.info(`Test flight usage - ${isTestFlight}`); + } + identifyFlow(); + eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); - const connectedToHardhat = getIsHardhatConnected(); + const p1 = analyticsV2.initializeRudderstack(); + const p2 = setupDeeplinking(); + const p3 = saveFCMToken(); + Promise.all([p1, p2, p3]).then(() => { + initWalletConnectListeners(); + PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); + analyticsV2.track(analyticsV2.event.applicationDidMount); + }); - const updateBalancesAfter = (timeout, isL2, network) => { - const { accountAddress, nativeCurrency } = store.getState().settings; - setTimeout(() => { - logger.debug('Reloading balances for network', network); - if (isL2) { - if (tx.internalType !== TransactionType.authorize) { - // for swaps, we don't want to trigger update balances on unlock txs - queryClient.invalidateQueries({ - queryKey: userAssetsQueryKey({ - address: accountAddress, - currency: nativeCurrency, - connectedToHardhat, - }), - }); - } - } else { - queryClient.invalidateQueries({ - queryKey: userAssetsQueryKey({ - address: accountAddress, - currency: nativeCurrency, - connectedToHardhat, - }), - }); - } - }, timeout); + return () => { + eventSubscription.current?.remove(); + branchListenerRef.current?.(); }; - logger.debug('reloading balances soon...'); - updateBalancesAfter(2000, isL2, network); - updateBalancesAfter(isL2 ? 10000 : 5000, isL2, network); - }; - - render() { - return ( - - - {this.state.initialRoute && ( - - - - - )} - - - - - ); - } + }, [handleAppStateChange, identifyFlow, setupDeeplinking]); + + useEffect(() => { + if (walletReady) { + logger.info('✅ Wallet ready!'); + runWalletBackupStatusChecks(); + } + }, [walletReady]); + + return ( + + + {initialRoute && ( + + + + + )} + + + + + ); } -const OldAppWithRedux = connect(state => ({ - walletReady: state.appState.walletReady, -}))(OldApp); +export type AppStore = typeof store; +export type RootState = ReturnType; +export type AppDispatch = AppStore['dispatch']; -function App() { - return ; -} +const AppWithRedux = connect(state => ({ + walletReady: state.appState.walletReady, +}))(App); function Root() { const [initializing, setInitializing] = React.useState(true); @@ -253,7 +202,7 @@ function Root() { const [deviceId, deviceIdWasJustCreated] = await getOrCreateDeviceId(); const currentWalletAddress = await keychain.loadString(addressKey); const currentWalletAddressHash = - typeof currentWalletAddress === 'string' ? securelyHashWalletAddress(currentWalletAddress) : undefined; + typeof currentWalletAddress === 'string' ? securelyHashWalletAddress(currentWalletAddress as Address) : undefined; Sentry.setUser({ id: deviceId, @@ -336,6 +285,7 @@ function Root() { }, [setInitializing]); return initializing ? null : ( + // @ts-expect-error - Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & Readonly<...>' @@ -345,7 +295,7 @@ function Root() { - + @@ -362,6 +312,7 @@ function Root() { const RootWithSentry = Sentry.wrap(Root); const PlaygroundWithReduxStore = () => ( + // @ts-expect-error - Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & Readonly<...>' diff --git a/src/entities/index.ts b/src/entities/index.ts index a663754fe93..e88fedea170 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -47,6 +47,7 @@ export type { RainbowTransaction, ZerionTransaction, ZerionTransactionChange, + transactionTypes, } from './transactions'; export { GasFeeTypes, TransactionDirection, TransactionDirections, TransactionStatus, TransactionStatusTypes } from './transactions'; export type { EthereumAddress } from './wallet'; diff --git a/src/entities/transactions/index.ts b/src/entities/transactions/index.ts index 4c08517b382..89a71dc3386 100644 --- a/src/entities/transactions/index.ts +++ b/src/entities/transactions/index.ts @@ -1,5 +1,6 @@ export type { NewTransaction, NewTransactionOrAddCashTransaction, RainbowTransaction } from './transaction'; export { default as TransactionStatusTypes, TransactionStatus } from './transactionStatus'; +export { transactionTypes } from './transactionType'; export type { ZerionTransaction, ZerionTransactionChange } from './zerionTransaction'; export { default as TransactionDirections, TransactionDirection } from './transactionDirection'; diff --git a/src/graphql/config.js b/src/graphql/config.js index 8b809049e5b..ec58c6bd808 100644 --- a/src/graphql/config.js +++ b/src/graphql/config.js @@ -37,4 +37,4 @@ exports.config = { headers: {}, }, }, -}; +}; \ No newline at end of file diff --git a/src/navigation/Routes.tsx b/src/navigation/Routes.tsx new file mode 100644 index 00000000000..5ef779dc6de --- /dev/null +++ b/src/navigation/Routes.tsx @@ -0,0 +1,6 @@ +import { Platform } from 'react-native'; + +export default Platform.select({ + ios: require('./Routes.ios'), + android: require('./Routes.android'), +}); diff --git a/src/navigation/initialRoute.js b/src/navigation/initialRoute.js deleted file mode 100644 index 877868af2a6..00000000000 --- a/src/navigation/initialRoute.js +++ /dev/null @@ -1,2 +0,0 @@ -import { createContext } from 'react'; -export const InitialRouteContext = createContext(null); diff --git a/src/navigation/initialRoute.ts b/src/navigation/initialRoute.ts new file mode 100644 index 00000000000..79ec5f5e3c4 --- /dev/null +++ b/src/navigation/initialRoute.ts @@ -0,0 +1,6 @@ +import { createContext } from 'react'; +import Routes from './routesNames'; + +export type InitialRoute = typeof Routes.WELCOME_SCREEN | typeof Routes.SWIPE_LAYOUT | null; + +export const InitialRouteContext = createContext(null); diff --git a/src/redux/appState.ts b/src/redux/appState.ts index 66094db7556..0197e288a74 100644 --- a/src/redux/appState.ts +++ b/src/redux/appState.ts @@ -11,7 +11,7 @@ const APP_STATE_UPDATE = 'contacts/APP_STATE_UPDATE'; * is called `appState`, matching the pattern used by other reducers makes * this interface `AppStateState` :). */ -interface AppStateState { +export interface AppStateState { /** * Whether or not the user's wallet has loaded. */ diff --git a/src/storage/schema.ts b/src/storage/schema.ts index 3448d9f3ed7..2983aa51317 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -35,7 +35,7 @@ export type Account = { totalTokens: number; }; -export const enum ReviewPromptAction { +export enum ReviewPromptAction { UserPrompt = 'UserPrompt', // this is a special action that we use if the user manually prompts for review TimesLaunchedSinceInstall = 'TimesLaunchedSinceInstall', SuccessfulFiatToCryptoPurchase = 'SuccessfulFiatToCryptoPurchase', diff --git a/src/theme/ThemeContext.tsx b/src/theme/ThemeContext.tsx index 43b4b059f1e..29d77d0be33 100644 --- a/src/theme/ThemeContext.tsx +++ b/src/theme/ThemeContext.tsx @@ -37,7 +37,7 @@ export const ThemeContext = createContext({ const { RNThemeModule } = NativeModules; -export const MainThemeProvider = (props: PropsWithChildren>) => { +export const MainThemeProvider = (props: PropsWithChildren) => { const [colorScheme, setColorScheme] = useState(null); // looks like one works on Android and another one on iOS. good. const isSystemDarkModeIOS = useDarkMode(); From 39e1c73885085523771ff466c169b9f6367c3651 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 6 Aug 2024 14:38:28 -0400 Subject: [PATCH 08/78] Fix token to buy list empty spaces (#5983) * fixes empty spaces * also add searching to recents * rm log * remove duplicate recents and add migration * fix sort --- .../Swap/hooks/useSearchCurrencyLists.ts | 94 ++++++++++++++----- src/migrations/index.ts | 2 + .../migrations/removeDuplicateRecentSwaps.ts | 30 ++++++ src/migrations/types.ts | 1 + src/state/swaps/swapsStore.ts | 30 +++++- 5 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 src/migrations/migrations/removeDuplicateRecentSwaps.ts diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index 42031846014..62d09cba7f2 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -27,6 +27,14 @@ export interface AssetToBuySection { const MAX_UNVERIFIED_RESULTS = 8; const MAX_VERIFIED_RESULTS = 48; +const mergeAssetsFavoriteStatus = ({ + assets, + favoritesList, +}: { + assets: SearchAsset[] | undefined; + favoritesList: SearchAsset[] | undefined; +}): SearchAsset[] => assets?.map(asset => ({ ...asset, favorite: favoritesList?.some(fav => fav.address === asset.address) })) || []; + const filterAssetsFromBridge = ({ assets, filteredBridgeAssetAddress, @@ -35,16 +43,43 @@ const filterAssetsFromBridge = ({ filteredBridgeAssetAddress: string | undefined; }): SearchAsset[] => assets?.filter(curatedAsset => !isLowerCaseMatch(curatedAsset?.address, filteredBridgeAssetAddress)) || []; -const filterAssetsFromFavoritesAndBridge = ({ +const filterAssetsFromRecentSwaps = ({ + assets, + recentSwaps, +}: { + assets: SearchAsset[] | undefined; + recentSwaps: RecentSwap[] | undefined; +}): SearchAsset[] => (assets || []).filter(asset => !recentSwaps?.some(recent => recent.address === asset.address)); + +const filterAssetsFromBridgeAndRecent = ({ + assets, + recentSwaps, + filteredBridgeAssetAddress, +}: { + assets: SearchAsset[] | undefined; + recentSwaps: RecentSwap[] | undefined; + filteredBridgeAssetAddress: string | undefined; +}): SearchAsset[] => + filterAssetsFromRecentSwaps({ + assets: filterAssetsFromBridge({ assets, filteredBridgeAssetAddress }), + recentSwaps: recentSwaps, + }); + +const filterAssetsFromFavoritesAndBridgeAndRecent = ({ assets, favoritesList, filteredBridgeAssetAddress, + recentSwaps, }: { assets: SearchAsset[] | undefined; favoritesList: SearchAsset[] | undefined; filteredBridgeAssetAddress: string | undefined; + recentSwaps: RecentSwap[] | undefined; }): SearchAsset[] => - filterAssetsFromBridge({ assets, filteredBridgeAssetAddress })?.filter( + filterAssetsFromRecentSwaps({ + assets: filterAssetsFromBridge({ assets, filteredBridgeAssetAddress }), + recentSwaps: recentSwaps, + })?.filter( curatedAsset => !favoritesList?.some(({ address }) => curatedAsset.address === address || curatedAsset.mainnetAddress === address) ) || []; @@ -79,49 +114,65 @@ const buildListSectionsData = ({ }; if (combinedData.bridgeAsset) { - addSection('bridge', [combinedData.bridgeAsset]); + addSection( + 'bridge', + mergeAssetsFavoriteStatus({ + assets: [combinedData.bridgeAsset], + favoritesList, + }) + ); } if (combinedData.recentSwaps?.length) { - const filteredRecents = filterAssetsFromFavoritesAndBridge({ + const filteredRecents = filterAssetsFromBridge({ assets: combinedData.recentSwaps, - favoritesList, filteredBridgeAssetAddress, }); - addSection('recent', filteredRecents); + + addSection( + 'recent', + mergeAssetsFavoriteStatus({ + assets: filteredRecents, + favoritesList, + }) + ); } if (favoritesList?.length) { - const filteredFavorites = filterAssetsFromBridge({ + const filteredFavorites = filterAssetsFromBridgeAndRecent({ assets: favoritesList, filteredBridgeAssetAddress, + recentSwaps: combinedData.recentSwaps, }); addSection('favorites', filteredFavorites); } if (combinedData.verifiedAssets?.length) { - const filteredVerified = filterAssetsFromFavoritesAndBridge({ + const filteredVerified = filterAssetsFromFavoritesAndBridgeAndRecent({ assets: combinedData.verifiedAssets, favoritesList, filteredBridgeAssetAddress, + recentSwaps: combinedData.recentSwaps, }); addSection('verified', filteredVerified); } if (!formattedData.length && combinedData.crosschainExactMatches?.length) { - const filteredCrosschain = filterAssetsFromFavoritesAndBridge({ + const filteredCrosschain = filterAssetsFromFavoritesAndBridgeAndRecent({ assets: combinedData.crosschainExactMatches, favoritesList, filteredBridgeAssetAddress, + recentSwaps: combinedData.recentSwaps, }); addSection('other_networks', filteredCrosschain); } if (combinedData.unverifiedAssets?.length) { - const filteredUnverified = filterAssetsFromFavoritesAndBridge({ + const filteredUnverified = filterAssetsFromFavoritesAndBridgeAndRecent({ assets: combinedData.unverifiedAssets, favoritesList, filteredBridgeAssetAddress, + recentSwaps: combinedData.recentSwaps, }); addSection('unverified', filteredUnverified); } @@ -236,10 +287,6 @@ export function useSearchCurrencyLists() { })) as SearchAsset[]; }, [favorites, state.toChainId]); - const recentsForChain = useMemo(() => { - return getRecentSwapsByChain(state.toChainId); - }, [getRecentSwapsByChain, state.toChainId]); - const memoizedData = useMemo(() => { const queryIsAddress = isAddress(query); const keys: TokenSearchAssetKey[] = queryIsAddress ? ['address'] : ['name', 'symbol']; @@ -256,23 +303,22 @@ export function useSearchCurrencyLists() { : null; const filteredBridgeAsset = bridgeAsset && filterBridgeAsset({ asset: bridgeAsset, filter: query }) ? bridgeAsset : null; - const isBridgeAssetUserFavorite = - bridgeAsset && - unfilteredFavorites.some(asset => asset.mainnetAddress === bridgeAsset.mainnetAddress || asset.address === bridgeAsset.address); return { queryIsAddress, keys, threshold, enableUnverifiedSearch, - filteredBridgeAsset: filteredBridgeAsset - ? { - ...filteredBridgeAsset, - favorite: isBridgeAssetUserFavorite, - } - : null, + filteredBridgeAsset, }; - }, [assetToSell, query, selectedOutputChainId, state, verifiedAssets, unfilteredFavorites]); + }, [assetToSell, query, selectedOutputChainId, state, verifiedAssets]); + + const recentsForChain = useMemo(() => { + return filterList(getRecentSwapsByChain(state.toChainId), query, memoizedData.keys, { + threshold: memoizedData.queryIsAddress ? rankings.CASE_SENSITIVE_EQUAL : rankings.CONTAINS, + sorter: matchItems => matchItems.sort((a, b) => b.item.swappedAt - a.item.swappedAt), + }); + }, [getRecentSwapsByChain, state.toChainId, query, memoizedData.keys, memoizedData.queryIsAddress]); const favoritesList = useMemo(() => { if (query === '') { diff --git a/src/migrations/index.ts b/src/migrations/index.ts index d36aff807f7..8ec0a3efcb0 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -16,6 +16,7 @@ import { migratePinnedAndHiddenTokenUniqueIds } from './migrations/migratePinned import { migrateRemotePromoSheetsToZustand } from './migrations/migrateRemotePromoSheetsToZustand'; import { migrateUnlockableAppIconStorage } from './migrations/migrateUnlockableAppIconStorage'; import { purgeWcConnectionsWithoutAccounts } from './migrations/purgeWcConnectionsWithoutAccounts'; +import { removeDuplicateRecentSwaps } from './migrations/removeDuplicateRecentSwaps'; /** * Local storage for migrations only. Should not be exported. @@ -43,6 +44,7 @@ const migrations: Migration[] = [ migratePersistedQueriesToMMKV(), migrateRemotePromoSheetsToZustand(), migrateFavoritesToV2(), + removeDuplicateRecentSwaps(), ]; /** diff --git a/src/migrations/migrations/removeDuplicateRecentSwaps.ts b/src/migrations/migrations/removeDuplicateRecentSwaps.ts new file mode 100644 index 00000000000..9a8662c1000 --- /dev/null +++ b/src/migrations/migrations/removeDuplicateRecentSwaps.ts @@ -0,0 +1,30 @@ +import { UniqueId } from '@/__swaps__/types/assets'; +import { Migration, MigrationName } from '../types'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { RecentSwap } from '@/__swaps__/types/swap'; +import { ChainId } from '@/__swaps__/types/chains'; + +export function removeDuplicateRecentSwaps(): Migration { + return { + name: MigrationName.removeDuplicateRecentSwaps, + async migrate() { + const { recentSwaps } = swapsStore.getState(); + const updatedRecentSwaps = new Map(); + + for (const [chainId] of recentSwaps) { + const uniqueSwaps = new Map(); + const sortedSwaps = swapsStore.getState().getRecentSwapsByChain(chainId); + + for (const swap of sortedSwaps) { + if (!uniqueSwaps.has(swap.uniqueId)) { + uniqueSwaps.set(swap.uniqueId, swap); + } + } + + updatedRecentSwaps.set(chainId, Array.from(uniqueSwaps.values())); + } + + swapsStore.setState({ recentSwaps: updatedRecentSwaps }); + }, + }; +} diff --git a/src/migrations/types.ts b/src/migrations/types.ts index a21fe98e5e7..cdd1a940aeb 100644 --- a/src/migrations/types.ts +++ b/src/migrations/types.ts @@ -19,6 +19,7 @@ export enum MigrationName { migratePersistedQueriesToMMKV = 'migration_migratePersistedQueriesToMMKV', migrateRemotePromoSheetsToZustand = 'migration_migrateRemotePromoSheetsToZustand', migrateFavoritesToV2 = 'migration_migrateFavoritesToV2', + removeDuplicateRecentSwaps = 'migration_removeDuplicateRecentSwaps', } export type Migration = { diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index 3a914f06b2c..948a84be859 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -162,8 +162,7 @@ export const swapsStore = createRainbowStore( getRecentSwapsByChain: (chainId: ChainId) => { const { recentSwaps } = get(); - const chainSwaps = recentSwaps.get(chainId) || []; - return chainSwaps.sort((a, b) => b.swappedAt - a.swappedAt); + return recentSwaps.get(chainId) || []; }, addRecentSwap(asset) { const { recentSwaps, latestSwapAt } = get(); @@ -171,11 +170,34 @@ export const swapsStore = createRainbowStore( const chainId = asset.chainId; const chainSwaps = recentSwaps.get(chainId) || []; - const updatedSwaps = [...chainSwaps, { ...asset, swappedAt: now }].slice(-3); + const [latestSwap] = chainSwaps; + + // Check if the most recent swap is the same as the incoming asset + if (latestSwap && latestSwap.uniqueId === asset.uniqueId) { + latestSwapAt.set(chainId, now); + recentSwaps.set(chainId, [ + ...chainSwaps.slice(1), + { + ...latestSwap, + swappedAt: now, + }, + ]); + + set({ latestSwapAt: new Map(latestSwapAt), recentSwaps: new Map(recentSwaps) }); + return; + } + + // Remove any existing entries of the same asset + const filteredSwaps = chainSwaps.filter(swap => swap.uniqueId !== asset.uniqueId); + + const updatedSwaps = [...filteredSwaps, { ...asset, swappedAt: now }].slice(0, 3); recentSwaps.set(chainId, updatedSwaps); latestSwapAt.set(chainId, now); - set({ recentSwaps: new Map(recentSwaps) }); + set({ + recentSwaps: new Map(recentSwaps), + latestSwapAt: new Map(latestSwapAt), + }); }, }), { From 0fd389f24a5f3deb1f4a9582aaf6399559c737c4 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 6 Aug 2024 14:44:51 -0400 Subject: [PATCH 09/78] fix bridge button sending users to swaps v1 (#5971) --- .../control-panel/ControlPanel.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index 6985c949832..dad846c6c85 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -472,11 +472,26 @@ const HomePanel = ({ }, screen: Routes.MAIN_EXCHANGE_SCREEN, }); - }, [runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId]); + }, [navigate, runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId, swapsV2Enabled]); const handleOnPressBridge = useCallback(async () => { const valid = await runWalletChecksBeforeSwapOrBridge(); if (!valid) return; + + const { swaps_v2 } = getRemoteConfig(); + + if (swaps_v2 || swapsV2Enabled) { + // TODO: We need to set something in swapsStore that deliniates between a swap and bridge + // for now let's just treat it like a normal swap + swapsStore.setState({ + inputAsset: userAssetsStore.getState().getHighestValueAsset(), + }); + InteractionManager.runAfterInteractions(() => { + navigate(Routes.SWAP); + }); + return; + } + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(Network.mainnet, selectedWallet?.uniqueId); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, @@ -485,7 +500,7 @@ const HomePanel = ({ }, screen: Routes.MAIN_EXCHANGE_SCREEN, }); - }, [runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId]); + }, [navigate, runWalletChecksBeforeSwapOrBridge, selectedWallet?.uniqueId, swapsV2Enabled]); const isOnHomepage = useBrowserStore(state => (state.getActiveTabUrl() || DEFAULT_TAB_URL) === RAINBOW_HOME); From eabc40e5f816ee8b84fe567ff53ce9745e96ef8f Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 6 Aug 2024 16:52:35 -0400 Subject: [PATCH 10/78] fix (#5989) --- src/migrations/index.ts | 2 -- .../migrations/removeDuplicateRecentSwaps.ts | 30 ------------------- src/state/swaps/swapsStore.ts | 19 +----------- 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 src/migrations/migrations/removeDuplicateRecentSwaps.ts diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 8ec0a3efcb0..d36aff807f7 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -16,7 +16,6 @@ import { migratePinnedAndHiddenTokenUniqueIds } from './migrations/migratePinned import { migrateRemotePromoSheetsToZustand } from './migrations/migrateRemotePromoSheetsToZustand'; import { migrateUnlockableAppIconStorage } from './migrations/migrateUnlockableAppIconStorage'; import { purgeWcConnectionsWithoutAccounts } from './migrations/purgeWcConnectionsWithoutAccounts'; -import { removeDuplicateRecentSwaps } from './migrations/removeDuplicateRecentSwaps'; /** * Local storage for migrations only. Should not be exported. @@ -44,7 +43,6 @@ const migrations: Migration[] = [ migratePersistedQueriesToMMKV(), migrateRemotePromoSheetsToZustand(), migrateFavoritesToV2(), - removeDuplicateRecentSwaps(), ]; /** diff --git a/src/migrations/migrations/removeDuplicateRecentSwaps.ts b/src/migrations/migrations/removeDuplicateRecentSwaps.ts deleted file mode 100644 index 9a8662c1000..00000000000 --- a/src/migrations/migrations/removeDuplicateRecentSwaps.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { UniqueId } from '@/__swaps__/types/assets'; -import { Migration, MigrationName } from '../types'; -import { swapsStore } from '@/state/swaps/swapsStore'; -import { RecentSwap } from '@/__swaps__/types/swap'; -import { ChainId } from '@/__swaps__/types/chains'; - -export function removeDuplicateRecentSwaps(): Migration { - return { - name: MigrationName.removeDuplicateRecentSwaps, - async migrate() { - const { recentSwaps } = swapsStore.getState(); - const updatedRecentSwaps = new Map(); - - for (const [chainId] of recentSwaps) { - const uniqueSwaps = new Map(); - const sortedSwaps = swapsStore.getState().getRecentSwapsByChain(chainId); - - for (const swap of sortedSwaps) { - if (!uniqueSwaps.has(swap.uniqueId)) { - uniqueSwaps.set(swap.uniqueId, swap); - } - } - - updatedRecentSwaps.set(chainId, Array.from(uniqueSwaps.values())); - } - - swapsStore.setState({ recentSwaps: updatedRecentSwaps }); - }, - }; -} diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index 948a84be859..ac5fb83d92f 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -170,27 +170,10 @@ export const swapsStore = createRainbowStore( const chainId = asset.chainId; const chainSwaps = recentSwaps.get(chainId) || []; - const [latestSwap] = chainSwaps; - - // Check if the most recent swap is the same as the incoming asset - if (latestSwap && latestSwap.uniqueId === asset.uniqueId) { - latestSwapAt.set(chainId, now); - recentSwaps.set(chainId, [ - ...chainSwaps.slice(1), - { - ...latestSwap, - swappedAt: now, - }, - ]); - - set({ latestSwapAt: new Map(latestSwapAt), recentSwaps: new Map(recentSwaps) }); - return; - } - // Remove any existing entries of the same asset const filteredSwaps = chainSwaps.filter(swap => swap.uniqueId !== asset.uniqueId); - const updatedSwaps = [...filteredSwaps, { ...asset, swappedAt: now }].slice(0, 3); + const updatedSwaps = [{ ...asset, swappedAt: now }, ...filteredSwaps].slice(0, 3); recentSwaps.set(chainId, updatedSwaps); latestSwapAt.set(chainId, now); From c1f0d5f2a695f45f3178fce217ba5c0acf7bfcb4 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 7 Aug 2024 02:46:03 -0300 Subject: [PATCH 11/78] fix favorites tokens with no icon (#5982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix favorites default list * wip * 👍 * Update src/resources/favorites.ts * fix format --------- Co-authored-by: Matthew Wall --- src/App.tsx | 9 +- src/migrations/index.ts | 5 +- src/migrations/migrations/migrateFavorites.ts | 54 +++++++++++ .../migrations/migrateFavoritesToV2.ts | 31 ------ src/migrations/types.ts | 3 +- src/resources/favorites.ts | 94 ++++--------------- 6 files changed, 83 insertions(+), 113 deletions(-) create mode 100644 src/migrations/migrations/migrateFavorites.ts delete mode 100644 src/migrations/migrations/migrateFavoritesToV2.ts diff --git a/src/App.tsx b/src/App.tsx index b32cb85b7ea..7eeb21c04b4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,6 +53,7 @@ import { RootStackParamList } from './navigation/types'; import { Address } from 'viem'; import { IS_DEV } from './env'; import { checkIdentifierOnLaunch } from './model/backup'; +import { prefetchDefaultFavorites } from './resources/favorites'; if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -288,7 +289,13 @@ function Root() { // @ts-expect-error - Property 'children' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes> & Readonly<...>' - + { + prefetchDefaultFavorites(); + }} + > diff --git a/src/migrations/index.ts b/src/migrations/index.ts index d36aff807f7..8abb7f47509 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -10,7 +10,7 @@ import { prepareDefaultNotificationGroupSettingsState } from '@/migrations/migra import { Migration, MigrationName, MIGRATIONS_DEBUG_CONTEXT, MIGRATIONS_STORAGE_ID } from '@/migrations/types'; import { changeLanguageKeys } from './migrations/changeLanguageKeys'; import { fixHiddenUSDC } from './migrations/fixHiddenUSDC'; -import { migrateFavoritesToV2 } from './migrations/migrateFavoritesToV2'; +import { migrateFavoritesV2, migrateFavoritesV3 } from './migrations/migrateFavorites'; import { migratePersistedQueriesToMMKV } from './migrations/migratePersistedQueriesToMMKV'; import { migratePinnedAndHiddenTokenUniqueIds } from './migrations/migratePinnedAndHiddenTokenUniqueIds'; import { migrateRemotePromoSheetsToZustand } from './migrations/migrateRemotePromoSheetsToZustand'; @@ -42,7 +42,8 @@ const migrations: Migration[] = [ migrateUnlockableAppIconStorage(), migratePersistedQueriesToMMKV(), migrateRemotePromoSheetsToZustand(), - migrateFavoritesToV2(), + migrateFavoritesV2(), + migrateFavoritesV3(), ]; /** diff --git a/src/migrations/migrations/migrateFavorites.ts b/src/migrations/migrations/migrateFavorites.ts new file mode 100644 index 00000000000..46845e56225 --- /dev/null +++ b/src/migrations/migrations/migrateFavorites.ts @@ -0,0 +1,54 @@ +import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; +import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; +import { EthereumAddress, RainbowToken } from '@/entities'; +import { createQueryKey, persistOptions, queryClient } from '@/react-query'; +import { favoritesQueryKey } from '@/resources/favorites'; +import { ethereumUtils } from '@/utils'; +import { persistQueryClientRestore, persistQueryClientSave } from '@tanstack/react-query-persist-client'; +import { Migration, MigrationName } from '../types'; + +const favoritesV1QueryKey = createQueryKey('favorites', {}, { persisterVersion: 1 }); +const favoritesV2QueryKey = createQueryKey('favorites', {}, { persisterVersion: 2 }); + +export function migrateFavoritesV2(): Migration { + return { + name: MigrationName.migrateFavoritesV2, + async migrate() { + await persistQueryClientRestore({ queryClient, persister: persistOptions.persister }); // first restore persisted data + + // v1 used just the address as key, v2 uses uniqueId as key and builds this uniqueId with ChainId instead of Network + const v1Data = queryClient.getQueryData>(favoritesV1QueryKey); + + if (!v1Data) return; + + const migratedFavorites: Record = {}; + for (const favorite of Object.values(v1Data)) { + const uniqueId = getStandardizedUniqueIdWorklet({ + address: favorite.address as AddressOrEth, + chainId: ethereumUtils.getChainIdFromNetwork(favorite.network), + }); + favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network + migratedFavorites[uniqueId] = favorite; + } + queryClient.setQueryData(favoritesQueryKey, migratedFavorites); + + await persistQueryClientSave({ queryClient, persister: persistOptions.persister }); + }, + }; +} + +export function migrateFavoritesV3(): Migration { + return { + name: MigrationName.migrateFavoritesV3, + async migrate() { + await persistQueryClientRestore({ queryClient, persister: persistOptions.persister }); // first restore persisted data + + const v2Data = queryClient.getQueryData>(favoritesV2QueryKey); + if (!v2Data) return; + + queryClient.setQueryData(favoritesQueryKey, v2Data, { updatedAt: 0 }); + + await persistQueryClientSave({ queryClient, persister: persistOptions.persister }); + }, + }; +} diff --git a/src/migrations/migrations/migrateFavoritesToV2.ts b/src/migrations/migrations/migrateFavoritesToV2.ts deleted file mode 100644 index f9a50dfd96b..00000000000 --- a/src/migrations/migrations/migrateFavoritesToV2.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; -import { EthereumAddress, RainbowToken } from '@/entities'; -import { createQueryKey, queryClient } from '@/react-query'; -import { favoritesQueryKey } from '@/resources/favorites'; -import { ethereumUtils } from '@/utils'; -import { Migration, MigrationName } from '../types'; - -export function migrateFavoritesToV2(): Migration { - return { - name: MigrationName.migrateFavoritesToV2, - async migrate() { - // v1 used just the address as key, v2 uses uniqueId as key and builds this uniqueId with ChainId instead of Network - const favoritesV1QueryKey = createQueryKey('favorites', {}, { persisterVersion: 1 }); - const v1Data = queryClient.getQueryData>(favoritesV1QueryKey); - if (v1Data) { - const migratedFavorites: Record = {}; - for (const favorite of Object.values(v1Data)) { - const uniqueId = getStandardizedUniqueIdWorklet({ - address: favorite.address as AddressOrEth, - chainId: ethereumUtils.getChainIdFromNetwork(favorite.network), - }); - favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network - migratedFavorites[uniqueId] = favorite; - } - queryClient.setQueryData(favoritesQueryKey, migratedFavorites); - queryClient.setQueryData(favoritesV1QueryKey, undefined); // clear v1 store data - } - }, - }; -} diff --git a/src/migrations/types.ts b/src/migrations/types.ts index cdd1a940aeb..6fd5aba3819 100644 --- a/src/migrations/types.ts +++ b/src/migrations/types.ts @@ -18,7 +18,8 @@ export enum MigrationName { migrateUnlockableAppIconStorage = 'migration_migrateUnlockableAppIconStorage', migratePersistedQueriesToMMKV = 'migration_migratePersistedQueriesToMMKV', migrateRemotePromoSheetsToZustand = 'migration_migrateRemotePromoSheetsToZustand', - migrateFavoritesToV2 = 'migration_migrateFavoritesToV2', + migrateFavoritesV2 = 'migration_migrateFavoritesV2', + migrateFavoritesV3 = 'migration_migrateFavoritesV3', removeDuplicateRecentSwaps = 'migration_removeDuplicateRecentSwaps', } diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 355b9c6c59b..f220740b06c 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -11,80 +11,11 @@ import { useQuery } from '@tanstack/react-query'; import { omit } from 'lodash'; import { externalTokenQueryKey, fetchExternalToken } from './assets/externalAssetsQuery'; -export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 2 }); +export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 3 }); -const getUniqueId = (address: AddressOrEth, chainId: ChainId) => getStandardizedUniqueIdWorklet({ address, chainId }); +const DEFAULT_FAVORITES = [DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS]; -const DAI_uniqueId = getUniqueId(DAI_ADDRESS, ChainId.mainnet); -const ETH_uniqueId = getUniqueId(ETH_ADDRESS, ChainId.mainnet); -const SOCKS_uniqueId = getUniqueId(SOCKS_ADDRESS, ChainId.mainnet); -const WBTC_uniqueId = getUniqueId(WBTC_ADDRESS, ChainId.mainnet); - -const DEFAULT: Record = { - [DAI_uniqueId]: { - address: DAI_ADDRESS, - color: '#F0B340', - decimals: 18, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Dai', - symbol: 'DAI', - network: Network.mainnet, - uniqueId: DAI_uniqueId, - networks: { - [ChainId.mainnet]: { address: DAI_ADDRESS }, - }, - }, - [ETH_uniqueId]: { - address: ETH_ADDRESS, - color: '#25292E', - decimals: 18, - favorite: true, - highLiquidity: true, - isVerified: true, - name: 'Ethereum', - symbol: 'ETH', - network: Network.mainnet, - uniqueId: ETH_uniqueId, - networks: { - [ChainId.mainnet]: { address: ETH_ADDRESS }, - }, - }, - [SOCKS_uniqueId]: { - address: SOCKS_ADDRESS, - color: '#E15EE5', - decimals: 18, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Unisocks', - symbol: 'SOCKS', - network: Network.mainnet, - uniqueId: SOCKS_uniqueId, - networks: { - [ChainId.mainnet]: { address: SOCKS_ADDRESS }, - }, - }, - [WBTC_uniqueId]: { - address: WBTC_ADDRESS, - color: '#FF9900', - decimals: 8, - favorite: true, - highLiquidity: true, - isRainbowCurated: true, - isVerified: true, - name: 'Wrapped Bitcoin', - symbol: 'WBTC', - network: Network.mainnet, - uniqueId: WBTC_uniqueId, - networks: { - [ChainId.mainnet]: { address: WBTC_ADDRESS }, - }, - }, -}; +const getUniqueId = (address: AddressOrEth, chainId: ChainId) => getStandardizedUniqueIdWorklet({ address, chainId }); /** * Returns a map of the given `addresses` to their corresponding `RainbowToken` metadata. @@ -151,7 +82,8 @@ async function fetchMetadata(addresses: string[], chainId = ChainId.mainnet) { * Refreshes the metadata associated with all favorites. */ export async function refreshFavorites() { - const favorites = queryClient.getQueryData>(favoritesQueryKey) ?? DEFAULT; + const favorites = queryClient.getQueryData>(favoritesQueryKey); + if (!favorites) return; const favoritesByNetwork = Object.values(favorites).reduce( (favoritesByChain, token) => { @@ -192,13 +124,20 @@ export async function toggleFavorite(address: string, chainId = ChainId.mainnet) queryClient.setQueryData(favoritesQueryKey, omit(favorites, uniqueId)); } else { const metadata = await fetchMetadata([lowercasedAddress], chainId); - queryClient.setQueryData(favoritesQueryKey, { - ...favorites, - ...metadata, - }); + queryClient.setQueryData(favoritesQueryKey, { ...favorites, ...metadata }); } } +export async function prefetchDefaultFavorites() { + const favorites = queryClient.getQueryData>(favoritesQueryKey); + if (favorites) return; + + const defaultFavorites = await fetchMetadata(DEFAULT_FAVORITES, ChainId.mainnet); + queryClient.setQueryData(favoritesQueryKey, defaultFavorites); + + return defaultFavorites; +} + /** * Returns `favorites`, an array of favorited addresses, as well as `favoritesMetadata`, a map of these * addresses to their corresponding `RainbowToken`. These values are cached in AsyncStorage and is only @@ -213,7 +152,6 @@ export function useFavorites(): { queryFn: refreshFavorites, staleTime: 24 * 60 * 60 * 1000, // 24hrs cacheTime: Infinity, - initialData: DEFAULT, }); const favoritesMetadata = query.data ?? {}; From e7157d40fde7291a205f2adb0d07586cffa37d77 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:29:15 -0400 Subject: [PATCH 12/78] codeowners file removed (#5991) --- CODEOWNERS | 71 ------------------------------------------------------ 1 file changed, 71 deletions(-) delete mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 004856baa97..00000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,71 +0,0 @@ -# Rainbow CODEOWNERS file -CODEOWNERS @walmat @benisgold @jinchung -LICENSE @jinchung @brunobar79 @nickbytes @estebanmino -README.md @nickbytes @walmat @benisgold @jinchung -app.json @walmat @benisgold @jinchung - -# ANDROID -android/ - -# IOS -ios/ @walmat @benisgold - -# SECURITY / APP INFRA -patches/ @walmat @benisgold -scripts/ - -# DESIGN SYSTEM -src/design-system @walmat @benisgold - -# Handlers -src/handlers/aesEncryption.ts @jinchung -src/handlers/authentication.ts @jinchung -src/handlers/cloudBackup.ts @jinchung - -# Model -src/model/backup.ts @jinchung -src/model/keychain.ts @jinchung -src/model/migrations.ts @walmat @benisgold @jinchung -src/model/wallet.ts @jinchung - -# redux -src/redux/gas.ts @walmat @benisgold @jinchung - -# navigation -src/navigation @walmat @benisgold - -# analytics & logging -src/analytics @walmat @benisgold - -# APP INFRA -package.json @walmat @benisgold @jinchung -.eslintignore @walmat @benisgold @jinchung -.eslintrc.js @walmat @benisgold @jinchung -.gitattributes @walmat @benisgold @jinchung -.gitignore @walmat @benisgold @jinchung -.nvmrc @walmat @benisgold @jinchung -.prettierrc.js @walmat @benisgold @jinchung -.yarnrc @walmat @benisgold @jinchung -audit-ci.json @walmat @benisgold @jinchung -babel.config.js -globals.d.ts @walmat @benisgold -yarn.lock @walmat @benisgold @jinchung -tsconfig.json @walmat @benisgold @jinchung -hardhat.config.js @jinchung - -# APP INFRA -globalVariables.js @walmat @benisgold -shim.js -index.js @walmat @benisgold -src/App.js @walmat @benisgold @jinchung - -# SECURITY / APP INFRA -metro.config.js @walmat @benisgold -metro.transform.js @walmat @benisgold -react-native.config.js @walmat @benisgold - -# DAPP BROWSER -src/browser @brunobar79 @benisgold -src/screens/dapp-browser @brunobar79 @benisgold -src/components/DappBrowser @brunobar79 @benisgold - From 136e6ba74671921807db563109c1bbec2dc79e3a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 7 Aug 2024 16:42:53 -0400 Subject: [PATCH 13/78] Trending DApps (#5974) * initial work for trending dapps * progress on trending dapps * impl. trending hooks * combine selectors --- src/components/DappBrowser/Homepage.tsx | 16 +++++---- src/graphql/queries/metadata.graphql | 20 +++++++++++ src/resources/metadata/trendingDapps.ts | 47 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 src/resources/metadata/trendingDapps.ts diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index d609feae647..6bda90a0717 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -34,6 +34,8 @@ import { analyticsV2 } from '@/analytics'; import haptics from '@/utils/haptics'; import * as i18n from '@/languages'; import { getNameFromFormattedUrl } from './utils'; +import { useTrendingDApps } from '@/resources/metadata/trendingDapps'; +import { DApp } from '@/graphql/__generated__/metadata'; const HORIZONTAL_PAGE_INSET = 24; const MAX_RECENTS_TO_DISPLAY = 6; @@ -73,9 +75,9 @@ export const Homepage = () => { }; const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { - const { dapps } = useDapps({ select: dapps => dapps.filter(dapp => dapp.trending).slice(0, 8) }); + const { data } = useTrendingDApps(); - if (dapps.length === 0) { + if (!data?.dApps?.length) { return null; } @@ -95,13 +97,15 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { decelerationRate="fast" disableIntervalMomentum showsHorizontalScrollIndicator={false} - snapToOffsets={dapps.map((_, index) => index * (CARD_WIDTH + CARD_PADDING))} + snapToOffsets={data.dApps.map((_, index) => index * (CARD_WIDTH + CARD_PADDING))} > - {dapps.map((site, index) => ( - - ))} + {data.dApps + .filter((dApp): dApp is DApp => dApp !== null) + .map((dApp, index) => ( + + ))} diff --git a/src/graphql/queries/metadata.graphql b/src/graphql/queries/metadata.graphql index e69a3c6c7f5..e398cbdd2ea 100644 --- a/src/graphql/queries/metadata.graphql +++ b/src/graphql/queries/metadata.graphql @@ -604,3 +604,23 @@ mutation claimUserRewards($address: String!) { txHash } } + +query trendingDApps($period: DAppRankingPeriod) { + dApps(trending: true, period: $period) { + name + shortName + description + url + iconURL + colors { + primary + fallback + shadow + } + status + report { + url + } + trending + } +} diff --git a/src/resources/metadata/trendingDapps.ts b/src/resources/metadata/trendingDapps.ts new file mode 100644 index 00000000000..c52975d79c4 --- /dev/null +++ b/src/resources/metadata/trendingDapps.ts @@ -0,0 +1,47 @@ +import { metadataClient } from '@/graphql'; +import { DAppRankingPeriod, DAppStatus, TrendingDAppsQuery } from '@/graphql/__generated__/metadata'; +import { createQueryKey, QueryConfigWithSelect } from '@/react-query'; +import { QueryFunction, useQuery } from '@tanstack/react-query'; + +const trendingDAppsQueryKey = ({ period }: { period: DAppRankingPeriod }) => + createQueryKey('trendingDApps', { period }, { persisterVersion: 1 }); + +type TrendingDAppsQueryKey = ReturnType; + +const fetchTrendingDApps: QueryFunction = async ({ queryKey }) => { + const [{ period }] = queryKey; + + return metadataClient.trendingDApps({ + period, + }); +}; + +export function selectValidNonScamDApps(data: TrendingDAppsQuery): TrendingDAppsQuery { + if (!data.dApps?.length) return { ...data, dApps: [] }; + return { + ...data, + dApps: data.dApps.filter(d => Boolean(d) && d?.status !== DAppStatus.Scam), + }; +} + +export function selectFirstEightDApps(data: TrendingDAppsQuery): TrendingDAppsQuery { + if (!data.dApps?.length) return { ...data, dApps: [] }; + return { + ...data, + dApps: data.dApps.slice(0, 8), + }; +} + +export function useTrendingDApps( + period = DAppRankingPeriod.Day, + config: QueryConfigWithSelect, TrendingDAppsQueryKey> = {} +) { + return useQuery(trendingDAppsQueryKey({ period }), fetchTrendingDApps, { + staleTime: 1000 * 60 * 20, // 20 minutes + cacheTime: 1000 * 60 * 60 * 24 * 2, // 2 days + retry: 3, + keepPreviousData: true, + select: data => selectFirstEightDApps(selectValidNonScamDApps(data)), + ...config, + }); +} From 39198669bfd70e5a8a7426710ed3fc27eef5f8e4 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 8 Aug 2024 13:10:19 -0400 Subject: [PATCH 14/78] fix crash and fix empty state not showing up (#5975) --- src/components/activity-list/ActivityList.js | 64 +++++++++---------- src/hooks/useAccountTransactions.ts | 4 +- .../SectionListScrollToTopContext.tsx | 1 + src/screens/ProfileScreen.js | 2 +- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index 4367a80af1a..d829d4fa8dc 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -79,8 +79,6 @@ function ListFooterComponent({ label, onPress }) { const ActivityList = ({ hasPendingTransaction, header, - isEmpty, - isLoading, nativeCurrency, network, nextPage, @@ -109,41 +107,39 @@ const ActivityList = ({ setScrollToTopRef(ref); }; - if (network === networkTypes.mainnet || sections.length) { - if (isEmpty && !isLoading) { - return {header}; - } else { - return ( - remainingItemsLabel && } - ref={handleListRef} - ListHeaderComponent={header} - alwaysBounceVertical={false} - contentContainerStyle={{ paddingBottom: !transactionsCount ? 0 : 90 }} - extraData={{ - hasPendingTransaction, - nativeCurrency, - pendingTransactionsCount, - }} - getItemLayout={getItemLayout} - initialNumToRender={12} - keyExtractor={keyExtractor} - removeClippedSubviews - renderSectionHeader={renderSectionHeaderWithTheme} - scrollIndicatorInsets={{ - bottom: safeAreaInsetValues.bottom + 14, - }} - sections={sections} - /> - ); - } - } else { + if (network === networkTypes.mainnet) { return ( - - {header} - + remainingItemsLabel && } + ref={handleListRef} + ListHeaderComponent={header} + alwaysBounceVertical={false} + contentContainerStyle={{ paddingBottom: !transactionsCount ? 0 : 90 }} + extraData={{ + hasPendingTransaction, + nativeCurrency, + pendingTransactionsCount, + }} + testID={'wallet-activity-list'} + ListEmptyComponent={} + getItemLayout={getItemLayout} + initialNumToRender={12} + keyExtractor={keyExtractor} + removeClippedSubviews + renderSectionHeader={renderSectionHeaderWithTheme} + scrollIndicatorInsets={{ + bottom: safeAreaInsetValues.bottom + 14, + }} + sections={sections} + /> ); } + + return ( + + {header} + + ); }; export default ActivityList; diff --git a/src/hooks/useAccountTransactions.ts b/src/hooks/useAccountTransactions.ts index 68ac1f9d468..f7dc48cdac1 100644 --- a/src/hooks/useAccountTransactions.ts +++ b/src/hooks/useAccountTransactions.ts @@ -19,7 +19,7 @@ export default function useAccountTransactions() { const pendingTransactions = usePendingTransactionsStore(state => state.pendingTransactions[accountAddress] || []); - const { data, fetchNextPage, hasNextPage } = useConsolidatedTransactions({ + const { data, isLoading, fetchNextPage, hasNextPage } = useConsolidatedTransactions({ address: accountAddress, currency: nativeCurrency, }); @@ -132,7 +132,7 @@ export default function useAccountTransactions() { }, [hasNextPage]); return { - isLoadingTransactions: !!allTransactions, + isLoadingTransactions: isLoading, nextPage: fetchNextPage, remainingItemsLabel, sections, diff --git a/src/navigation/SectionListScrollToTopContext.tsx b/src/navigation/SectionListScrollToTopContext.tsx index 8562da56a6b..6ceb07b898c 100644 --- a/src/navigation/SectionListScrollToTopContext.tsx +++ b/src/navigation/SectionListScrollToTopContext.tsx @@ -17,6 +17,7 @@ const SectionListScrollToTopProvider: React.FC = ({ ch const [scrollToTopRef, setScrollToTopRef] = useState(null); const scrollToTop = () => { + if (!scrollToTopRef?.props.sections.length) return; scrollToTopRef?.scrollToLocation({ animated: true, itemIndex: 0, diff --git a/src/screens/ProfileScreen.js b/src/screens/ProfileScreen.js index bda8e9cf75c..bede588182e 100644 --- a/src/screens/ProfileScreen.js +++ b/src/screens/ProfileScreen.js @@ -61,7 +61,7 @@ export default function ProfileScreen() { } /> - + ); } From b30eedfbe84f663f9f14c902ff9515a1f6017400 Mon Sep 17 00:00:00 2001 From: Ibrahim Taveras Date: Thu, 8 Aug 2024 13:16:59 -0400 Subject: [PATCH 15/78] bump version to v1.9.35 (#5992) --- CHANGELOG.md | 35 +++++++++++++++++++++++++++ android/app/build.gradle | 4 +-- ios/Rainbow.xcodeproj/project.pbxproj | 8 +++--- package.json | 2 +- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b613dc4ee0c..b31b387ed40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,41 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.34] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.34) + +### Added + +- Added the ability to copy/paste swap inputs (#5938) +- Added tracking of critical errors to sentry (#5936) +- Added ability to open in Rainbow from mobile browsers (#5939) +- Added Degen mode to skip review sheet for swaps (#5933, #5963, #5969) +- Added ability to set max on a balance when tapping on badge (#5947) +- Added e2e coverage for manual wallet backups (#5913) +- Added a hold to swap button on swaps flow (#5920, #5976) +- Added a new section that shows the last three user swaps per chain (#5956) +- Added performance tracking on TimeToSign (#5962) + +### Changed + +- Disabled location APIs in VisionCamera since we don’t use location features (#5942) +- Removed FULL_SCREEN_INTENT permission from the manifest (#5955) +- App is now using different referrer for ETH rewards claims (#5954) +- Bumped Android dependencies (#5960) +- Updated Degen mode copy and enabled tracking (#5979) + +### Fixed + +- Fixed a crash on explainer sheet when there wasn’t a read more link (#5945) +- Fixed a bug where some bridges couldn’t be made due to gas (#5949) +- Fixed bugs around flipping logic in swap flow (#5948) +- Fixed issue where there would be a tab swipe from dapp browser on Android devices (#5964) +- Fixed a bug where one could proceed to review on a swap when they shouldn’t (#5967) +- Fixed favorites bugs on search list (#5972) +- Fixed bugs around copy and pasting (#5953) +- Fixed an old route that led to Swaps v1 (#5971) +- Fixed a bug that showed an empty space on token to buy list (#5983, #5989) +- Fixed issues when saving assets as favorites (#5972, #5982) + ## [1.9.33] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.33) ## Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index 87967e32328..f2510af5934 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -139,8 +139,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 225 - versionName "1.9.34" + versionCode 226 + versionName "1.9.35" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 0d0eba8e9aa..62880732ac7 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1833,7 +1833,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.34; + MARKETING_VERSION = 1.9.35; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1895,7 +1895,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.34; + MARKETING_VERSION = 1.9.35; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2011,7 +2011,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.34; + MARKETING_VERSION = 1.9.35; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2127,7 +2127,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.34; + MARKETING_VERSION = 1.9.35; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index 6cf1f8c2b24..c5676ef3ca8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.34-1", + "version": "1.9.35-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", From 228517df6bcfa83aa31a2850a4222be404862f1c Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Thu, 8 Aug 2024 12:02:29 -0600 Subject: [PATCH 16/78] Swaps: Popular Tokens (#5990) --- .../components/TokenList/TokenToBuyList.tsx | 5 + .../Swap/hooks/useSearchCurrencyLists.ts | 88 +++++++- .../screens/Swap/providers/swap-provider.tsx | 3 + .../Swap/resources/_selectors/search.ts | 2 +- .../Swap/resources/search/discovery.ts | 41 ++++ .../screens/Swap/resources/search/index.ts | 202 +----------------- .../screens/Swap/resources/search/search.ts | 125 +++++++++++ .../screens/Swap/resources/search/utils.ts | 75 +++++++ src/__swaps__/types/search.ts | 3 + src/analytics/event.ts | 1 + src/languages/en_US.json | 1 + 11 files changed, 336 insertions(+), 210 deletions(-) create mode 100644 src/__swaps__/screens/Swap/resources/search/discovery.ts create mode 100644 src/__swaps__/screens/Swap/resources/search/search.ts create mode 100644 src/__swaps__/screens/Swap/resources/search/utils.ts diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index 589e4d082c1..c324d3e38c7 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -31,6 +31,11 @@ interface SectionHeaderProp { } const SECTION_HEADER_INFO: { [id in AssetToBuySectionId]: SectionHeaderProp } = { + popular: { + title: i18n.t(i18n.l.token_search.section_header.popular), + symbol: '􀙬', + color: 'rgba(255, 88, 77, 1)', + }, recent: { title: i18n.t(i18n.l.token_search.section_header.recent), symbol: '􀐫', diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index 62d09cba7f2..5d37829f703 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -1,4 +1,4 @@ -import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search'; +import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search/search'; import { AddressOrEth } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset, TokenSearchAssetKey, TokenSearchThreshold } from '@/__swaps__/types/search'; @@ -16,8 +16,9 @@ import { useDebouncedCallback } from 'use-debounce'; import { TokenToBuyListItem } from '../components/TokenList/TokenToBuyList'; import { useSwapContext } from '../providers/swap-provider'; import { RecentSwap } from '@/__swaps__/types/swap'; +import { useTokenDiscovery } from '../resources/search'; -export type AssetToBuySectionId = 'bridge' | 'recent' | 'favorites' | 'verified' | 'unverified' | 'other_networks'; +export type AssetToBuySectionId = 'bridge' | 'recent' | 'favorites' | 'verified' | 'unverified' | 'other_networks' | 'popular'; export interface AssetToBuySection { data: SearchAsset[]; @@ -51,6 +52,14 @@ const filterAssetsFromRecentSwaps = ({ recentSwaps: RecentSwap[] | undefined; }): SearchAsset[] => (assets || []).filter(asset => !recentSwaps?.some(recent => recent.address === asset.address)); +const filterAssetsFromPopularAssets = ({ + assets, + popularAssets, +}: { + assets: SearchAsset[] | undefined; + popularAssets: SearchAsset[] | undefined; +}): SearchAsset[] => (assets || []).filter(asset => !popularAssets?.some(popular => popular.address === asset.address)); + const filterAssetsFromBridgeAndRecent = ({ assets, recentSwaps, @@ -65,20 +74,44 @@ const filterAssetsFromBridgeAndRecent = ({ recentSwaps: recentSwaps, }); -const filterAssetsFromFavoritesAndBridgeAndRecent = ({ +const filterAssetsFromBridgeAndRecentAndPopular = ({ + assets, + recentSwaps, + popularAssets, + filteredBridgeAssetAddress, +}: { + assets: SearchAsset[] | undefined; + recentSwaps: RecentSwap[] | undefined; + popularAssets: SearchAsset[] | undefined; + filteredBridgeAssetAddress: string | undefined; +}): SearchAsset[] => + filterAssetsFromPopularAssets({ + assets: filterAssetsFromRecentSwaps({ + assets: filterAssetsFromBridge({ assets, filteredBridgeAssetAddress }), + recentSwaps: recentSwaps, + }), + popularAssets, + }); + +const filterAssetsFromFavoritesAndBridgeAndRecentAndPopular = ({ assets, favoritesList, filteredBridgeAssetAddress, recentSwaps, + popularAssets, }: { assets: SearchAsset[] | undefined; favoritesList: SearchAsset[] | undefined; filteredBridgeAssetAddress: string | undefined; recentSwaps: RecentSwap[] | undefined; + popularAssets: SearchAsset[] | undefined; }): SearchAsset[] => - filterAssetsFromRecentSwaps({ - assets: filterAssetsFromBridge({ assets, filteredBridgeAssetAddress }), - recentSwaps: recentSwaps, + filterAssetsFromPopularAssets({ + assets: filterAssetsFromRecentSwaps({ + assets: filterAssetsFromBridge({ assets, filteredBridgeAssetAddress }), + recentSwaps: recentSwaps, + }), + popularAssets, })?.filter( curatedAsset => !favoritesList?.some(({ address }) => curatedAsset.address === address || curatedAsset.mainnetAddress === address) ) || []; @@ -100,6 +133,7 @@ const buildListSectionsData = ({ unverifiedAssets?: SearchAsset[]; crosschainExactMatches?: SearchAsset[]; recentSwaps?: RecentSwap[]; + popularAssets?: SearchAsset[]; }; favoritesList: SearchAsset[] | undefined; filteredBridgeAssetAddress: string | undefined; @@ -138,41 +172,60 @@ const buildListSectionsData = ({ ); } + if (combinedData.popularAssets?.length) { + const filteredPopular = filterAssetsFromBridgeAndRecent({ + assets: combinedData.popularAssets, + recentSwaps: combinedData.recentSwaps, + filteredBridgeAssetAddress, + }).slice(0, 6); + addSection( + 'popular', + mergeAssetsFavoriteStatus({ + assets: filteredPopular, + favoritesList, + }) + ); + } + if (favoritesList?.length) { - const filteredFavorites = filterAssetsFromBridgeAndRecent({ + const filteredFavorites = filterAssetsFromBridgeAndRecentAndPopular({ assets: favoritesList, filteredBridgeAssetAddress, recentSwaps: combinedData.recentSwaps, + popularAssets: combinedData.popularAssets, }); addSection('favorites', filteredFavorites); } if (combinedData.verifiedAssets?.length) { - const filteredVerified = filterAssetsFromFavoritesAndBridgeAndRecent({ + const filteredVerified = filterAssetsFromFavoritesAndBridgeAndRecentAndPopular({ assets: combinedData.verifiedAssets, favoritesList, filteredBridgeAssetAddress, recentSwaps: combinedData.recentSwaps, + popularAssets: combinedData.popularAssets, }); addSection('verified', filteredVerified); } if (!formattedData.length && combinedData.crosschainExactMatches?.length) { - const filteredCrosschain = filterAssetsFromFavoritesAndBridgeAndRecent({ + const filteredCrosschain = filterAssetsFromFavoritesAndBridgeAndRecentAndPopular({ assets: combinedData.crosschainExactMatches, favoritesList, filteredBridgeAssetAddress, recentSwaps: combinedData.recentSwaps, + popularAssets: combinedData.popularAssets, }); addSection('other_networks', filteredCrosschain); } if (combinedData.unverifiedAssets?.length) { - const filteredUnverified = filterAssetsFromFavoritesAndBridgeAndRecent({ + const filteredUnverified = filterAssetsFromFavoritesAndBridgeAndRecentAndPopular({ assets: combinedData.unverifiedAssets, favoritesList, filteredBridgeAssetAddress, recentSwaps: combinedData.recentSwaps, + popularAssets: combinedData.popularAssets, }); addSection('unverified', filteredUnverified); } @@ -269,6 +322,8 @@ export function useSearchCurrencyLists() { } ); + const { data: popularAssets, isLoading: isLoadingPopularAssets } = useTokenDiscovery({ chainId: state.toChainId }); + const { favoritesMetadata: favorites } = useFavorites(); const unfilteredFavorites = useMemo(() => { @@ -320,6 +375,14 @@ export function useSearchCurrencyLists() { }); }, [getRecentSwapsByChain, state.toChainId, query, memoizedData.keys, memoizedData.queryIsAddress]); + const popularAssetsForChain = useMemo(() => { + if (!popularAssets) return []; + if (!query) return popularAssets; + return filterList(popularAssets, query, memoizedData.keys, { + threshold: memoizedData.queryIsAddress ? rankings.CASE_SENSITIVE_EQUAL : rankings.CONTAINS, + }); + }, [popularAssets, query, memoizedData.keys, memoizedData.queryIsAddress]); + const favoritesList = useMemo(() => { if (query === '') { return unfilteredFavorites; @@ -366,16 +429,18 @@ export function useSearchCurrencyLists() { unverifiedAssets: unverifiedResults, verifiedAssets: verifiedResults, recentSwaps: recentsForChain, + popularAssets: popularAssetsForChain, }, favoritesList, filteredBridgeAssetAddress: memoizedData.filteredBridgeAsset?.address, }), - isLoading: isLoadingVerifiedAssets || isLoadingUnverifiedAssets, + isLoading: isLoadingVerifiedAssets || isLoadingUnverifiedAssets || isLoadingPopularAssets, }; }, [ favoritesList, isLoadingUnverifiedAssets, isLoadingVerifiedAssets, + isLoadingPopularAssets, memoizedData.enableUnverifiedSearch, memoizedData.filteredBridgeAsset, query, @@ -383,5 +448,6 @@ export function useSearchCurrencyLists() { unverifiedAssets, verifiedAssets, recentsForChain, + popularAssetsForChain, ]); } diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 22569e89d96..bc7e2ee5a5d 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -204,6 +204,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; const isDegenModeEnabled = swapsStore.getState().degenMode; const slippage = swapsStore.getState().slippage; + const isSwappingToPopularAsset = swapsStore.getState().outputAsset?.sectionId === 'popular'; const selectedGas = getSelectedGas(parameters.chainId); if (!selectedGas) { @@ -288,6 +289,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, degenMode: isDegenModeEnabled, + isSwappingToPopularAsset, }); if (errorMessage !== 'handled') { @@ -341,6 +343,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, degenMode: isDegenModeEnabled, + isSwappingToPopularAsset, }); } catch (error) { isSwapping.value = false; diff --git a/src/__swaps__/screens/Swap/resources/_selectors/search.ts b/src/__swaps__/screens/Swap/resources/_selectors/search.ts index ad3c61cc2e8..d29c9f24456 100644 --- a/src/__swaps__/screens/Swap/resources/_selectors/search.ts +++ b/src/__swaps__/screens/Swap/resources/_selectors/search.ts @@ -1,4 +1,4 @@ -import { TokenSearchResult } from '../search'; +import { TokenSearchResult } from '../search/search'; export function filterNonTokenIconAssets(tokens: TokenSearchResult) { return tokens.filter(token => token.icon_url); diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts new file mode 100644 index 00000000000..b64c680ba04 --- /dev/null +++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts @@ -0,0 +1,41 @@ +import { ChainId } from '@/__swaps__/types/chains'; +import { SearchAsset } from '@/__swaps__/types/search'; +import { RainbowError, logger } from '@/logger'; +import { RainbowFetchClient } from '@/rainbow-fetch'; +import { QueryFunctionArgs, createQueryKey } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { parseTokenSearch } from './utils'; + +const tokenSearchHttp = new RainbowFetchClient({ + baseURL: 'https://token-search.rainbow.me/v3/discovery', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + timeout: 30000, +}); + +export type TokenDiscoveryArgs = { + chainId: ChainId; +}; + +const tokenDiscoveryQueryKey = ({ chainId }: TokenDiscoveryArgs) => createQueryKey('TokenDiscovery', { chainId }, { persisterVersion: 1 }); + +async function tokenSearchQueryFunction({ queryKey: [{ chainId }] }: QueryFunctionArgs) { + const url = `/${chainId}`; + + try { + const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); + return parseTokenSearch(tokenSearch.data.data, chainId); + } catch (e) { + logger.error(new RainbowError('Token discovery failed'), { url }); + return []; + } +} + +export function useTokenDiscovery({ chainId }: TokenDiscoveryArgs) { + return useQuery(tokenDiscoveryQueryKey({ chainId }), tokenSearchQueryFunction, { + staleTime: 15 * 60 * 1000, // 15 min + cacheTime: 24 * 60 * 60 * 1000, // 1 day + }); +} diff --git a/src/__swaps__/screens/Swap/resources/search/index.ts b/src/__swaps__/screens/Swap/resources/search/index.ts index 14435b99bc5..3da1ed6644b 100644 --- a/src/__swaps__/screens/Swap/resources/search/index.ts +++ b/src/__swaps__/screens/Swap/resources/search/index.ts @@ -1,198 +1,4 @@ -/* eslint-disable no-nested-ternary */ -import { ChainId } from '@/__swaps__/types/chains'; -import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '@/__swaps__/types/search'; -import { RainbowError, logger } from '@/logger'; -import { RainbowFetchClient } from '@/rainbow-fetch'; -import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; -import { - ARBITRUM_ETH_ADDRESS, - AVAX_AVALANCHE_ADDRESS, - BASE_ETH_ADDRESS, - BLAST_ETH_ADDRESS, - BNB_BSC_ADDRESS, - DEGEN_CHAIN_DEGEN_ADDRESS, - ETH_ADDRESS, - MATIC_POLYGON_ADDRESS, - OPTIMISM_ETH_ADDRESS, - ZORA_ETH_ADDRESS, -} from '@/references'; -import { isAddress } from '@ethersproject/address'; -import { useQuery } from '@tanstack/react-query'; -import qs from 'qs'; -import { Address } from 'viem'; - -const NATIVE_ASSET_UNIQUE_IDS = new Set([ - `${ETH_ADDRESS}_${ChainId.mainnet}`, - `${OPTIMISM_ETH_ADDRESS}_${ChainId.optimism}`, - `${ARBITRUM_ETH_ADDRESS}_${ChainId.arbitrum}`, - `${BNB_BSC_ADDRESS}_${ChainId.bsc}`, - `${MATIC_POLYGON_ADDRESS}_${ChainId.polygon}`, - `${BASE_ETH_ADDRESS}_${ChainId.base}`, - `${ZORA_ETH_ADDRESS}_${ChainId.zora}`, - `${AVAX_AVALANCHE_ADDRESS}_${ChainId.avalanche}`, - `${BLAST_ETH_ADDRESS}_${ChainId.blast}`, - `${DEGEN_CHAIN_DEGEN_ADDRESS}_${ChainId.degen}`, -]); - -const ALL_VERIFIED_TOKENS_PARAM = '/?list=verifiedAssets'; - -const tokenSearchHttp = new RainbowFetchClient({ - baseURL: 'https://token-search.rainbow.me/v2', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - timeout: 30000, -}); - -// /////////////////////////////////////////////// -// Query Types - -export type TokenSearchArgs = { - chainId?: ChainId; - fromChainId?: ChainId | ''; - keys?: TokenSearchAssetKey[]; - list: TokenSearchListId; - threshold?: TokenSearchThreshold; - query?: string; -}; - -// /////////////////////////////////////////////// -// Query Key - -const tokenSearchQueryKey = ({ chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs) => - createQueryKey('TokenSearch', { chainId, fromChainId, keys, list, threshold, query }, { persisterVersion: 2 }); - -type TokenSearchQueryKey = ReturnType; - -// /////////////////////////////////////////////// -// Query Function - -async function tokenSearchQueryFunction({ - queryKey: [{ chainId, fromChainId, keys, list, threshold, query }], -}: QueryFunctionArgs) { - const queryParams: { - keys?: string; - list: TokenSearchListId; - threshold?: TokenSearchThreshold; - query?: string; - fromChainId?: number | string; - } = { - keys: keys?.join(','), - list, - threshold, - query, - fromChainId: fromChainId, - }; - - const isAddressSearch = query && isAddress(query); - - if (isAddressSearch) { - queryParams.keys = `networks.${chainId}.address`; - } - - const url = `${chainId ? `/${chainId}` : ''}/?${qs.stringify(queryParams)}`; - const isSearchingVerifiedAssets = queryParams.list === 'verifiedAssets'; - - try { - if (isAddressSearch && isSearchingVerifiedAssets) { - const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); - - if (tokenSearch && tokenSearch.data.data.length > 0) { - return parseTokenSearch(tokenSearch.data.data, chainId); - } - - const allVerifiedTokens = await tokenSearchHttp.get<{ data: SearchAsset[] }>(ALL_VERIFIED_TOKENS_PARAM); - - const addressQuery = query.trim().toLowerCase(); - const addressMatchesOnOtherChains = allVerifiedTokens.data.data.filter(a => - Object.values(a.networks).some(n => n?.address === addressQuery) - ); - - return parseTokenSearch(addressMatchesOnOtherChains); - } else { - const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); - return parseTokenSearch(tokenSearch.data.data, chainId); - } - } catch (e) { - logger.error(new RainbowError('Token search failed'), { url }); - return []; - } -} - -function parseTokenSearch(assets: SearchAsset[], chainId?: ChainId): SearchAsset[] { - const results: SearchAsset[] = []; - - if (chainId !== undefined) { - for (const asset of assets) { - const assetNetworks = asset.networks; - const mainnetInfo = assetNetworks[ChainId.mainnet]; - const networkInfo = assetNetworks[chainId]; - const address = networkInfo ? networkInfo.address : asset.address; - const uniqueId = `${address}_${chainId}`; - - results.push({ - ...asset, - address, - chainId, - decimals: networkInfo ? networkInfo.decimals : asset.decimals, - isNativeAsset: NATIVE_ASSET_UNIQUE_IDS.has(uniqueId), - mainnetAddress: mainnetInfo ? mainnetInfo.address : chainId === ChainId.mainnet ? address : ('' as Address), - uniqueId, - }); - } - } else { - for (const asset of assets) { - const assetNetworks = asset.networks; - const mainnetInfo = assetNetworks[ChainId.mainnet]; - for (const chainIdString in assetNetworks) { - const networkChainId = parseInt(chainIdString); - const networkInfo = assetNetworks[networkChainId]; - const address = networkInfo ? networkInfo.address : asset.address; - const uniqueId = `${address}_${networkChainId}`; - - results.push({ - ...asset, - address, - chainId: networkChainId, - decimals: networkInfo ? networkInfo.decimals : asset.decimals, - isNativeAsset: NATIVE_ASSET_UNIQUE_IDS.has(uniqueId), - mainnetAddress: mainnetInfo ? mainnetInfo.address : networkChainId === ChainId.mainnet ? address : ('' as Address), - uniqueId, - }); - } - } - } - - return results; -} - -export type TokenSearchResult = QueryFunctionResult; - -// /////////////////////////////////////////////// -// Query Fetcher - -export async function fetchTokenSearch( - { chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs, - config: QueryConfigWithSelect = {} -) { - return await queryClient.fetchQuery( - tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }), - tokenSearchQueryFunction, - config - ); -} - -// /////////////////////////////////////////////// -// Query Hook - -export function useTokenSearch( - { chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs, - config: QueryConfigWithSelect = {} -) { - return useQuery( - tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }), - tokenSearchQueryFunction, - config ? { ...config, keepPreviousData: true } : { keepPreviousData: true } - ); -} +export { useTokenDiscovery } from './discovery'; +export type { TokenDiscoveryArgs } from './discovery'; +export { useTokenSearch, fetchTokenSearch } from './search'; +export type { TokenSearchArgs, TokenSearchResult } from './search'; diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts new file mode 100644 index 00000000000..86c45602cdd --- /dev/null +++ b/src/__swaps__/screens/Swap/resources/search/search.ts @@ -0,0 +1,125 @@ +/* eslint-disable no-nested-ternary */ +import { ChainId } from '@/__swaps__/types/chains'; +import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '@/__swaps__/types/search'; +import { RainbowError, logger } from '@/logger'; +import { RainbowFetchClient } from '@/rainbow-fetch'; +import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; +import { isAddress } from '@ethersproject/address'; +import { useQuery } from '@tanstack/react-query'; +import qs from 'qs'; +import { parseTokenSearch } from './utils'; + +const ALL_VERIFIED_TOKENS_PARAM = '/?list=verifiedAssets'; + +const tokenSearchHttp = new RainbowFetchClient({ + baseURL: 'https://token-search.rainbow.me/v2', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + timeout: 30000, +}); + +// /////////////////////////////////////////////// +// Query Types + +export type TokenSearchArgs = { + chainId?: ChainId; + fromChainId?: ChainId | ''; + keys?: TokenSearchAssetKey[]; + list: TokenSearchListId; + threshold?: TokenSearchThreshold; + query?: string; +}; + +// /////////////////////////////////////////////// +// Query Key + +const tokenSearchQueryKey = ({ chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs) => + createQueryKey('TokenSearch', { chainId, fromChainId, keys, list, threshold, query }, { persisterVersion: 2 }); + +type TokenSearchQueryKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Function + +async function tokenSearchQueryFunction({ + queryKey: [{ chainId, fromChainId, keys, list, threshold, query }], +}: QueryFunctionArgs) { + const queryParams: { + keys?: string; + list: TokenSearchListId; + threshold?: TokenSearchThreshold; + query?: string; + fromChainId?: number | string; + } = { + keys: keys?.join(','), + list, + threshold, + query, + fromChainId: fromChainId, + }; + + const isAddressSearch = query && isAddress(query); + + if (isAddressSearch) { + queryParams.keys = `networks.${chainId}.address`; + } + + const url = `${chainId ? `/${chainId}` : ''}/?${qs.stringify(queryParams)}`; + const isSearchingVerifiedAssets = queryParams.list === 'verifiedAssets'; + + try { + if (isAddressSearch && isSearchingVerifiedAssets) { + const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); + + if (tokenSearch && tokenSearch.data.data.length > 0) { + return parseTokenSearch(tokenSearch.data.data, chainId); + } + + const allVerifiedTokens = await tokenSearchHttp.get<{ data: SearchAsset[] }>(ALL_VERIFIED_TOKENS_PARAM); + + const addressQuery = query.trim().toLowerCase(); + const addressMatchesOnOtherChains = allVerifiedTokens.data.data.filter(a => + Object.values(a.networks).some(n => n?.address === addressQuery) + ); + + return parseTokenSearch(addressMatchesOnOtherChains); + } else { + const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); + return parseTokenSearch(tokenSearch.data.data, chainId); + } + } catch (e) { + logger.error(new RainbowError('Token search failed'), { url }); + return []; + } +} + +export type TokenSearchResult = QueryFunctionResult; + +// /////////////////////////////////////////////// +// Query Fetcher + +export async function fetchTokenSearch( + { chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs, + config: QueryConfigWithSelect = {} +) { + return await queryClient.fetchQuery( + tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }), + tokenSearchQueryFunction, + config + ); +} + +// /////////////////////////////////////////////// +// Query Hook + +export function useTokenSearch( + { chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs, + config: QueryConfigWithSelect = {} +) { + return useQuery(tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }), tokenSearchQueryFunction, { + ...config, + keepPreviousData: true, + }); +} diff --git a/src/__swaps__/screens/Swap/resources/search/utils.ts b/src/__swaps__/screens/Swap/resources/search/utils.ts new file mode 100644 index 00000000000..0ed74aeb80e --- /dev/null +++ b/src/__swaps__/screens/Swap/resources/search/utils.ts @@ -0,0 +1,75 @@ +import { ChainId } from '@/__swaps__/types/chains'; +import { SearchAsset } from '@/__swaps__/types/search'; +import { + ARBITRUM_ETH_ADDRESS, + AVAX_AVALANCHE_ADDRESS, + BASE_ETH_ADDRESS, + BLAST_ETH_ADDRESS, + BNB_BSC_ADDRESS, + DEGEN_CHAIN_DEGEN_ADDRESS, + ETH_ADDRESS, + MATIC_POLYGON_ADDRESS, + OPTIMISM_ETH_ADDRESS, + ZORA_ETH_ADDRESS, +} from '@/references'; +import { Address } from 'viem'; + +const NATIVE_ASSET_UNIQUE_IDS = new Set([ + `${ETH_ADDRESS}_${ChainId.mainnet}`, + `${OPTIMISM_ETH_ADDRESS}_${ChainId.optimism}`, + `${ARBITRUM_ETH_ADDRESS}_${ChainId.arbitrum}`, + `${BNB_BSC_ADDRESS}_${ChainId.bsc}`, + `${MATIC_POLYGON_ADDRESS}_${ChainId.polygon}`, + `${BASE_ETH_ADDRESS}_${ChainId.base}`, + `${ZORA_ETH_ADDRESS}_${ChainId.zora}`, + `${AVAX_AVALANCHE_ADDRESS}_${ChainId.avalanche}`, + `${BLAST_ETH_ADDRESS}_${ChainId.blast}`, + `${DEGEN_CHAIN_DEGEN_ADDRESS}_${ChainId.degen}`, +]); + +export function parseTokenSearch(assets: SearchAsset[], chainId?: ChainId): SearchAsset[] { + const results: SearchAsset[] = []; + + if (chainId !== undefined) { + for (const asset of assets) { + const assetNetworks = asset.networks; + const mainnetInfo = assetNetworks[ChainId.mainnet]; + const networkInfo = assetNetworks[chainId]; + const address = networkInfo ? networkInfo.address : asset.address; + const uniqueId = `${address}_${chainId}`; + + results.push({ + ...asset, + address, + chainId, + decimals: networkInfo ? networkInfo.decimals : asset.decimals, + isNativeAsset: NATIVE_ASSET_UNIQUE_IDS.has(uniqueId), + mainnetAddress: mainnetInfo ? mainnetInfo.address : chainId === ChainId.mainnet ? address : ('' as Address), + uniqueId, + }); + } + } else { + for (const asset of assets) { + const assetNetworks = asset.networks; + const mainnetInfo = assetNetworks[ChainId.mainnet]; + for (const chainIdString in assetNetworks) { + const networkChainId = parseInt(chainIdString); + const networkInfo = assetNetworks[networkChainId]; + const address = networkInfo ? networkInfo.address : asset.address; + const uniqueId = `${address}_${networkChainId}`; + + results.push({ + ...asset, + address, + chainId: networkChainId, + decimals: networkInfo ? networkInfo.decimals : asset.decimals, + isNativeAsset: NATIVE_ASSET_UNIQUE_IDS.has(uniqueId), + mainnetAddress: mainnetInfo ? mainnetInfo.address : networkChainId === ChainId.mainnet ? address : ('' as Address), + uniqueId, + }); + } + } + } + + return results; +} diff --git a/src/__swaps__/types/search.ts b/src/__swaps__/types/search.ts index 335b8bb847b..5acf427de4e 100644 --- a/src/__swaps__/types/search.ts +++ b/src/__swaps__/types/search.ts @@ -2,6 +2,7 @@ import { Address } from 'viem'; import { AddressOrEth, AssetType, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; +import { AssetToBuySectionId } from '../screens/Swap/hooks/useSearchCurrencyLists'; export type TokenSearchAssetKey = keyof ParsedAsset; @@ -16,6 +17,7 @@ export type SearchAsset = { decimals: number; highLiquidity: boolean; icon_url?: string; + isPopular?: boolean; isRainbowCurated: boolean; isNativeAsset?: boolean; isVerified: boolean; @@ -28,6 +30,7 @@ export type SearchAsset = { }; }; rainbowMetadataId?: number; + sectionId?: AssetToBuySectionId; symbol: string; type?: AssetType; uniqueId: UniqueId; diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 7c8a797b545..ebb6849433e 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -163,6 +163,7 @@ type SwapEventParameters = { selectedGasSpeed: GasSpeed; slippage: string; degenMode: boolean; + isSwappingToPopularAsset: boolean; }; type SwapsEventFailedParameters = { diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 39be923db06..70c93854a3f 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2059,6 +2059,7 @@ "token_search": { "section_header": { "recent": "Recent", + "popular": "Popular in Rainbow", "favorites": "Favorites", "bridge": "Bridge", "verified": "Verified", From 13ac9e396b47516547b9a03e3f960d61a9e404cf Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Fri, 9 Aug 2024 07:34:38 -0600 Subject: [PATCH 17/78] Default the Swap input currency to ETH (#5994) --- src/__swaps__/screens/Swap/Swap.tsx | 12 ++++---- .../control-panel/ControlPanel.tsx | 4 +-- .../ProfileActionButtonsRow.tsx | 2 +- src/state/assets/userAssets.ts | 28 +++++++++++-------- src/state/sync/UserAssetsSync.tsx | 2 +- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 74f0880de0f..6634ee6b99b 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -105,12 +105,12 @@ const useMountSignal = () => { const useCleanupOnUnmount = () => { useEffect(() => { return () => { - const highestValueAsset = userAssetsStore.getState().getHighestValueAsset(); - const parsedAsset = highestValueAsset + const highestValueEth = userAssetsStore.getState().getHighestValueEth(); + const parsedAsset = highestValueEth ? parseSearchAsset({ assetWithPrice: undefined, - searchAsset: highestValueAsset, - userAsset: highestValueAsset, + searchAsset: highestValueEth, + userAsset: highestValueEth, }) : null; @@ -135,7 +135,7 @@ const WalletAddressObserver = () => { const { setAsset } = useSwapContext(); const setNewInputAsset = useCallback(() => { - const newHighestValueAsset = userAssetsStore.getState().getHighestValueAsset(); + const newHighestValueEth = userAssetsStore.getState().getHighestValueEth(); if (userAssetsStore.getState().filter !== 'all') { userAssetsStore.setState({ filter: 'all' }); @@ -143,7 +143,7 @@ const WalletAddressObserver = () => { setAsset({ type: SwapAssetType.inputAsset, - asset: newHighestValueAsset, + asset: newHighestValueEth, }); if (userAssetsStore.getState().userAssets.size === 0) { diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index dad846c6c85..60d60904d56 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -456,7 +456,7 @@ const HomePanel = ({ if (swaps_v2 || swapsV2Enabled) { swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueAsset(), + inputAsset: userAssetsStore.getState().getHighestValueEth(), }); InteractionManager.runAfterInteractions(() => { navigate(Routes.SWAP); @@ -484,7 +484,7 @@ const HomePanel = ({ // TODO: We need to set something in swapsStore that deliniates between a swap and bridge // for now let's just treat it like a normal swap swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueAsset(), + inputAsset: userAssetsStore.getState().getHighestValueEth(), }); InteractionManager.runAfterInteractions(() => { navigate(Routes.SWAP); diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index e2eb7f8ded0..723fef826aa 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -181,7 +181,7 @@ function SwapButton() { }); if (swapsV2Enabled) { swapsStore.setState({ - inputAsset: userAssetsStore.getState().getHighestValueAsset(), + inputAsset: userAssetsStore.getState().getHighestValueEth(), }); InteractionManager.runAfterInteractions(() => { navigate(Routes.SWAP); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index ecb5330ed39..90834dde2e0 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; -import { SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; +import { ETH_ADDRESS, SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; @@ -35,7 +35,7 @@ export interface UserAssetsState { getBalanceSortedChainList: () => ChainId[]; getChainsWithBalance: () => ChainId[]; getFilteredUserAssetIds: () => UniqueId[]; - getHighestValueAsset: (usePreferredNetwork?: boolean) => ParsedSearchAsset | null; + getHighestValueEth: () => ParsedSearchAsset | null; getUserAsset: (uniqueId: UniqueId) => ParsedSearchAsset | null; getUserAssets: () => ParsedSearchAsset[]; selectUserAssetIds: (selector: (asset: ParsedSearchAsset) => boolean, filter?: UserAssetFilter) => Generator; @@ -180,21 +180,25 @@ export const userAssetsStore = createRainbowStore( } }, - getHighestValueAsset: (usePreferredNetwork = true) => { - const preferredNetwork = usePreferredNetwork ? swapsStore.getState().preferredNetwork : undefined; + getHighestValueEth: () => { + const preferredNetwork = swapsStore.getState().preferredNetwork; const assets = get().userAssets; - if (preferredNetwork && get().getChainsWithBalance().includes(preferredNetwork)) { - // Find the highest-value asset on the preferred network - for (const [, asset] of assets) { - if (asset.chainId === preferredNetwork) { - return asset; - } + let highestValueEth = null; + + for (const [, asset] of assets) { + if (asset.mainnetAddress !== ETH_ADDRESS) continue; + + if (preferredNetwork && asset.chainId === preferredNetwork) { + return asset; + } + + if (!highestValueEth || asset.balance > highestValueEth.balance) { + highestValueEth = asset; } } - // If no preferred network asset, return the highest-value asset - return assets.values().next().value || null; + return highestValueEth; }, getUserAsset: (uniqueId: UniqueId) => get().userAssets.get(uniqueId) || null, diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 636b937c0d0..f4573bb7834 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -30,7 +30,7 @@ export const UserAssetsSync = memo(function UserAssetsSync() { if (!isSwapsOpen || userAssetsWalletAddress !== currentAddress) { userAssetsStore.getState().setUserAssets(currentAddress as Address, data as ParsedSearchAsset[]); - const inputAsset = userAssetsStore.getState().getHighestValueAsset(); + const inputAsset = userAssetsStore.getState().getHighestValueEth(); useSwapsStore.setState({ inputAsset, selectedOutputChainId: inputAsset?.chainId ?? ChainId.mainnet, From 624356334dd027781dbbf4b033c7ddc9f0f648fd Mon Sep 17 00:00:00 2001 From: brdy <41711440+BrodyHughes@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:49:39 -0500 Subject: [PATCH 18/78] . (#5993) --- .github/workflows/macstadium-e2e.yml | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-e2e.yml index 0ae2e69dd9d..cdac044842d 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-e2e.yml @@ -1,13 +1,14 @@ name: iOS e2e tests on: [pull_request, workflow_dispatch] + jobs: - ios-e2e: + # SETUP JOB + setup: runs-on: ["self-hosted"] - concurrency: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read @@ -30,7 +31,7 @@ jobs: mv rainbow-env/dotenv .env && rm -rf rainbow-env eval $CI_SCRIPTS_RN_UPGRADE sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - + - name: Get Yarn cache directory path id: yarn-cache-dir-path run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT @@ -50,6 +51,11 @@ jobs: run: | yarn cache clean --all && yarn install && yarn setup + # LINT JOB + lint: + runs-on: ["self-hosted"] + needs: [setup] + steps: - name: Check for frozen lockfile run: ./scripts/check-lockfile.sh @@ -59,16 +65,26 @@ jobs: - name: Lint run: yarn lint:ci + # UNIT TESTS JOB + unit-tests: + runs-on: ["self-hosted"] + needs: [setup] + steps: - name: Unit tests run: yarn test + # BUILD JOB + build: + runs-on: ["self-hosted"] + needs: [setup] + steps: - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache - + - name: Version debug run: | npx react-native info - + - name: Install pods run: yarn install-bundle && yarn install-pods @@ -80,6 +96,10 @@ jobs: - name: Build the app in release mode run: yarn detox build --configuration ios.sim.release - # change the '3' here to how many times you want the tests to rerun on failure + # E2E TESTS JOB + e2e-tests: + runs-on: ["self-hosted"] + needs: [build] + steps: - name: Run iOS e2e tests with retry run: ./scripts/run-retry-tests.sh 3 From d0608929b5ea314df95a609a077d52b370bdfb3f Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:27:19 -0400 Subject: [PATCH 19/78] Design system improvements (#5984) --- .../screens/Swap/components/ReviewPanel.tsx | 2 +- .../color/AccentColorContext.tsx | 17 ++++--- .../components/Border/Border.tsx | 44 ++++++++++++++--- src/design-system/components/Box/Box.tsx | 49 +++++++++++++++++-- .../components/Inline/Inline.tsx | 43 +++++++--------- src/design-system/components/Stack/Stack.tsx | 44 +++++++---------- .../private/ApplyShadow/AndroidShadow.tsx | 4 +- .../private/ApplyShadow/ApplyShadow.tsx | 6 ++- .../private/ApplyShadow/IOSShadow.tsx | 4 +- 9 files changed, 137 insertions(+), 76 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index c5ad06855ee..1988c2fa001 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -471,7 +471,7 @@ export function ReviewPanel() { - + diff --git a/src/design-system/color/AccentColorContext.tsx b/src/design-system/color/AccentColorContext.tsx index 91e58fbff1f..af5fdd82ad1 100644 --- a/src/design-system/color/AccentColorContext.tsx +++ b/src/design-system/color/AccentColorContext.tsx @@ -1,5 +1,6 @@ import chroma from 'chroma-js'; import React, { createContext, ReactNode, useContext, useMemo } from 'react'; +import { useBackgroundColor } from '../components/BackgroundProvider/BackgroundProvider'; import { BackgroundColorValue } from './palettes'; export const AccentColorContext = createContext(null); @@ -21,14 +22,14 @@ export function useAccentColor(): BackgroundColorValue { * @description Sets the `"accent"` color for an entire subtree of the app. */ export function AccentColorProvider({ color, children }: AccentColorProviderProps) { - const contextValue = useMemo( - () => - ({ - color, - mode: chroma.contrast(color, '#fff') > 2.125 ? 'darkTinted' : 'lightTinted', - }) as const, - [color] - ); + const blue = useBackgroundColor('blue'); + const contextValue = useMemo(() => { + const isValid = chroma.valid(color); + return { + color: isValid ? color : blue, + mode: isValid && chroma.contrast(color, '#fff') > 2.125 ? 'darkTinted' : 'lightTinted', + } as const; + }, [blue, color]); return {children}; } diff --git a/src/design-system/components/Border/Border.tsx b/src/design-system/components/Border/Border.tsx index 463d7eb0971..84b8fcb655d 100644 --- a/src/design-system/components/Border/Border.tsx +++ b/src/design-system/components/Border/Border.tsx @@ -1,24 +1,49 @@ -import React from 'react'; +import React, { memo } from 'react'; import { Cover, useColorMode, useForegroundColor } from '@/design-system'; import { ForegroundColor } from '@/design-system/color/palettes'; import { CustomColor } from '@/design-system/color/useForegroundColor'; import { IS_IOS } from '@/env'; -export interface BorderProps { +export type BorderProps = { + borderBottomLeftRadius?: number; + borderBottomRightRadius?: number; borderColor?: ForegroundColor | CustomColor; - borderRadius: number; + borderTopLeftRadius?: number; + borderTopRightRadius?: number; + borderRadius?: number; borderWidth?: number; enableInLightMode?: boolean; enableOnAndroid?: boolean; -} +} & ( + | { + borderBottomRadius?: number; + borderLeftRadius?: never; + borderRightRadius?: never; + borderTopRadius?: number; + } + | { + borderBottomRadius?: never; + borderLeftRadius?: number; + borderRightRadius?: number; + borderTopRadius?: never; + } +); -export const Border = ({ +export const Border = memo(function Border({ + borderBottomLeftRadius, + borderBottomRadius, + borderBottomRightRadius, borderColor = 'separatorSecondary', + borderLeftRadius, borderRadius, + borderRightRadius, + borderTopLeftRadius, + borderTopRadius, + borderTopRightRadius, borderWidth = 1, enableInLightMode, enableOnAndroid = true, -}: BorderProps) => { +}: BorderProps) { const { isDarkMode } = useColorMode(); const color = useForegroundColor(borderColor); @@ -26,13 +51,16 @@ export const Border = ({ return (isDarkMode || enableInLightMode) && (IS_IOS || enableOnAndroid) ? ( ) : null; -}; +}); diff --git a/src/design-system/components/Box/Box.tsx b/src/design-system/components/Box/Box.tsx index 7513404770b..26287386771 100644 --- a/src/design-system/components/Box/Box.tsx +++ b/src/design-system/components/Box/Box.tsx @@ -1,11 +1,13 @@ import React, { forwardRef, ReactNode, useMemo } from 'react'; import { View, ViewStyle } from 'react-native'; +import Animated from 'react-native-reanimated'; import { useForegroundColor, useForegroundColors } from '../../color/useForegroundColor'; import { useColorMode } from '../../color/ColorMode'; import { Shadow, shadows } from '../../layout/shadow'; import { Height, heights, Width, widths } from '../../layout/size'; import { NegativeSpace, negativeSpace, positionSpace, PositionSpace, Space, space } from '../../layout/space'; import { BackgroundProvider, BackgroundProviderProps } from '../BackgroundProvider/BackgroundProvider'; +import { Border, BorderProps } from '../Border/Border'; import { ApplyShadow } from '../private/ApplyShadow/ApplyShadow'; import type * as Polymorphic from './polymorphic'; @@ -26,6 +28,8 @@ export type BoxProps = { borderTopRightRadius?: number; borderBottomLeftRadius?: number; borderBottomRightRadius?: number; + borderColor?: BorderProps['borderColor']; + borderWidth?: BorderProps['borderWidth']; bottom?: PositionSpace; children?: ReactNode; flexBasis?: 0; @@ -45,6 +49,7 @@ export type BoxProps = { marginRight?: NegativeSpace; marginTop?: NegativeSpace; marginVertical?: NegativeSpace; + overflow?: 'visible' | 'hidden' | 'scroll'; padding?: Space; paddingBottom?: Space; paddingHorizontal?: Space; @@ -61,7 +66,6 @@ export type BoxProps = { elevation?: number; shadowOpacity?: number; shadowRadius?: number; - overflow?: 'visible' | 'hidden' | 'scroll' | 'auto'; } & ( | { borderBottomRadius?: number; @@ -103,12 +107,14 @@ export const Box = forwardRef(function Box( borderBottomLeftRadius, borderBottomRadius, borderBottomRightRadius, + borderColor, borderLeftRadius, borderRadius, borderRightRadius, borderTopLeftRadius, borderTopRadius, borderTopRightRadius, + borderWidth, bottom: bottomProp, children, flexBasis, @@ -165,14 +171,25 @@ export const Box = forwardRef(function Box( const right = resolveToken(positionSpace, rightProp); const top = resolveToken(positionSpace, topProp); - const width = resolveToken(widths, widthProp); - const height = resolveToken(heights, heightProp); + const width = typeof widthProp === 'number' ? widthProp : resolveToken(widths, widthProp); + const height = typeof heightProp === 'number' ? heightProp : resolveToken(heights, heightProp); + + const isView = Component === View || Component === Animated.View; + + const shadowStylesExist = + !!styleProp && + ('shadowColor' in styleProp || + 'shadowOffset' in styleProp || + 'shadowOpacity' in styleProp || + 'shadowRadius' in styleProp || + 'elevation' in styleProp); const shadows = useShadow(shadow); const styles = useMemo(() => { return { alignItems, + borderRadius: borderRadius, // Apply this first as certain components don't support individual corner radii borderBottomLeftRadius: borderBottomLeftRadius ?? borderBottomRadius ?? borderLeftRadius ?? borderRadius, borderBottomRightRadius: borderBottomRightRadius ?? borderBottomRadius ?? borderRightRadius ?? borderRadius, borderCurve: 'continuous' as ViewStyle['borderCurve'], @@ -194,6 +211,7 @@ export const Box = forwardRef(function Box( marginRight, marginTop, marginVertical, + ...((isView || borderRadius) && !shadowStylesExist && { overflow: borderRadius ? 'hidden' : overflow }), padding, paddingBottom, paddingHorizontal, @@ -224,6 +242,7 @@ export const Box = forwardRef(function Box( flexShrink, flexWrap, height, + isView, justifyContent, left, margin, @@ -233,6 +252,7 @@ export const Box = forwardRef(function Box( marginRight, marginTop, marginVertical, + overflow, padding, paddingBottom, paddingHorizontal, @@ -242,6 +262,7 @@ export const Box = forwardRef(function Box( paddingVertical, position, right, + shadowStylesExist, top, width, ]); @@ -254,6 +275,17 @@ export const Box = forwardRef(function Box( {children} + {borderColor || borderWidth ? ( + + ) : null} )} @@ -261,6 +293,17 @@ export const Box = forwardRef(function Box( ) : ( {children} + {borderColor || borderWidth ? ( + + ) : null} ); }) as PolymorphicBox; diff --git a/src/design-system/components/Inline/Inline.tsx b/src/design-system/components/Inline/Inline.tsx index cb007b5cd4e..5754bae6a93 100644 --- a/src/design-system/components/Inline/Inline.tsx +++ b/src/design-system/components/Inline/Inline.tsx @@ -1,8 +1,7 @@ -import React, { Children, ReactElement, ReactNode, useMemo } from 'react'; -import flattenChildren from 'react-flatten-children'; +import React, { Children, ReactElement, ReactNode } from 'react'; import { AlignHorizontal, alignHorizontalToFlexAlign, AlignVertical, alignVerticalToFlexAlign } from '../../layout/alignment'; -import { negateSpace, Space } from '../../layout/space'; -import { Box } from '../Box/Box'; +import { Space, space as spaceTokens } from '../../layout/space'; +import { Box, resolveToken } from '../Box/Box'; export type InlineProps = { children: ReactNode; @@ -39,34 +38,28 @@ export function Inline({ const verticalSpace = verticalSpaceProp ?? space; const horizontalSpace = horizontalSpaceProp ?? space; - const flattenedChildren = useMemo(() => flattenChildren(children), [children]); - return ( - {Children.map(flattenedChildren, (child, index) => { - if (wrap) { - return ( - - {child} - - ); - } - - const isLastChild = index === flattenedChildren.length - 1; - return ( - <> - {horizontalSpace && !isLastChild ? {child} : child} - {separator && !isLastChild ? {separator} : null} - - ); - })} + {wrap || !separator + ? children + : Children.map(children, (child, index) => { + if (!child) return null; + return ( + <> + {index > 0 && separator} + {child} + + ); + })} ); } diff --git a/src/design-system/components/Stack/Stack.tsx b/src/design-system/components/Stack/Stack.tsx index 8cd80c3f69e..b22cd28e86f 100644 --- a/src/design-system/components/Stack/Stack.tsx +++ b/src/design-system/components/Stack/Stack.tsx @@ -1,7 +1,6 @@ import React, { Children, isValidElement, ReactElement, ReactNode } from 'react'; -import flattenChildren from 'react-flatten-children'; -import { Space } from '../../layout/space'; -import { Box } from '../Box/Box'; +import { Space, space as spaceTokens } from '../../layout/space'; +import { Box, resolveToken } from '../Box/Box'; import { Width } from '@/design-system/layout/size'; const alignHorizontalToFlexAlign = { @@ -28,33 +27,28 @@ export type StackProps = { * within each other for layouts with differing amounts of space between groups * of content. */ -export function Stack({ children: childrenProp, alignHorizontal, separator, space, width }: StackProps) { +export function Stack({ children, alignHorizontal, separator, space, width }: StackProps) { if (__DEV__ && separator && !isValidElement(separator)) { throw new Error(`Stack: The 'separator' prop must be a React element`); } - const children = flattenChildren(childrenProp).filter(child => isValidElement(child)); - return ( - - {Children.map(children, (child, index) => { - const isLastChild = index === children.length - 1; - - return ( - <> - {space && !isLastChild ? {child} : child} - {separator && !isLastChild ? ( - - {separator} - - ) : null} - - ); - })} + + {!separator + ? children + : Children.map(children, (child, index) => { + if (!child) return null; + return ( + <> + {index > 0 && separator} + {child} + + ); + })} ); } diff --git a/src/design-system/components/private/ApplyShadow/AndroidShadow.tsx b/src/design-system/components/private/ApplyShadow/AndroidShadow.tsx index b1c0207530b..4aa94af273a 100644 --- a/src/design-system/components/private/ApplyShadow/AndroidShadow.tsx +++ b/src/design-system/components/private/ApplyShadow/AndroidShadow.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, ViewStyle } from 'react-native'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import type { AndroidShadowItem } from './ApplyShadow'; @@ -10,7 +10,7 @@ export function AndroidShadow({ }: { backgroundColor: ViewStyle['backgroundColor']; shadow: AndroidShadowItem; - style: ViewStyle; + style: StyleProp; }) { const { color, elevation = 0, opacity } = shadow; return ( diff --git a/src/design-system/components/private/ApplyShadow/ApplyShadow.tsx b/src/design-system/components/private/ApplyShadow/ApplyShadow.tsx index a4bc14cf2ad..e275b62bf58 100644 --- a/src/design-system/components/private/ApplyShadow/ApplyShadow.tsx +++ b/src/design-system/components/private/ApplyShadow/ApplyShadow.tsx @@ -97,8 +97,10 @@ export const ApplyShadow = React.forwardRef(({ backgroundColor, children: child, return ( - {(ios || web) && } - {android && } + {(ios || web) && } + {android && ( + + )} {React.cloneElement(child, { style: [{ flex: 1 }, childStyles, android ? androidChildStyles : undefined], diff --git a/src/design-system/components/private/ApplyShadow/IOSShadow.tsx b/src/design-system/components/private/ApplyShadow/IOSShadow.tsx index b962dbb8ee7..ee291dededd 100644 --- a/src/design-system/components/private/ApplyShadow/IOSShadow.tsx +++ b/src/design-system/components/private/ApplyShadow/IOSShadow.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, ViewStyle } from 'react-native'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import type { ShadowItem } from './ApplyShadow'; @@ -10,7 +10,7 @@ export function IOSShadow({ }: { backgroundColor: ViewStyle['backgroundColor']; shadows: ShadowItem[]; - style: ViewStyle; + style: StyleProp; }) { return ( <> From 796ec0b945c58894f5e114d04d4b92423aad13d3 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:26:07 -0400 Subject: [PATCH 20/78] Revert ". (#5993)" (#5999) --- .github/workflows/macstadium-e2e.yml | 34 ++++++---------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-e2e.yml index cdac044842d..0ae2e69dd9d 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-e2e.yml @@ -1,14 +1,13 @@ name: iOS e2e tests on: [pull_request, workflow_dispatch] - jobs: - # SETUP JOB - setup: + ios-e2e: runs-on: ["self-hosted"] - concurrency: + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + permissions: contents: read @@ -31,7 +30,7 @@ jobs: mv rainbow-env/dotenv .env && rm -rf rainbow-env eval $CI_SCRIPTS_RN_UPGRADE sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - + - name: Get Yarn cache directory path id: yarn-cache-dir-path run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT @@ -51,11 +50,6 @@ jobs: run: | yarn cache clean --all && yarn install && yarn setup - # LINT JOB - lint: - runs-on: ["self-hosted"] - needs: [setup] - steps: - name: Check for frozen lockfile run: ./scripts/check-lockfile.sh @@ -65,26 +59,16 @@ jobs: - name: Lint run: yarn lint:ci - # UNIT TESTS JOB - unit-tests: - runs-on: ["self-hosted"] - needs: [setup] - steps: - name: Unit tests run: yarn test - # BUILD JOB - build: - runs-on: ["self-hosted"] - needs: [setup] - steps: - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache - + - name: Version debug run: | npx react-native info - + - name: Install pods run: yarn install-bundle && yarn install-pods @@ -96,10 +80,6 @@ jobs: - name: Build the app in release mode run: yarn detox build --configuration ios.sim.release - # E2E TESTS JOB - e2e-tests: - runs-on: ["self-hosted"] - needs: [build] - steps: + # change the '3' here to how many times you want the tests to rerun on failure - name: Run iOS e2e tests with retry run: ./scripts/run-retry-tests.sh 3 From d0c2f167ab17e022eea7b08a12886fd3a6fbe5ac Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 13 Aug 2024 11:03:10 -0600 Subject: [PATCH 21/78] swaps bug fix: missing asset balance for some tokens (#5998) * remove filter * forgot this --- .../screens/Swap/resources/assets/userAssets.ts | 4 ++-- src/__swaps__/utils/assets.ts | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index 37423b6d930..cd0e6abdca8 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -11,7 +11,7 @@ import { SupportedCurrencyKey, SUPPORTED_CHAIN_IDS } from '@/references'; import { ParsedAssetsDictByChain, ZerionAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; -import { filterAsset, parseUserAsset } from '@/__swaps__/utils/assets'; +import { parseUserAsset } from '@/__swaps__/utils/assets'; import { greaterThan } from '@/__swaps__/utils/numbers'; import { fetchUserAssetsByChain } from './userAssetsByChain'; @@ -190,7 +190,7 @@ export async function parseUserAssets({ }) { const parsedAssetsDict = chainIds.reduce((dict, currentChainId) => ({ ...dict, [currentChainId]: {} }), {}) as ParsedAssetsDictByChain; for (const { asset, quantity, small_balance } of assets) { - if (!filterAsset(asset) && greaterThan(quantity, 0)) { + if (greaterThan(quantity, 0)) { const parsedAsset = parseUserAsset({ asset, currency, diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts index 96d0b9f2dc0..8b888c36fc2 100644 --- a/src/__swaps__/utils/assets.ts +++ b/src/__swaps__/utils/assets.ts @@ -305,15 +305,6 @@ export const parseSearchAsset = ({ type: userAsset?.type || assetWithPrice?.type || searchAsset?.type, }); -export function filterAsset(asset: ZerionAsset) { - const nameFragments = asset?.name?.split(' '); - const nameContainsURL = nameFragments.some(f => isValidDomain(f)); - const symbolFragments = asset?.symbol?.split(' '); - const symbolContainsURL = symbolFragments.some(f => isValidDomain(f)); - const shouldFilter = nameContainsURL || symbolContainsURL; - return shouldFilter; -} - const assetQueryFragment = ( address: AddressOrEth, chainId: ChainId, From 5feae69c2033898bee7b0a2cea608c6b0721e7c1 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:15:08 -0400 Subject: [PATCH 22/78] Drag and drop components, browser favorites reordering (#5978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add drag-and-drop components, useLayoutWorklet Slightly modified implementation based on https://github.com/mgcrea/react-native-dnd * Make browser favorites reorderable, migrate favoriteDapps store to createRainbowStore * Fix fallback logos, add useLayoutWorklet documentation * Reorder imports * Clean up favorites stores, types Removes unnecessary additions to the legacy store * Improve reliability of browser favorites URL handling Resolves issues causing certain favorites not to be removable (due to params in the URL or a subtly different URL path), by: - Adding more lenient fallback lookup mechanisms if no exact match is found - Trimming params from the standardized URLs Also moves the browser favorites store to within the /state/browser/ folder. * Create EasingGradient, useEasingGradient * Add activeScale, default activeOpacity to 1 * Clean up activeScale * Replace JS-based timer with useAnimatedTimeout, misc. cleanup * Add dragDirection option to Draggable * Add DraggableFlatList Fully working with the exception of auto scrolling while dragging * Improve browser homepage cards * Fix light mode card contrast, Android card gradient * Expose stop function in useAnimatedTimeout * useAnimatedTime: stop → clearTimeout * Improve card BlurView rendering * Make reorderable favorites mounting/unmounting smoother * Clean up animated reaction dependencies * Add missing favorites store method --- .../AnimatedComponents/AnimatedFlatList.ts | 4 + src/components/DappBrowser/BrowserTab.tsx | 2 +- src/components/DappBrowser/Homepage.tsx | 347 ++++++++++---- .../control-panel/ControlPanel.tsx | 5 +- .../DappBrowser/search-input/SearchInput.tsx | 5 +- src/components/drag-and-drop/DndContext.ts | 40 ++ src/components/drag-and-drop/DndProvider.tsx | 428 ++++++++++++++++++ .../drag-and-drop/components/Draggable.tsx | 103 +++++ .../components/DraggableFlatList.tsx | 236 ++++++++++ .../drag-and-drop/components/Droppable.tsx | 77 ++++ .../drag-and-drop/components/index.ts | 3 + .../drag-and-drop/features/index.ts | 1 + .../sort/components/DraggableGrid.tsx | 55 +++ .../sort/components/DraggableStack.tsx | 54 +++ .../features/sort/components/index.ts | 2 + .../features/sort/hooks/index.ts | 3 + .../features/sort/hooks/useDraggableGrid.ts | 103 +++++ .../features/sort/hooks/useDraggableSort.ts | 138 ++++++ .../features/sort/hooks/useDraggableStack.ts | 96 ++++ .../drag-and-drop/features/sort/index.ts | 2 + src/components/drag-and-drop/hooks/index.ts | 13 + .../hooks/useActiveDragReaction.ts | 17 + .../hooks/useActiveDropReaction.ts | 16 + .../drag-and-drop/hooks/useDraggable.ts | 177 ++++++++ .../hooks/useDraggableActiveId.ts | 19 + .../drag-and-drop/hooks/useDraggableStyle.tsx | 23 + .../drag-and-drop/hooks/useDroppable.ts | 120 +++++ .../drag-and-drop/hooks/useDroppableStyle.tsx | 17 + .../drag-and-drop/hooks/useEvent.ts | 20 + .../hooks/useLatestSharedValue.ts | 20 + .../drag-and-drop/hooks/useLatestValue.ts | 14 + .../drag-and-drop/hooks/useNodeRef.ts | 24 + .../drag-and-drop/hooks/useSharedPoint.ts | 11 + .../drag-and-drop/hooks/useSharedValuePair.ts | 9 + src/components/drag-and-drop/index.ts | 7 + src/components/drag-and-drop/types/common.ts | 19 + src/components/drag-and-drop/types/index.ts | 2 + .../drag-and-drop/types/reanimated.ts | 3 + src/components/drag-and-drop/utils/array.ts | 17 + src/components/drag-and-drop/utils/assert.ts | 20 + .../drag-and-drop/utils/geometry.ts | 124 +++++ src/components/drag-and-drop/utils/index.ts | 6 + src/components/drag-and-drop/utils/random.ts | 15 + .../drag-and-drop/utils/reanimated.ts | 130 ++++++ src/components/drag-and-drop/utils/swap.ts | 25 + .../easing-gradient/EasingGradient.tsx | 66 +++ src/hooks/reanimated/useAnimatedTimeout.ts | 5 +- src/hooks/reanimated/useLayoutWorklet.ts | 62 +++ src/hooks/useEasingGradient.ts | 77 ++++ src/model/migrations.ts | 31 ++ .../favoriteDappsStore.test.ts} | 28 +- src/state/browser/favoriteDappsStore.ts | 105 +++++ .../index.ts | 30 +- src/state/remoteCards/remoteCards.ts | 45 +- 54 files changed, 2861 insertions(+), 160 deletions(-) create mode 100644 src/components/AnimatedComponents/AnimatedFlatList.ts create mode 100644 src/components/drag-and-drop/DndContext.ts create mode 100644 src/components/drag-and-drop/DndProvider.tsx create mode 100644 src/components/drag-and-drop/components/Draggable.tsx create mode 100644 src/components/drag-and-drop/components/DraggableFlatList.tsx create mode 100644 src/components/drag-and-drop/components/Droppable.tsx create mode 100644 src/components/drag-and-drop/components/index.ts create mode 100644 src/components/drag-and-drop/features/index.ts create mode 100644 src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx create mode 100644 src/components/drag-and-drop/features/sort/components/DraggableStack.tsx create mode 100644 src/components/drag-and-drop/features/sort/components/index.ts create mode 100644 src/components/drag-and-drop/features/sort/hooks/index.ts create mode 100644 src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts create mode 100644 src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts create mode 100644 src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts create mode 100644 src/components/drag-and-drop/features/sort/index.ts create mode 100644 src/components/drag-and-drop/hooks/index.ts create mode 100644 src/components/drag-and-drop/hooks/useActiveDragReaction.ts create mode 100644 src/components/drag-and-drop/hooks/useActiveDropReaction.ts create mode 100644 src/components/drag-and-drop/hooks/useDraggable.ts create mode 100644 src/components/drag-and-drop/hooks/useDraggableActiveId.ts create mode 100644 src/components/drag-and-drop/hooks/useDraggableStyle.tsx create mode 100644 src/components/drag-and-drop/hooks/useDroppable.ts create mode 100644 src/components/drag-and-drop/hooks/useDroppableStyle.tsx create mode 100644 src/components/drag-and-drop/hooks/useEvent.ts create mode 100644 src/components/drag-and-drop/hooks/useLatestSharedValue.ts create mode 100644 src/components/drag-and-drop/hooks/useLatestValue.ts create mode 100644 src/components/drag-and-drop/hooks/useNodeRef.ts create mode 100644 src/components/drag-and-drop/hooks/useSharedPoint.ts create mode 100644 src/components/drag-and-drop/hooks/useSharedValuePair.ts create mode 100644 src/components/drag-and-drop/index.ts create mode 100644 src/components/drag-and-drop/types/common.ts create mode 100644 src/components/drag-and-drop/types/index.ts create mode 100644 src/components/drag-and-drop/types/reanimated.ts create mode 100644 src/components/drag-and-drop/utils/array.ts create mode 100644 src/components/drag-and-drop/utils/assert.ts create mode 100644 src/components/drag-and-drop/utils/geometry.ts create mode 100644 src/components/drag-and-drop/utils/index.ts create mode 100644 src/components/drag-and-drop/utils/random.ts create mode 100644 src/components/drag-and-drop/utils/reanimated.ts create mode 100644 src/components/drag-and-drop/utils/swap.ts create mode 100644 src/components/easing-gradient/EasingGradient.tsx create mode 100644 src/hooks/reanimated/useLayoutWorklet.ts create mode 100644 src/hooks/useEasingGradient.ts rename src/state/{favoriteDapps/index.test.ts => browser/favoriteDappsStore.test.ts} (59%) create mode 100644 src/state/browser/favoriteDappsStore.ts rename src/state/{favoriteDapps => legacyFavoriteDapps}/index.ts (65%) diff --git a/src/components/AnimatedComponents/AnimatedFlatList.ts b/src/components/AnimatedComponents/AnimatedFlatList.ts new file mode 100644 index 00000000000..6fe82c3740d --- /dev/null +++ b/src/components/AnimatedComponents/AnimatedFlatList.ts @@ -0,0 +1,4 @@ +import { FlatList } from 'react-native-gesture-handler'; +import Animated from 'react-native-reanimated'; + +export const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); diff --git a/src/components/DappBrowser/BrowserTab.tsx b/src/components/DappBrowser/BrowserTab.tsx index b6fee2bbc05..893b0c7f86b 100644 --- a/src/components/DappBrowser/BrowserTab.tsx +++ b/src/components/DappBrowser/BrowserTab.tsx @@ -123,7 +123,7 @@ const HomepageOrWebView = ({ const isOnHomepage = useBrowserStore(state => !state.getTabData?.(tabId)?.url || state.getTabData?.(tabId)?.url === RAINBOW_HOME); return isOnHomepage ? ( - + ) : ( { +export const Homepage = ({ tabId }: { tabId: string }) => { const { goToUrl } = useBrowserContext(); const { isDarkMode } = useColorMode(); + const backgroundStyle = isDarkMode ? styles.pageBackgroundDark : styles.pageBackgroundLight; + return ( - + - + - + - + ); @@ -114,11 +122,90 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { ); }; -const Favorites = ({ goToUrl }: { goToUrl: (url: string) => void }) => { - const favoriteDapps = useFavoriteDappsStore(state => state.favoriteDapps); +const Favorites = ({ goToUrl, tabId }: { goToUrl: (url: string) => void; tabId: string }) => { + const { animatedTabUrls, activeTabInfo, currentlyOpenTabIds, tabViewProgress, tabViewVisible } = useBrowserContext(); + + const [localGridSort, setLocalGridSort] = useState(() => { + const orderedIds = useFavoriteDappsStore.getState().getOrderedIds(); + return orderedIds && orderedIds.length > 0 ? orderedIds : undefined; + }); + + const favoriteDapps = useFavoriteDappsStore(state => state.getFavorites(localGridSort)); + const gridKey = useMemo(() => localGridSort?.join('-'), [localGridSort]); + const isFirstRender = useRef(true); + + const reorderFavorites = useFavoriteDappsStore(state => state.reorderFavorites); + + const onGridOrderChange: DraggableGridProps['onOrderChange'] = useCallback( + (value: UniqueIdentifier[]) => { + reorderFavorites(value as string[]); + }, + [reorderFavorites] + ); + + const reinitializeGridSort = useCallback(() => { + setLocalGridSort(useFavoriteDappsStore.getState().getOrderedIds()); + }, []); + + const needsToSyncWorklet = useCallback( + ({ currentGridSort, isActiveTab }: { currentGridSort: string[] | undefined; isActiveTab: boolean }) => { + 'worklet'; + const homepageTabsCount = currentlyOpenTabIds.value.filter( + tabId => !animatedTabUrls.value[tabId] || animatedTabUrls.value[tabId] === DEFAULT_TAB_URL + ).length; + const inactiveAndMounted = !isActiveTab && currentGridSort !== undefined; + + if (homepageTabsCount === 1) { + if (inactiveAndMounted) return true; + return false; + } + + const activeAndUnmounted = isActiveTab && !currentGridSort; + + return activeAndUnmounted || inactiveAndMounted; + }, + [animatedTabUrls, currentlyOpenTabIds] + ); + + // Unmount drag and drop grid on inactive homepage tabs + useAnimatedReaction( + () => ({ + needsToSync: needsToSyncWorklet({ currentGridSort: localGridSort, isActiveTab: activeTabInfo.value.tabId === tabId }), + tabAnimationProgress: tabViewProgress.value, + }), + (current, previous) => { + if (!previous || (!current.needsToSync && current.tabAnimationProgress === previous.tabAnimationProgress) || !favoriteDapps.length) { + return; + } + + const enterTabViewAnimationIsComplete = + !tabViewVisible.value && previous.tabAnimationProgress < 0 && current.tabAnimationProgress >= 0; + + if (!enterTabViewAnimationIsComplete) return; + + if (activeTabInfo.value.tabId === tabId) { + runOnJS(reinitializeGridSort)(); + } else { + runOnJS(setLocalGridSort)(undefined); + } + }, + [] + ); + + // Reinitialize grid sort when favorites are added or removed + useLayoutEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + if (!favoriteDapps.length || !useBrowserStore.getState().isTabActive(tabId)) { + return; + } + reinitializeGridSort(); + }, [favoriteDapps.length, reinitializeGridSort, tabId]); return ( - + 􀋃 @@ -127,14 +214,35 @@ const Favorites = ({ goToUrl }: { goToUrl: (url: string) => void }) => { {i18n.t(i18n.l.dapp_browser.homepage.favorites)} - - {favoriteDapps.length > 0 - ? favoriteDapps.map(dapp => ) - : Array(4) - .fill(null) - .map((_, index) => )} - - + {favoriteDapps.length > 0 && localGridSort ? ( + + + {favoriteDapps.map(dapp => + dapp ? ( + + + + ) : null + )} + + + ) : ( + + {favoriteDapps.length > 0 + ? favoriteDapps.map(dapp => ) + : Array(4) + .fill(null) + .map((_, index) => )} + + )} + ); }; @@ -164,7 +272,7 @@ const Recents = ({ goToUrl }: { goToUrl: (url: string) => void }) => { ); }; -const Card = React.memo(function Card({ +const Card = memo(function Card({ goToUrl, site, showMenuButton, @@ -283,25 +391,7 @@ const Card = React.memo(function Card({ width={{ custom: CARD_WIDTH }} > - {(site.screenshot || dappIconUrl) && ( - - - - - - - )} + - + {IS_IOS ? ( ) : ( @@ -361,13 +451,19 @@ const Card = React.memo(function Card({ - + 􀍠 @@ -379,7 +475,47 @@ const Card = React.memo(function Card({ ); }); -export const PlaceholderCard = React.memo(function PlaceholderCard() { +const CardBackground = memo(function CardBackgroundOverlay({ + imageUrl, + isDarkMode, +}: { + imageUrl: string | undefined; + isDarkMode: boolean; +}) { + return imageUrl ? ( + + + {IS_IOS ? ( + <> + + {!isDarkMode && ( + + )} + + ) : ( + + )} + + ) : null; +}); + +export const PlaceholderCard = memo(function PlaceholderCard() { const { isDarkMode } = useColorMode(); const fillTertiary = useBackgroundColor('fillTertiary'); @@ -417,56 +553,69 @@ export const PlaceholderCard = React.memo(function PlaceholderCard() { ); }); -export const Logo = React.memo(function Logo({ goToUrl, site }: { goToUrl: (url: string) => void; site: Omit }) { +export const Logo = memo(function Logo({ goToUrl, site }: { goToUrl: (url: string) => void; site: FavoritedSite }) { const { isDarkMode } = useColorMode(); + const imageOrFallback = useMemo(() => { + return ( + <> + {site.image && ( + + )} + + + + {!site.image && ( + + + 􀎭 + + + )} + + ); + }, [isDarkMode, site.image]); + return ( goToUrl(site.url)}> - - {IS_IOS && !site.image && ( - - - 􀎭 - - - )} - - {IS_IOS && ( - - )} - + {imageOrFallback} - + {site.name} @@ -477,13 +626,13 @@ export const Logo = React.memo(function Logo({ goToUrl, site }: { goToUrl: (url: ); }); -export const PlaceholderLogo = React.memo(function PlaceholderLogo() { +export const PlaceholderLogo = memo(function PlaceholderLogo() { const { isDarkMode } = useColorMode(); const borderRadius = IS_ANDROID ? LOGO_BORDER_RADIUS / 2 : LOGO_BORDER_RADIUS; return ( - + {IS_IOS && ( = { + const site: FavoritedSite = { name: tabData?.title || '', url: tabData?.url || '', image: tabData?.logoUrl || '', diff --git a/src/components/DappBrowser/search-input/SearchInput.tsx b/src/components/DappBrowser/search-input/SearchInput.tsx index 7c880d75981..68b1979fefc 100644 --- a/src/components/DappBrowser/search-input/SearchInput.tsx +++ b/src/components/DappBrowser/search-input/SearchInput.tsx @@ -22,9 +22,8 @@ import { IS_IOS } from '@/env'; import * as i18n from '@/languages'; import { fontWithWidth } from '@/styles'; import font from '@/styles/fonts'; -import { Site } from '@/state/browserHistory'; import { useBrowserStore } from '@/state/browser/browserStore'; -import { useFavoriteDappsStore } from '@/state/favoriteDapps'; +import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { FadeMask } from '@/__swaps__/screens/Swap/components/FadeMask'; @@ -64,7 +63,7 @@ const TheeDotMenu = function TheeDotMenu({ if (isFavorite) { removeFavorite(url); } else { - const site: Omit = { + const site: FavoritedSite = { name: getNameFromFormattedUrl(formattedUrlValue.value), url: url, image: useBrowserStore.getState().getActiveTabLogo() || `https://${formattedUrlValue.value}/apple-touch-icon.png`, diff --git a/src/components/drag-and-drop/DndContext.ts b/src/components/drag-and-drop/DndContext.ts new file mode 100644 index 00000000000..6e58313f1c2 --- /dev/null +++ b/src/components/drag-and-drop/DndContext.ts @@ -0,0 +1,40 @@ +import { createContext, useContext, type RefObject } from 'react'; +import type { LayoutRectangle, View } from 'react-native'; +import type { GestureEventPayload } from 'react-native-gesture-handler'; +import type { SharedValue } from 'react-native-reanimated'; +import type { DraggableConstraints, SharedPoint } from './hooks'; +import type { SharedData, UniqueIdentifier } from './types'; + +export type ItemOptions = { id: UniqueIdentifier; data: SharedData; disabled: boolean }; +export type DraggableItemOptions = ItemOptions & DraggableConstraints; +export type DraggableOptions = Record; +export type DroppableOptions = Record; +export type Layouts = Record>; +export type Offsets = Record; +export type DraggableState = 'resting' | 'pending' | 'dragging' | 'dropping' | 'acting'; +export type DraggableStates = Record>; + +export type DndContextValue = { + containerRef: RefObject; + draggableLayouts: SharedValue; + droppableLayouts: SharedValue; + draggableOptions: SharedValue; + droppableOptions: SharedValue; + draggableOffsets: SharedValue; + draggableRestingOffsets: SharedValue; + draggableStates: SharedValue; + draggablePendingId: SharedValue; + draggableActiveId: SharedValue; + droppableActiveId: SharedValue; + draggableActiveLayout: SharedValue; + draggableInitialOffset: SharedPoint; + draggableContentOffset: SharedPoint; + panGestureState: SharedValue; +}; + +// @ts-expect-error ignore detached state +export const DndContext = createContext(null); + +export const useDndContext = () => { + return useContext(DndContext); +}; diff --git a/src/components/drag-and-drop/DndProvider.tsx b/src/components/drag-and-drop/DndProvider.tsx new file mode 100644 index 00000000000..5f0ad9553e6 --- /dev/null +++ b/src/components/drag-and-drop/DndProvider.tsx @@ -0,0 +1,428 @@ +import React, { + ComponentType, + forwardRef, + MutableRefObject, + PropsWithChildren, + RefObject, + useCallback, + useImperativeHandle, + useMemo, + useRef, +} from 'react'; +import { LayoutRectangle, StyleProp, View, ViewStyle } from 'react-native'; +import { + Gesture, + GestureDetector, + GestureEventPayload, + GestureStateChangeEvent, + GestureUpdateEvent, + PanGesture, + PanGestureHandlerEventPayload, + State, +} from 'react-native-gesture-handler'; +import ReactNativeHapticFeedback, { HapticFeedbackTypes } from 'react-native-haptic-feedback'; +import { cancelAnimation, runOnJS, useAnimatedReaction, useSharedValue, type WithSpringConfig } from 'react-native-reanimated'; +import { useAnimatedTimeout } from '@/hooks/reanimated/useAnimatedTimeout'; +import { + DndContext, + DraggableStates, + type DndContextValue, + type DraggableOptions, + type DroppableOptions, + type ItemOptions, + type Layouts, + type Offsets, +} from './DndContext'; +import { useSharedPoint } from './hooks'; +import type { UniqueIdentifier } from './types'; +import { animatePointWithSpring, applyOffset, getDistance, includesPoint, overlapsRectangle, Point, Rectangle } from './utils'; + +export type DndProviderProps = { + activationDelay?: number; + debug?: boolean; + disabled?: boolean; + gestureRef?: MutableRefObject; + hapticFeedback?: HapticFeedbackTypes; + minDistance?: number; + onBegin?: ( + event: GestureStateChangeEvent, + meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle } + ) => void; + onDragEnd?: (event: { active: ItemOptions; over: ItemOptions | null }) => void; + onFinalize?: ( + event: GestureStateChangeEvent, + meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle } + ) => void; + onUpdate?: ( + event: GestureUpdateEvent, + meta: { activeId: UniqueIdentifier; activeLayout: LayoutRectangle } + ) => void; + simultaneousHandlers?: RefObject>; + springConfig?: WithSpringConfig; + style?: StyleProp; + waitFor?: RefObject>; +}; + +export type DndProviderHandle = Pick< + DndContextValue, + 'draggableLayouts' | 'draggableOffsets' | 'draggableRestingOffsets' | 'draggableActiveId' +>; + +export const DndProvider = forwardRef>(function DndProvider( + { + activationDelay = 0, + children, + debug, + disabled, + gestureRef, + hapticFeedback, + minDistance = 0, + onBegin, + onDragEnd, + onFinalize, + onUpdate, + simultaneousHandlers, + springConfig = {}, + style, + waitFor, + }, + ref +) { + const containerRef = useRef(null); + const draggableLayouts = useSharedValue({}); + const droppableLayouts = useSharedValue({}); + const draggableOptions = useSharedValue({}); + const droppableOptions = useSharedValue({}); + const draggableOffsets = useSharedValue({}); + const draggableRestingOffsets = useSharedValue({}); + const draggableStates = useSharedValue({}); + const draggablePendingId = useSharedValue(null); + const draggableActiveId = useSharedValue(null); + const droppableActiveId = useSharedValue(null); + const draggableActiveLayout = useSharedValue(null); + const draggableInitialOffset = useSharedPoint(0, 0); + const draggableContentOffset = useSharedPoint(0, 0); + const panGestureState = useSharedValue(0); + + const runFeedback = () => { + if (hapticFeedback) { + ReactNativeHapticFeedback.trigger(hapticFeedback); + } + }; + + useAnimatedReaction( + () => (hapticFeedback ? draggableActiveId.value : null), + current => { + if (current !== null) { + runOnJS(runFeedback)(); + } + }, + [] + ); + + const contextValue = useRef({ + containerRef, + draggableLayouts, + droppableLayouts, + draggableOptions, + droppableOptions, + draggableOffsets, + draggableRestingOffsets, + draggableStates, + draggablePendingId, + draggableActiveId, + droppableActiveId, + panGestureState, + draggableInitialOffset, + draggableActiveLayout, + draggableContentOffset, + }); + + useImperativeHandle( + ref, + () => { + return { + draggableLayouts, + draggableOffsets, + draggableRestingOffsets, + draggableActiveId, + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const setActiveId = useCallback(() => { + 'worklet'; + const id = draggablePendingId.value; + + if (id !== null) { + debug && console.log(`draggableActiveId.value = ${id}`); + draggableActiveId.value = id; + + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: activeLayout } = layouts[id]; + const activeOffset = offsets[id]; + + draggableActiveLayout.value = applyOffset(activeLayout, { + x: activeOffset.x.value, + y: activeOffset.y.value, + }); + draggableStates.value[id].value = 'dragging'; + } + }, [debug, draggableActiveId, draggableActiveLayout, draggableLayouts, draggableOffsets, draggablePendingId, draggableStates]); + + const { clearTimeout: clearActiveIdTimeout, start: setActiveIdWithDelay } = useAnimatedTimeout({ + delayMs: activationDelay, + onTimeoutWorklet: setActiveId, + }); + + const panGesture = useMemo(() => { + const findActiveLayoutId = (point: Point): UniqueIdentifier | null => { + 'worklet'; + const { x, y } = point; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: options } = draggableOptions; + for (const [id, layout] of Object.entries(layouts)) { + // console.log({ [id]: floorLayout(layout.value) }); + const offset = offsets[id]; + const isDisabled = options[id].disabled; + if ( + !isDisabled && + includesPoint(layout.value, { + x: x - offset.x.value + draggableContentOffset.x.value, + y: y - offset.y.value + draggableContentOffset.y.value, + }) + ) { + return id; + } + } + return null; + }; + + const findDroppableLayoutId = (activeLayout: LayoutRectangle): UniqueIdentifier | null => { + 'worklet'; + const { value: layouts } = droppableLayouts; + const { value: options } = droppableOptions; + for (const [id, layout] of Object.entries(layouts)) { + // console.log({ [id]: floorLayout(layout.value) }); + const isDisabled = options[id].disabled; + if (!isDisabled && overlapsRectangle(activeLayout, layout.value)) { + return id; + } + } + return null; + }; + + const panGesture = Gesture.Pan() + .maxPointers(1) + .onBegin(event => { + const { state, x, y } = event; + debug && console.log('begin', { state, x, y }); + // Gesture is globally disabled + if (disabled) { + return; + } + // console.log("begin", { state, x, y }); + // Track current state for cancellation purposes + panGestureState.value = state; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: restingOffsets } = draggableRestingOffsets; + const { value: options } = draggableOptions; + const { value: states } = draggableStates; + // for (const [id, offset] of Object.entries(offsets)) { + // console.log({ [id]: [offset.x.value, offset.y.value] }); + // } + // Find the active layout key under {x, y} + const activeId = findActiveLayoutId({ x, y }); + // Check if an item was actually selected + if (activeId !== null) { + // Record any ongoing current offset as our initial offset for the gesture + const activeLayout = layouts[activeId].value; + const activeOffset = offsets[activeId]; + const restingOffset = restingOffsets[activeId]; + const { value: activeState } = states[activeId]; + draggableInitialOffset.x.value = activeOffset.x.value; + draggableInitialOffset.y.value = activeOffset.y.value; + // Cancel the ongoing animation if we just reactivated an acting/dragging item + if (['dragging', 'acting'].includes(activeState)) { + cancelAnimation(activeOffset.x); + cancelAnimation(activeOffset.y); + // If not we should reset the resting offset to the current offset value + // But only if the item is not currently still animating + } else { + // active or pending + // Record current offset as our natural resting offset for the gesture + restingOffset.x.value = activeOffset.x.value; + restingOffset.y.value = activeOffset.y.value; + } + // Update activeId directly or with an optional delay + const { activationDelay } = options[activeId]; + + if (activationDelay > 0) { + draggablePendingId.value = activeId; + draggableStates.value[activeId].value = 'pending'; + setActiveIdWithDelay(); + } else { + draggableActiveId.value = activeId; + draggableActiveLayout.value = applyOffset(activeLayout, { + x: activeOffset.x.value, + y: activeOffset.y.value, + }); + draggableStates.value[activeId].value = 'dragging'; + } + + if (onBegin) { + onBegin(event, { activeId, activeLayout }); + } + } + }) + .onUpdate(event => { + // console.log(draggableStates.value); + const { state, translationX, translationY } = event; + debug && console.log('update', { state, translationX, translationY }); + // Track current state for cancellation purposes + panGestureState.value = state; + const { value: activeId } = draggableActiveId; + const { value: pendingId } = draggablePendingId; + const { value: options } = draggableOptions; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + if (activeId === null) { + // Check if we are currently waiting for activation delay + if (pendingId !== null) { + const { activationTolerance } = options[pendingId]; + // Check if we've moved beyond the activation tolerance + const distance = getDistance(translationX, translationY); + if (distance > activationTolerance) { + draggablePendingId.value = null; + clearActiveIdTimeout(); + } + } + // Ignore item-free interactions + return; + } + // Update our active offset to pan the active item + const activeOffset = offsets[activeId]; + activeOffset.x.value = draggableInitialOffset.x.value + translationX; + activeOffset.y.value = draggableInitialOffset.y.value + translationY; + // Check potential droppable candidates + const activeLayout = layouts[activeId].value; + draggableActiveLayout.value = applyOffset(activeLayout, { + x: activeOffset.x.value, + y: activeOffset.y.value, + }); + droppableActiveId.value = findDroppableLayoutId(draggableActiveLayout.value); + if (onUpdate) { + onUpdate(event, { activeId, activeLayout: draggableActiveLayout.value }); + } + }) + .onFinalize(event => { + const { state, velocityX, velocityY } = event; + debug && console.log('finalize', { state, velocityX, velocityY }); + // Track current state for cancellation purposes + panGestureState.value = state; // can be `FAILED` or `ENDED` + const { value: activeId } = draggableActiveId; + const { value: pendingId } = draggablePendingId; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: restingOffsets } = draggableRestingOffsets; + const { value: states } = draggableStates; + // Ignore item-free interactions + if (activeId === null) { + // Check if we were currently waiting for activation delay + if (pendingId !== null) { + draggablePendingId.value = null; + clearActiveIdTimeout(); + } + return; + } + // Reset interaction-related shared state for styling purposes + draggableActiveId.value = null; + if (onFinalize) { + const activeLayout = layouts[activeId].value; + const activeOffset = offsets[activeId]; + const updatedLayout = applyOffset(activeLayout, { + x: activeOffset.x.value, + y: activeOffset.y.value, + }); + onFinalize(event, { activeId, activeLayout: updatedLayout }); + } + // Callback + if (state !== State.FAILED && onDragEnd) { + const { value: dropActiveId } = droppableActiveId; + onDragEnd({ + active: draggableOptions.value[activeId], + over: dropActiveId !== null ? droppableOptions.value[dropActiveId] : null, + }); + } + // Reset droppable + droppableActiveId.value = null; + // Move back to initial position + const activeOffset = offsets[activeId]; + const restingOffset = restingOffsets[activeId]; + states[activeId].value = 'acting'; + const [targetX, targetY] = [restingOffset.x.value, restingOffset.y.value]; + animatePointWithSpring( + activeOffset, + [targetX, targetY], + [ + { ...springConfig, velocity: velocityX / 4 }, + { ...springConfig, velocity: velocityY / 4 }, + ], + () => { + // Cancel if we are interacting again with this item + if (panGestureState.value !== State.END && panGestureState.value !== State.FAILED && states[activeId].value !== 'acting') { + return; + } + if (states[activeId]) { + states[activeId].value = 'resting'; + } + // for (const [id, offset] of Object.entries(offsets)) { + // console.log({ [id]: [offset.x.value.toFixed(2), offset.y.value.toFixed(2)] }); + // } + } + ); + }) + .withTestId('DndProvider.pan'); + + if (simultaneousHandlers) { + panGesture.simultaneousWithExternalGesture(simultaneousHandlers); + } + + if (waitFor) { + panGesture.requireExternalGestureToFail(waitFor); + } + + if (gestureRef) { + panGesture.withRef(gestureRef); + } + + // Duration in milliseconds of the LongPress gesture before Pan is allowed to activate. + // If the finger is moved during that period, the gesture will fail. + if (activationDelay > 0) { + panGesture.activateAfterLongPress(activationDelay); + } + + // Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points. + if (minDistance > 0) { + panGesture.minDistance(minDistance); + } + + return panGesture; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disabled]); + + return ( + + + + {children} + + + + ); +}); diff --git a/src/components/drag-and-drop/components/Draggable.tsx b/src/components/drag-and-drop/components/Draggable.tsx new file mode 100644 index 00000000000..24e767646ad --- /dev/null +++ b/src/components/drag-and-drop/components/Draggable.tsx @@ -0,0 +1,103 @@ +import React, { type FunctionComponent, type PropsWithChildren } from 'react'; +import { type ViewProps } from 'react-native'; +import Animated, { useAnimatedStyle, withTiming, type AnimatedProps } from 'react-native-reanimated'; +import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; +import { IS_IOS } from '@/env'; +import { useDraggable, type DraggableConstraints, type UseDroppableOptions } from '../hooks'; +import type { AnimatedStyleWorklet } from '../types'; + +export type DraggableProps = AnimatedProps & + UseDroppableOptions & + Partial & { + animatedStyleWorklet?: AnimatedStyleWorklet; + activeOpacity?: number; + activeScale?: number; + dragDirection?: 'x' | 'y'; + }; + +/** + * Draggable is a React functional component that can be used to create elements that can be dragged within a Drag and Drop context. + * + * @component + * @example + * + * Drag me! + * + * + * @param {object} props - The properties that define the Draggable component. + * @param {number} props.activationDelay - A number representing the duration, in milliseconds, that this draggable item needs to be held for before allowing a drag to start. + * @param {number} props.activationTolerance - A number representing the distance, in pixels, of motion that is tolerated before the drag operation is aborted. + * @param {number} props.activeOpacity - A number that defines the opacity of the Draggable component when it is active. + * @param {number} props.activeScale - A number that defines the scale of the Draggable component when it is active. + * @param {Function} props.animatedStyleWorklet - A worklet function that modifies the animated style of the Draggable component. + * @param {object} props.data - An object that contains data associated with the Draggable component. + * @param {boolean} props.disabled - A flag that indicates whether the Draggable component is disabled. + * @param {string} props.dragDirection - Locks the drag direction to the x or y axis. + * @param {string} props.id - A unique identifier for the Draggable component. + * @param {object} props.style - An object that defines the style of the Draggable component. + * @returns {React.Component} Returns a Draggable component that can be moved by the user within a Drag and Drop context. + */ +export const Draggable: FunctionComponent> = ({ + activationDelay, + activationTolerance, + activeOpacity = 1, + activeScale = 1.05, + animatedStyleWorklet, + children, + data, + disabled, + dragDirection, + id, + style, + ...otherProps +}) => { + const { setNodeRef, onLayout, onLayoutWorklet, offset, state } = useDraggable({ + id, + data, + disabled, + activationDelay, + activationTolerance, + }); + + const animatedStyle = useAnimatedStyle(() => { + const isActive = state.value === 'dragging'; + const isActing = state.value === 'acting'; + // eslint-disable-next-line no-nested-ternary + const zIndex = isActive ? 999 : isActing ? 998 : 1; + const style = { + opacity: withTiming(isActive ? activeOpacity : 1, TIMING_CONFIGS.tabPressConfig), + zIndex, + transform: [ + { + translateX: + // eslint-disable-next-line no-nested-ternary + dragDirection !== 'y' ? (isActive ? offset.x.value : withTiming(offset.x.value, TIMING_CONFIGS.slowestFadeConfig)) : 0, + }, + { + translateY: + // eslint-disable-next-line no-nested-ternary + dragDirection !== 'x' ? (isActive ? offset.y.value : withTiming(offset.y.value, TIMING_CONFIGS.slowestFadeConfig)) : 0, + }, + { scale: activeScale === undefined ? 1 : withTiming(isActive ? activeScale : 1, TIMING_CONFIGS.tabPressConfig) }, + ], + }; + if (animatedStyleWorklet) { + Object.assign(style, animatedStyleWorklet(style, { isActive, isActing, isDisabled: !!disabled })); + } + return style; + }, [id, state, activeOpacity, activeScale]); + + return ( + + {children} + + ); +}; diff --git a/src/components/drag-and-drop/components/DraggableFlatList.tsx b/src/components/drag-and-drop/components/DraggableFlatList.tsx new file mode 100644 index 00000000000..597d07b8df0 --- /dev/null +++ b/src/components/drag-and-drop/components/DraggableFlatList.tsx @@ -0,0 +1,236 @@ +import React, { + ComponentProps, + ReactElement, + // useCallback, +} from 'react'; +import { + CellRendererProps, + // FlatListProps, +} from 'react-native'; +import { FlatList } from 'react-native-gesture-handler'; +import Animated, { + AnimatedProps, + // runOnJS, + useAnimatedReaction, + useAnimatedRef, + useAnimatedScrollHandler, + // useSharedValue, +} from 'react-native-reanimated'; +import { AnimatedFlatList } from '@/components/AnimatedComponents/AnimatedFlatList'; +import { useDndContext } from '../DndContext'; +import { useDraggableSort, UseDraggableStackOptions } from '../features'; +import type { UniqueIdentifier } from '../types'; +import { swapByItemCenterPoint } from '../utils'; +import { Draggable } from './Draggable'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnimatedFlatListProps = AnimatedProps>>; + +export type ViewableRange = { + first: number | null; + last: number | null; +}; + +export type DraggableFlatListProps = AnimatedFlatListProps & + Pick & { + debug?: boolean; + gap?: number; + horizontal?: boolean; + initialOrder?: UniqueIdentifier[]; + }; + +export const DraggableFlatList = ({ + data, + // debug, + gap = 0, + horizontal = false, + initialOrder, + onOrderChange, + onOrderUpdate, + renderItem, + shouldSwapWorklet = swapByItemCenterPoint, + ...otherProps +}: DraggableFlatListProps): ReactElement => { + const { draggableActiveId, draggableContentOffset, draggableLayouts, draggableOffsets, draggableRestingOffsets } = useDndContext(); + const animatedFlatListRef = useAnimatedRef>(); + + const { + // draggablePlaceholderIndex, + draggableSortOrder, + } = useDraggableSort({ + horizontal, + initialOrder, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + }); + + const direction = horizontal ? 'column' : 'row'; + const size = 1; + + // Track sort order changes and update the offsets + useAnimatedReaction( + () => draggableSortOrder.value, + (nextOrder, prevOrder) => { + // Ignore initial reaction + if (prevOrder === null) { + return; + } + const { value: activeId } = draggableActiveId; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: restingOffsets } = draggableRestingOffsets; + if (!activeId) { + return; + } + + const activeLayout = layouts[activeId].value; + const { width, height } = activeLayout; + const restingOffset = restingOffsets[activeId]; + + for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) { + const itemId = nextOrder[nextIndex]; + const prevIndex = prevOrder.findIndex(id => id === itemId); + // Skip items that haven't changed position + if (nextIndex === prevIndex) { + continue; + } + + const prevRow = Math.floor(prevIndex / size); + const prevCol = prevIndex % size; + const nextRow = Math.floor(nextIndex / size); + const nextCol = nextIndex % size; + const moveCol = nextCol - prevCol; + const moveRow = nextRow - prevRow; + + const offset = itemId === activeId ? restingOffset : offsets[itemId]; + + if (!restingOffset || !offsets[itemId]) { + continue; + } + + switch (direction) { + case 'row': + offset.y.value += moveRow * (height + gap); + break; + case 'column': + offset.x.value += moveCol * (width + gap); + break; + default: + break; + } + } + }, + [] + ); + + const scrollHandler = useAnimatedScrollHandler(event => { + draggableContentOffset.y.value = event.contentOffset.y; + }); + + /** ⚠️ TODO: Implement auto scrolling when dragging above or below the visible range */ + // const scrollToIndex = useCallback( + // (index: number) => { + // animatedFlatListRef.current?.scrollToIndex({ + // index, + // viewPosition: 0, + // animated: true, + // }); + // }, + // [animatedFlatListRef] + // ); + + // const viewableRange = useSharedValue({ + // first: null, + // last: null, + // }); + + // const onViewableItemsChanged = useCallback['onViewableItemsChanged']>>( + // ({ viewableItems }) => { + // viewableRange.value = { + // first: viewableItems[0].index, + // last: viewableItems[viewableItems.length - 1].index, + // }; + // if (debug) + // console.log(`First viewable item index: ${viewableItems[0].index}, last: ${viewableItems[viewableItems.length - 1].index}`); + // }, + // [debug, viewableRange] + // ); + + // useAnimatedReaction( + // () => draggablePlaceholderIndex.value, + // (next, prev) => { + // if (!Array.isArray(data)) { + // return; + // } + // if (debug) console.log(`placeholderIndex: ${prev} -> ${next}}, last visible= ${viewableRange.value.last}`); + // const { + // value: { first, last }, + // } = viewableRange; + // if (last !== null && next >= last && last < data.length - 1) { + // if (next < data.length) { + // runOnJS(scrollToIndex)(next + 1); + // } + // } else if (first !== null && first > 0 && next <= first) { + // if (next > 0) { + // runOnJS(scrollToIndex)(next - 1); + // } + // } + // } + // ); + /** END */ + + /** 🛠️ DEBUGGING */ + // useAnimatedReaction( + // () => { + // const activeId = draggableActiveId.value; + // return activeId ? draggableStates.value[activeId].value : 'resting'; + // }, + // (next, prev) => { + // if (debug) console.log(`Active item state: ${prev} -> ${next}`); + // if (debug) console.log(`translationY.value=${draggableContentOffset.y.value}`); + // } + // ); + + // useAnimatedReaction( + // () => draggableActiveId.value, + // (next, prev) => { + // if (debug) console.log(`activeId: ${prev} -> ${next}`); + // } + // ); + /** END */ + + return ( + { + return ( + + ); + }} + viewabilityConfig={{ itemVisiblePercentThreshold: 50 }} + // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any + {...(otherProps as any)} + /> + ); +}; + +export const DraggableFlatListCellRenderer = function DraggableFlatListCellRenderer( + props: CellRendererProps +) { + const { item, children } = props; + return ( + + {children} + + ); +}; diff --git a/src/components/drag-and-drop/components/Droppable.tsx b/src/components/drag-and-drop/components/Droppable.tsx new file mode 100644 index 00000000000..154a2e01dbc --- /dev/null +++ b/src/components/drag-and-drop/components/Droppable.tsx @@ -0,0 +1,77 @@ +import React, { type FunctionComponent, type PropsWithChildren } from 'react'; +import { type ViewProps } from 'react-native'; +import Animated, { useAnimatedStyle, withTiming, type AnimatedProps } from 'react-native-reanimated'; +import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; +import { IS_IOS } from '@/env'; +import { useDroppable, type UseDraggableOptions } from '../hooks'; +import type { AnimatedStyleWorklet } from '../types'; + +export type DroppableProps = AnimatedProps & + UseDraggableOptions & { + animatedStyleWorklet?: AnimatedStyleWorklet; + activeOpacity?: number; + activeScale?: number; + }; + +/** + * Droppable is a React functional component that can be used to create a drop target in a Drag and Drop context. + * + * @component + * @example + * + * Drop here! + * + * + * @param {object} props - The properties that define the Droppable component. + * @param {string} props.id - A unique identifier for the Droppable component. + * @param {boolean} props.disabled - A flag that indicates whether the Droppable component is disabled. + * @param {object} props.data - An object that contains data associated with the Droppable component. + * @param {object} props.style - An object that defines the style of the Droppable component. + * @param {number} props.activeOpacity - A number that defines the opacity of the Droppable component when it is active. + * @param {number} props.activeScale - A number that defines the scale of the Droppable component when it is active. + * @param {Function} props.animatedStyleWorklet - A worklet function that modifies the animated style of the Droppable component. + * @returns {React.Component} Returns a Droppable component that can serve as a drop target within a Drag and Drop context. + */ +export const Droppable: FunctionComponent> = ({ + children, + id, + disabled, + data, + style, + activeOpacity = 0.9, + activeScale, + animatedStyleWorklet, + ...otherProps +}) => { + const { setNodeRef, onLayout, onLayoutWorklet, activeId } = useDroppable({ + id, + disabled, + data, + }); + + const animatedStyle = useAnimatedStyle(() => { + const isActive = activeId.value === id; + const style = { + opacity: withTiming(isActive ? activeOpacity : 1, TIMING_CONFIGS.tabPressConfig), + transform: [{ scale: activeScale === undefined ? 1 : withTiming(isActive ? activeScale : 1, TIMING_CONFIGS.tabPressConfig) }], + }; + if (animatedStyleWorklet) { + Object.assign(style, animatedStyleWorklet(style, { isActive, isDisabled: !!disabled })); + } + return style; + }, [id, activeOpacity, activeScale]); + + return ( + + {children} + + ); +}; diff --git a/src/components/drag-and-drop/components/index.ts b/src/components/drag-and-drop/components/index.ts new file mode 100644 index 00000000000..88f40fe1543 --- /dev/null +++ b/src/components/drag-and-drop/components/index.ts @@ -0,0 +1,3 @@ +export * from './Draggable'; +export * from './DraggableFlatList'; +export * from './Droppable'; diff --git a/src/components/drag-and-drop/features/index.ts b/src/components/drag-and-drop/features/index.ts new file mode 100644 index 00000000000..b79f1a3e386 --- /dev/null +++ b/src/components/drag-and-drop/features/index.ts @@ -0,0 +1 @@ +export * from './sort'; diff --git a/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx b/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx new file mode 100644 index 00000000000..c6f9282c805 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/components/DraggableGrid.tsx @@ -0,0 +1,55 @@ +import React, { Children, useMemo, type FunctionComponent, type PropsWithChildren } from 'react'; +import { StyleProp, View, ViewStyle, type FlexStyle, type ViewProps } from 'react-native'; +import type { UniqueIdentifier } from '../../../types'; +import { useDraggableGrid, type UseDraggableGridOptions } from '../hooks/useDraggableGrid'; + +export type DraggableGridProps = Pick & + Pick & { + direction?: FlexStyle['flexDirection']; + size: number; + gap?: number; + }; + +export const DraggableGrid: FunctionComponent> = ({ + children, + direction = 'row', + gap = 0, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + size, + style: styleProp, +}) => { + const initialOrder = useMemo( + () => + Children.map(children, child => { + if (React.isValidElement(child)) { + return child.props.id; + } + return null; + })?.filter(Boolean) as UniqueIdentifier[], + [children] + ); + + const style: StyleProp = useMemo( + () => ({ + flexDirection: direction, + gap, + flexWrap: 'wrap', + ...(styleProp as object), + }), + [gap, direction, styleProp] + ); + + useDraggableGrid({ + direction: style.flexDirection, + gap: style.gap, + initialOrder, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + size, + }); + + return {children}; +}; diff --git a/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx b/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx new file mode 100644 index 00000000000..34dddffd0d2 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/components/DraggableStack.tsx @@ -0,0 +1,54 @@ +import React, { Children, useMemo, type FunctionComponent, type PropsWithChildren } from 'react'; +import { View, type FlexStyle, type ViewProps } from 'react-native'; +import type { UniqueIdentifier } from '../../../types'; +import { useDraggableStack, type UseDraggableStackOptions } from '../hooks/useDraggableStack'; + +export type DraggableStackProps = Pick & + Pick & { + direction?: FlexStyle['flexDirection']; + gap?: number; + }; + +export const DraggableStack: FunctionComponent> = ({ + children, + direction = 'row', + gap = 0, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + style: styleProp, +}) => { + const initialOrder = useMemo( + () => + Children.map(children, child => { + // console.log("in"); + if (React.isValidElement(child)) { + return child.props.id; + } + return null; + })?.filter(Boolean) as UniqueIdentifier[], + [children] + ); + + const style = useMemo( + () => ({ + flexDirection: direction, + gap, + ...(styleProp as object), + }), + [gap, direction, styleProp] + ); + + const horizontal = ['row', 'row-reverse'].includes(style.flexDirection); + + useDraggableStack({ + gap: style.gap, + horizontal, + initialOrder, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + }); + + return {children}; +}; diff --git a/src/components/drag-and-drop/features/sort/components/index.ts b/src/components/drag-and-drop/features/sort/components/index.ts new file mode 100644 index 00000000000..ec52d14a680 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/components/index.ts @@ -0,0 +1,2 @@ +export * from './DraggableGrid'; +export * from './DraggableStack'; diff --git a/src/components/drag-and-drop/features/sort/hooks/index.ts b/src/components/drag-and-drop/features/sort/hooks/index.ts new file mode 100644 index 00000000000..0ce999dc5ab --- /dev/null +++ b/src/components/drag-and-drop/features/sort/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useDraggableGrid'; +export * from './useDraggableSort'; +export * from './useDraggableStack'; diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts new file mode 100644 index 00000000000..af298b207c4 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/hooks/useDraggableGrid.ts @@ -0,0 +1,103 @@ +import { type FlexStyle } from 'react-native'; +import { useAnimatedReaction } from 'react-native-reanimated'; +import { swapByItemCenterPoint } from '../../../utils'; +import { useDndContext } from './../../../DndContext'; +import { useDraggableSort, type UseDraggableSortOptions } from './useDraggableSort'; + +export type UseDraggableGridOptions = Pick< + UseDraggableSortOptions, + 'initialOrder' | 'onOrderChange' | 'onOrderUpdate' | 'shouldSwapWorklet' +> & { + gap?: number; + size: number; + direction: FlexStyle['flexDirection']; +}; + +export const useDraggableGrid = ({ + initialOrder, + onOrderChange, + onOrderUpdate, + gap = 0, + size, + direction = 'row', + shouldSwapWorklet = swapByItemCenterPoint, +}: UseDraggableGridOptions) => { + const { draggableActiveId, draggableOffsets, draggableRestingOffsets, draggableLayouts } = useDndContext(); + const horizontal = ['row', 'row-reverse'].includes(direction); + + const { draggablePlaceholderIndex, draggableSortOrder } = useDraggableSort({ + horizontal, + initialOrder, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, + }); + + // Track sort order changes and update the offsets + useAnimatedReaction( + () => draggableSortOrder.value, + (nextOrder, prevOrder) => { + // Ignore initial reaction + if (prevOrder === null) { + return; + } + const { value: activeId } = draggableActiveId; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: restingOffsets } = draggableRestingOffsets; + if (!activeId) { + return; + } + + const activeLayout = layouts[activeId].value; + const { width, height } = activeLayout; + const restingOffset = restingOffsets[activeId]; + + for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) { + const itemId = nextOrder[nextIndex]; + const prevIndex = prevOrder.findIndex(id => id === itemId); + // Skip items that haven't changed position + if (nextIndex === prevIndex) { + continue; + } + + const prevRow = Math.floor(prevIndex / size); + const prevCol = prevIndex % size; + const nextRow = Math.floor(nextIndex / size); + const nextCol = nextIndex % size; + const moveCol = nextCol - prevCol; + const moveRow = nextRow - prevRow; + + const offset = itemId === activeId ? restingOffset : offsets[itemId]; + + if (!restingOffset || !offsets[itemId]) { + continue; + } + + switch (direction) { + case 'row': + offset.x.value += moveCol * (width + gap); + offset.y.value += moveRow * (height + gap); + break; + case 'row-reverse': + offset.x.value += -1 * moveCol * (width + gap); + offset.y.value += moveRow * (height + gap); + break; + case 'column': + offset.y.value += moveCol * (width + gap); + offset.x.value += moveRow * (height + gap); + break; + case 'column-reverse': + offset.y.value += -1 * moveCol * (width + gap); + offset.x.value += moveRow * (height + gap); + break; + default: + break; + } + } + }, + [direction, gap, size] + ); + + return { draggablePlaceholderIndex, draggableSortOrder }; +}; diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts new file mode 100644 index 00000000000..7bdf88469f8 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/hooks/useDraggableSort.ts @@ -0,0 +1,138 @@ +import { LayoutRectangle } from 'react-native'; +import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; +import { useDndContext } from '../../../DndContext'; +import type { UniqueIdentifier } from '../../../types'; +import { applyOffset, arraysEqual, centerAxis, moveArrayIndex, overlapsAxis, type Rectangle } from '../../../utils'; + +export type ShouldSwapWorklet = (activeLayout: Rectangle, itemLayout: Rectangle) => boolean; + +export type UseDraggableSortOptions = { + initialOrder?: UniqueIdentifier[]; + horizontal?: boolean; + onOrderChange?: (order: UniqueIdentifier[]) => void; + onOrderUpdate?: (nextOrder: UniqueIdentifier[], prevOrder: UniqueIdentifier[]) => void; + shouldSwapWorklet?: ShouldSwapWorklet; +}; + +export const useDraggableSort = ({ + horizontal = false, + initialOrder = [], + onOrderChange, + onOrderUpdate, + shouldSwapWorklet, +}: UseDraggableSortOptions) => { + const { draggableActiveId, draggableActiveLayout, draggableOffsets, draggableLayouts } = useDndContext(); + + const draggablePlaceholderIndex = useSharedValue(-1); + const draggableLastOrder = useSharedValue(initialOrder); + const draggableSortOrder = useSharedValue(initialOrder); + + // Core placeholder index logic + const findPlaceholderIndex = (activeLayout: LayoutRectangle): number => { + 'worklet'; + const { value: activeId } = draggableActiveId; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: sortOrder } = draggableSortOrder; + const activeIndex = sortOrder.findIndex(id => id === activeId); + // const activeCenterPoint = centerPoint(activeLayout); + // console.log(`activeLayout: ${JSON.stringify(activeLayout)}`); + for (let itemIndex = 0; itemIndex < sortOrder.length; itemIndex++) { + const itemId = sortOrder[itemIndex]; + if (itemId === activeId) { + continue; + } + if (!layouts[itemId]) { + console.warn(`Unexpected missing layout ${itemId} in layouts!`); + continue; + } + const itemLayout = applyOffset(layouts[itemId].value, { + x: offsets[itemId].x.value, + y: offsets[itemId].y.value, + }); + + if (shouldSwapWorklet) { + if (shouldSwapWorklet(activeLayout, itemLayout)) { + // console.log(`Found placeholder index ${itemIndex} using custom shouldSwapWorklet!`); + return itemIndex; + } + continue; + } + + // Default to center axis + const itemCenterAxis = centerAxis(itemLayout, horizontal); + if (overlapsAxis(activeLayout, itemCenterAxis, horizontal)) { + return itemIndex; + } + } + // Fallback to current index + // console.log(`Fallback to current index ${activeIndex}`); + return activeIndex; + }; + + // Track active layout changes and update the placeholder index + useAnimatedReaction( + () => [draggableActiveId.value, draggableActiveLayout.value] as const, + ([nextActiveId, nextActiveLayout], prev) => { + // Ignore initial reaction + if (prev === null) { + return; + } + // const [_prevActiveId, _prevActiveLayout] = prev; + // No active layout + if (nextActiveLayout === null) { + return; + } + // Reset the placeholder index when the active id changes + if (nextActiveId === null) { + draggablePlaceholderIndex.value = -1; + return; + } + // const axis = direction === "row" ? "x" : "y"; + // const delta = prevActiveLayout !== null ? nextActiveLayout[axis] - prevActiveLayout[axis] : 0; + draggablePlaceholderIndex.value = findPlaceholderIndex(nextActiveLayout); + }, + [] + ); + + // Track placeholder index changes and update the sort order + useAnimatedReaction( + () => [draggableActiveId.value, draggablePlaceholderIndex.value] as const, + (next, prev) => { + // Ignore initial reaction + if (prev === null) { + return; + } + const [, prevPlaceholderIndex] = prev; + const [nextActiveId, nextPlaceholderIndex] = next; + const { value: prevOrder } = draggableSortOrder; + // if (nextPlaceholderIndex !== prevPlaceholderIndex) { + // console.log(`Placeholder index changed from ${prevPlaceholderIndex} to ${nextPlaceholderIndex}`); + // } + if (prevPlaceholderIndex !== -1 && nextPlaceholderIndex === -1) { + // Notify the parent component of the order change + if (nextActiveId === null && onOrderChange) { + if (!arraysEqual(prevOrder, draggableLastOrder.value)) { + runOnJS(onOrderChange)(prevOrder); + } + draggableLastOrder.value = prevOrder; + } + } + // Only update the sort order when the placeholder index changes between two valid values + if (prevPlaceholderIndex === -1 || nextPlaceholderIndex === -1) { + return; + } + // Finally update the sort order + const nextOrder = moveArrayIndex(prevOrder, prevPlaceholderIndex, nextPlaceholderIndex); + // Notify the parent component of the order update + if (onOrderUpdate) { + runOnJS(onOrderUpdate)(nextOrder, prevOrder); + } + + draggableSortOrder.value = nextOrder; + }, + [onOrderChange] + ); + + return { draggablePlaceholderIndex, draggableSortOrder }; +}; diff --git a/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts b/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts new file mode 100644 index 00000000000..cb6626aab85 --- /dev/null +++ b/src/components/drag-and-drop/features/sort/hooks/useDraggableStack.ts @@ -0,0 +1,96 @@ +import { useMemo } from 'react'; +import { useAnimatedReaction } from 'react-native-reanimated'; +import { useDndContext } from '../../../DndContext'; +import { swapByItemHorizontalAxis, swapByItemVerticalAxis } from '../../../utils'; +import { useDraggableSort, type UseDraggableSortOptions } from './useDraggableSort'; + +export type UseDraggableStackOptions = Pick< + UseDraggableSortOptions, + 'initialOrder' | 'onOrderChange' | 'onOrderUpdate' | 'shouldSwapWorklet' +> & { + gap?: number; + horizontal?: boolean; +}; +export const useDraggableStack = ({ + initialOrder, + onOrderChange, + onOrderUpdate, + gap = 0, + horizontal = false, + shouldSwapWorklet, +}: UseDraggableStackOptions) => { + const { draggableActiveId, draggableOffsets, draggableRestingOffsets, draggableLayouts } = useDndContext(); + const axis = horizontal ? 'x' : 'y'; + const size = horizontal ? 'width' : 'height'; + const worklet = useMemo( + () => + // eslint-disable-next-line no-nested-ternary + shouldSwapWorklet ? shouldSwapWorklet : horizontal ? swapByItemHorizontalAxis : swapByItemVerticalAxis, + [horizontal, shouldSwapWorklet] + ); + + const { draggablePlaceholderIndex, draggableSortOrder } = useDraggableSort({ + horizontal, + initialOrder, + onOrderChange, + onOrderUpdate, + shouldSwapWorklet: worklet, + }); + + // Track sort order changes and update the offsets + useAnimatedReaction( + () => draggableSortOrder.value, + (nextOrder, prevOrder) => { + // Ignore initial reaction + if (prevOrder === null) { + return; + } + const { value: activeId } = draggableActiveId; + const { value: layouts } = draggableLayouts; + const { value: offsets } = draggableOffsets; + const { value: restingOffsets } = draggableRestingOffsets; + if (!activeId) { + return; + } + + const activeLayout = layouts[activeId].value; + const prevActiveIndex = prevOrder.findIndex(id => id === activeId); + const nextActiveIndex = nextOrder.findIndex(id => id === activeId); + const nextActiveOffset = { x: 0, y: 0 }; + const restingOffset = restingOffsets[activeId]; + + for (let nextIndex = 0; nextIndex < nextOrder.length; nextIndex++) { + const itemId = nextOrder[nextIndex]; + // Skip the active item + if (itemId === activeId) { + continue; + } + // @TODO grid x,y + + // Skip items that haven't changed position + const prevIndex = prevOrder.findIndex(id => id === itemId); + if (nextIndex === prevIndex) { + continue; + } + // Calculate the directional offset + const moveCol = nextIndex - prevIndex; + // Apply the offset to the item from its resting position + offsets[itemId][axis].value = restingOffsets[itemId][axis].value + moveCol * (activeLayout[size] + gap); + // Reset resting offsets to new updated position + restingOffsets[itemId][axis].value = offsets[itemId][axis].value; + + // Accummulate the directional offset for the active item + if (nextIndex < nextActiveIndex && prevIndex > prevActiveIndex) { + nextActiveOffset[axis] += layouts[itemId].value[size] + gap; + } else if (nextIndex > nextActiveIndex && prevIndex < prevActiveIndex) { + nextActiveOffset[axis] -= layouts[itemId].value[size] + gap; + } + } + // Update the active item offset + restingOffset[axis].value += nextActiveOffset[axis]; + }, + [horizontal] + ); + + return { draggablePlaceholderIndex, draggableSortOrder }; +}; diff --git a/src/components/drag-and-drop/features/sort/index.ts b/src/components/drag-and-drop/features/sort/index.ts new file mode 100644 index 00000000000..f76fd6f166e --- /dev/null +++ b/src/components/drag-and-drop/features/sort/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './hooks'; diff --git a/src/components/drag-and-drop/hooks/index.ts b/src/components/drag-and-drop/hooks/index.ts new file mode 100644 index 00000000000..26087b68cc4 --- /dev/null +++ b/src/components/drag-and-drop/hooks/index.ts @@ -0,0 +1,13 @@ +export * from './useActiveDragReaction'; +export * from './useActiveDropReaction'; +export * from './useDraggable'; +export * from './useDraggableActiveId'; +export * from './useDraggableStyle'; +export * from './useDroppable'; +export * from './useDroppableStyle'; +export * from './useEvent'; +export * from './useLatestSharedValue'; +export * from './useLatestValue'; +export * from './useNodeRef'; +export * from './useSharedPoint'; +export * from './useSharedValuePair'; diff --git a/src/components/drag-and-drop/hooks/useActiveDragReaction.ts b/src/components/drag-and-drop/hooks/useActiveDragReaction.ts new file mode 100644 index 00000000000..cd55cd87c8a --- /dev/null +++ b/src/components/drag-and-drop/hooks/useActiveDragReaction.ts @@ -0,0 +1,17 @@ +import { State } from 'react-native-gesture-handler'; +import { useAnimatedReaction } from 'react-native-reanimated'; +import { useDndContext } from '../DndContext'; +import type { UniqueIdentifier } from '../types'; + +export const useActiveDragReaction = (id: UniqueIdentifier, callback: (isActive: boolean) => void) => { + const { draggableActiveId: activeId, panGestureState } = useDndContext(); + useAnimatedReaction( + () => activeId.value === id && ([State.BEGAN, State.ACTIVE] as State[]).includes(panGestureState.value), + (next, prev) => { + if (next !== prev) { + callback(next); + } + }, + [] + ); +}; diff --git a/src/components/drag-and-drop/hooks/useActiveDropReaction.ts b/src/components/drag-and-drop/hooks/useActiveDropReaction.ts new file mode 100644 index 00000000000..c7be27157b9 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useActiveDropReaction.ts @@ -0,0 +1,16 @@ +import { useAnimatedReaction } from 'react-native-reanimated'; +import { useDndContext } from '../DndContext'; +import type { UniqueIdentifier } from '../types'; + +export const useActiveDropReaction = (id: UniqueIdentifier, callback: (isActive: boolean) => void) => { + const { droppableActiveId: activeId } = useDndContext(); + useAnimatedReaction( + () => activeId.value === id, + (next, prev) => { + if (next !== prev) { + callback(next); + } + }, + [] + ); +}; diff --git a/src/components/drag-and-drop/hooks/useDraggable.ts b/src/components/drag-and-drop/hooks/useDraggable.ts new file mode 100644 index 00000000000..d27ee31da55 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useDraggable.ts @@ -0,0 +1,177 @@ +import { useCallback, useLayoutEffect } from 'react'; +import { LayoutRectangle, ViewProps } from 'react-native'; +import { runOnUI, useSharedValue } from 'react-native-reanimated'; +import { IS_IOS } from '@/env'; +import { useLayoutWorklet } from '@/hooks/reanimated/useLayoutWorklet'; +import { DraggableState, useDndContext } from '../DndContext'; +import { useLatestSharedValue, useNodeRef } from '../hooks'; +import { Data, NativeElement, UniqueIdentifier } from '../types'; +import { assert, isReanimatedSharedValue } from '../utils'; +import { useSharedPoint } from './useSharedPoint'; + +export type DraggableConstraints = { + activationDelay: number; + activationTolerance: number; +}; + +export type UseDraggableOptions = Partial & { + id: UniqueIdentifier; + data?: Data; + disabled?: boolean; +}; + +/** + * useDraggable is a custom hook that provides functionality for making a component draggable within a drag and drop context. + * + * @function + * @example + * const { offset, setNodeRef, activeId, setNodeLayout, draggableState } = useDraggable({ id: 'draggable-1' }); + * + * @param {object} options - The options that define the behavior of the draggable component. + * @param {string} options.id - A unique identifier for the draggable component. + * @param {object} [options.data={}] - Optional data associated with the draggable component. + * @param {boolean} [options.disabled=false] - A flag that indicates whether the draggable component is disabled. + * @param {number} [options.activationDelay=0] - A number representing the duration, in milliseconds, that this draggable item needs to be held for before allowing a drag to start. + * @param {number} [options.activationTolerance=Infinity] - A number representing the distance, in points, of motion that is tolerated before the drag operation is aborted. + * + * @returns {object} Returns an object with properties and methods related to the draggable component. + * @property {object} offset - An object representing the current offset of the draggable component. + * @property {Function} setNodeRef - A function that can be used to set the ref of the draggable component. + * @property {string} activeId - The unique identifier of the currently active draggable component. + * @property {string} actingId - The unique identifier of the currently interacti draggable component. + * @property {Function} setNodeLayout - A function that handles the layout event of the draggable component. + * @property {object} draggableState - An object representing the current state of the draggable component. + */ +export const useDraggable = ({ + id, + data = {}, + disabled = false, + activationDelay = 0, + activationTolerance = Infinity, +}: UseDraggableOptions) => { + const { + containerRef, + draggableLayouts, + draggableOffsets, + draggableRestingOffsets, + draggableOptions, + draggableStates, + draggableActiveId, + draggablePendingId, + panGestureState, + } = useDndContext(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [node, setNodeRef] = useNodeRef(); + // const key = useUniqueId("Draggable"); + // eslint-disable-next-line react-hooks/rules-of-hooks + const sharedData = isReanimatedSharedValue(data) ? data : useLatestSharedValue(data); + + const layout = useSharedValue({ + x: 0, + y: 0, + width: 0, + height: 0, + }); + const offset = useSharedPoint(0, 0); + const restingOffset = useSharedPoint(0, 0); + const state = useSharedValue('resting'); + + // Register early to allow proper referencing in useDraggableStyle + draggableStates.value[id] = state; + + useLayoutEffect(() => { + const runLayoutEffect = () => { + 'worklet'; + draggableLayouts.modify(prev => { + const newValue = { ...prev, [id]: layout }; + return newValue; + }); + draggableOffsets.modify(prev => { + const newValue = { ...prev, [id]: offset }; + return newValue; + }); + draggableRestingOffsets.modify(prev => { + const newValue = { ...prev, [id]: restingOffset }; + return newValue; + }); + draggableOptions.modify(prev => { + const newValue = { ...prev, [id]: { id, data: sharedData, disabled, activationDelay, activationTolerance } }; + return newValue; + }); + draggableStates.modify(prev => { + const newValue = { ...prev, [id]: state }; + return newValue; + }); + }; + runOnUI(runLayoutEffect)(); + + return () => { + const cleanupLayoutEffect = () => { + 'worklet'; + draggableLayouts.modify(value => { + delete value[id]; + return value; + }); + draggableOffsets.modify(value => { + delete value[id]; + return value; + }); + draggableRestingOffsets.modify(value => { + delete value[id]; + return value; + }); + draggableOptions.modify(value => { + delete value[id]; + return value; + }); + draggableStates.modify(value => { + delete value[id]; + return value; + }); + }; + runOnUI(cleanupLayoutEffect)(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + // Standard onLayout event for Android — also required to trigger 'topLayout' event on iOS + const onLayout: ViewProps['onLayout'] = useCallback(() => { + if (IS_IOS) return; + + assert(containerRef.current); + node.current?.measureLayout(containerRef.current, (x, y, width, height) => { + layout.modify(value => { + 'worklet'; + value.x = x; + value.y = y; + value.width = width; + value.height = height; + return value; + }); + }); + }, [containerRef, node, layout]); + + // Worklet-based onLayout event for iOS + const onLayoutWorklet = useLayoutWorklet(layoutInfo => { + 'worklet'; + + layout.modify(value => { + value.x = layoutInfo.x; + value.y = layoutInfo.y; + value.width = layoutInfo.width; + value.height = layoutInfo.height; + return value; + }); + }); + + return { + offset, + state, + setNodeRef, + activeId: draggableActiveId, + pendingId: draggablePendingId, + onLayout, + onLayoutWorklet, + panGestureState, + }; +}; diff --git a/src/components/drag-and-drop/hooks/useDraggableActiveId.ts b/src/components/drag-and-drop/hooks/useDraggableActiveId.ts new file mode 100644 index 00000000000..e40b948ce5f --- /dev/null +++ b/src/components/drag-and-drop/hooks/useDraggableActiveId.ts @@ -0,0 +1,19 @@ +import { useState } from 'react'; +import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; +import { useDndContext } from '../DndContext'; +import type { UniqueIdentifier } from '../types'; + +export const useDraggableActiveId = () => { + const [activeId, setActiveId] = useState(null); + const { draggableActiveId } = useDndContext(); + useAnimatedReaction( + () => draggableActiveId.value, + (next, prev) => { + if (next !== prev) { + runOnJS(setActiveId)(next); + } + }, + [] + ); + return activeId; +}; diff --git a/src/components/drag-and-drop/hooks/useDraggableStyle.tsx b/src/components/drag-and-drop/hooks/useDraggableStyle.tsx new file mode 100644 index 00000000000..6d610c9aa15 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useDraggableStyle.tsx @@ -0,0 +1,23 @@ +import { useAnimatedStyle } from 'react-native-reanimated'; +import { useDndContext } from '..'; +import type { AnimatedStyle, UniqueIdentifier } from '../types'; + +export type UseDraggableStyleCallback = (_: { + isActive: boolean; + isDisabled: boolean; + isActing: boolean; +}) => StyleT; + +export const useDraggableStyle = ( + id: UniqueIdentifier, + callback: UseDraggableStyleCallback +): StyleT => { + const { draggableStates: states, draggableActiveId: activeId, draggableOptions: options } = useDndContext(); + const state = states.value[id]; + return useAnimatedStyle(() => { + const isActive = activeId.value === id; + const isActing = state?.value === 'acting'; + const isDisabled = !options.value[id]?.disabled; + return callback({ isActive, isActing, isDisabled }); + }, [id, state]); +}; diff --git a/src/components/drag-and-drop/hooks/useDroppable.ts b/src/components/drag-and-drop/hooks/useDroppable.ts new file mode 100644 index 00000000000..803104d4329 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useDroppable.ts @@ -0,0 +1,120 @@ +import { useCallback, useLayoutEffect } from 'react'; +import { ViewProps, type LayoutRectangle } from 'react-native'; +import { runOnUI, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; +import { IS_IOS } from '@/env'; +import { useLayoutWorklet } from '@/hooks/reanimated/useLayoutWorklet'; +import { useDndContext } from '../DndContext'; +import { useLatestSharedValue, useNodeRef } from '../hooks'; +import type { Data, NativeElement, UniqueIdentifier } from '../types'; +import { assert, isReanimatedSharedValue } from '../utils'; + +export type UseDroppableOptions = { id: UniqueIdentifier; data?: Data; disabled?: boolean }; + +/** + * useDroppable is a custom hook that provides functionality for making a component droppable within a drag and drop context. + * + * @function + * @example + * const { setNodeRef, setNodeLayout, activeId, panGestureState } = useDroppable({ id: 'droppable-1' }); + * + * @param {object} options - The options that define the behavior of the droppable component. + * @param {string} options.id - A unique identifier for the droppable component. + * @param {object} [options.data={}] - Optional data associated with the droppable component. + * @param {boolean} [options.disabled=false] - A flag that indicates whether the droppable component is disabled. + * + * @returns {object} Returns an object with properties and methods related to the droppable component. + * @property {Function} setNodeRef - A function that can be used to set the ref of the droppable component. + * @property {Function} setNodeLayout - A function that handles the layout event of the droppable component. + * @property {string} activeId - The unique identifier of the currently active droppable component. + * @property {object} panGestureState - An object representing the current state of the draggable component within the context. + */ +export const useDroppable = ({ id, data = {}, disabled = false }: UseDroppableOptions) => { + const { droppableLayouts, droppableOptions, droppableActiveId, containerRef, panGestureState } = useDndContext(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [node, setNodeRef] = useNodeRef(); + // eslint-disable-next-line react-hooks/rules-of-hooks + const sharedData = isReanimatedSharedValue(data) ? data : useLatestSharedValue(data); + + const layout = useSharedValue({ + x: 0, + y: 0, + width: 0, + height: 0, + }); + + useAnimatedReaction( + () => disabled, + (next, prev) => { + if (next !== prev) { + droppableOptions.value[id].disabled = disabled; + } + }, + [disabled] + ); + + useLayoutEffect(() => { + const runLayoutEffect = () => { + 'worklet'; + // droppableLayouts.value[id] = layout; + // droppableOptions.value[id] = { id, data: sharedData, disabled }; + + droppableLayouts.modify(value => { + const newValue = { ...value, [id]: layout }; + return newValue; + }); + droppableOptions.modify(value => { + const newValue = { ...value, [id]: { id, data: sharedData, disabled } }; + return newValue; + }); + }; + runOnUI(runLayoutEffect)(); + return () => { + const runLayoutEffect = () => { + 'worklet'; + droppableLayouts.modify(value => { + delete value[id]; + return value; + }); + droppableOptions.modify(value => { + delete value[id]; + return value; + }); + }; + // if(node && node.key === key) + runOnUI(runLayoutEffect)(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Standard onLayout event for Android — also required to trigger 'topLayout' event on iOS + const onLayout: ViewProps['onLayout'] = useCallback(() => { + if (IS_IOS) return; + + assert(containerRef.current); + node.current?.measureLayout(containerRef.current, (x, y, width, height) => { + layout.modify(value => { + 'worklet'; + value.x = x; + value.y = y; + value.width = width; + value.height = height; + return value; + }); + }); + }, [containerRef, node, layout]); + + // Worklet-based onLayout event for iOS + const onLayoutWorklet = useLayoutWorklet(layoutInfo => { + 'worklet'; + + layout.modify(value => { + value.x = layoutInfo.x; + value.y = layoutInfo.y; + value.width = layoutInfo.width; + value.height = layoutInfo.height; + return value; + }); + }); + + return { setNodeRef, onLayout, onLayoutWorklet, activeId: droppableActiveId, panGestureState }; +}; diff --git a/src/components/drag-and-drop/hooks/useDroppableStyle.tsx b/src/components/drag-and-drop/hooks/useDroppableStyle.tsx new file mode 100644 index 00000000000..3d7c6c4c3fd --- /dev/null +++ b/src/components/drag-and-drop/hooks/useDroppableStyle.tsx @@ -0,0 +1,17 @@ +import { useAnimatedStyle } from 'react-native-reanimated'; +import { useDndContext } from '..'; +import type { AnimatedStyle, UniqueIdentifier } from '../types'; + +export type UseDroppableStyleCallback = (_: { isActive: boolean; isDisabled: boolean }) => StyleT; + +export const useDroppableStyle = ( + id: UniqueIdentifier, + callback: UseDroppableStyleCallback +): StyleT => { + const { droppableActiveId: activeId, droppableOptions: options } = useDndContext(); + return useAnimatedStyle(() => { + const isActive = activeId.value === id; + const isDisabled = !options.value[id]?.disabled; + return callback({ isActive, isDisabled }); + }, []); +}; diff --git a/src/components/drag-and-drop/hooks/useEvent.ts b/src/components/drag-and-drop/hooks/useEvent.ts new file mode 100644 index 00000000000..8e4615a6b72 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useEvent.ts @@ -0,0 +1,20 @@ +import { useCallback, useLayoutEffect, useRef } from 'react'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type EventHandler = (...args: any[]) => void; + +/** + * Hook to define an event handler with a function identity that is always stable + * {@link https://blog.logrocket.com/what-you-need-know-react-useevent-hook-rfc/} + */ +export const useEvent = (handler: T | undefined) => { + const handlerRef = useRef(handler); + + useLayoutEffect(() => { + handlerRef.current = handler; + }); + + return useCallback((...args: unknown[]) => { + return handlerRef.current?.(...args); + }, []); +}; diff --git a/src/components/drag-and-drop/hooks/useLatestSharedValue.ts b/src/components/drag-and-drop/hooks/useLatestSharedValue.ts new file mode 100644 index 00000000000..20d506fd149 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useLatestSharedValue.ts @@ -0,0 +1,20 @@ +import { useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; +import type { DependencyList } from '../types'; + +export function useLatestSharedValue(value: T, dependencies: DependencyList = [value]) { + const sharedValue = useSharedValue(value); + + useAnimatedReaction( + () => value, + (next, prev) => { + // Ignore initial reaction + if (prev === null) { + return; + } + sharedValue.value = next; + }, + dependencies + ); + + return sharedValue; +} diff --git a/src/components/drag-and-drop/hooks/useLatestValue.ts b/src/components/drag-and-drop/hooks/useLatestValue.ts new file mode 100644 index 00000000000..1d9f8869117 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useLatestValue.ts @@ -0,0 +1,14 @@ +import { DependencyList, useLayoutEffect, useRef } from 'react'; + +export function useLatestValue(value: T, dependencies: DependencyList = [value]) { + const valueRef = useRef(value); + + useLayoutEffect(() => { + if (valueRef.current !== value) { + valueRef.current = value; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependencies); + + return valueRef; +} diff --git a/src/components/drag-and-drop/hooks/useNodeRef.ts b/src/components/drag-and-drop/hooks/useNodeRef.ts new file mode 100644 index 00000000000..07bdf66dd5d --- /dev/null +++ b/src/components/drag-and-drop/hooks/useNodeRef.ts @@ -0,0 +1,24 @@ +import { useCallback, useRef } from 'react'; +import { useEvent } from './useEvent'; + +type NodeChangeHandler = (nextElement: T | null, prevElement: T | null) => void; + +/** + * Hook to receive a stable ref setter with an optional onChange handler + */ +export const useNodeRef = (onChange?: NodeChangeHandler) => { + const onChangeHandler = useEvent(onChange); + const nodeRef = useRef(null); + const setNodeRef = useCallback( + (element: U | null) => { + if (element !== nodeRef.current) { + onChangeHandler?.(element, nodeRef.current); + } + nodeRef.current = element as T; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return [nodeRef, setNodeRef] as const; +}; diff --git a/src/components/drag-and-drop/hooks/useSharedPoint.ts b/src/components/drag-and-drop/hooks/useSharedPoint.ts new file mode 100644 index 00000000000..0335c41ad61 --- /dev/null +++ b/src/components/drag-and-drop/hooks/useSharedPoint.ts @@ -0,0 +1,11 @@ +import { useSharedValue, type SharedValue } from 'react-native-reanimated'; +import type { Point } from '../utils'; + +export type SharedPoint = Point>; + +export const useSharedPoint = (x: number, y: number): SharedPoint => { + return { + x: useSharedValue(x), + y: useSharedValue(y), + }; +}; diff --git a/src/components/drag-and-drop/hooks/useSharedValuePair.ts b/src/components/drag-and-drop/hooks/useSharedValuePair.ts new file mode 100644 index 00000000000..a921b8059fd --- /dev/null +++ b/src/components/drag-and-drop/hooks/useSharedValuePair.ts @@ -0,0 +1,9 @@ +import { useSharedValue, type SharedValue } from 'react-native-reanimated'; + +export type SharedValues> = { + [K in keyof T]: SharedValue; +}; + +export const useSharedValuePair = (x: number, y: number) => { + return [useSharedValue(x), useSharedValue(y)]; +}; diff --git a/src/components/drag-and-drop/index.ts b/src/components/drag-and-drop/index.ts new file mode 100644 index 00000000000..3ed5a7f1cfa --- /dev/null +++ b/src/components/drag-and-drop/index.ts @@ -0,0 +1,7 @@ +export * from './DndContext'; +export * from './DndProvider'; +export * from './components'; +export * from './features'; +export * from './hooks'; +export * from './types'; +export * from './utils'; diff --git a/src/components/drag-and-drop/types/common.ts b/src/components/drag-and-drop/types/common.ts new file mode 100644 index 00000000000..455f5bbc0d8 --- /dev/null +++ b/src/components/drag-and-drop/types/common.ts @@ -0,0 +1,19 @@ +import type { HostComponent, ViewProps, ViewStyle } from 'react-native'; +import type { SharedValue, useAnimatedStyle } from 'react-native-reanimated'; + +export type UniqueIdentifier = string | number; +export type ObjectWithId = { id: UniqueIdentifier; [s: string]: unknown }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyData = Record; +export type Data = T | SharedValue; +export type SharedData = SharedValue; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type NativeElement = InstanceType>; + +export type AnimatedStyle = ReturnType; +export type AnimatedViewStyle = ReturnType>; +export type AnimatedStyleWorklet = ( + style: Readonly, + options: { isActive: boolean; isDisabled: boolean; isActing?: boolean } +) => T; diff --git a/src/components/drag-and-drop/types/index.ts b/src/components/drag-and-drop/types/index.ts new file mode 100644 index 00000000000..251629bf35e --- /dev/null +++ b/src/components/drag-and-drop/types/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './reanimated'; diff --git a/src/components/drag-and-drop/types/reanimated.ts b/src/components/drag-and-drop/types/reanimated.ts new file mode 100644 index 00000000000..6c14f0cba3d --- /dev/null +++ b/src/components/drag-and-drop/types/reanimated.ts @@ -0,0 +1,3 @@ +import { useAnimatedReaction } from 'react-native-reanimated'; + +export type DependencyList = Parameters[2]; diff --git a/src/components/drag-and-drop/utils/array.ts b/src/components/drag-and-drop/utils/array.ts new file mode 100644 index 00000000000..de114bd58c8 --- /dev/null +++ b/src/components/drag-and-drop/utils/array.ts @@ -0,0 +1,17 @@ +export const arraysEqual = (a: unknown[], b: unknown[]): boolean => { + 'worklet'; + if (a === b) { + return true; + } + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +}; diff --git a/src/components/drag-and-drop/utils/assert.ts b/src/components/drag-and-drop/utils/assert.ts new file mode 100644 index 00000000000..9f290f53f05 --- /dev/null +++ b/src/components/drag-and-drop/utils/assert.ts @@ -0,0 +1,20 @@ +class AssertionError extends Error { + name = 'AssertionError'; + code = 'ERR_ASSERTION'; + constructor( + // eslint-disable-next-line default-param-last + message = '', + public actual: unknown, + public expected: unknown = 'true', + public operator = '==' + ) { + super(message || `${actual} ${operator} ${expected}`); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export const assert: (value: unknown, message?: string) => asserts value = (value, message) => { + if (value === undefined || value === null) { + throw new AssertionError(message, value); + } +}; diff --git a/src/components/drag-and-drop/utils/geometry.ts b/src/components/drag-and-drop/utils/geometry.ts new file mode 100644 index 00000000000..6f017685a10 --- /dev/null +++ b/src/components/drag-and-drop/utils/geometry.ts @@ -0,0 +1,124 @@ +export type Point = { + x: T; + y: T; +}; + +export type Offset = { + x: number; + y: number; +}; + +export type Rectangle = { + x: number; + y: number; + width: number; + height: number; +}; + +/** + * @summary Split a `Rectangle` in two + * @worklet + */ +export const splitLayout = (layout: Rectangle, axis: 'x' | 'y') => { + 'worklet'; + const { x, y, width, height } = layout; + if (axis === 'x') { + return [ + { x, y, width: width / 2, height }, + { x: x + width / 2, y, width: width / 2, height }, + ]; + } + return [ + { x, y, width, height: height / 2 }, + { x, y: y + height / 2, width, height: height / 2 }, + ]; +}; + +/** + * @summary Checks if a `Point` is included inside a `Rectangle` + * @worklet + */ +export const includesPoint = (layout: Rectangle, { x, y }: Point, strict?: boolean) => { + 'worklet'; + if (strict) { + return layout.x < x && x < layout.x + layout.width && layout.y < y && y < layout.y + layout.height; + } + return layout.x <= x && x <= layout.x + layout.width && layout.y <= y && y <= layout.y + layout.height; +}; + +/** + * @summary Checks if a `Rectangle` overlaps with another `Rectangle` + * @worklet + */ +export const overlapsRectangle = (layout: Rectangle, other: Rectangle) => { + 'worklet'; + return ( + layout.x < other.x + other.width && + layout.x + layout.width > other.x && + layout.y < other.y + other.height && + layout.y + layout.height > other.y + ); +}; + +/** + * @summary Checks if a `Rectange` overlaps with another `Rectangle` with a margin + * @worklet + */ +export const overlapsRectangleBy = (layout: Rectangle, other: Rectangle, by: number) => { + 'worklet'; + return ( + layout.x < other.x + other.width - by && + layout.x + layout.width > other.x + by && + layout.y < other.y + other.height - by && + layout.y + layout.height > other.y + by + ); +}; + +/** + * @summary Apply an offset to a layout + * @worklet + */ +export const applyOffset = (layout: Rectangle, { x, y }: Offset): Rectangle => { + 'worklet'; + return { + width: layout.width, + height: layout.height, + x: layout.x + x, + y: layout.y + y, + }; +}; + +/** + * @summary Compute a center point + * @worklet + */ +export const centerPoint = (layout: Rectangle): Point => { + 'worklet'; + return { + x: layout.x + layout.width / 2, + y: layout.y + layout.height / 2, + }; +}; + +/** + * @summary Compute a center axis + * @worklet + */ +export const centerAxis = (layout: Rectangle, horizontal: boolean): number => { + 'worklet'; + return horizontal ? layout.x + layout.width / 2 : layout.y + layout.height / 2; +}; + +/** + * @summary Checks if a `Rectangle` overlaps with an axis + * @worklet + */ +export const overlapsAxis = (layout: Rectangle, axis: number, horizontal: boolean) => { + 'worklet'; + return horizontal ? layout.x < axis && layout.x + layout.width > axis : layout.y < axis && layout.y + layout.height > axis; +}; + +export const getDistance = (x: number, y: number): number => { + 'worklet'; + return Math.sqrt(Math.abs(x) ** 2 + Math.abs(y) ** 2); +}; diff --git a/src/components/drag-and-drop/utils/index.ts b/src/components/drag-and-drop/utils/index.ts new file mode 100644 index 00000000000..513e2860213 --- /dev/null +++ b/src/components/drag-and-drop/utils/index.ts @@ -0,0 +1,6 @@ +export * from './array'; +export * from './assert'; +export * from './geometry'; +export * from './random'; +export * from './reanimated'; +export * from './swap'; diff --git a/src/components/drag-and-drop/utils/random.ts b/src/components/drag-and-drop/utils/random.ts new file mode 100644 index 00000000000..54baa9f23b4 --- /dev/null +++ b/src/components/drag-and-drop/utils/random.ts @@ -0,0 +1,15 @@ +/** + * Returns a random integer between min (inclusive) and max (inclusive). + * The value is no lower than min (or the next integer greater than min + * if min isn't an integer) and no greater than max (or the next integer + * lower than max if max isn't an integer). + * Using Math.round() will give you a non-uniform distribution! + */ +export const getRandomInt = (min: number, max: number): number => { + 'worklet'; + // eslint-disable-next-line no-param-reassign + min = Math.ceil(min); + // eslint-disable-next-line no-param-reassign + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +}; diff --git a/src/components/drag-and-drop/utils/reanimated.ts b/src/components/drag-and-drop/utils/reanimated.ts new file mode 100644 index 00000000000..6c1b59bfce9 --- /dev/null +++ b/src/components/drag-and-drop/utils/reanimated.ts @@ -0,0 +1,130 @@ +import { LayoutRectangle } from 'react-native'; +import { SharedValue, withSpring, type AnimatableValue, type AnimationCallback, type WithSpringConfig } from 'react-native-reanimated'; +import type { SharedPoint } from '../hooks'; +import type { AnyData } from '../types'; + +export const DND_DEFAULT_SPRING_CONFIG: WithSpringConfig = { + damping: 10, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10. + mass: 1, // The mass of the object attached to the end of the spring. Default 1. + stiffness: 100, // The spring stiffness coefficient. Default 100. + overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false. + restSpeedThreshold: 0.001, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001. + restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001. +}; +export const DND_FAST_SPRING_CONFIG: WithSpringConfig = { + damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10. + mass: 0.5, // The mass of the object attached to the end of the spring. Default 1. + stiffness: 100, // The spring stiffness coefficient. Default 100. + overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false. + restSpeedThreshold: 0.2, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001. + restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001. +}; +export const DND_DEFAULT_SPRING_CONFIG_3: WithSpringConfig = { + damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10. + mass: 0.5, // The mass of the object attached to the end of the spring. Default 1. + stiffness: 100, // The spring stiffness coefficient. Default 100. + overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false. + restSpeedThreshold: 0.01, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001. + restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001. +}; +export const DND_SLOW_SPRING_CONFIG: WithSpringConfig = { + damping: 20, // Defines how the spring’s motion should be damped due to the forces of friction. Default 10. + mass: 1, // The mass of the object attached to the end of the spring. Default 1. + stiffness: 10, // The spring stiffness coefficient. Default 100. + overshootClamping: false, // Indicates whether the spring should be clamped and not bounce. Default false. + restSpeedThreshold: 0.01, // The speed at which the spring should be considered at rest in pixels per second. Default 0.001. + restDisplacementThreshold: 0.2, // The threshold of displacement from rest below which the spring should be considered at rest. Default 0.001. +}; + +/** + * @summary Waits for n-callbacks + * @worklet + */ +export const waitForAll = (callback: (...args: T) => void, count = 2) => { + 'worklet'; + const status = new Array(count).fill(false); + const result = new Array(count).fill(undefined); + return status.map((_v, index) => { + return (...args: unknown[]) => { + status[index] = true; + result[index] = args; + if (status.every(Boolean)) { + callback(...(result as T)); + } + }; + }); +}; + +type AnimationCallbackParams = Parameters; + +export type AnimationPointCallback = ( + finished: [boolean | undefined, boolean | undefined], + current: [AnimatableValue | undefined, AnimatableValue | undefined] +) => void; + +// eslint-disable-next-line default-param-last +export const withDefaultSpring: typeof withSpring = (toValue, userConfig: WithSpringConfig = {}, callback) => { + 'worklet'; + // eslint-disable-next-line prefer-object-spread + const config: WithSpringConfig = Object.assign({}, DND_SLOW_SPRING_CONFIG, userConfig); + return withSpring(toValue, config, callback); +}; + +/** + * @summary Easily animate a `SharePoint` + * @worklet + */ +export const animatePointWithSpring = ( + point: SharedPoint, + [toValueX, toValueY]: [number, number], + // eslint-disable-next-line default-param-last + [configX, configY]: [WithSpringConfig | undefined, WithSpringConfig | undefined] = [undefined, undefined], + callback?: AnimationPointCallback +) => { + 'worklet'; + const [waitForX, waitForY] = waitForAll<[AnimationCallbackParams, AnimationCallbackParams]>( + ([finishedX, currentX], [finishedY, currentY]) => { + if (!callback) { + return; + } + callback([finishedX, finishedY], [currentX, currentY]); + } + ); + point.x.value = withSpring(toValueX, configX, waitForX); + point.y.value = withSpring(toValueY, configY, waitForY); +}; + +export const moveArrayIndex = (input: T[], from: number, to: number) => { + 'worklet'; + const output = input.slice(); + output.splice(to, 0, output.splice(from, 1)[0]); + return output; +}; + +export const stringifySharedPoint = ({ x, y }: SharedPoint) => { + 'worklet'; + return `{"x": ${Math.floor(x.value)}, "y": ${Math.floor(y.value)}}`; +}; + +export const stringifyLayout = ({ x, y, width, height }: LayoutRectangle) => { + 'worklet'; + return `{"x": ${Math.floor(x)}, "y": ${Math.floor(y)}, "width": ${Math.floor(width)}, "height": ${Math.floor(height)}}`; +}; + +export const floorLayout = ({ x, y, width, height }: LayoutRectangle) => { + 'worklet'; + return { + x: Math.floor(x), + y: Math.floor(y), + width: Math.floor(width), + height: Math.floor(height), + }; +}; + +/** + * @summary Checks if a value is a `Reanimated` shared value + * @param {object} value - The value to check + * @returns {boolean} Whether the value is a `Reanimated` shared value + */ +export const isReanimatedSharedValue = (value: unknown): value is SharedValue => + typeof value === 'object' && (value as { _isReanimatedSharedValue: boolean })?._isReanimatedSharedValue; diff --git a/src/components/drag-and-drop/utils/swap.ts b/src/components/drag-and-drop/utils/swap.ts new file mode 100644 index 00000000000..5290df09dbb --- /dev/null +++ b/src/components/drag-and-drop/utils/swap.ts @@ -0,0 +1,25 @@ +import { centerAxis, centerPoint, includesPoint, overlapsAxis, type Rectangle } from './geometry'; + +export const swapByItemCenterPoint = (activeLayout: Rectangle, itemLayout: Rectangle) => { + 'worklet'; + const itemCenterPoint = centerPoint(itemLayout); + return includesPoint(activeLayout, itemCenterPoint); +}; + +export const swapByItemAxis = (activeLayout: Rectangle, itemLayout: Rectangle, horizontal: boolean) => { + 'worklet'; + const itemCenterAxis = centerAxis(itemLayout, horizontal); + return overlapsAxis(activeLayout, itemCenterAxis, horizontal); +}; + +export const swapByItemHorizontalAxis = (activeLayout: Rectangle, itemLayout: Rectangle) => { + 'worklet'; + const itemCenterAxis = centerAxis(itemLayout, true); + return overlapsAxis(activeLayout, itemCenterAxis, true); +}; + +export const swapByItemVerticalAxis = (activeLayout: Rectangle, itemLayout: Rectangle) => { + 'worklet'; + const itemCenterAxis = centerAxis(itemLayout, false); + return overlapsAxis(activeLayout, itemCenterAxis, false); +}; diff --git a/src/components/easing-gradient/EasingGradient.tsx b/src/components/easing-gradient/EasingGradient.tsx new file mode 100644 index 00000000000..0cddb0157b9 --- /dev/null +++ b/src/components/easing-gradient/EasingGradient.tsx @@ -0,0 +1,66 @@ +import React, { memo } from 'react'; +import { ViewProps } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import { useEasingGradient, UseEasingGradientParams } from '@/hooks/useEasingGradient'; + +interface EasingGradientProps extends UseEasingGradientParams, ViewProps {} + +/** + * ### EasingGradient + * + * Renders a linear gradient with easing applied to the color transitions. + * + * **Required:** + * @param endColor The color at the end of the gradient. + * @param startColor The color at the start of the gradient. + * + * **Optional:** + * @param easing The easing function to apply to the gradient. + * @param endOpacity The opacity at the end of the gradient. + * @param endPosition The end position of the gradient ('top', 'bottom', 'left', 'right'). + * @param startOpacity The opacity at the start of the gradient. + * @param startPosition The start position of the gradient ('top', 'bottom', 'left', 'right'). Defaults to 'top'. + * @param steps The number of color steps in the gradient. Defaults to 16. + * @param props Additional ViewProps to apply to the LinearGradient component. + * + * @returns A LinearGradient component with the specified easing and color properties. + * + * @example + * ```tsx + * + * ``` + */ +export const EasingGradient = memo(function EasingGradient({ + easing, + endColor, + endOpacity, + endPosition, + startColor, + startOpacity, + startPosition = 'top', + steps = 16, + ...props +}: EasingGradientProps) { + const { colors, end, locations, start } = useEasingGradient({ + easing, + endColor, + endOpacity, + endPosition, + startColor, + startOpacity, + startPosition, + steps, + }); + + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}); diff --git a/src/hooks/reanimated/useAnimatedTimeout.ts b/src/hooks/reanimated/useAnimatedTimeout.ts index 04704dedc7c..c075662a09b 100644 --- a/src/hooks/reanimated/useAnimatedTimeout.ts +++ b/src/hooks/reanimated/useAnimatedTimeout.ts @@ -20,6 +20,7 @@ interface TimeoutConfig { * - `onTimeoutWorklet` - The worklet function to be executed when the timeout completes. * * @returns An object containing: + * - `clearTimeout` - A function to clear the timeout. * - `start` - A function to initiate the timeout. * * @example @@ -38,11 +39,11 @@ interface TimeoutConfig { export function useAnimatedTimeout(config: TimeoutConfig) { const { autoStart, delayMs, onTimeoutWorklet } = config; - const { start } = useAnimatedTime({ + const { start, stop: clearTimeout } = useAnimatedTime({ autoStart, durationMs: delayMs, onEndWorklet: onTimeoutWorklet, }); - return { start }; + return { clearTimeout, start }; } diff --git a/src/hooks/reanimated/useLayoutWorklet.ts b/src/hooks/reanimated/useLayoutWorklet.ts new file mode 100644 index 00000000000..84d2efb81be --- /dev/null +++ b/src/hooks/reanimated/useLayoutWorklet.ts @@ -0,0 +1,62 @@ +import { useEvent } from 'react-native-reanimated'; +import { WorkletFunction } from 'react-native-reanimated/lib/typescript/commonTypes'; +import { IS_IOS } from '@/env'; + +interface Layout { + x: number; + y: number; + width: number; + height: number; +} + +const SHOULD_REBUILD = IS_IOS ? undefined : false; + +/** + * ### `📐 useLayoutWorklet 📐` + * @warning This hook is experimental and currently only works on iOS. + * + * Allows reacting to `onLayout` events directly from the UI thread. + * + * Meant to be used with ``. + * + * @param worklet - A worklet function to be called when the layout changes. + * The worklet receives a {@link Layout} object containing the new layout information. + * + * @returns A worklet function that can be passed to an `Animated.View` to handle layout changes. + * + * @example + * ```tsx + * const onLayoutWorklet = useLayoutWorklet((layout) => { + * 'worklet'; + * console.log('New layout:', layout); + * }); + * + * return ( + * { + * if (IS_IOS) return; + * handleAndroidLayout(layout); + * }} + * // @ts-expect-error The name of this prop does not matter but the + * // function must be passed to a prop + * onLayoutWorklet={IS_IOS ? onLayoutWorklet : undefined} + * > + * Measure me + * + * ); + * ``` + */ + +// @ts-expect-error This overload is required by the Reanimated API +export function useLayoutWorklet(worklet: (layout: Layout) => void); +export function useLayoutWorklet(worklet: WorkletFunction) { + return useEvent( + (event: { layout: Layout }) => { + 'worklet'; + worklet(event.layout); + }, + ['topLayout'], + SHOULD_REBUILD + ); +} diff --git a/src/hooks/useEasingGradient.ts b/src/hooks/useEasingGradient.ts new file mode 100644 index 00000000000..a70a6a95b6d --- /dev/null +++ b/src/hooks/useEasingGradient.ts @@ -0,0 +1,77 @@ +import chroma from 'chroma-js'; +import { useMemo } from 'react'; +import { Easing, EasingFunction } from 'react-native-reanimated'; + +type PositionObject = { x: number; y: number }; +type Position = 'bottom' | 'left' | 'right' | 'top' | PositionObject; + +export interface UseEasingGradientParams { + easing?: EasingFunction; + endColor: string; + endOpacity?: number; + endPosition?: Position; + startColor: string; + startOpacity?: number; + startPosition?: Position; + steps?: number; +} + +interface GradientOutput { + colors: string[]; + end: PositionObject; + locations: number[]; + start: PositionObject; +} + +const getPositionCoordinates = (position: Position): PositionObject => { + if (typeof position === 'object') { + return position; + } + switch (position) { + case 'bottom': + return { x: 0.5, y: 1 }; + case 'left': + return { x: 0, y: 0.5 }; + case 'right': + return { x: 1, y: 0.5 }; + case 'top': + default: + return { x: 0.5, y: 0 }; + } +}; + +export const useEasingGradient = ({ + easing = Easing.inOut(Easing.sin), + endColor, + endOpacity = 1, + endPosition = 'bottom', + startColor, + startOpacity = 0, + startPosition = 'top', + steps = 16, +}: UseEasingGradientParams): GradientOutput => { + return useMemo(() => { + const colors: string[] = []; + const locations: number[] = []; + + const startColorWithOpacity = chroma(startColor).alpha(startOpacity); + const endColorWithOpacity = chroma(endColor).alpha(endOpacity); + + for (let i = 0; i <= steps; i++) { + const t = i / steps; + const easedT = easing(t); + + const interpolatedColor = chroma.mix(startColorWithOpacity, endColorWithOpacity, easedT, 'rgb'); + + colors.push(interpolatedColor.css()); + locations.push(t); + } + + return { + colors, + end: getPositionCoordinates(endPosition), + locations, + start: getPositionCoordinates(startPosition), + }; + }, [easing, endColor, endOpacity, endPosition, startColor, startOpacity, startPosition, steps]); +}; diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 4eb72cf0de7..c79715a0db9 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -37,6 +37,8 @@ import { queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; +import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps'; export default async function runMigrations() { // get current version @@ -637,6 +639,35 @@ export default async function runMigrations() { migrations.push(v18); + /** + *************** Migration v19 ****************** + * Migrates dapp browser favorites store from createStore to createRainbowStore + */ + const v19 = async () => { + const initializeLegacyStore = () => { + return new Promise(resolve => { + // Give the async legacy store a moment to initialize + setTimeout(() => { + resolve(); + }, 1000); + }); + }; + + await initializeLegacyStore(); + const legacyFavorites = useLegacyFavoriteDappsStore.getState().favoriteDapps; + + if (legacyFavorites.length > 0) { + // Re-standardize URLs to ensure they're in the correct format + for (const favorite of legacyFavorites) { + favorite.url = standardizeUrl(favorite.url); + } + useFavoriteDappsStore.setState({ favoriteDapps: legacyFavorites }); + useLegacyFavoriteDappsStore.setState({ favoriteDapps: [] }); + } + }; + + migrations.push(v19); + logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); if (migrations.length === currentVersion) { diff --git a/src/state/favoriteDapps/index.test.ts b/src/state/browser/favoriteDappsStore.test.ts similarity index 59% rename from src/state/favoriteDapps/index.test.ts rename to src/state/browser/favoriteDappsStore.test.ts index 566645fed99..10cd8e636a3 100644 --- a/src/state/favoriteDapps/index.test.ts +++ b/src/state/browser/favoriteDappsStore.test.ts @@ -1,10 +1,10 @@ -import { favoriteDappsStore } from '.'; +import { useFavoriteDappsStore } from './favoriteDappsStore'; // TODO: Fix test. skipping for now to unblock CI describe.skip('FavoriteDappsStore', () => { beforeEach(() => { // Reset the store to its initial state before each test - favoriteDappsStore.setState( + useFavoriteDappsStore.setState( { favoriteDapps: [], }, @@ -13,52 +13,52 @@ describe.skip('FavoriteDappsStore', () => { }); test('should be able to add a favorite site', () => { - const { addFavorite } = favoriteDappsStore.getState(); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0); + const { addFavorite } = useFavoriteDappsStore.getState(); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0); addFavorite({ name: 'Uniswap', url: 'uniswap.org', image: 'uniswap.org/favicon', }); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1); }); test('adding a duplicate favorite site should not increase the array', () => { - const { addFavorite } = favoriteDappsStore.getState(); + const { addFavorite } = useFavoriteDappsStore.getState(); addFavorite({ name: 'Zora', url: 'zora.co', image: 'zora.png', }); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1); addFavorite({ name: 'Zora', url: 'zora.co', image: 'zora.png', }); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1); }); test('should be able to remove a favorite site', () => { - const { addFavorite, removeFavorite } = favoriteDappsStore.getState(); + const { addFavorite, removeFavorite } = useFavoriteDappsStore.getState(); addFavorite({ name: 'Mint.fun', url: 'mint.fun', image: 'mint.fun/favicon', }); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(1); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(1); removeFavorite('mint.fun'); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0); }); test('removing a non-existent favorite site should do nothing', () => { - const { removeFavorite } = favoriteDappsStore.getState(); + const { removeFavorite } = useFavoriteDappsStore.getState(); removeFavorite('https://nonexistentdapp.com'); - expect(favoriteDappsStore.getState().favoriteDapps.length).toBe(0); + expect(useFavoriteDappsStore.getState().favoriteDapps.length).toBe(0); }); test('should be able to check if a site is a favorite', () => { - const { addFavorite, isFavorite } = favoriteDappsStore.getState(); + const { addFavorite, isFavorite } = useFavoriteDappsStore.getState(); addFavorite({ name: 'Uniswap', url: 'uniswap.org', diff --git a/src/state/browser/favoriteDappsStore.ts b/src/state/browser/favoriteDappsStore.ts new file mode 100644 index 00000000000..532e9a88b21 --- /dev/null +++ b/src/state/browser/favoriteDappsStore.ts @@ -0,0 +1,105 @@ +import { createRainbowStore } from '../internal/createRainbowStore'; + +export interface FavoritedSite { + name: string; + url: string; + image: string; +} + +interface FavoriteDappsStore { + favoriteDapps: FavoritedSite[]; + addFavorite: (site: FavoritedSite) => void; + getFavorites: (sort?: FavoritedSite['url'][]) => FavoritedSite[]; + getOrderedIds: () => FavoritedSite['url'][]; + removeFavorite: (url: string) => void; + isFavorite: (url: string) => boolean; + reorderFavorites: (newOrder: FavoritedSite['url'][]) => void; +} + +/** + * Strips a URL down from e.g. `https://www.rainbow.me/app/` to `rainbow.me/app`. + * @param stripPath - Optionally strip the path from the URL, leaving `rainbow.me`. + */ +export const standardizeUrl = (url: string, stripPath?: boolean) => { + let standardizedUrl = url?.trim(); + standardizedUrl = standardizedUrl?.replace(/^https?:\/\//, ''); + standardizedUrl = standardizedUrl?.replace(/^www\./, ''); + if (standardizedUrl?.endsWith('/')) { + standardizedUrl = standardizedUrl?.slice(0, -1); + } + if (standardizedUrl?.includes('?')) { + standardizedUrl = standardizedUrl?.split('?')[0]; + } + if (stripPath) { + standardizedUrl = standardizedUrl?.split('/')?.[0] || standardizedUrl; + } + return standardizedUrl; +}; + +export const useFavoriteDappsStore = createRainbowStore( + (set, get) => ({ + favoriteDapps: [], + + addFavorite: site => { + const { favoriteDapps } = get(); + const standardizedUrl = standardizeUrl(site.url); + + if (!favoriteDapps.some(dapp => dapp.url === standardizedUrl)) { + set({ favoriteDapps: [...favoriteDapps, { ...site, url: standardizedUrl }] }); + } + }, + + getFavorites: sort => { + const { favoriteDapps } = get(); + if (!sort) return favoriteDapps; + + const sortMap = new Map(sort.map((url, index) => [url, index])); + + return [...favoriteDapps].sort((a, b) => { + const indexA = sortMap.get(a.url) ?? Infinity; + const indexB = sortMap.get(b.url) ?? Infinity; + return indexA - indexB; + }); + }, + + getOrderedIds: () => get().favoriteDapps.map(dapp => dapp.url), + + isFavorite: url => { + const { favoriteDapps } = get(); + const standardizedUrl = standardizeUrl(url); + const foundMatch = favoriteDapps.some(dapp => dapp.url === standardizedUrl); + if (foundMatch) return true; + + const baseUrl = standardizeUrl(url, true); + return favoriteDapps.some(dapp => dapp.url.startsWith(baseUrl)); + }, + + removeFavorite: url => { + const { favoriteDapps } = get(); + const standardizedUrl = standardizeUrl(url); + const match = favoriteDapps.find(dapp => dapp.url === standardizedUrl); + + if (match) { + set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) }); + } else { + const baseUrl = standardizeUrl(url, true); + const baseUrlMatch = favoriteDapps.find(dapp => dapp.url.startsWith(baseUrl)); + if (baseUrlMatch) { + set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== baseUrlMatch.url) }); + } + } + }, + + reorderFavorites: newOrder => { + const { favoriteDapps } = get(); + const urlMap = new Map(favoriteDapps.map(dapp => [dapp.url, dapp])); + const reorderedFavorites = newOrder.map(url => urlMap.get(url)).filter((dapp): dapp is FavoritedSite => dapp !== undefined); + const remainingFavorites = favoriteDapps.filter(dapp => !newOrder.includes(dapp.url)); + set({ favoriteDapps: [...reorderedFavorites, ...remainingFavorites] }); + }, + }), + { + storageKey: 'browserFavorites', + version: 1, + } +); diff --git a/src/state/favoriteDapps/index.ts b/src/state/legacyFavoriteDapps/index.ts similarity index 65% rename from src/state/favoriteDapps/index.ts rename to src/state/legacyFavoriteDapps/index.ts index 8298eba7fb4..7808f57d452 100644 --- a/src/state/favoriteDapps/index.ts +++ b/src/state/legacyFavoriteDapps/index.ts @@ -1,34 +1,24 @@ import create from 'zustand'; +import { standardizeUrl } from '../browser/favoriteDappsStore'; import { createStore } from '../internal/createStore'; -// need to combine types here interface Site { name: string; url: string; image: string; } -interface FavoriteDappsStore { +interface LegacyFavoriteDappsStore { favoriteDapps: Site[]; addFavorite: (site: Site) => void; removeFavorite: (url: string) => void; isFavorite: (url: string) => boolean; } -const standardizeUrl = (url: string) => { - // Strips the URL down from e.g. "https://www.rainbow.me/app/" to "rainbow.me/app" - let standardizedUrl = url?.trim(); - standardizedUrl = standardizedUrl?.replace(/^https?:\/\//, ''); - standardizedUrl = standardizedUrl?.replace(/^www\./, ''); - if (standardizedUrl?.endsWith('/')) { - standardizedUrl = standardizedUrl?.slice(0, -1); - } - return standardizedUrl; -}; - -export const favoriteDappsStore = createStore( +export const legacyFavoriteDappsStore = createStore( (set, get) => ({ favoriteDapps: [], + addFavorite: site => { const { favoriteDapps } = get(); const standardizedUrl = standardizeUrl(site.url); @@ -37,15 +27,17 @@ export const favoriteDappsStore = createStore( set({ favoriteDapps: [...favoriteDapps, { ...site, url: standardizedUrl }] }); } }, - removeFavorite: url => { + + isFavorite: url => { const { favoriteDapps } = get(); const standardizedUrl = standardizeUrl(url); - set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) }); + return favoriteDapps.some(dapp => dapp.url === standardizedUrl); }, - isFavorite: url => { + + removeFavorite: url => { const { favoriteDapps } = get(); const standardizedUrl = standardizeUrl(url); - return favoriteDapps.some(dapp => dapp.url === standardizedUrl); + set({ favoriteDapps: favoriteDapps.filter(dapp => dapp.url !== standardizedUrl) }); }, }), { @@ -56,4 +48,4 @@ export const favoriteDappsStore = createStore( } ); -export const useFavoriteDappsStore = create(favoriteDappsStore); +export const useLegacyFavoriteDappsStore = create(legacyFavoriteDappsStore); diff --git a/src/state/remoteCards/remoteCards.ts b/src/state/remoteCards/remoteCards.ts index 205c59a1cb4..bd354a10588 100644 --- a/src/state/remoteCards/remoteCards.ts +++ b/src/state/remoteCards/remoteCards.ts @@ -7,28 +7,25 @@ import { createRainbowStore } from '@/state/internal/createRainbowStore'; export type CardKey = string; export interface RemoteCardsState { - cardsById: Set; cards: Map; - - setCards: (cards: TrimmedCards) => void; - - getCard: (id: string) => TrimmedCard | undefined; - getCardPlacement: (id: string) => TrimmedCard['placement'] | undefined; + cardsById: Set; dismissCard: (id: string) => void; - + getCard: (id: string) => TrimmedCard | undefined; getCardIdsForScreen: (screen: keyof typeof Routes) => string[]; + getCardPlacement: (id: string) => TrimmedCard['placement'] | undefined; + setCards: (cards: TrimmedCards) => void; } type RoutesWithIndex = typeof Routes & { [key: string]: string }; -type RemoteCardsStateWithTransforms = Omit, 'cards' | 'cardsById'> & { - cardsById: Array; +type SerializedRemoteCardsState = Omit, 'cards' | 'cardsById'> & { cards: Array<[string, TrimmedCard]>; + cardsById: Array; }; function serializeState(state: Partial, version?: number) { try { - const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys && card.sys.id); + const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys?.id); if (state.cards && validCards.length < state.cards.size) { logger.error(new RainbowError('remoteCardsStore: filtered cards without sys.id during serialization'), { @@ -36,10 +33,10 @@ function serializeState(state: Partial, version?: number) { }); } - const transformedStateToPersist: RemoteCardsStateWithTransforms = { + const transformedStateToPersist: SerializedRemoteCardsState = { ...state, - cardsById: state.cardsById ? Array.from(state.cardsById) : [], cards: validCards, + cardsById: state.cardsById ? Array.from(state.cardsById) : [], }; return JSON.stringify({ @@ -53,7 +50,7 @@ function serializeState(state: Partial, version?: number) { } function deserializeState(serializedState: string) { - let parsedState: { state: RemoteCardsStateWithTransforms; version: number }; + let parsedState: { state: SerializedRemoteCardsState; version: number }; try { parsedState = JSON.parse(serializedState); } catch (error) { @@ -76,7 +73,7 @@ function deserializeState(serializedState: string) { let cardsData: Map = new Map(); try { if (state.cards.length) { - const validCards = state.cards.filter(([, card]) => card && card.sys && typeof card.sys.id === 'string'); + const validCards = state.cards.filter(([, card]) => card && card.sys?.id); if (validCards.length < state.cards.length) { logger.error(new RainbowError('Filtered out cards without sys.id during deserialization'), { @@ -108,10 +105,10 @@ export const remoteCardsStore = createRainbowStore( setCards: (cards: TrimmedCards) => { const cardsData = new Map(); - const validCards = Object.values(cards).filter(card => card.sys.id); + const validCards = Object.values(cards).filter(card => card?.sys?.id); validCards.forEach(card => { - const existingCard = get().getCard(card.sys.id as string); + const existingCard = get().getCard(card.sys.id); if (existingCard) { cardsData.set(card.sys.id, { ...existingCard, ...card }); } else { @@ -121,18 +118,18 @@ export const remoteCardsStore = createRainbowStore( set({ cards: cardsData, - cardsById: new Set(validCards.map(card => card.sys.id as string)), + cardsById: new Set(validCards.map(card => card.sys.id)), }); }, getCard: (id: string) => { const card = get().cards.get(id); - return card && card.sys.id ? card : undefined; + return card?.sys?.id ? card : undefined; }, getCardPlacement: (id: string) => { const card = get().getCard(id); - if (!card || !card.sys.id || !card.placement) { + if (!card || !card.sys?.id || !card.placement) { return undefined; } @@ -158,17 +155,21 @@ export const remoteCardsStore = createRainbowStore( // NOTE: This is kinda a hack to immediately dismiss the card from the carousel and not have an empty space // it will be added back during the next fetch - state.cardsById.delete(id); - + const newCardsById = new Set(state.cardsById); + newCardsById.delete(id); return { ...state, cards: new Map(state.cards.set(id, newCard)), + cardsById: newCardsById, }; }), getCardIdsForScreen: (screen: keyof typeof Routes) => { return Array.from(get().cards.values()) - .filter(card => card.sys.id && get().getCardPlacement(card.sys.id) === screen && !card.dismissed) + .filter( + (card): card is TrimmedCard & { sys: { id: string } } => + !!card?.sys?.id && !card.dismissed && get().getCardPlacement(card.sys.id) === screen + ) .sort((a, b) => { if (a.index === b.index) return 0; if (a.index === undefined || a.index === null) return 1; From e5ab3609edb8ab2666d3c306208b2f72be0f72ba Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 14 Aug 2024 14:32:01 -0400 Subject: [PATCH 23/78] Fix account balance discrepancies (#5959) * fix balance discrepency between bx and app and app and change wallet sheet * Update src/helpers/buildWalletSections.tsx * fix missing import from commit * also fix send sheet balance discrepancy * fix swap user assets not refetching on pending txn resolution and PTR * . * update en_US key * rmeove unused hook and bake it into the user assets store * good lord * separate invalidateQueries because they don't update when grouped for some reason * more instances * cleanup * cleanup useWatchPendingTxs * code suggestions * break up useWallets * rm latestBackup from useWallets * useMemo -> useEffect * logging * cleanup * remove unused withPositions params * type * fix PROFILE_STICKY_HEADER loading value * fix * optional chaining * fixes to buildWalletSections + related code * fix accountInfo isLoadingAccountBalance * clean up useWalletBalances * stuff is still broken but committing walletsWithBalancesAndNames fix * refactor useWalletsWithBalancesAndNames and fix consumers * fix wallet screen balance loading state * console.log * switch amount types from string | number -> string * align stale/cache times, remove keepPreviousData * remove spammy isDamaged logging * add loading balance copy + remove incorrectly used no balance copy * fix i18n mistake --------- Co-authored-by: Ben Goldberg --- .../screens/Swap/providers/swap-provider.tsx | 32 ++++---- .../control-panel/ControlPanel.tsx | 27 +++---- .../RecyclerAssetList2/core/RowRenderer.tsx | 2 +- .../RecyclerAssetList2/core/ViewTypes.ts | 4 +- .../profile-header/ProfileBalanceRow.tsx | 4 +- src/components/backup/useCreateBackup.ts | 7 +- src/components/change-wallet/AddressRow.tsx | 16 +--- src/components/contacts/ContactRow.js | 16 +--- .../ens-profile/ActionButtons/MoreButton.tsx | 5 +- src/helpers/buildWalletSections.tsx | 32 ++++---- src/hooks/index.ts | 1 + src/hooks/useRefreshAccountData.ts | 36 ++++----- src/hooks/useSwitchWallet.ts | 30 ++++++++ src/hooks/useWalletBalances.ts | 75 ++++++++++++++++++- src/hooks/useWalletCloudBackup.ts | 7 +- src/hooks/useWalletSectionsData.ts | 14 +++- src/hooks/useWallets.ts | 43 +---------- src/hooks/useWalletsWithBalancesAndNames.ts | 6 +- src/hooks/useWatchPendingTxs.ts | 49 ++++++------ src/languages/en_US.json | 2 +- src/notifications/NotificationsHandler.tsx | 15 ++-- src/resources/summary/summary.ts | 3 +- src/screens/SignTransactionSheet.tsx | 5 +- src/screens/WalletScreen/index.tsx | 8 +- .../points/components/LeaderboardRow.tsx | 5 +- 25 files changed, 250 insertions(+), 194 deletions(-) create mode 100644 src/hooks/useSwitchWallet.ts diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index bc7e2ee5a5d..6368bc161b9 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -300,24 +300,20 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } } - queryClient.invalidateQueries([ - // old user assets invalidation (will cause a re-fetch) - { - queryKey: userAssetsQueryKey({ - address: parameters.quote.from, - currency: nativeCurrency, - connectedToHardhat, - }), - }, - // new swaps user assets invalidations - { - queryKey: swapsUserAssetsQueryKey({ - address: parameters.quote.from as Address, - currency: nativeCurrency, - testnetMode: !!connectedToHardhat, - }), - }, - ]); + queryClient.invalidateQueries( + userAssetsQueryKey({ + address: parameters.quote.from, + currency: nativeCurrency, + connectedToHardhat, + }) + ); + queryClient.invalidateQueries( + swapsUserAssetsQueryKey({ + address: parameters.quote.from as Address, + currency: nativeCurrency, + testnetMode: !!connectedToHardhat, + }) + ); swapsStore.getState().addRecentSwap(parameters.assetToBuy as ExtendedAnimatedAssetWithColors); clearCustomGasSettings(chainId); diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index f6ea1ce4cd0..4f0b56593b2 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -43,9 +43,8 @@ import { RouteProp, useRoute } from '@react-navigation/native'; import { toHex } from 'viem'; import { RainbowNetworks } from '@/networks'; import * as i18n from '@/languages'; -import { convertAmountToNativeDisplay } from '@/helpers/utilities'; -import { useDispatch, useSelector } from 'react-redux'; -import store, { AppState } from '@/redux/store'; +import { useDispatch } from 'react-redux'; +import store from '@/redux/store'; import { getDappHost } from '@/utils/connectedApps'; import WebView from 'react-native-webview'; import { Navigation, useNavigation } from '@/navigation'; @@ -63,6 +62,7 @@ import { getRemoteConfig } from '@/model/remoteConfig'; import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { greaterThan } from '@/helpers/utilities'; const PAGES = { HOME: 'home', @@ -87,7 +87,6 @@ export const ControlPanel = () => { const { params: { activeTabRef }, } = useRoute>(); - const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); const activeTabUrl = useBrowserStore(state => state.getActiveTabUrl()); const activeTabHost = getDappHost(activeTabUrl || '') || DEFAULT_TAB_URL; @@ -139,12 +138,14 @@ export const ControlPanel = () => { const bluetoothWallets: ControlPanelMenuItemProps[] = []; const readOnlyWallets: ControlPanelMenuItemProps[] = []; - const accountBalances: Record = {}; + const accountBalances: Record = {}; Object.values(walletsWithBalancesAndNames).forEach(wallet => { wallet.addresses .filter(account => account.visible) .forEach(account => { + const balanceText = account.balances ? account.balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); + const item: ControlPanelMenuItemProps = { IconComponent: account.image ? ( @@ -152,20 +153,14 @@ export const ControlPanel = () => { ), label: removeFirstEmojiFromString(account.label) || address(account.address, 6, 4), - secondaryLabel: - // eslint-disable-next-line no-nested-ternary - wallet.type === WalletTypes.readOnly - ? i18n.t(i18n.l.wallet.change_wallet.watching) - : account.balance - ? convertAmountToNativeDisplay(account.balance, nativeCurrency) - : i18n.t(i18n.l.wallet.change_wallet.no_balance), + secondaryLabel: wallet.type === WalletTypes.readOnly ? i18n.t(i18n.l.wallet.change_wallet.watching) : balanceText, uniqueId: account.address, color: colors.avatarBackgrounds[account.color], imageUrl: account.image || undefined, selected: account.address === currentAddress, }; - accountBalances[account.address] = Number(account.balance); + accountBalances[account.address] = account.balances?.totalBalanceAmount; if ([WalletTypes.mnemonic, WalletTypes.seed, WalletTypes.privateKey].includes(wallet.type)) { sortedWallets.push(item); @@ -177,13 +172,13 @@ export const ControlPanel = () => { }); }); - sortedWallets.sort((a, b) => accountBalances[b.uniqueId] - accountBalances[a.uniqueId]); - bluetoothWallets.sort((a, b) => accountBalances[b.uniqueId] - accountBalances[a.uniqueId]); + sortedWallets.sort((a, b) => (greaterThan(accountBalances[b.uniqueId], accountBalances[a.uniqueId]) ? 1 : -1)); + bluetoothWallets.sort((a, b) => (greaterThan(accountBalances[b.uniqueId], accountBalances[a.uniqueId]) ? 1 : -1)); const sortedItems = [...sortedWallets, ...bluetoothWallets, ...readOnlyWallets]; return sortedItems; - }, [walletsWithBalancesAndNames, currentAddress, nativeCurrency]); + }, [walletsWithBalancesAndNames, currentAddress]); const { testnetsEnabled } = store.getState().settings; diff --git a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx index 853fbe1cd9c..afbe410d580 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx @@ -111,7 +111,7 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten ); diff --git a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts index 697d5aca3ca..ab6df6115e6 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts +++ b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts @@ -54,9 +54,9 @@ export type AssetListHeaderExtraData = { }; export type AssetsHeaderExtraData = { - type: CellType.PROFILE_STICKY_HEADER; + type: CellType.PROFILE_BALANCE_ROW; value: string; - isLoadingUserAssets: boolean; + isLoadingBalance: boolean; }; export type CoinExtraData = { type: CellType.COIN; uniqueId: string }; export type NFTExtraData = { diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx index 9e219feb0fd..e84cc3d5f72 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx @@ -4,13 +4,13 @@ import { Box, Heading } from '@/design-system'; export const ProfileBalanceRowHeight = 24; -export function ProfileBalanceRow({ totalValue, isLoadingUserAssets }: { totalValue: string; isLoadingUserAssets: boolean }) { +export function ProfileBalanceRow({ totalValue, isLoadingBalance }: { totalValue: string; isLoadingBalance: boolean }) { const placeholderHeight = ProfileBalanceRowHeight; const placeholderWidth = 200; return ( <> - {isLoadingUserAssets ? ( + {isLoadingBalance ? ( diff --git a/src/components/backup/useCreateBackup.ts b/src/components/backup/useCreateBackup.ts index 71a778b2252..92d62c01de2 100644 --- a/src/components/backup/useCreateBackup.ts +++ b/src/components/backup/useCreateBackup.ts @@ -1,6 +1,6 @@ /* eslint-disable no-promise-executor-return */ -import { useCallback, useState } from 'react'; -import { backupAllWalletsToCloud, getLocalBackupPassword, saveLocalBackupPassword } from '@/model/backup'; +import { useCallback, useMemo, useState } from 'react'; +import { backupAllWalletsToCloud, findLatestBackUp, getLocalBackupPassword, saveLocalBackupPassword } from '@/model/backup'; import { useCloudBackups } from './CloudBackupProvider'; import { cloudPlatform } from '@/utils/platform'; import { analytics } from '@/analytics'; @@ -34,7 +34,8 @@ export const useCreateBackup = ({ walletId, navigateToRoute }: UseCreateBackupPr const { fetchBackups } = useCloudBackups(); const walletCloudBackup = useWalletCloudBackup(); - const { latestBackup, wallets } = useWallets(); + const { wallets } = useWallets(); + const latestBackup = useMemo(() => findLatestBackUp(wallets), [wallets]); const [loading, setLoading] = useState('none'); const [password, setPassword] = useState(''); diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index 3b638cbf248..a67479f22a6 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -22,9 +22,6 @@ import { EditWalletContextMenuActions } from '@/screens/ChangeWalletSheet'; import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; -import { convertAmountToNativeDisplay } from '@/helpers/utilities'; -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; import { useForegroundColor } from '@/design-system'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; @@ -121,22 +118,15 @@ interface AddressRowProps { } export default function AddressRow({ contextMenuActions, data, editMode, onPress }: AddressRowProps) { - const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); const notificationsEnabled = useExperimentalFlag(NOTIFICATIONS); - const { address, balance, color: accountColor, ens, image: accountImage, isSelected, isReadOnly, isLedger, label, walletId } = data; + const { address, balances, color: accountColor, ens, image: accountImage, isSelected, isReadOnly, isLedger, label, walletId } = data; const { colors, isDarkMode } = useTheme(); const labelQuaternary = useForegroundColor('labelQuaternary'); - const cleanedUpBalance = useMemo(() => { - if (balance) { - return convertAmountToNativeDisplay(balance, nativeCurrency); - } else { - return lang.t('wallet.change_wallet.no_balance'); - } - }, [balance, nativeCurrency]); + const balanceText = balances ? balances.totalBalanceDisplay : lang.t('wallet.change_wallet.loading_balance'); const cleanedUpLabel = useMemo(() => removeFirstEmojiFromString(label), [label]); @@ -254,7 +244,7 @@ export default function AddressRow({ contextMenuActions, data, editMode, onPress {walletName} - {cleanedUpBalance} + {balanceText} diff --git a/src/components/contacts/ContactRow.js b/src/components/contacts/ContactRow.js index f1a22d53236..b213d33dd23 100644 --- a/src/components/contacts/ContactRow.js +++ b/src/components/contacts/ContactRow.js @@ -11,12 +11,11 @@ import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import { fetchReverseRecord } from '@/handlers/ens'; import { ENS_DOMAIN } from '@/helpers/ens'; import { isENSAddressFormat, isValidDomainFormat } from '@/helpers/validators'; -import { useAccountSettings, useContacts, useDimensions, useENSAvatar } from '@/hooks'; +import { useContacts, useDimensions, useENSAvatar } from '@/hooks'; import styled from '@/styled-thing'; import { margin } from '@/styles'; import { addressHashedColorIndex, addressHashedEmoji } from '@/utils/profileUtils'; import * as i18n from '@/languages'; -import { convertAmountToNativeDisplay } from '@/helpers/utilities'; const ContactAddress = styled(TruncatedAddress).attrs(({ theme: { colors }, lite }) => ({ align: 'left', @@ -58,17 +57,10 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const profilesEnabled = useExperimentalFlag(PROFILES); const { width: deviceWidth } = useDimensions(); const { onAddOrUpdateContacts } = useContacts(); - const { nativeCurrency } = useAccountSettings(); const { colors } = useTheme(); - const { accountType, balance, ens, image, label, network, onPress, showcaseItem, testID } = props; + const { accountType, balances, ens, image, label, network, onPress, showcaseItem, testID } = props; - const cleanedUpBalance = useMemo(() => { - if (balance) { - return convertAmountToNativeDisplay(balance, nativeCurrency); - } else { - return i18n.t(i18n.l.wallet.change_wallet.no_balance); - } - }, [balance, nativeCurrency]); + const balanceText = balances ? balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); // show avatar for contact rows that are accounts, not contacts const avatar = accountType !== 'contacts' ? returnStringFirstEmoji(label) || profileUtils.addressHashedEmoji(address) : null; @@ -141,7 +133,7 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, )} - {cleanedUpBalance} + {balanceText} ) : ( diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index 95b96316997..c9ac185cef6 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -6,7 +6,7 @@ import { MenuActionConfig } from 'react-native-ios-context-menu'; import { showDeleteContactActionSheet } from '../../contacts'; import More from '../MoreButton/MoreButton'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { useClipboard, useContacts, useWallets, useWatchWallet } from '@/hooks'; +import { useClipboard, useContacts, useSwitchWallet, useWallets, useWatchWallet } from '@/hooks'; import { useNavigation } from '@/navigation'; import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; @@ -24,7 +24,8 @@ const ACTIONS = { }; export default function MoreButton({ address, ensName }: { address?: string; ensName?: string }) { - const { switchToWalletWithAddress, selectedWallet } = useWallets(); + const { selectedWallet } = useWallets(); + const { switchToWalletWithAddress } = useSwitchWallet(); const { isWatching } = useWatchWallet({ address }); const { navigate } = useNavigation(); const { setClipboard } = useClipboard(); diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx index 2557bf80487..255add920b8 100644 --- a/src/helpers/buildWalletSections.tsx +++ b/src/helpers/buildWalletSections.tsx @@ -1,6 +1,5 @@ import { createSelector } from 'reselect'; import { buildBriefCoinsList, buildBriefUniqueTokenList } from './assets'; -import { add, convertAmountToNativeDisplay } from './utilities'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { queryClient } from '@/react-query'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; @@ -39,9 +38,11 @@ const EMPTY_WALLET_CONTENT = [ const ONLY_NFTS_CONTENT = [{ type: 'ETH_CARD', uid: 'eth-card' }]; const sortedAssetsSelector = (state: any) => state.sortedAssets; +const accountBalanceDisplaySelector = (state: any) => state.accountBalanceDisplay; const hiddenCoinsSelector = (state: any) => state.hiddenCoins; const isCoinListEditedSelector = (state: any) => state.isCoinListEdited; const isLoadingUserAssetsSelector = (state: any) => state.isLoadingUserAssets; +const isLoadingBalanceSelector = (state: any) => state.isLoadingBalance; const isReadOnlyWalletSelector = (state: any) => state.isReadOnlyWallet; const nativeCurrencySelector = (state: any) => state.nativeCurrency; const pinnedCoinsSelector = (state: any) => state.pinnedCoins; @@ -105,23 +106,15 @@ const withPositionsSection = (isLoadingUserAssets: boolean) => { const withBriefBalanceSection = ( sortedAssets: ParsedAddressAsset[], isLoadingUserAssets: boolean, + isLoadingBalance: boolean, + accountBalanceDisplay: string | undefined, nativeCurrency: NativeCurrencyKey, isCoinListEdited: boolean, pinnedCoins: any, hiddenCoins: any, - collectibles: any, - nftSort: string + collectibles: any ) => { - const { briefAssets, totalBalancesValue } = buildBriefCoinsList(sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, hiddenCoins); - - const { accountAddress: address } = store.getState().settings; - const positionsObj: RainbowPositions | undefined = queryClient.getQueryData(positionsQueryKey({ address, currency: nativeCurrency })); - - const positionsTotal = positionsObj?.totals?.total?.amount || '0'; - - const totalBalanceWithPositionsValue = add(totalBalancesValue, positionsTotal); - - const totalValue = convertAmountToNativeDisplay(totalBalanceWithPositionsValue, nativeCurrency); + const { briefAssets } = buildBriefCoinsList(sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, hiddenCoins); const hasTokens = briefAssets?.length; const hasNFTs = collectibles?.length; @@ -133,8 +126,6 @@ const withBriefBalanceSection = ( { type: 'PROFILE_STICKY_HEADER', uid: 'assets-profile-header-compact', - value: totalValue, - isLoadingUserAssets, }, { type: 'PROFILE_AVATAR_ROW_SPACE_BEFORE', @@ -156,13 +147,14 @@ const withBriefBalanceSection = ( type: 'PROFILE_NAME_ROW_SPACE_AFTER', uid: 'profile-name-space-after', }, - ...(!hasTokens && !isLoadingUserAssets + ...(!hasTokens && !isLoadingUserAssets && !isLoadingBalance ? [] : [ { type: 'PROFILE_BALANCE_ROW', uid: 'profile-balance', - value: totalValue, + value: accountBalanceDisplay, + isLoadingBalance, }, { type: 'PROFILE_BALANCE_ROW_SPACE_AFTER', @@ -172,13 +164,13 @@ const withBriefBalanceSection = ( { type: 'PROFILE_ACTION_BUTTONS_ROW', uid: 'profile-action-buttons', - value: totalValue, + value: accountBalanceDisplay, }, hasTokens ? { type: 'PROFILE_ACTION_BUTTONS_ROW_SPACE_AFTER', uid: 'profile-action-buttons-space-after', - value: totalValue, + value: accountBalanceDisplay, } : { type: 'BIG_EMPTY_WALLET_SPACER', uid: 'big-empty-wallet-spacer-1' }, ]; @@ -226,6 +218,8 @@ const briefBalanceSectionSelector = createSelector( [ sortedAssetsSelector, isLoadingUserAssetsSelector, + isLoadingBalanceSelector, + accountBalanceDisplaySelector, nativeCurrencySelector, isCoinListEditedSelector, pinnedCoinsSelector, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 048802245d6..f550c40c845 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -94,6 +94,7 @@ export { default as useSwapSettings } from './useSwapSettings'; export { default as useSwapDerivedOutputs } from './useSwapDerivedOutputs'; export { default as useSwapDerivedValues } from './useSwapDerivedValues'; export { default as useSwapRefuel } from './useSwapRefuel'; +export { default as useSwitchWallet } from './useSwitchWallet'; export { default as useTimeout } from './useTimeout'; export { default as useTransactionConfirmation } from './useTransactionConfirmation'; export { default as usePendingTransactions } from './usePendingTransactions'; diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index 4de563b7108..df30a1a18e6 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,6 +1,6 @@ import { captureException } from '@sentry/react-native'; import delay from 'delay'; -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { getIsHardhatConnected } from '@/handlers/web3'; import { walletConnectLoadState } from '../redux/walletconnect'; @@ -10,8 +10,12 @@ import { PROFILES, useExperimentalFlag } from '@/config'; import logger from '@/utils/logger'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { nftsQueryKey } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import { Address } from 'viem'; +import { addysSummaryQueryKey } from '@/resources/summary/summary'; +import useWallets from './useWallets'; export default function useRefreshAccountData() { const dispatch = useDispatch(); @@ -19,25 +23,23 @@ export default function useRefreshAccountData() { const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); + const { wallets } = useWallets(); + + const allAddresses = useMemo( + () => Object.values(wallets || {}).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), + [wallets] + ); + const fetchAccountData = useCallback(async () => { const connectedToHardhat = getIsHardhatConnected(); - queryClient.invalidateQueries({ - queryKey: nftsQueryKey({ address: accountAddress }), - }); - queryClient.invalidateQueries({ - queryKey: positionsQueryKey({ - address: accountAddress, - currency: nativeCurrency, - }), - }); - queryClient.invalidateQueries({ - queryKey: userAssetsQueryKey({ - address: accountAddress, - currency: nativeCurrency, - connectedToHardhat, - }), - }); + queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress })); + queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); + queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); + queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat })); + queryClient.invalidateQueries( + swapsUserAssetsQueryKey({ address: accountAddress as Address, currency: nativeCurrency, testnetMode: !!connectedToHardhat }) + ); try { const getWalletNames = dispatch(fetchWalletNames()); diff --git a/src/hooks/useSwitchWallet.ts b/src/hooks/useSwitchWallet.ts new file mode 100644 index 00000000000..dd0bda69a0f --- /dev/null +++ b/src/hooks/useSwitchWallet.ts @@ -0,0 +1,30 @@ +import { useDispatch } from 'react-redux'; +import { addressSetSelected, walletsSetSelected } from '../redux/wallets'; +import useInitializeWallet from './useInitializeWallet'; +import { toChecksumAddress } from '@/handlers/web3'; +import { RainbowAccount } from '@/model/wallet'; +import useWallets from './useWallets'; + +export default function useSwitchWallet() { + const initializeWallet = useInitializeWallet(); + const dispatch = useDispatch(); + const { wallets } = useWallets(); + + const switchToWalletWithAddress = async (address: string): Promise => { + const walletKey = Object.keys(wallets!).find(key => { + // Addresses + return wallets![key].addresses.find((account: RainbowAccount) => account.address.toLowerCase() === address.toLowerCase()); + }); + + if (!walletKey) return null; + const p1 = dispatch(walletsSetSelected(wallets![walletKey])); + const p2 = dispatch(addressSetSelected(toChecksumAddress(address)!)); + await Promise.all([p1, p2]); + // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 7. + return initializeWallet(null, null, null, false, false, null, true); + }; + + return { + switchToWalletWithAddress, + }; +} diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index bd368aa3042..c4c4e818d76 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -3,19 +3,86 @@ import { useMemo } from 'react'; import { Address } from 'viem'; import useAccountSettings from './useAccountSettings'; import { useAddysSummary } from '@/resources/summary/summary'; +import { useQueries } from '@tanstack/react-query'; +import { fetchPositions, positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import { RainbowPositions } from '@/resources/defi/types'; +import { add, convertAmountToNativeDisplay } from '@/helpers/utilities'; +import { queryClient } from '@/react-query'; -const useWalletBalances = (wallets: AllRainbowWallets) => { +const QUERY_CONFIG = { + staleTime: 1000 * 60 * 2, // 2 minutes + cacheTime: 1000 * 60 * 60 * 24, // 24 hours +}; + +type WalletBalance = { + assetBalanceAmount: string; + assetBalanceDisplay: string; + positionsBalanceAmount: string; + positionsBalanceDisplay: string; + totalBalanceAmount: string; + totalBalanceDisplay: string; +}; + +type WalletBalanceResult = { + balances: Record; + isLoading: boolean; +}; + +const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); - const walletAddresses: Address[] = useMemo( + const allAddresses = useMemo( () => Object.values(wallets).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), [wallets] ); - const { data, isLoading } = useAddysSummary({ addresses: walletAddresses, currency: nativeCurrency }); + const { data: summaryData, isLoading: isSummaryLoading } = useAddysSummary( + { + addresses: allAddresses, + currency: nativeCurrency, + }, + QUERY_CONFIG + ); + + const positionQueries = useQueries({ + queries: allAddresses.map(address => ({ + queryKey: positionsQueryKey({ address, currency: nativeCurrency }), + queryFn: () => fetchPositions({ address, currency: nativeCurrency }), + enabled: !!address, + ...QUERY_CONFIG, + })), + }); + + const isLoading = isSummaryLoading || positionQueries.some(query => query.isLoading); + + const balances = useMemo(() => { + const result: Record = {}; + + if (isLoading) return result; + + for (const address of allAddresses) { + const lowerCaseAddress = address.toLowerCase() as Address; + const assetBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.asset_value.toString() || '0'; + + const positionData = queryClient.getQueryData(positionsQueryKey({ address, currency: nativeCurrency })); + const positionsBalance = positionData?.totals?.total?.amount || '0'; + const totalAccountBalance = add(assetBalance, positionsBalance); + + result[lowerCaseAddress] = { + assetBalanceAmount: assetBalance, + assetBalanceDisplay: convertAmountToNativeDisplay(assetBalance, nativeCurrency), + positionsBalanceAmount: positionsBalance, + positionsBalanceDisplay: convertAmountToNativeDisplay(positionsBalance, nativeCurrency), + totalBalanceAmount: totalAccountBalance, + totalBalanceDisplay: convertAmountToNativeDisplay(totalAccountBalance, nativeCurrency), + }; + } + + return result; + }, [allAddresses, summaryData, nativeCurrency]); return { - balances: data?.data?.addresses, + balances, isLoading, }; }; diff --git a/src/hooks/useWalletCloudBackup.ts b/src/hooks/useWalletCloudBackup.ts index c0c76e6e0cb..f0efed48182 100644 --- a/src/hooks/useWalletCloudBackup.ts +++ b/src/hooks/useWalletCloudBackup.ts @@ -1,10 +1,10 @@ import { captureException } from '@sentry/react-native'; import lang from 'i18n-js'; import { values } from 'lodash'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { Linking } from 'react-native'; import { useDispatch } from 'react-redux'; -import { addWalletToCloudBackup, backupWalletToCloud } from '../model/backup'; +import { addWalletToCloudBackup, backupWalletToCloud, findLatestBackUp } from '../model/backup'; import { setWalletBackedUp } from '../redux/wallets'; import { cloudPlatform } from '../utils/platform'; import useWallets from './useWallets'; @@ -40,7 +40,8 @@ export function getUserError(e: Error) { export default function useWalletCloudBackup() { const dispatch = useDispatch(); - const { latestBackup, wallets } = useWallets(); + const { wallets } = useWallets(); + const latestBackup = useMemo(() => findLatestBackUp(wallets), [wallets]); const walletCloudBackup = useCallback( async ({ diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 9b2b60c9c4c..7fe57995040 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -11,12 +11,14 @@ import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections' import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import { useLegacyNFTs } from '@/resources/nfts'; import useNftSort from './useNFTsSortBy'; +import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; export default function useWalletSectionsData({ type, }: { type?: string; } = {}) { + const { selectedWallet, isReadOnlyWallet } = useWallets(); const { isLoading: isLoadingUserAssets, data: sortedAssets = [] } = useSortedUserAssets(); const isWalletEthZero = useIsWalletEthZero(); @@ -27,9 +29,15 @@ export default function useWalletSectionsData({ } = useLegacyNFTs({ address: accountAddress, }); + + const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); + + const accountWithBalance = walletsWithBalancesAndNames[selectedWallet.id]?.addresses.find( + address => address.address.toLowerCase() === accountAddress.toLowerCase() + ); + const { showcaseTokens } = useShowcaseTokens(); const { hiddenTokens } = useHiddenTokens(); - const { isReadOnlyWallet } = useWallets(); const { hiddenCoinsObj: hiddenCoins, pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); @@ -48,6 +56,8 @@ export default function useWalletSectionsData({ pinnedCoins, sendableUniqueTokens, sortedAssets, + accountBalanceDisplay: accountWithBalance?.balances?.totalBalanceDisplay, + isLoadingBalance: !accountWithBalance?.balances, // @ts-expect-error ts-migrate(2698) FIXME: Spread types may only be created from object types... Remove this comment to see the full error message ...isWalletEthZero, hiddenTokens, @@ -64,6 +74,7 @@ export default function useWalletSectionsData({ return { hasNFTs, isEmpty, + isLoadingBalance: !accountWithBalance?.balances, isLoadingUserAssets, isWalletEthZero, briefSectionsData, @@ -78,6 +89,7 @@ export default function useWalletSectionsData({ pinnedCoins, sendableUniqueTokens, sortedAssets, + accountWithBalance, isWalletEthZero, hiddenTokens, isReadOnlyWallet, diff --git a/src/hooks/useWallets.ts b/src/hooks/useWallets.ts index 64d5c2abcf3..38363886917 100644 --- a/src/hooks/useWallets.ts +++ b/src/hooks/useWallets.ts @@ -1,14 +1,8 @@ -import { useMemo } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; import { createSelector } from 'reselect'; -import { findLatestBackUp } from '../model/backup'; -import { addressSetSelected, walletsSetSelected } from '../redux/wallets'; -import useInitializeWallet from './useInitializeWallet'; -import { toChecksumAddress } from '@/handlers/web3'; import WalletTypes from '@/helpers/walletTypes'; -import { RainbowAccount, RainbowWallet } from '@/model/wallet'; +import { RainbowWallet } from '@/model/wallet'; import { AppState } from '@/redux/store'; -import logger from '@/utils/logger'; const walletSelector = createSelector( ({ wallets: { isWalletLoading, selected = {} as RainbowWallet, walletNames, wallets } }: AppState) => ({ @@ -19,7 +13,6 @@ const walletSelector = createSelector( }), ({ isWalletLoading, selectedWallet, walletNames, wallets }) => ({ isWalletLoading, - latestBackup: findLatestBackUp(wallets), selectedWallet, walletNames, wallets, @@ -27,42 +20,14 @@ const walletSelector = createSelector( ); export default function useWallets() { - const initializeWallet = useInitializeWallet(); - const dispatch = useDispatch(); - const { isWalletLoading, latestBackup, selectedWallet, walletNames, wallets } = useSelector(walletSelector); - - const isDamaged = useMemo(() => { - const bool = selectedWallet?.damaged; - if (bool) { - logger.sentry('Wallet is damaged. Check values below:'); - logger.sentry('selectedWallet: ', selectedWallet); - logger.sentry('wallets: ', wallets); - } - return bool; - }, [selectedWallet, wallets]); - - const switchToWalletWithAddress = async (address: string): Promise => { - const walletKey = Object.keys(wallets!).find(key => { - // Addresses - return wallets![key].addresses.find((account: RainbowAccount) => account.address.toLowerCase() === address.toLowerCase()); - }); - - if (!walletKey) return null; - const p1 = dispatch(walletsSetSelected(wallets![walletKey])); - const p2 = dispatch(addressSetSelected(toChecksumAddress(address)!)); - await Promise.all([p1, p2]); - // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 7. - return initializeWallet(null, null, null, false, false, null, true); - }; + const { isWalletLoading, selectedWallet, walletNames, wallets } = useSelector(walletSelector); return { - isDamaged, + isDamaged: selectedWallet?.damaged, isReadOnlyWallet: selectedWallet.type === WalletTypes.readOnly, isHardwareWallet: !!selectedWallet.deviceId, isWalletLoading, - latestBackup, selectedWallet, - switchToWalletWithAddress, walletNames, wallets, }; diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index 19fd3eaeab2..dcf48245d98 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -6,19 +6,19 @@ import { Address } from 'viem'; export default function useWalletsWithBalancesAndNames() { const { walletNames, wallets } = useWallets(); - const walletBalances = useWalletBalances(wallets!); + const { balances } = useWalletBalances(wallets || {}); const walletsWithBalancesAndNames = useMemo( () => mapValues(wallets, wallet => { const updatedAccounts = (wallet.addresses ?? []).map(account => ({ ...account, - balance: walletBalances?.balances?.[account.address?.toLowerCase() as Address]?.summary?.asset_value, + balances: balances[account.address.toLowerCase() as Address], ens: walletNames[account.address], })); return { ...wallet, addresses: updatedAccounts }; }), - [walletBalances, walletNames, wallets] + [balances, walletNames, wallets] ); return walletsWithBalancesAndNames; diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 777a45aac9b..aed5c6fad25 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,21 +1,22 @@ import { useCallback, useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import { RainbowTransaction, MinedTransaction } from '@/entities/transactions/transaction'; -import { fetchUserAssets } from '@/resources/assets/UserAssetsQuery'; +import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { RainbowError, logger } from '@/logger'; import { Network } from '@/networks/types'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getIsHardhatConnected, getProviderForNetwork } from '@/handlers/web3'; import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; import { RainbowNetworks } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import { getTransactionFlashbotStatus } from '@/handlers/transactions'; import { usePendingTransactionsStore } from '@/state/pendingTransactions'; import { useNonceStore } from '@/state/nonces'; +import { Address } from 'viem'; +import { nftsQueryKey } from '@/resources/nfts'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { - //const { swapRefreshAssets } = useSwapRefreshAssets(); - const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ storePendingTransactions: state.pendingTransactions, setPendingTransactions: state.setPendingTransactions, @@ -25,23 +26,29 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const pendingTransactions = useMemo(() => storePendingTransactions[address] || [], [address, storePendingTransactions]); - const { nativeCurrency, accountAddress } = useAccountSettings(); + const { nativeCurrency } = useAccountSettings(); const refreshAssets = useCallback( - (tx: RainbowTransaction) => { - if (tx.type === 'swap') { - // update swap assets - //swapRefreshAssets(tx.nonce); - } else { - // fetch assets again - fetchUserAssets({ - address: accountAddress, + (_: RainbowTransaction) => { + // NOTE: We have two user assets stores right now, so let's invalidate both queries and trigger a refetch + const connectedToHardhat = getIsHardhatConnected(); + queryClient.invalidateQueries( + userAssetsQueryKey({ + address, currency: nativeCurrency, - connectedToHardhat: false, - }); - } + connectedToHardhat, + }) + ); + queryClient.invalidateQueries( + swapsUserAssetsQueryKey({ + address: address as Address, + currency: nativeCurrency, + testnetMode: !!connectedToHardhat, + }) + ); + queryClient.invalidateQueries(nftsQueryKey({ address })); }, - [accountAddress, nativeCurrency] + [address, nativeCurrency] ); const processFlashbotsTransaction = useCallback(async (tx: RainbowTransaction): Promise => { @@ -183,7 +190,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); await queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ - address: accountAddress, + address, currency: nativeCurrency, chainIds, }), @@ -193,7 +200,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => setTimeout(() => { queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ - address: accountAddress, + address, currency: nativeCurrency, chainIds, }), @@ -201,10 +208,10 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => }, 2000); } setPendingTransactions({ - address: accountAddress, + address, pendingTransactions: newPendingTransactions, }); - }, [accountAddress, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); + }, [address, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); return { watchPendingTransactions }; }; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 70c93854a3f..c243830f84d 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2488,7 +2488,7 @@ "balance_title": "Balance", "buy": "Buy", "change_wallet": { - "no_balance": "No Balance", + "loading_balance": "Loading Balance...", "balance_eth": "%{balanceEth} ETH", "watching": "Watching", "ledger": "Ledger" diff --git a/src/notifications/NotificationsHandler.tsx b/src/notifications/NotificationsHandler.tsx index a076c628f09..d809e788d47 100644 --- a/src/notifications/NotificationsHandler.tsx +++ b/src/notifications/NotificationsHandler.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, useCallback, useEffect, useRef } from 'react'; -import { usePrevious, useWallets } from '@/hooks'; +import { usePrevious, useSwitchWallet, useWallets } from '@/hooks'; import { setupAndroidChannels } from '@/notifications/setupAndroidChannels'; import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; import { @@ -44,9 +44,10 @@ type Callback = () => void; type Props = PropsWithChildren<{ walletReady: boolean }>; export const NotificationsHandler = ({ walletReady }: Props) => { - const wallets = useWallets(); + const { wallets } = useWallets(); + const walletSwitcher = useSwitchWallet(); const dispatch: ThunkDispatch = useDispatch(); - const walletsRef = useRef(wallets); + const walletSwitcherRef = useRef(walletSwitcher); const prevWalletReady = usePrevious(walletReady); const subscriptionChangesListener = useRef(); const onTokenRefreshListener = useRef(); @@ -61,7 +62,7 @@ export const NotificationsHandler = ({ walletReady }: Props) => { We need to save wallets property to a ref in order to have an up-to-date value inside the event listener callbacks closure */ - walletsRef.current = wallets; + walletSwitcherRef.current = walletSwitcher; const onForegroundRemoteNotification = (remoteMessage: FirebaseMessagingTypes.RemoteMessage) => { const type = remoteMessage?.data?.type; @@ -152,12 +153,12 @@ export const NotificationsHandler = ({ walletReady }: Props) => { // casting data payload to type that was agreed on with backend const data = notification.data as unknown as TransactionNotificationData; - const wallets = walletsRef.current; + const walletSwitcher = walletSwitcherRef.current; const { accountAddress, nativeCurrency } = store.getState().settings; let walletAddress: string | null | undefined = accountAddress; if (!isLowerCaseMatch(accountAddress, data.address)) { - walletAddress = await wallets.switchToWalletWithAddress(data.address); + walletAddress = await walletSwitcher.switchToWalletWithAddress(data.address); } if (!walletAddress) { return; @@ -242,7 +243,7 @@ export const NotificationsHandler = ({ walletReady }: Props) => { if (walletReady && !alreadyRanInitialization.current) { const addresses: AddressWithRelationship[] = []; - Object.values(wallets.wallets ?? {}).forEach(wallet => + Object.values(wallets ?? {}).forEach(wallet => wallet?.addresses.forEach( ({ address, visible }: { address: string; visible: boolean }) => visible && diff --git a/src/resources/summary/summary.ts b/src/resources/summary/summary.ts index 04337c43eb2..90705878f46 100644 --- a/src/resources/summary/summary.ts +++ b/src/resources/summary/summary.ts @@ -56,7 +56,7 @@ export type AddysSummaryArgs = { // /////////////////////////////////////////////// // Query Key -const addysSummaryQueryKey = ({ addresses, currency }: AddysSummaryArgs) => +export const addysSummaryQueryKey = ({ addresses, currency }: AddysSummaryArgs) => createQueryKey('addysSummary', { addresses, currency }, { persisterVersion: 1 }); type AddysSummaryQueryKey = ReturnType; @@ -88,6 +88,5 @@ export function useAddysSummary( ...config, staleTime: 1000 * 60 * 2, // Set data to become stale after 2 minutes cacheTime: 1000 * 60 * 60 * 24, // Keep unused data in cache for 24 hours - keepPreviousData: true, // Use previous data while new data is loading after it becomes stale }); } diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 84a0a3acdaa..d4b8b505431 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -57,7 +57,7 @@ import { import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { getAccountProfileInfo } from '@/helpers/accountInfo'; -import { useAccountSettings, useClipboard, useDimensions, useGas, useWallets } from '@/hooks'; +import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWallet, useWallets } from '@/hooks'; import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; @@ -162,7 +162,8 @@ export const SignTransactionSheet = () => { const [simulationScanResult, setSimulationScanResult] = useState(undefined); const { params: routeParams } = useRoute(); - const { wallets, walletNames, switchToWalletWithAddress } = useWallets(); + const { wallets, walletNames } = useWallets(); + const { switchToWalletWithAddress } = useSwitchWallet(); const { transactionDetails, onSuccess: onSuccessCallback, diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index c4c4a647676..02ea352ec5c 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -74,7 +74,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { }, [currentNetwork, revertToMainnet]); const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady); - const { isWalletEthZero, isLoadingUserAssets, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData(); + const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData(); useEffect(() => { // This is the fix for Android wallet creation problem. @@ -86,7 +86,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { if (isWelcomeScreen) { removeFirst(); } - }, [dangerouslyGetState, removeFirst]); + }, [dangerouslyGetParent, dangerouslyGetState, removeFirst]); useEffect(() => { const initializeAndSetParams = async () => { @@ -132,8 +132,8 @@ const WalletScreen: React.FC = ({ navigation, route }) => { {/* @ts-expect-error JavaScript component */} Date: Wed, 14 Aug 2024 16:16:13 -0300 Subject: [PATCH 24/78] update swaps sdk (#5996) --- package.json | 2 +- .../Swap/hooks/useSwapInputsController.ts | 55 +++++++++---------- src/__swaps__/utils/swaps.ts | 26 +++++---- src/hooks/useSwapDerivedOutputs.ts | 17 +++--- src/raps/actions/claimBridge.ts | 23 +++++--- yarn.lock | 10 ++-- 6 files changed, 68 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index c5676ef3ca8..ebd6edf061c 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@notifee/react-native": "7.8.2", "@rainbow-me/provider": "0.0.12", "@rainbow-me/react-native-animated-number": "0.0.2", - "@rainbow-me/swaps": "0.22.0", + "@rainbow-me/swaps": "0.23.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-camera-roll/camera-roll": "7.7.0", "@react-native-clipboard/clipboard": "1.13.2", diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 266caea1063..03214c85123 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,38 +1,38 @@ -import { useCallback } from 'react'; -import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; -import { useDebouncedCallback } from 'use-debounce'; +import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; +import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { valueBasedDecimalFormatter } from '@/__swaps__/utils/decimalFormatter'; -import { addCommasToNumber, buildQuoteParams, clamp, getDefaultSlippageWorklet, trimTrailingZeros } from '@/__swaps__/utils/swaps'; -import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; -import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; -import store from '@/redux/store'; -import { swapsStore } from '@/state/swaps/swapsStore'; +import { getInputValuesForSliderPositionWorklet, updateInputValuesAfterFlip } from '@/__swaps__/utils/flipAssets'; import { convertAmountToNativeDisplayWorklet, convertRawAmountToDecimalFormat, handleSignificantDecimalsWorklet, } from '@/__swaps__/utils/numbers'; -import { NavigationSteps } from './useSwapNavigation'; +import { addCommasToNumber, buildQuoteParams, clamp, getDefaultSlippageWorklet, trimTrailingZeros } from '@/__swaps__/utils/swaps'; +import { analyticsV2 } from '@/analytics'; +import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; +import { useAccountSettings } from '@/hooks'; +import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; import { RainbowError, logger } from '@/logger'; +import { getRemoteConfig } from '@/model/remoteConfig'; +import { queryClient } from '@/react-query'; +import store from '@/redux/store'; import { EXTERNAL_TOKEN_STALE_TIME, ExternalTokenQueryFunctionResult, externalTokenQueryKey, fetchExternalToken, } from '@/resources/assets/externalAssetsQuery'; -import { ethereumUtils } from '@/utils'; -import { queryClient } from '@/react-query'; -import { useAccountSettings } from '@/hooks'; -import { analyticsV2 } from '@/analytics'; -import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; -import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { triggerHapticFeedback } from '@/screens/points/constants'; -import { getInputValuesForSliderPositionWorklet, updateInputValuesAfterFlip } from '@/__swaps__/utils/flipAssets'; -import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/__swaps__/types/chains'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { ethereumUtils } from '@/utils'; +import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; +import { useCallback } from 'react'; +import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; +import { useDebouncedCallback } from 'use-debounce'; +import { NavigationSteps } from './useSwapNavigation'; const REMOTE_CONFIG = getRemoteConfig(); @@ -433,19 +433,14 @@ export function useSwapInputsController({ } try { - const [quoteResponse, fetchedPrices] = await Promise.all([ - params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params), - fetchAssetPrices({ - inputAsset: internalSelectedInputAsset.value, - outputAsset: internalSelectedOutputAsset.value, - }), - ]); + const quoteResponse = await (params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params)); + if (!quoteResponse || 'error' in quoteResponse) throw ''; const quotedInputAmount = lastTypedInputParam === 'outputAmount' ? Number( convertRawAmountToDecimalFormat( - (quoteResponse as Quote)?.sellAmount?.toString(), + quoteResponse.sellAmount.toString(), internalSelectedInputAsset.value?.networks[internalSelectedInputAsset.value.chainId]?.decimals || internalSelectedInputAsset.value?.decimals || 18 @@ -457,7 +452,7 @@ export function useSwapInputsController({ lastTypedInputParam === 'inputAmount' ? Number( convertRawAmountToDecimalFormat( - (quoteResponse as Quote)?.buyAmountMinusFees?.toString(), + quoteResponse.buyAmountMinusFees.toString(), internalSelectedOutputAsset.value?.networks[internalSelectedOutputAsset.value.chainId]?.decimals || internalSelectedOutputAsset.value?.decimals || 18 @@ -475,7 +470,7 @@ export function useSwapInputsController({ setQuote({ data: quoteResponse, inputAmount: quotedInputAmount, - inputPrice: fetchedPrices.inputPrice, + inputPrice: quoteResponse.sellTokenAsset.price.value, originalQuoteParams: { assetToBuyUniqueId: originalOutputAssetUniqueId, assetToSellUniqueId: originalInputAssetUniqueId, @@ -484,7 +479,7 @@ export function useSwapInputsController({ outputAmount: outputAmount, }, outputAmount: quotedOutputAmount, - outputPrice: fetchedPrices.outputPrice, + outputPrice: quoteResponse.buyTokenAsset.price.value, quoteFetchingInterval, }); })(); diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 1925cdffdc7..33071792138 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -1,9 +1,6 @@ import c from 'chroma-js'; import { SharedValue, convertToRGBA, isColor } from 'react-native-reanimated'; -import * as i18n from '@/languages'; -import { globalColors } from '@/design-system'; -import { ForegroundColor, palettes } from '@/design-system/color/palettes'; import { ETH_COLOR, ETH_COLOR_DARK, @@ -12,33 +9,37 @@ import { SLIDER_WIDTH, STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; -import { chainNameFromChainId, chainNameFromChainIdWorklet } from '@/__swaps__/utils/chains'; import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { chainNameFromChainId, chainNameFromChainIdWorklet } from '@/__swaps__/utils/chains'; import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; +import { globalColors } from '@/design-system'; +import { ForegroundColor, palettes } from '@/design-system/color/palettes'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import * as i18n from '@/languages'; import { RainbowConfig } from '@/model/remoteConfig'; +import store from '@/redux/store'; +import { ETH_ADDRESS } from '@/references'; import { userAssetsStore } from '@/state/assets/userAssets'; import { colors } from '@/styles'; import { BigNumberish } from '@ethersproject/bignumber'; import { CrosschainQuote, ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR, Quote, QuoteParams, SwapType, WRAPPED_ASSET } from '@rainbow-me/swaps'; import { swapsStore } from '../../state/swaps/swapsStore'; -import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '../types/assets'; -import { inputKeys } from '../types/swap'; -import { valueBasedDecimalFormatter } from './decimalFormatter'; -import { convertAmountToRawAmount } from './numbers'; import { divWorklet, equalWorklet, + greaterThanOrEqualToWorklet, + isNumberStringWorklet, lessThanOrEqualToWorklet, mulWorklet, + orderOfMagnitudeWorklet, powWorklet, roundWorklet, toFixedWorklet, - greaterThanOrEqualToWorklet, - orderOfMagnitudeWorklet, - isNumberStringWorklet, } from '../safe-math/SafeMath'; -import { ETH_ADDRESS } from '@/references'; +import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '../types/assets'; +import { inputKeys } from '../types/swap'; +import { valueBasedDecimalFormatter } from './decimalFormatter'; +import { convertAmountToRawAmount } from './numbers'; // /---- 🎨 Color functions 🎨 ----/ // // @@ -659,5 +660,6 @@ export const buildQuoteParams = ({ refuel: false, swapType: isCrosschainSwap ? SwapType.crossChain : SwapType.normal, toChainId: isCrosschainSwap ? outputAsset.chainId : inputAsset.chainId, + currency: store.getState().settings.nativeCurrency, }; }; diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index 84d6abb7ee4..34738fe1681 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -13,17 +13,11 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo, useState } from 'react'; // DO NOT REMOVE THESE COMMENTED ENV VARS // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { IS_APK_BUILD, IS_TESTING } from 'react-native-dotenv'; +import { IS_TESTING } from 'react-native-dotenv'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -import isTestFlight from '@/helpers/isTestFlight'; -import { useDispatch, useSelector } from 'react-redux'; -import { SwappableAsset } from '../entities/tokens'; -import useAccountSettings from './useAccountSettings'; import { analytics } from '@/analytics'; import { EthereumAddress } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; -import { AppState } from '@/redux/store'; -import { SwapModalField, updateSwapQuote } from '@/redux/swap'; import { convertAmountFromNativeValue, convertAmountToNativeAmount, @@ -33,8 +27,13 @@ import { isZero, updatePrecisionToDisplay, } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; +import store, { AppState } from '@/redux/store'; +import { SwapModalField, updateSwapQuote } from '@/redux/swap'; +import { ethereumUtils } from '@/utils'; +import { useDispatch, useSelector } from 'react-redux'; +import { SwappableAsset } from '../entities/tokens'; +import useAccountSettings from './useAccountSettings'; const SWAP_POLLING_INTERVAL = 5000; @@ -104,6 +103,7 @@ const getInputAmount = async ( slippage: IS_TESTING !== 'true' ? slippage : 5, ...(quoteSource ? { source } : {}), swapType: SwapType.normal, + currency: store.getState().settings.nativeCurrency, }; const rand = Math.floor(Math.random() * 100); @@ -200,6 +200,7 @@ const getOutputAmount = async ( swapType: isCrosschainSwap ? SwapType.crossChain : SwapType.normal, toChainId: Number(outputChainId), refuel, + currency: store.getState().settings.nativeCurrency, }; const rand = Math.floor(Math.random() * 100); diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 64f07171bdf..0e0f1bf747a 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -1,17 +1,18 @@ -import { AddressZero } from '@ethersproject/constants'; -import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rainbow-me/swaps'; -import { Address } from 'viem'; -import { ActionProps } from '../references'; -import { executeCrosschainSwap } from './crosschainSwap'; -import { RainbowError } from '@/logger'; -import { add, addBuffer, greaterThan, lessThan, multiply, subtract } from '@/helpers/utilities'; +import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { getProviderForNetwork } from '@/handlers/web3'; import { Network } from '@/helpers'; +import { add, addBuffer, greaterThan, lessThan, multiply, subtract } from '@/helpers/utilities'; +import { RainbowError } from '@/logger'; +import store from '@/redux/store'; +import { REFERRER_CLAIM } from '@/references'; import { TxHash } from '@/resources/transactions/types'; -import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { addNewTransaction } from '@/state/pendingTransactions'; import ethereumUtils, { getNetworkFromChainId } from '@/utils/ethereumUtils'; -import { REFERRER_CLAIM, ReferrerType } from '@/references'; +import { AddressZero } from '@ethersproject/constants'; +import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rainbow-me/swaps'; +import { Address } from 'viem'; +import { ActionProps } from '../references'; +import { executeCrosschainSwap } from './crosschainSwap'; // This action is used to bridge the claimed funds to another chain export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) { @@ -25,6 +26,8 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps let maxBridgeableAmount = sellAmount; let needsNewQuote = false; + const currency = store.getState().settings.nativeCurrency; + // 1 - Get a quote to bridge the claimed funds const claimBridgeQuote = await getClaimBridgeQuote({ chainId, @@ -35,6 +38,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps sellAmount: sellAmount, slippage: 2, swapType: SwapType.crossChain, + currency, }); // if we don't get a quote or there's an error we can't continue @@ -95,6 +99,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps sellAmount: maxBridgeableAmount, slippage: 2, swapType: SwapType.crossChain, + currency, }); if (!newQuote || (newQuote as QuoteError)?.error) { diff --git a/yarn.lock b/yarn.lock index d39d92e225c..2a2f3946569 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4299,9 +4299,9 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/swaps@npm:0.22.0": - version: 0.22.0 - resolution: "@rainbow-me/swaps@npm:0.22.0" +"@rainbow-me/swaps@npm:0.23.0": + version: 0.23.0 + resolution: "@rainbow-me/swaps@npm:0.23.0" dependencies: "@ethereumjs/util": "npm:9.0.0" "@ethersproject/abi": "npm:5.7.0" @@ -4316,7 +4316,7 @@ __metadata: "@ethersproject/transactions": "npm:5.7.0" "@ethersproject/wallet": "npm:5.7.0" "@metamask/eth-sig-util": "npm:7.0.0" - checksum: 10c0/5a466d3d03c5e2f80970750a111f5531ade3a1d85c76d3863e538869adebeaf7c4ce6a7c892f340c603ce5f980f742b3bfc2865fc60cf1fe062072aaa2a710e9 + checksum: 10c0/3029ca9ed16a45af1961d2fdc79c545ae20a55212b38c8d3daba707617f8630f5328bc52e0e4732d97bffdbda62408093b3c871ef696322f1853d71231561a0f languageName: node linkType: hard @@ -7811,7 +7811,7 @@ __metadata: "@notifee/react-native": "npm:7.8.2" "@rainbow-me/provider": "npm:0.0.12" "@rainbow-me/react-native-animated-number": "npm:0.0.2" - "@rainbow-me/swaps": "npm:0.22.0" + "@rainbow-me/swaps": "npm:0.23.0" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-camera-roll/camera-roll": "npm:7.7.0" "@react-native-clipboard/clipboard": "npm:1.13.2" From cb3720973a09fb51adda36092f18d77c824f7047 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 14 Aug 2024 14:18:16 -0600 Subject: [PATCH 25/78] Swaps: Change fee denomination from USD -> actual payment token (#6000) * hm * done * idk how this got deleted * cleanup * fix bad rebase --------- Co-authored-by: gregs --- .../screens/Swap/components/ReviewPanel.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 1988c2fa001..ae3d9243882 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -8,7 +8,6 @@ import { useEstimatedTime } from '@/__swaps__/utils/meteorology'; import { convertRawAmountToBalance, convertRawAmountToBalanceWorklet, - convertRawAmountToNativeDisplay, handleSignificantDecimals, multiply, } from '@/__swaps__/utils/numbers'; @@ -91,14 +90,12 @@ const RainbowFee = () => { decimals: 18, }).amount; - const feeInEth = convertRawAmountToNativeDisplay( - quote.feeInEth.toString(), - nativeAsset?.value?.decimals || 18, - nativeAsset?.value?.price?.value || '0', - nativeCurrency - ).display; + const { display: feeDisplay } = convertRawAmountToBalance(quote.fee.toString(), { + decimals: quote.feeTokenAsset.decimals, + symbol: quote.feeTokenAsset.symbol, + }); - rainbowFee.value = [feeInEth, `${handleSignificantDecimals(multiply(feePercentage, 100), 2)}%`]; + rainbowFee.value = [feeDisplay, `${handleSignificantDecimals(multiply(feePercentage, 100), 2)}%`]; }, [nativeAsset?.value?.decimals, nativeAsset?.value?.price?.value, nativeCurrency, rainbowFee] ); From 37918f57730c75d7b9906881077e5241cd01bc82 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 14 Aug 2024 16:18:46 -0400 Subject: [PATCH 26/78] Fun stuff (#5995) * add filteration * clenaup --- src/logger/sentry.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index b7506b79772..ba9cb80d46f 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -18,6 +18,13 @@ export const defaultOptions: Sentry.ReactNativeOptions = { integrations: [], maxBreadcrumbs: 5, tracesSampleRate: 0, + beforeSend(event) { + if (!event.tags?.['device.family']) { + return null; + } + + return event; + }, }; export function initSentry() { From 4b468b981734261ec828571cc12234489595e3ec Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 15 Aug 2024 11:09:40 -0400 Subject: [PATCH 27/78] fix runtime error (#6001) --- src/components/backup/ChooseBackupStep.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/backup/ChooseBackupStep.tsx b/src/components/backup/ChooseBackupStep.tsx index fa2442f2673..2f7b68cedf6 100644 --- a/src/components/backup/ChooseBackupStep.tsx +++ b/src/components/backup/ChooseBackupStep.tsx @@ -6,7 +6,7 @@ import { useDimensions } from '@/hooks'; import { useNavigation } from '@/navigation'; import styled from '@/styled-thing'; import { margin, padding } from '@/styles'; -import { Box, Stack } from '@/design-system'; +import { Box, Stack, Text } from '@/design-system'; import { RouteProp, useRoute } from '@react-navigation/native'; import { sharedCoolModalTopOffset } from '@/navigation/config'; import { ImgixImage } from '../images'; @@ -162,7 +162,7 @@ export function ChooseBackupStep() { )} - {!isFetching && cloudBackups.length && ( + {!isFetching && cloudBackups.length > 0 && ( {mostRecentBackup && ( {android ? : } - { - - {lang.t(lang.l.back_up.cloud.fetching_backups, { - cloudPlatformName: cloudPlatform, - })} - - } + + {lang.t(lang.l.back_up.cloud.fetching_backups, { + cloudPlatformName: cloudPlatform, + })} + )} From d60942d3a4926f2f6f9e4fecaf2ae45f3a5062fb Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 15 Aug 2024 12:43:36 -0400 Subject: [PATCH 28/78] rotate user-agent (#6003) --- src/components/DappBrowser/constants.ts | 2 +- src/components/DappBrowser/scripts.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DappBrowser/constants.ts b/src/components/DappBrowser/constants.ts index ec70a0ecf38..482bfd23962 100644 --- a/src/components/DappBrowser/constants.ts +++ b/src/components/DappBrowser/constants.ts @@ -10,7 +10,7 @@ export const DEFAULT_TAB_URL = RAINBOW_HOME; export const USER_AGENT = { IOS: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1', - ANDROID: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/53', + ANDROID: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36', }; export const USER_AGENT_APPLICATION_NAME = 'Rainbow'; diff --git a/src/components/DappBrowser/scripts.ts b/src/components/DappBrowser/scripts.ts index 9c45577b6d1..4c542b12170 100644 --- a/src/components/DappBrowser/scripts.ts +++ b/src/components/DappBrowser/scripts.ts @@ -102,7 +102,7 @@ export const getWebsiteMetadata = ` }; window.ReactNativeWebView.postMessage(JSON.stringify(websiteMetadata)); - true; + true; }); `; From 381647cf0d6e08f07344b03894e3f1501f9516ac Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 15 Aug 2024 13:17:08 -0400 Subject: [PATCH 29/78] fixed dapp browser height on android (#6004) --- src/components/DappBrowser/Dimensions.ts | 10 +++++++--- src/components/DappBrowser/search/Search.tsx | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/DappBrowser/Dimensions.ts b/src/components/DappBrowser/Dimensions.ts index f96dc970db5..a367b0967aa 100644 --- a/src/components/DappBrowser/Dimensions.ts +++ b/src/components/DappBrowser/Dimensions.ts @@ -1,9 +1,13 @@ -import { IS_IOS } from '@/env'; +import { IS_ANDROID, IS_IOS } from '@/env'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; +import { StatusBar } from 'react-native'; +import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; -export const TOP_INSET = IS_IOS ? safeAreaInsetValues.top : 40; -export const WEBVIEW_HEIGHT = deviceUtils.dimensions.height - TOP_INSET - TAB_BAR_HEIGHT - 88; +export const BOTTOM_BAR_HEIGHT = 88; +export const TOP_INSET = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight ?? 40; +export const BOTTOM_INSET = IS_ANDROID ? (isUsingButtonNavigation() ? 32 : 12) : BOTTOM_BAR_HEIGHT; +export const WEBVIEW_HEIGHT = deviceUtils.dimensions.height - TOP_INSET - TAB_BAR_HEIGHT - BOTTOM_INSET; export const COLLAPSED_WEBVIEW_ASPECT_RATIO = 4 / 3; export const COLLAPSED_WEBVIEW_HEIGHT_UNSCALED = Math.min(WEBVIEW_HEIGHT, deviceUtils.dimensions.width * COLLAPSED_WEBVIEW_ASPECT_RATIO); diff --git a/src/components/DappBrowser/search/Search.tsx b/src/components/DappBrowser/search/Search.tsx index df60db17000..df2ef1470db 100644 --- a/src/components/DappBrowser/search/Search.tsx +++ b/src/components/DappBrowser/search/Search.tsx @@ -18,6 +18,7 @@ import { SearchResults } from './results/SearchResults'; import { useSearchContext } from './SearchContext'; import { useSyncSharedValue } from '@/hooks/reanimated/useSyncSharedValue'; import { useSharedValueState } from '@/hooks/reanimated/useSharedValueState'; +import { BOTTOM_BAR_HEIGHT } from '../Dimensions'; export const Search = () => { const { goToUrl, searchViewProgress, tabViewProgress, tabViewVisible } = useBrowserContext(); @@ -179,7 +180,7 @@ const styles = StyleSheet.create({ }, bottomBarStyle: { bottom: 0, - height: TAB_BAR_HEIGHT + 88, + height: BOTTOM_BAR_HEIGHT + TAB_BAR_HEIGHT, paddingTop: 20, pointerEvents: 'box-none', position: 'absolute', From fc87001ee2dd31758d3d6962a4518de7de8d54e2 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 15 Aug 2024 13:46:40 -0400 Subject: [PATCH 30/78] fixes android light mode button navigation colors (#6005) --- .../SystemNavigationBarModule.java | 41 +++++++++---------- src/handlers/localstorage/theme.ts | 10 +++-- src/theme/ThemeContext.tsx | 2 +- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java index a0c2aec95e6..c72641cf994 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java @@ -2,6 +2,8 @@ package me.rainbow.NativeModules.SystemNavigationBar; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsControllerCompat; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.annotation.TargetApi; @@ -84,6 +86,9 @@ public void changeNavigationBarColor(final String color, final Boolean light, fi runOnUiThread(new Runnable() { @Override public void run() { + WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(window, window.getDecorView()); + insetsController.setAppearanceLightNavigationBars(!light); + if (color.equals("transparent") || color.equals("translucent")) { window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); @@ -92,31 +97,25 @@ public void run() { } else { window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); } - setNavigationBarTheme(getCurrentActivity(), light); - map.putBoolean("success", true); - promise.resolve(map); - return; } else { window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - if (animated) { - Integer colorFrom = window.getNavigationBarColor(); - Integer colorTo = Color.parseColor(String.valueOf(color)); - //window.setNavigationBarColor(colorTo); - ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); - colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - window.setNavigationBarColor((Integer) animator.getAnimatedValue()); - } - }); - colorAnimation.start(); - } else { - window.setNavigationBarColor(Color.parseColor(String.valueOf(color))); + if (animated) { + Integer colorFrom = window.getNavigationBarColor(); + Integer colorTo = Color.parseColor(String.valueOf(color)); + ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); + colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + window.setNavigationBarColor((Integer) animator.getAnimatedValue()); + } + }); + colorAnimation.start(); + } else { + window.setNavigationBarColor(Color.parseColor(String.valueOf(color))); + } } setNavigationBarTheme(getCurrentActivity(), light); - WritableMap map = Arguments.createMap(); map.putBoolean("success", true); promise.resolve(map); } @@ -125,10 +124,8 @@ public void onAnimationUpdate(ValueAnimator animator) { map.putBoolean("success", false); promise.reject("error", e); } - } else { promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE)); - } } else { promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE)); diff --git a/src/handlers/localstorage/theme.ts b/src/handlers/localstorage/theme.ts index 3129275c26c..32011a99809 100644 --- a/src/handlers/localstorage/theme.ts +++ b/src/handlers/localstorage/theme.ts @@ -3,17 +3,18 @@ import { getGlobal, saveGlobal } from './common'; import { NativeModules } from 'react-native'; import { colors } from '@/styles'; import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; +import { Themes, ThemesType } from '@/theme'; const { NavigationBar } = NativeModules; const THEME = 'theme'; -export const getColorForThemeAndNavigationStyle = (theme: string) => { +export const getColorForThemeAndNavigationStyle = (theme: ThemesType) => { if (!isUsingButtonNavigation()) { return 'transparent'; } - return theme === 'dark' ? '#191A1C' : colors.white; + return theme === Themes.DARK ? '#191A1C' : colors.white; }; /** @@ -25,9 +26,10 @@ export const getTheme = () => getGlobal(THEME, 'system'); /** * @desc save theme */ -export const saveTheme = (theme: string) => { +export const saveTheme = (theme: ThemesType, isSystemDarkMode: boolean) => { if (IS_ANDROID) { - NavigationBar.changeNavigationBarColor(getColorForThemeAndNavigationStyle(theme), theme === 'light', true); + const themeToUse = theme === Themes.SYSTEM ? (isSystemDarkMode ? Themes.DARK : Themes.LIGHT) : theme; + NavigationBar.changeNavigationBarColor(getColorForThemeAndNavigationStyle(themeToUse), themeToUse === Themes.DARK, true); } return saveGlobal(THEME, theme); diff --git a/src/theme/ThemeContext.tsx b/src/theme/ThemeContext.tsx index 29d77d0be33..95b18f7ac38 100644 --- a/src/theme/ThemeContext.tsx +++ b/src/theme/ThemeContext.tsx @@ -66,7 +66,7 @@ export const MainThemeProvider = (props: PropsWithChildren) => { // Listening to changes of device appearance while in run-time useEffect(() => { if (colorScheme) { - saveTheme(colorScheme); + saveTheme(colorScheme, isSystemDarkMode); } }, [colorScheme]); From f15bed158a8e43b2d90943d099bb1a00f5eb8f41 Mon Sep 17 00:00:00 2001 From: gregs Date: Thu, 15 Aug 2024 14:46:52 -0300 Subject: [PATCH 31/78] fix TokenToBuyList weirdness when favoriting (#6002) * wip * fix * ops * bring back slice --- .../screens/Swap/components/CoinRow.tsx | 6 ++-- .../components/TokenList/TokenToBuyList.tsx | 30 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 9d619ef0b96..2c2bcc372ca 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -14,7 +14,7 @@ import { toggleFavorite } from '@/resources/favorites'; import { userAssetsStore } from '@/state/assets/userAssets'; import { ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; import { startCase } from 'lodash'; -import React, { memo, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { GestureResponderEvent } from 'react-native'; import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; import { SwapCoinIcon } from './SwapCoinIcon'; @@ -66,7 +66,7 @@ interface OutputCoinRowProps extends PartialAsset { type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { +export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -180,7 +180,7 @@ export const CoinRow = memo(function CoinRow({ isFavorite, onPress, output, uniq ); -}); +} const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) => { const network = RainbowNetworks.find(network => network.id === chainId)?.value; diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index c324d3e38c7..ee486ec0fe9 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -17,7 +17,8 @@ import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { FlashList } from '@shopify/flash-list'; -import React, { memo, useCallback, useMemo } from 'react'; +import React, { ComponentType, forwardRef, memo, useCallback, useMemo } from 'react'; +import { ScrollViewProps } from 'react-native'; import Animated, { runOnUI, useAnimatedProps, useAnimatedStyle, withTiming } from 'react-native-reanimated'; import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants'; import { ChainSelection } from './ChainSelection'; @@ -72,6 +73,16 @@ export type HeaderItem = { listItemType: 'header'; id: AssetToBuySectionId; data export type CoinRowItem = SearchAsset & { listItemType: 'coinRow'; sectionId: AssetToBuySectionId }; export type TokenToBuyListItem = HeaderItem | CoinRowItem; +const ScrollViewWithRef = forwardRef(function ScrollViewWithRef(props, ref) { + const { outputProgress } = useSwapContext(); + const animatedListProps = useAnimatedProps(() => { + const isFocused = outputProgress.value === 2; + return { scrollIndicatorInsets: { bottom: 28 + (isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0) } }; + }); + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}); + export const TokenToBuyList = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, outputProgress, setAsset } = useSwapContext(); const { results: sections, isLoading } = useSearchCurrencyLists(); @@ -119,13 +130,6 @@ export const TokenToBuyList = () => { return { height: bottomPadding }; }); - const animatedListProps = useAnimatedProps(() => { - const isFocused = outputProgress.value === 2; - return { - scrollIndicatorInsets: { bottom: 28 + (isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0) }, - }; - }); - const averageItemSize = useMemo(() => { const numberOfHeaders = sections.filter(section => section.listItemType === 'header').length; const numberOfCoinRows = sections.filter(section => section.listItemType === 'coinRow').length; @@ -171,15 +175,7 @@ export const TokenToBuyList = () => { /> ); }} - renderScrollComponent={props => { - return ( - - ); - }} + renderScrollComponent={ScrollViewWithRef as ComponentType} style={{ height: EXPANDED_INPUT_HEIGHT - 77, width: DEVICE_WIDTH - 24 }} /> From a1c43da3d14a5a710fe45d43530bdd441c7c1c3a Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:21:29 -0400 Subject: [PATCH 32/78] ignore graphql changes (#6006) --- scripts/postinstall.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh index d4d410ec2f6..63509c906a7 100755 --- a/scripts/postinstall.sh +++ b/scripts/postinstall.sh @@ -100,6 +100,7 @@ git update-index --assume-unchanged "android/app/src/main/res/raw/extras.json" git update-index --assume-unchanged "android/app/src/main/java/me/rainbow/NativeModules/Internals/InternalModule.java" git update-index --assume-unchanged "android/app/src/main/java/me/rainbow/MainActivity.kt" git update-index --assume-unchanged "src/components/DappBrowser/DappBrowserWebview.tsx" +git update-index --assume-unchanged "src/graphql/config.js" # Specifying ONLY the node packages that we need to install via browserify From 091ceac3e61f5f40472050750a3463eb6d32df5e Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 15 Aug 2024 16:37:38 -0400 Subject: [PATCH 33/78] Implement NFTs V2 Arc endpoint (#5973) * start work on implementing nftsV2 * add collectibles loading state and empty state * fixes * temp chagne arc endpoint to dev workers endpoint * rm unnecessary enums * rm log * cleanup * change to isLoading * artistic flair * fix lint --------- Co-authored-by: Ben Goldberg --- .../RecyclerAssetList2/NFTEmptyState.tsx | 104 ++++++++++++++++++ .../RecyclerAssetList2/NFTLoadingSkeleton.tsx | 87 +++++++++++++++ .../WrappedCollectiblesHeader.tsx | 25 ++--- .../RecyclerAssetList2/core/RowRenderer.tsx | 8 +- .../core/ViewDimensions.tsx | 8 +- .../RecyclerAssetList2/core/ViewTypes.ts | 2 + .../core/getLayoutProvider.tsx | 1 - .../core/useMemoBriefSectionData.ts | 5 - src/components/list/ListHeaderMenu.tsx | 4 +- src/components/skeleton/Skeleton.tsx | 7 ++ src/graphql/queries/arc.graphql | 4 +- src/helpers/assets.ts | 83 ++++++-------- src/helpers/buildWalletSections.tsx | 7 +- src/hooks/useNFTsSortBy.ts | 17 +-- src/hooks/useRefreshAccountData.ts | 4 +- src/hooks/useWalletSectionsData.ts | 10 +- src/hooks/useWatchPendingTxs.ts | 3 +- src/languages/en_US.json | 8 +- src/resources/nfts/index.ts | 17 ++- 19 files changed, 305 insertions(+), 99 deletions(-) create mode 100644 src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx create mode 100644 src/components/asset-list/RecyclerAssetList2/NFTLoadingSkeleton.tsx diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx new file mode 100644 index 00000000000..a23be055a48 --- /dev/null +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -0,0 +1,104 @@ +import React, { useCallback } from 'react'; +import Animated from 'react-native-reanimated'; +import { Box, Stack, Text, useColorMode } from '@/design-system'; +import * as i18n from '@/languages'; +import { TokenFamilyHeaderHeight } from './NFTLoadingSkeleton'; +import { MINTS, useExperimentalFlag } from '@/config'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { IS_TEST } from '@/env'; +import { useMints } from '@/resources/mints'; +import { useAccountSettings } from '@/hooks'; +import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; +import { StyleSheet } from 'react-native'; +import { LIGHT_SEPARATOR_COLOR, SEPARATOR_COLOR } from '@/__swaps__/screens/Swap/constants'; +import { analyticsV2 } from '@/analytics'; +import { convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; +import { ethereumUtils } from '@/utils'; +import { navigateToMintCollection } from '@/resources/reservoir/mints'; + +type LaunchFeaturedMintButtonProps = { + featuredMint: ReturnType['data']['featuredMint']; +}; + +const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProps) => { + const { isDarkMode } = useColorMode(); + + const handlePress = useCallback(() => { + if (featuredMint) { + analyticsV2.track(analyticsV2.event.mintsPressedFeaturedMintCard, { + contractAddress: featuredMint.contractAddress, + chainId: featuredMint.chainId, + totalMints: featuredMint.totalMints, + mintsLastHour: featuredMint.totalMints, + priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), + }); + const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); + } + }, [featuredMint]); + + return ( + + + + + + {i18n.t(i18n.l.nfts.collect_now)} + + + + + + ); +}; + +export function NFTEmptyState() { + const { mints_enabled } = useRemoteConfig(); + const { accountAddress } = useAccountSettings(); + + const { + data: { featuredMint }, + } = useMints({ walletAddress: accountAddress }); + + const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; + + return ( + + + + + 🌟 + + + + {i18n.t(i18n.l.nfts.empty)} + + + + {i18n.t(i18n.l.nfts.will_appear_here)} + + + {mintsEnabled && featuredMint && } + + + + ); +} + +const styles = StyleSheet.create({ + buttonPadding: { + paddingHorizontal: 24, + paddingVertical: 12, + }, +}); diff --git a/src/components/asset-list/RecyclerAssetList2/NFTLoadingSkeleton.tsx b/src/components/asset-list/RecyclerAssetList2/NFTLoadingSkeleton.tsx new file mode 100644 index 00000000000..3e9788a0c0b --- /dev/null +++ b/src/components/asset-list/RecyclerAssetList2/NFTLoadingSkeleton.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { useForegroundColor } from '@/design-system'; +import { useTheme } from '@/theme'; +import { opacity } from '@/__swaps__/utils/swaps'; +import { deviceUtils } from '@/utils'; + +export const TokenFamilyHeaderHeight = 50; + +const getRandomBetween = (min: number, max: number) => { + return Math.random() * (max - min) + min; +}; + +const NFTItem = () => { + const { colors } = useTheme(); + const labelTertiary = useForegroundColor('labelTertiary'); + + return ( + + + + + + + ); +}; + +const NFTLoadingSkeleton = ({ items = 5 }) => { + return ( + + {[...Array(items)].map((_, index) => ( + + ))} + + ); +}; + +const sx = StyleSheet.create({ + container: { + flex: 1, + width: '100%', + gap: 5, + }, + image: { + height: 30, + width: 30, + borderRadius: 15, + marginRight: 12, + }, + textContainer: { + justifyContent: 'center', + }, + title: { + width: deviceUtils.dimensions.width / 2, + height: 14, + borderRadius: 7, + paddingRight: 9, + }, + content: { + flexDirection: 'row', + alignItems: 'center', + height: TokenFamilyHeaderHeight, + padding: 19, + width: '100%', + }, +}); + +export default NFTLoadingSkeleton; diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 3ea62e41da6..bb233041e82 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -2,32 +2,31 @@ import React from 'react'; import { Box, Inline, Text } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; -import useNftSort, { CollectibleSortByOptions } from '@/hooks/useNFTsSortBy'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; +import useNftSort from '@/hooks/useNFTsSortBy'; const TokenFamilyHeaderHeight = 48; -const getIconForSortType = (selected: string) => { +const getIconForSortType = (selected: NftCollectionSortCriterion) => { switch (selected) { - case CollectibleSortByOptions.ABC: + case NftCollectionSortCriterion.Abc: return '􀋲'; - case CollectibleSortByOptions.FLOOR_PRICE: + case NftCollectionSortCriterion.FloorPrice: return '􀅺'; - case CollectibleSortByOptions.MOST_RECENT: + case NftCollectionSortCriterion.MostRecent: return '􀐫'; } - return ''; }; -const getMenuItemIcon = (value: CollectibleSortByOptions) => { +const getMenuItemIcon = (value: NftCollectionSortCriterion) => { switch (value) { - case CollectibleSortByOptions.ABC: + case NftCollectionSortCriterion.Abc: return 'list.bullet'; - case CollectibleSortByOptions.FLOOR_PRICE: + case NftCollectionSortCriterion.FloorPrice: return 'plus.forwardslash.minus'; - case CollectibleSortByOptions.MOST_RECENT: + case NftCollectionSortCriterion.MostRecent: return 'clock'; } - return ''; }; const CollectiblesHeader = () => { @@ -48,13 +47,13 @@ const CollectiblesHeader = () => { ({ + menuItems={Object.entries(NftCollectionSortCriterion).map(([key, value]) => ({ actionKey: value, actionTitle: i18n.t(i18n.l.nfts.sort[value]), icon: { iconType: 'SYSTEM', iconValue: getMenuItemIcon(value) }, menuState: nftSort === key ? 'on' : 'off', }))} - selectItem={string => updateNFTSort(string as CollectibleSortByOptions)} + selectItem={string => updateNFTSort(string as NftCollectionSortCriterion)} icon={getIconForSortType(nftSort)} text={i18n.t(i18n.l.nfts.sort[nftSort])} /> diff --git a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx index afbe410d580..75f9844cdde 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx @@ -29,9 +29,10 @@ import { DiscoverMoreButton } from './DiscoverMoreButton'; import { RotatingLearnCard } from '@/components/cards/RotatingLearnCard'; import WrappedPosition from '../WrappedPosition'; import WrappedPositionsListHeader from '../WrappedPositionsListHeader'; -import * as lang from '@/languages'; import { RemoteCardCarousel } from '@/components/cards/remote-cards'; import WrappedCollectiblesHeader from '../WrappedCollectiblesHeader'; +import NFTLoadingSkeleton from '../NFTLoadingSkeleton'; +import { NFTEmptyState } from '../NFTEmptyState'; function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, extendedState: ExtendedState) { const data = extendedState.additionalData[uid]; @@ -123,8 +124,13 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten ); case CellType.NFTS_HEADER: return ; + case CellType.NFTS_LOADING: + return ; + case CellType.NFTS_EMPTY: + return ; case CellType.FAMILY_HEADER: { const { name, image, total } = data as NFTFamilyExtraData; + return ( = { [CellType.FAMILY_HEADER]: { height: TokenFamilyHeaderHeight }, [CellType.NFT]: { // @ts-expect-error - height: UniqueTokenRow.cardSize + UniqueTokenRow.cardMargin, + height: UniqueTokenRow.height, width: deviceUtils.dimensions.width / 2 - 0.1, }, + [CellType.NFTS_LOADING]: { + height: TokenFamilyHeaderHeight * 5, + }, + [CellType.NFTS_EMPTY]: { + height: TokenFamilyHeaderHeight * 5, + }, [CellType.NFT_SPACE_AFTER]: { height: 5 }, [CellType.LOADING_ASSETS]: { height: AssetListItemSkeletonHeight }, [CellType.POSITIONS_HEADER]: { height: AssetListHeaderHeight }, diff --git a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts index ab6df6115e6..a38a6d1ab05 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts +++ b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts @@ -17,6 +17,8 @@ export enum CellType { PROFILE_NAME_ROW_SPACE_AFTER = 'PROFILE_NAME_ROW_SPACE_AFTER', PROFILE_STICKY_HEADER = 'PROFILE_STICKY_HEADER', NFTS_HEADER = 'NFTS_HEADER', + NFTS_LOADING = 'NFTS_LOADING', + NFTS_EMPTY = 'NFTS_EMPTY', NFTS_HEADER_SPACE_BEFORE = 'NFTS_HEADER_SPACE_BEFORE', NFTS_HEADER_SPACE_AFTER = 'NFTS_HEADER_SPACE_AFTER', FAMILY_HEADER = 'FAMILY_HEADER', diff --git a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx index 82c89dd7dfe..13619bef984 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx @@ -2,7 +2,6 @@ import { Dimension, Layout, LayoutManager, LayoutProvider } from 'recyclerlistvi import ViewDimensions from './ViewDimensions'; import { BaseCellType, CellType } from './ViewTypes'; import { deviceUtils } from '@/utils'; -import { TrimmedCard } from '@/resources/cards/cardCollectionQuery'; const getStyleOverridesForIndex = (indices: number[]) => (index: number) => { if (indices.includes(index)) { diff --git a/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts b/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts index e3285e63fe6..875d7f91d90 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts +++ b/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts @@ -73,11 +73,6 @@ export default function useMemoBriefSectionData({ return false; } - // removes NFTS_HEADER if wallet doesn't have NFTs - if (data.type === CellType.NFTS_HEADER && !arr[arrIndex + 2]) { - return false; - } - if (data.type === CellType.PROFILE_STICKY_HEADER) { stickyHeaders.push(index); } diff --git a/src/components/list/ListHeaderMenu.tsx b/src/components/list/ListHeaderMenu.tsx index 289498f1432..f39df68a365 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -3,7 +3,7 @@ import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { ButtonPressAnimation } from '@/components/animations'; import { Bleed, Box, Inline, Text, useForegroundColor } from '@/design-system'; import { haptics } from '@/utils'; -import { CollectibleSortByOptions } from '@/hooks/useNFTsSortBy'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; type MenuItem = { actionKey: string; @@ -12,7 +12,7 @@ type MenuItem = { }; type ListHeaderMenuProps = { - selected: CollectibleSortByOptions; + selected: NftCollectionSortCriterion; menuItems: MenuItem[]; selectItem: (item: string) => void; icon: string; diff --git a/src/components/skeleton/Skeleton.tsx b/src/components/skeleton/Skeleton.tsx index 69d39e7a1ed..628090fc8a6 100644 --- a/src/components/skeleton/Skeleton.tsx +++ b/src/components/skeleton/Skeleton.tsx @@ -24,6 +24,13 @@ export const FakeAvatar = styled.View({ borderRadius: 20, }); +// @ts-expect-error Property 'View' does not exist on type... +export const FakeNFT = styled.View({ + ...position.sizeAsObject(32), + backgroundColor: ({ theme: { colors }, color }: FakeItemProps) => color ?? colors.skeleton, + borderRadius: 16, +}); + export const FakeRow = styled(Row).attrs({ align: 'flex-end', flex: 0, diff --git a/src/graphql/queries/arc.graphql b/src/graphql/queries/arc.graphql index 24c89874153..4b8d55ebb58 100644 --- a/src/graphql/queries/arc.graphql +++ b/src/graphql/queries/arc.graphql @@ -298,8 +298,8 @@ fragment simpleHashPaymentToken on SimpleHashPaymentToken { decimals } -query getNFTs($walletAddress: String!) { - nfts(walletAddress: $walletAddress) { +query getNFTs($walletAddress: String!, $sortBy: NFTCollectionSortCriterion, $sortDirection: SortDirection) { + nftsV2(walletAddress: $walletAddress, sortBy: $sortBy, sortDirection: $sortDirection) { nft_id chain contract_address diff --git a/src/helpers/assets.ts b/src/helpers/assets.ts index 847e9d2f912..b6cf424d7e8 100644 --- a/src/helpers/assets.ts +++ b/src/helpers/assets.ts @@ -5,9 +5,8 @@ import { AssetListType } from '@/components/asset-list/RecyclerAssetList2'; import { supportedNativeCurrencies } from '@/references'; import { getUniqueTokenFormat, getUniqueTokenType } from '@/utils'; import * as i18n from '@/languages'; -import * as ls from '@/storage'; import { UniqueAsset } from '@/entities'; -import { CollectibleSortByOptions } from '@/hooks/useNFTsSortBy'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const COINS_TO_SHOW = 5; @@ -235,39 +234,15 @@ export const buildUniqueTokenList = (uniqueTokens: any, selectedShowcaseTokens: return rows; }; -const regex = RegExp(/\s*(the)\s/, 'i'); - -const sortCollectibles = (assetsByName: Dictionary, collectibleSortBy: string) => { - const families = Object.keys(assetsByName); - - switch (collectibleSortBy) { - case CollectibleSortByOptions.MOST_RECENT: - return families.sort((a, b) => { - const maxDateA = Math.max(Number(...assetsByName[a].map(asset => asset.acquisition_date))); - const maxDateB = Math.max(Number(...assetsByName[b].map(asset => asset.acquisition_date))); - return maxDateB - maxDateA; - }); - case CollectibleSortByOptions.ABC: - return families.sort((a, b) => a.replace(regex, '').toLowerCase().localeCompare(b.replace(regex, '').toLowerCase())); - case CollectibleSortByOptions.FLOOR_PRICE: - return families.sort((a, b) => { - const minPriceA = Math.min(...assetsByName[a].map(asset => (asset.floorPriceEth !== undefined ? asset.floorPriceEth : -1))); - const minPriceB = Math.min(...assetsByName[b].map(asset => (asset.floorPriceEth !== undefined ? asset.floorPriceEth : -1))); - return minPriceB - minPriceA; - }); - default: - return families; - } -}; - export const buildBriefUniqueTokenList = ( - uniqueTokens: any, + uniqueTokens: UniqueAsset[], selectedShowcaseTokens: any, sellingTokens: any[] = [], hiddenTokens: string[] = [], listType: AssetListType = 'wallet', isReadOnlyWallet = false, - nftSort: string = CollectibleSortByOptions.MOST_RECENT + nftSort = NftCollectionSortCriterion.MostRecent, + isFetchingNfts = false ) => { const hiddenUniqueTokensIds = uniqueTokens .filter(({ fullUniqueId }: any) => hiddenTokens.includes(fullUniqueId)) @@ -277,7 +252,7 @@ export const buildBriefUniqueTokenList = ( .filter(({ uniqueId }: any) => selectedShowcaseTokens?.includes(uniqueId)) .map(({ uniqueId }: any) => uniqueId); - const filteredUniqueTokens = nonHiddenUniqueTokens.filter((token: any) => { + const filteredUniqueTokens = nonHiddenUniqueTokens.filter((token: UniqueAsset) => { if (listType === 'select-nft') { const format = getUniqueTokenFormat(token); const type = getUniqueTokenType(token); @@ -287,10 +262,7 @@ export const buildBriefUniqueTokenList = ( }); // group the assets by collection name - const assetsByName = groupBy(filteredUniqueTokens, token => token.familyName); - - // depending on the sort by option, sort the collections - const families2 = sortCollectibles(assetsByName, nftSort); + const assetsByName = groupBy(filteredUniqueTokens, token => token.familyName); const result = [ { @@ -342,25 +314,37 @@ export const buildBriefUniqueTokenList = ( } result.push({ type: 'NFT_SPACE_AFTER', uid: `showcase-space-after` }); } - for (const family of families2) { - result.push({ - // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. - image: assetsByName[family][0].familyImage, - name: family, - total: assetsByName[family].length, - type: 'FAMILY_HEADER', - uid: family, - }); - const tokens = assetsByName[family].map(({ uniqueId }) => uniqueId); - for (let index = 0; index < tokens.length; index++) { - const uniqueId = tokens[index]; - // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. - result.push({ index, type: 'NFT', uid: uniqueId, uniqueId }); + if (!Object.keys(assetsByName).length) { + if (!isFetchingNfts) { + // empty NFT section + result.push({ type: 'NFTS_EMPTY', uid: `nft-empty` }); + } else { + // loading NFTs section (most likely from a sortBy change) but initial load too + result.push({ type: 'NFTS_LOADING', uid: `nft-loading-${nftSort}` }); } + } else { + for (const family of Object.keys(assetsByName)) { + result.push({ + // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. + image: assetsByName[family][0].familyImage, + name: family, + total: assetsByName[family].length, + type: 'FAMILY_HEADER', + uid: family, + }); + const tokens = assetsByName[family].map(({ uniqueId }) => uniqueId); + for (let index = 0; index < tokens.length; index++) { + const uniqueId = tokens[index]; - result.push({ type: 'NFT_SPACE_AFTER', uid: `${family}-space-after` }); + // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. + result.push({ index, type: 'NFT', uid: uniqueId, uniqueId }); + } + + result.push({ type: 'NFT_SPACE_AFTER', uid: `${family}-space-after` }); + } } + if (hiddenUniqueTokensIds.length > 0 && listType === 'wallet' && !isReadOnlyWallet) { result.push({ // @ts-expect-error "name" does not exist in type. @@ -382,6 +366,7 @@ export const buildBriefUniqueTokenList = ( result.push({ type: 'NFT_SPACE_AFTER', uid: `showcase-space-after` }); } + return result; }; diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx index 255add920b8..8cc4dc1e504 100644 --- a/src/helpers/buildWalletSections.tsx +++ b/src/helpers/buildWalletSections.tsx @@ -50,6 +50,8 @@ const sellingTokensSelector = (state: any) => state.sellingTokens; const showcaseTokensSelector = (state: any) => state.showcaseTokens; const hiddenTokensSelector = (state: any) => state.hiddenTokens; const uniqueTokensSelector = (state: any) => state.uniqueTokens; +const nftSortSelector = (state: any) => state.nftSort; +const isFetchingNftsSelector = (state: any) => state.isFetchingNfts; const listTypeSelector = (state: any) => state.listType; const buildBriefWalletSections = (balanceSectionData: any, uniqueTokenFamiliesSection: any) => { @@ -209,7 +211,8 @@ const briefUniqueTokenDataSelector = createSelector( hiddenTokensSelector, listTypeSelector, isReadOnlyWalletSelector, - (state: any, nftSort: string) => nftSort, + nftSortSelector, + isFetchingNftsSelector, ], buildBriefUniqueTokenList ); @@ -230,6 +233,6 @@ const briefBalanceSectionSelector = createSelector( ); export const buildBriefWalletSectionsSelector = createSelector( - [briefBalanceSectionSelector, (state: any, nftSort: string) => briefUniqueTokenDataSelector(state, nftSort)], + [briefBalanceSectionSelector, (state: any) => briefUniqueTokenDataSelector(state)], buildBriefWalletSections ); diff --git a/src/hooks/useNFTsSortBy.ts b/src/hooks/useNFTsSortBy.ts index 544782ee1db..345f20132a7 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -1,29 +1,24 @@ import { useCallback } from 'react'; import { MMKV, useMMKVString } from 'react-native-mmkv'; import useAccountSettings from './useAccountSettings'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const mmkv = new MMKV(); const getStorageKey = (accountAddress: string) => `nfts-sort-${accountAddress}`; -export enum CollectibleSortByOptions { - MOST_RECENT = 'most_recent', - ABC = 'abc', - FLOOR_PRICE = 'floor_price', -} - export const getNftSortForAddress = (accountAddress: string) => { - mmkv.getString(getStorageKey(accountAddress)); + return mmkv.getString(getStorageKey(accountAddress)) as NftCollectionSortCriterion; }; export default function useNftSort(): { - nftSort: CollectibleSortByOptions; - updateNFTSort: (sortBy: CollectibleSortByOptions) => void; + nftSort: NftCollectionSortCriterion; + updateNFTSort: (sortBy: NftCollectionSortCriterion) => void; } { const { accountAddress } = useAccountSettings(); const [nftSort, setNftSort] = useMMKVString(getStorageKey(accountAddress)); const updateNFTSort = useCallback( - (sortBy: CollectibleSortByOptions) => { + (sortBy: NftCollectionSortCriterion) => { setNftSort(sortBy); }, [setNftSort] @@ -31,6 +26,6 @@ export default function useNftSort(): { return { updateNFTSort, - nftSort: (nftSort as CollectibleSortByOptions) || CollectibleSortByOptions.MOST_RECENT, + nftSort: (nftSort as NftCollectionSortCriterion) || NftCollectionSortCriterion.MostRecent, }; } diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index df30a1a18e6..ad94660351a 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -13,6 +13,7 @@ import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { nftsQueryKey } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import useNftSort from './useNFTsSortBy'; import { Address } from 'viem'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; import useWallets from './useWallets'; @@ -22,6 +23,7 @@ export default function useRefreshAccountData() { const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); + const { nftSort } = useNftSort(); const { wallets } = useWallets(); @@ -33,7 +35,7 @@ export default function useRefreshAccountData() { const fetchAccountData = useCallback(async () => { const connectedToHardhat = getIsHardhatConnected(); - queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress })); + queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort })); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat })); diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 7fe57995040..72e89e3de55 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -22,12 +22,16 @@ export default function useWalletSectionsData({ const { isLoading: isLoadingUserAssets, data: sortedAssets = [] } = useSortedUserAssets(); const isWalletEthZero = useIsWalletEthZero(); + const { nftSort } = useNftSort(); + const { accountAddress, language, network, nativeCurrency } = useAccountSettings(); const { sendableUniqueTokens } = useSendableUniqueTokens(); const { data: { nfts: allUniqueTokens }, + isLoading: isFetchingNfts, } = useLegacyNFTs({ address: accountAddress, + sortBy: nftSort, }); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); @@ -43,8 +47,6 @@ export default function useWalletSectionsData({ const { isCoinListEdited } = useCoinListEdited(); - const { nftSort } = useNftSort(); - const walletSections = useMemo(() => { const accountInfo = { hiddenCoins, @@ -65,10 +67,11 @@ export default function useWalletSectionsData({ listType: type, showcaseTokens, uniqueTokens: allUniqueTokens, + isFetchingNfts, nftSort, }; - const { briefSectionsData, isEmpty } = buildBriefWalletSectionsSelector(accountInfo, nftSort); + const { briefSectionsData, isEmpty } = buildBriefWalletSectionsSelector(accountInfo); const hasNFTs = allUniqueTokens.length > 0; return { @@ -96,6 +99,7 @@ export default function useWalletSectionsData({ type, showcaseTokens, allUniqueTokens, + isFetchingNfts, nftSort, ]); return walletSections; diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index aed5c6fad25..51a710ff73d 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -15,6 +15,7 @@ import { usePendingTransactionsStore } from '@/state/pendingTransactions'; import { useNonceStore } from '@/state/nonces'; import { Address } from 'viem'; import { nftsQueryKey } from '@/resources/nfts'; +import { getNftSortForAddress } from './useNFTsSortBy'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ @@ -46,7 +47,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => testnetMode: !!connectedToHardhat, }) ); - queryClient.invalidateQueries(nftsQueryKey({ address })); + queryClient.invalidateQueries(nftsQueryKey({ address, sortBy: getNftSortForAddress(address) })); }, [address, nativeCurrency] ); diff --git a/src/languages/en_US.json b/src/languages/en_US.json index c243830f84d..5130d449fc6 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1315,10 +1315,16 @@ "nfts": { "selling": "Selling", "sort": { + "ABC": "Alphabetical", "abc": "Alphabetical", + "MOST_RECENT": "Recent", "most_recent": "Recent", + "FLOOR_PRICE": "Floor Price", "floor_price": "Floor Price" - } + }, + "empty": "Collectibles", + "collect_now": "Collect Now", + "will_appear_here": "Your Collectibles Will Appear Here" }, "nft_offers": { "card": { diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index d7b1c88c198..a39f3c6848d 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -9,12 +9,14 @@ import { Network } from '@/helpers'; import { UniqueAsset } from '@/entities'; import { arcClient } from '@/graphql'; import { createSelector } from 'reselect'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour const NFTS_CACHE_TIME_INTERNAL = 604800000; // 1 week -export const nftsQueryKey = ({ address }: { address: string }) => createQueryKey('nfts', { address }, { persisterVersion: 3 }); +export const nftsQueryKey = ({ address, sortBy }: { address: string; sortBy: NftCollectionSortCriterion }) => + createQueryKey('nfts', { address, sortBy }, { persisterVersion: 4 }); export const nftListingQueryKey = ({ contractAddress, @@ -52,10 +54,10 @@ interface NFTData { type NFTQueryKey = ReturnType; const fetchNFTData: QueryFunction = async ({ queryKey }) => { - const [{ address }] = queryKey; - const queryResponse = await arcClient.getNFTs({ walletAddress: address }); + const [{ address, sortBy }] = queryKey; + const queryResponse = await arcClient.getNFTs({ walletAddress: address, sortBy }); - const nfts = queryResponse?.nfts?.map(nft => simpleHashNFTToUniqueAsset(nft, address)); + const nfts = queryResponse?.nftsV2?.map(nft => simpleHashNFTToUniqueAsset(nft, address)); // ⚠️ TODO: Delete this and rework the code that uses it const nftsMap = nfts?.reduce( @@ -75,14 +77,16 @@ const FALLBACK_DATA: NFTData = { nfts: [], nftsMap: {} }; export function useLegacyNFTs({ address, + sortBy = NftCollectionSortCriterion.MostRecent, config, }: { address: string; + sortBy?: NftCollectionSortCriterion; config?: QueryConfigWithSelect; }) { const isImportedWallet = useSelector((state: AppState) => isImportedWalletSelector(state, address)); - const { data, error, isFetching } = useQuery(nftsQueryKey({ address }), fetchNFTData, { + const { data, error, isLoading, isInitialLoading } = useQuery(nftsQueryKey({ address, sortBy }), fetchNFTData, { cacheTime: isImportedWallet ? NFTS_CACHE_TIME_INTERNAL : NFTS_CACHE_TIME_EXTERNAL, enabled: !!address, retry: 3, @@ -93,7 +97,8 @@ export function useLegacyNFTs({ return { data: (config?.select ? data ?? config.select(FALLBACK_DATA) : data ?? FALLBACK_DATA) as TSelected, error, - isInitialLoading: !data && isFetching, + isLoading, + isInitialLoading, }; } From a57b0a58967e636e2b1bcb049a928307f0370ec5 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 16 Aug 2024 12:50:55 -0400 Subject: [PATCH 34/78] remove useeffect deps to match componentDidMount (#6008) --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 7eeb21c04b4..93ec4a4f782 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -158,7 +158,7 @@ function App({ walletReady }: AppProps) { eventSubscription.current?.remove(); branchListenerRef.current?.(); }; - }, [handleAppStateChange, identifyFlow, setupDeeplinking]); + }, []); useEffect(() => { if (walletReady) { From 4ebaa70e8001d9050ae853883726fa00a0737169 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 19 Aug 2024 13:06:54 -0400 Subject: [PATCH 35/78] Remote Card Fixes (#6007) * fix button wrapping * change some remote config values * fix wrap condition * more fixes --- .../cards/remote-cards/RemoteCard.tsx | 252 ++++++++---------- src/model/remoteConfig.ts | 4 +- 2 files changed, 115 insertions(+), 141 deletions(-) diff --git a/src/components/cards/remote-cards/RemoteCard.tsx b/src/components/cards/remote-cards/RemoteCard.tsx index 26304b7670a..50d35d9084b 100644 --- a/src/components/cards/remote-cards/RemoteCard.tsx +++ b/src/components/cards/remote-cards/RemoteCard.tsx @@ -1,12 +1,10 @@ import { Linking, StyleSheet } from 'react-native'; import React, { useCallback } from 'react'; import { get } from 'lodash'; -import ConditionalWrap from 'conditional-wrap'; -import { Border, Box, Cover, IconContainer, Text, TextShadow, useColorMode, useForegroundColor } from '@/design-system'; -import { ButtonPressAnimation } from '@/components/animations'; +import { Border, Box, Column, Columns, Cover, IconContainer, Text, TextShadow, useColorMode, useForegroundColor } from '@/design-system'; import { ImgixImage } from '@/components/images'; -import { IS_ANDROID, IS_IOS } from '@/env'; +import { IS_IOS } from '@/env'; import { useNavigation } from '@/navigation'; import { Language } from '@/languages'; import { useAccountSettings, useDimensions } from '@/hooks'; @@ -16,8 +14,8 @@ import { colors } from '@/styles'; import LinearGradient from 'react-native-linear-gradient'; import { analyticsV2 } from '@/analytics'; import { FlashList } from '@shopify/flash-list'; -import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types'; import { remoteCardsStore } from '@/state/remoteCards/remoteCards'; +import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; const ICON_SIZE = 36; const CARD_BORDER_RADIUS = 20; @@ -84,32 +82,26 @@ export const RemoteCard: React.FC = ({ id, gutterSize, carousel } }, [navigate, card?.primaryButton, card?.cardKey]); - const onDismiss = useCallback( - (e: ButtonPressAnimationTouchEvent) => { - if (e && 'stopPropagation' in e) { - e.stopPropagation(); - } - analyticsV2.track(analyticsV2.event.remoteCardDismissed, { - cardKey: card?.cardKey ?? card?.sys.id ?? 'unknown-backend-driven-card', - }); + const onDismiss = useCallback(() => { + analyticsV2.track(analyticsV2.event.remoteCardDismissed, { + cardKey: card?.cardKey ?? card?.sys.id ?? 'unknown-backend-driven-card', + }); - const { cards } = remoteCardsStore.getState(); + const { cards } = remoteCardsStore.getState(); - const isLastCard = cards.size === 1; + const isLastCard = cards.size === 1; - card?.sys.id && remoteCardsStore.getState().dismissCard(card.sys.id); - if (carouselRef?.current) { - // check if this is the last card and don't scroll if so - if (isLastCard) return; + card?.sys.id && remoteCardsStore.getState().dismissCard(card.sys.id); + if (carouselRef?.current) { + // check if this is the last card and don't scroll if so + if (isLastCard) return; - carouselRef.current.scrollToIndex({ - index: Array.from(cards.values()).findIndex(c => c.sys.id === card?.sys.id), - animated: true, - }); - } - }, - [card?.cardKey, card?.sys.id, carouselRef] - ); + carouselRef.current.scrollToIndex({ + index: Array.from(cards.values()).findIndex(c => c.sys.id === card?.sys.id), + animated: true, + }); + } + }, [card?.cardKey, card?.sys.id, carouselRef]); const imageForPlatform = () => { if (!card?.imageCollection?.items?.length) { @@ -146,128 +138,110 @@ export const RemoteCard: React.FC = ({ id, gutterSize, carousel // device width - gutter - icon size const contentWidth = width - gutterSize - 16 * 2 - ICON_SIZE - 12; return ( - ( - - {children} - - )} + - - - - - - {card?.imageIcon && ( - - - {card?.imageIcon} + + + + + + + + {card?.imageIcon && ( + + + {card?.imageIcon} + + + )} + + {!card?.imageIcon && imageUri && ( + + )} + + + + + + + {getKeyForLanguage('subtitle', card, language as Language)} + + + + {getKeyForLanguage('title', card, language as Language)} + + + + + {getKeyForLanguage('primaryButton.text', card, language as Language)} - )} - - {!card?.imageIcon && imageUri && ( - - )} - + + - - - {card.dismissable && ( - - + + + + {card.dismissable && ( + + + 􀆄 - - - )} - - - - {getKeyForLanguage('subtitle', card, language as Language)} - - - - {getKeyForLanguage('title', card, language as Language)} - - - - - - {getKeyForLanguage('primaryButton.text', card, language as Language)} - - - + - - - - - + + )} + + ); }; diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index d4d956cb958..2f16922be6b 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -164,11 +164,11 @@ export const DEFAULT_CONFIG: RainbowConfig = { points_enabled: true, points_fully_enabled: true, rpc_proxy_enabled: true, - remote_cards_enabled: false, + remote_cards_enabled: true, remote_promo_enabled: false, points_notifications_toggle: true, dapp_browser: true, - swaps_v2: false, + swaps_v2: true, idfa_check_enabled: true, rewards_enabled: true, From d9bbee8928d21ca37be86c83e42be8249004853e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Mon, 19 Aug 2024 13:57:05 -0400 Subject: [PATCH 36/78] use chainId instead of network: part 1 (#5981) * remove chains * getnetworkobject chainid * Swap changes * use chainid in { it('Should search and open expanded state for SOCKS', async () => { await typeText('discover-search-input', 'SOCKS\n', true); await delayTime('very-long'); - await checkIfVisible('discover-currency-select-list-exchange-coin-row-SOCKS-mainnet'); - await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-mainnet'); - await waitAndTap('discover-currency-select-list-exchange-coin-row-SOCKS-mainnet'); + await checkIfVisible('discover-currency-select-list-exchange-coin-row-SOCKS-1'); + await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-1'); + await waitAndTap('discover-currency-select-list-exchange-coin-row-SOCKS-1'); await checkIfVisible('chart-header-Unisocks'); }); @@ -75,7 +75,7 @@ describe('Discover Screen Flow', () => { // RNBW-4035 await swipe('expanded-state-header', 'down'); } - await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-mainnet'); + await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-1'); }); it('Should display search results in the correct order', async () => { @@ -92,7 +92,7 @@ describe('Discover Screen Flow', () => { await waitAndTap('discover-search-clear-input'); await typeText('discover-search-input', 'rainbowwallet.eth\n', true); await checkIfVisible('discover-currency-select-list-contact-row-rainbowwallet.eth'); - await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-mainnet'); + await checkIfNotVisible('discover-currency-select-list-exchange-coin-row-ETH-1'); await waitAndTap('discover-currency-select-list-contact-row-rainbowwallet.eth'); await checkIfVisible('profile-sheet'); }); @@ -104,7 +104,7 @@ describe('Discover Screen Flow', () => { it.skip('Should close profile and return to Search on swiping down', async () => { await swipe('profile-sheet', 'down'); await waitAndTap('discover-search-clear-input'); - await checkIfVisible('discover-currency-select-list-exchange-coin-row-ETH-mainnet'); + await checkIfVisible('discover-currency-select-list-exchange-coin-row-ETH-1'); }); it('Should close search and return to Discover Home on pressing Done', async () => { diff --git a/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js b/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js index aa2343731d9..1ff90c981af 100644 --- a/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js +++ b/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js @@ -98,11 +98,11 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-output-selection-button'); - await Helpers.waitAndTap('network-switcher-item-optimism'); + await Helpers.waitAndTap('network-switcher-item-10'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-optimism'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-10'); await Helpers.typeText('exchange-modal-input', '0.001', true); if (ios) { await Helpers.tapAndLongPress('exchange-modal-confirm-button'); @@ -123,11 +123,11 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-1'); await Helpers.waitAndTap('exchange-modal-output-selection-button'); - await Helpers.waitAndTap('network-switcher-item-optimism'); + await Helpers.waitAndTap('network-switcher-item-10'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-optimism'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-USDC-10'); await Helpers.typeText('exchange-modal-input', '0.01', true); await Helpers.tapAndLongPress('exchange-modal-confirm-button'); await Helpers.tapAndLongPress('swap-details-confirm-button'); @@ -153,7 +153,7 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.waitAndTap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); @@ -161,7 +161,7 @@ describe.skip('Hardhat Transaction Flow', () => { }); it('Should be able to search random tokens (like SWYF) via address and swap them 2', async () => { - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-SWYF-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-SWYF-1'); await Helpers.tapByText('Continue'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.typeText('exchange-modal-input', '0.001', true, true); @@ -187,12 +187,12 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'WETH', true); - await Helpers.tap('currency-select-list-exchange-coin-row-WETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-WETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.typeText('exchange-modal-input', '0.001', true, true); if (ios) { @@ -216,11 +216,11 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'WETH', true); - await Helpers.tap('currency-select-list-exchange-coin-row-WETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-WETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.typeText('exchange-modal-input', '0.0005', true, true); if (ios) { @@ -244,12 +244,12 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'WETH', true); - await Helpers.tap('currency-select-list-exchange-coin-row-WETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-WETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.typeText('exchange-modal-input', '0.0005', true, true); if (ios) { await Helpers.tapAndLongPress('exchange-modal-confirm-button'); @@ -273,12 +273,12 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.tap('currency-select-list-exchange-coin-row-USDC-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-USDC-1'); await Helpers.typeText('exchange-modal-input', '10', true, true); if (ios) { await Helpers.tapAndLongPress('exchange-modal-confirm-button'); @@ -302,11 +302,11 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.typeText('exchange-modal-input', '4', true, true); if (ios) { await Helpers.tapAndLongPress('exchange-modal-confirm-button'); @@ -329,12 +329,12 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.tap('currency-select-list-exchange-coin-row-USDC-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-USDC-1'); await Helpers.typeText('exchange-modal-input', '0.005', true, true); await Helpers.delay(1000); if (ios) { @@ -357,12 +357,12 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.tap('currency-select-list-exchange-coin-row-USDC-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-USDC-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'WETH', true); - await Helpers.tap('currency-select-list-exchange-coin-row-WETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-WETH-1'); await Helpers.typeText('exchange-modal-input', '14', true, true); if (ios) { await Helpers.tapAndLongPress('exchange-modal-confirm-button'); @@ -385,11 +385,11 @@ describe.skip('Hardhat Transaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.tap('currency-select-list-exchange-coin-row-USDC-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-USDC-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.typeText('exchange-modal-input', '10', true, true); await Helpers.delay(1000); if (ios) { diff --git a/e2e/Disabled/swapSheetFlow1.disabled.js b/e2e/Disabled/swapSheetFlow1.disabled.js index afb1aec1841..a7ed282b8dc 100644 --- a/e2e/Disabled/swapSheetFlow1.disabled.js +++ b/e2e/Disabled/swapSheetFlow1.disabled.js @@ -39,10 +39,10 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-input-max'); await Helpers.tap('exchange-modal-output-selection-button'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.delay(ios ? 2000 : 5000); await Helpers.checkIfVisible('exchange-modal-confirm-button'); await Helpers.waitAndTap('exchange-modal-confirm-button'); @@ -61,11 +61,11 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'OP', true); - await Helpers.tap('currency-select-list-exchange-coin-row-OP-optimism'); + await Helpers.tap('currency-select-list-exchange-coin-row-OP-10'); await Helpers.waitAndTap('exchange-modal-input-max'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.typeText('currency-select-search-input', 'ETH', true); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-optimism'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-10'); await Helpers.delay(ios ? 2000 : 5000); await Helpers.checkIfVisible('exchange-modal-confirm-button'); await Helpers.waitAndTap('exchange-modal-confirm-button'); @@ -133,12 +133,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { // FIXME: Dependent on a state from the previous test it('Should toggle through token networks and show the respective tokens', async () => { - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.checkIfVisible('network-switcher-1'); - await Helpers.waitAndTap('network-switcher-item-optimism'); + await Helpers.waitAndTap('network-switcher-item-10'); await Helpers.checkIfVisible('network-switcher-10'); await Helpers.waitAndTap('network-switcher-item-arbitrum'); await Helpers.checkIfVisible('network-switcher-42161'); @@ -151,7 +151,7 @@ describe.skip('Swap Sheet Interaction Flow', () => { // FIXME: Dependent on a state from the previous test it('Should update input value after tapping Max Button', async () => { await Helpers.typeText('currency-select-search-input', 'BAT', true); - await Helpers.tap('currency-select-list-exchange-coin-row-BAT-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-BAT-1'); await Helpers.delay(ios ? 2000 : 5000); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.waitAndTap('exchange-modal-input-max'); @@ -173,20 +173,20 @@ describe.skip('Swap Sheet Interaction Flow', () => { it('Should reset all fields on selection of new input currency', async () => { await Helpers.waitAndTap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); }); // FIXME: Dependent on a state from the previous test it('Should change Currency Select List on search entry', async () => { await Helpers.waitAndTap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'SOCKS\n', true); - await Helpers.checkIfNotVisible('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.checkIfNotVisible('currency-select-list-exchange-coin-row-ETH-1'); }); // FIXME: Dependent on a state from the previous test it('Should reset Currency Select List on clearing search field', async () => { await Helpers.clearField('currency-select-search-input'); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-ETH-1'); if (android) { await device.pressBack(); await device.pressBack(); @@ -204,7 +204,7 @@ describe.skip('Swap Sheet Interaction Flow', () => { } await Helpers.waitAndTap('balance-coin-row-Ethereum'); await Helpers.waitAndTap('swap-action-button'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.checkIfElementHasString('exchange-modal-input-selection-button-text', 'Choose Token'); await Helpers.tap('exchange-modal-output-selection-button'); @@ -221,16 +221,16 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.checkIfElementHasString('exchange-modal-input-selection-button-text', 'DAI'); await Helpers.checkIfElementHasString('exchange-modal-output-selection-button-text', 'ETH'); @@ -254,11 +254,11 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ETH\n', true); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-optimism'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-10'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('network-switcher-10'); - await Helpers.tap('currency-select-list-exchange-coin-row-OP-optimism'); + await Helpers.tap('currency-select-list-exchange-coin-row-OP-10'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.typeText('exchange-modal-input', '24', false); await Helpers.clearField('exchange-modal-input-24'); diff --git a/e2e/Disabled/swapSheetFlow2.disabled.js b/e2e/Disabled/swapSheetFlow2.disabled.js index 0a86a585faa..7c91840a432 100644 --- a/e2e/Disabled/swapSheetFlow2.disabled.js +++ b/e2e/Disabled/swapSheetFlow2.disabled.js @@ -78,16 +78,16 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-input-max'); - await Helpers.checkIfVisible(`exchange-modal-input-DAI-mainnet`); + await Helpers.checkIfVisible(`exchange-modal-input-DAI-1`); await Helpers.checkIfVisible(`exchange-modal-output-empty-empty`); await Helpers.tap('exchange-modal-output-selection-button'); - await Helpers.tap('network-switcher-item-optimism'); + await Helpers.tap('network-switcher-item-10'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-optimism'); - await Helpers.checkIfVisible(`exchange-modal-input-DAI-mainnet`); - await Helpers.checkIfVisible(`exchange-modal-output-DAI-optimism`); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-10'); + await Helpers.checkIfVisible(`exchange-modal-input-DAI-1`); + await Helpers.checkIfVisible(`exchange-modal-output-DAI-10`); await Helpers.swipe('exchange-modal-notch', 'down', 'slow'); }); @@ -96,17 +96,17 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.typeText('currency-select-search-input', 'DAI', true); - await Helpers.tap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-input-max'); - await Helpers.checkIfVisible(`exchange-modal-input-DAI-mainnet`); + await Helpers.checkIfVisible(`exchange-modal-input-DAI-1`); await Helpers.checkIfVisible(`exchange-modal-output-empty-empty`); await Helpers.tap('exchange-modal-output-selection-button'); - await Helpers.tap('network-switcher-item-optimism'); + await Helpers.tap('network-switcher-item-10'); await Helpers.typeText('currency-select-search-input', 'USDC', true); - await Helpers.tap('currency-select-list-exchange-coin-row-USDC-optimism'); - await Helpers.checkIfVisible(`exchange-modal-input-DAI-mainnet`); - await Helpers.checkIfVisible(`exchange-modal-output-USDC-optimism`); - await Helpers.waitAndTap('exchange-modal-output-USDC-optimism'); + await Helpers.tap('currency-select-list-exchange-coin-row-USDC-10'); + await Helpers.checkIfVisible(`exchange-modal-input-DAI-1`); + await Helpers.checkIfVisible(`exchange-modal-output-USDC-10`); + await Helpers.waitAndTap('exchange-modal-output-USDC-10'); await Helpers.waitAndTap('explainer-sheet-accent-action-button'); await Helpers.tapAndLongPress('exchange-modal-confirm-button'); await Helpers.checkIfVisible('swaps-details-value-row'); @@ -122,16 +122,16 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfElementHasString('exchange-modal-input-selection-button-text', 'DAI'); await Helpers.checkIfElementHasString('exchange-modal-output-selection-button-text', 'ETH'); await Helpers.swipe('exchange-modal-notch', 'down', 'slow'); @@ -140,16 +140,16 @@ describe.skip('Swap Sheet Interaction Flow', () => { it('Should swap input & output and clear form on ETH -> ERC20 when selecting ERC20 as input', async () => { await Helpers.waitAndTap('swap-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.waitAndTap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.checkIfElementHasString('exchange-modal-input-selection-button-text', 'DAI'); await Helpers.checkIfElementHasString('exchange-modal-output-selection-button-text', 'ETH'); await Helpers.delay(4000); @@ -167,12 +167,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ZRX\n', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-1'); await Helpers.checkIfVisible('exchange-modal-input-native'); await Helpers.checkIfVisible('exchange-modal-output'); await Helpers.typeText('exchange-modal-input', '0.246\n', false); @@ -189,12 +189,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ZRX', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.checkIfVisible('exchange-modal-output'); await Helpers.typeText('exchange-modal-input-native', '0.246', false); @@ -211,12 +211,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.checkIfVisible('exchange-modal-output'); await Helpers.typeText('exchange-modal-input-native', '246', false); @@ -286,12 +286,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ZRX', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.checkIfVisible('exchange-modal-input-native'); await Helpers.typeText('exchange-modal-output', '0.246', false); @@ -308,12 +308,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ZRX', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-1'); if (ios) { // TODO await Helpers.typeText('exchange-modal-input', '500000000000', false); @@ -326,13 +326,13 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.typeText('exchange-modal-input', '.', false); await Helpers.checkIfVisible('exchange-modal-input-0.'); await Helpers.swipe('exchange-modal-notch', 'down', 'fast'); @@ -342,13 +342,13 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.typeText('exchange-modal-input-native', '.', false); await Helpers.checkIfVisible('exchange-modal-input-native-0.'); await Helpers.swipe('exchange-modal-notch', 'down', 'fast'); @@ -358,13 +358,13 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'DAI\n', true); - await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-mainnet'); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-mainnet'); + await Helpers.checkIfVisible('currency-select-list-exchange-coin-row-DAI-1'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-DAI-1'); await Helpers.typeText('exchange-modal-output', '.', false); await Helpers.checkIfVisible('exchange-modal-output-0.'); await Helpers.swipe('exchange-modal-notch', 'down', 'fast'); @@ -374,12 +374,12 @@ describe.skip('Swap Sheet Interaction Flow', () => { await Helpers.waitAndTap('swap-button'); await Helpers.tap('exchange-modal-input-selection-button'); await Helpers.checkIfVisible('currency-select-list'); - await Helpers.tap('currency-select-list-exchange-coin-row-ETH-mainnet'); + await Helpers.tap('currency-select-list-exchange-coin-row-ETH-1'); await Helpers.checkIfVisible('exchange-modal-input'); await Helpers.tap('exchange-modal-output-selection-button'); await Helpers.checkIfVisible('currency-select-list'); await Helpers.typeText('currency-select-search-input', 'ZRX\n', false); - await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-mainnet'); + await Helpers.waitAndTap('currency-select-list-exchange-coin-row-ZRX-1'); await Helpers.checkIfVisible('exchange-modal-gas'); await Helpers.checkIfElementByTextIsVisible('Fast'); }); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1623a7d566e..ea5bcba53fc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -27,7 +27,7 @@ PODS: - Firebase/RemoteConfig (10.27.0): - Firebase/CoreOnly - FirebaseRemoteConfig (~> 10.27.0) - - FirebaseABTesting (10.28.0): + - FirebaseABTesting (10.29.0): - FirebaseCore (~> 10.0) - FirebaseAnalytics (10.27.0): - FirebaseAnalytics/AdIdSupport (= 10.27.0) @@ -51,11 +51,11 @@ PODS: - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.28.0): + - FirebaseCoreExtension (10.29.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.28.0): + - FirebaseCoreInternal (10.29.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.28.0): + - FirebaseInstallations (10.29.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) @@ -77,8 +77,8 @@ PODS: - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseRemoteConfigInterop (10.28.0) - - FirebaseSharedSwift (10.28.0) + - FirebaseRemoteConfigInterop (10.29.0) + - FirebaseSharedSwift (10.29.0) - FLAnimatedImage (1.0.17) - fmt (9.1.0) - glog (0.3.5) @@ -168,9 +168,9 @@ PODS: - MetricsReporter (1.2.1): - RSCrashReporter (= 1.0.1) - RudderKit (= 1.4.0) - - MMKV (1.3.7): - - MMKVCore (~> 1.3.7) - - MMKVCore (1.3.7) + - MMKV (1.3.9): + - MMKVCore (~> 1.3.9) + - MMKVCore (1.3.9) - MultiplatformBleAdapter (0.1.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -2217,16 +2217,16 @@ SPEC CHECKSUMS: FasterImage: 1f65cdb2966c47b2a7b83162e5fc712534e50149 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 - FirebaseABTesting: 589bc28c0ab3e5554336895a34aa262e24276665 + FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1 FirebaseCore: a2b95ae4ce7c83ceecfbbbe3b6f1cddc7415a808 - FirebaseCoreExtension: f63147b723e2a700fe0f34ec6fb7f358d6fe83e0 - FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698 - FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e + FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f + FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 + FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd FirebaseMessaging: 585984d0a1df120617eb10b44cad8968b859815e FirebaseRemoteConfig: 37a2ba3c8c454be8553a41ba1a2f4a4f0b845670 - FirebaseRemoteConfigInterop: 70d200c6956ef3b5c3592a95e824c1210682d785 - FirebaseSharedSwift: 48de4aec81a6b79bb30404e5e6db43ea74848fed + FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d + FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f @@ -2236,8 +2236,8 @@ SPEC CHECKSUMS: hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843 - MMKV: 36a22a9ec84c9bb960613a089ddf6f48be9312b0 - MMKVCore: 158e61c8516401a9fac730288acb29e6fc19bbf9 + MMKV: 817ba1eea17421547e01e087285606eb270a8dcb + MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d nanopb: 438bc412db1928dac798aa6fd75726007be04262 PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3 @@ -2369,7 +2369,7 @@ SPEC CHECKSUMS: TOCropViewController: b9b2905938c0424f289ea2d9964993044913fac6 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d + Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c PODFILE CHECKSUM: 0839e4141c8f26133bf9a961f5ded1ea3127af54 diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 62880732ac7..ab496e61acf 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 0299CE7B2886202800B5C7E7 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0299CE7A2886202800B5C7E7 /* NotificationService.m */; }; 0299CE7F2886202800B5C7E7 /* ImageNotification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0299CE772886202800B5C7E7 /* ImageNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 13464CF42E86A8FABAB93E7C /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42DA97054E4E7DF632F81278 /* libPods-PriceWidgetExtension.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -27,7 +26,6 @@ 15E531DA242DAB7100797B89 /* NotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 15E531D9242DAB7100797B89 /* NotificationManager.m */; }; 24979E8920F84250007EB0DA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24979E7720F84004007EB0DA /* GoogleService-Info.plist */; }; 4D098C2F2811A9A5006A801A /* RNStartTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D098C2E2811A9A5006A801A /* RNStartTime.m */; }; - 5066D7571247D8748033AE88 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1E7E9698C0B62C844370E79 /* libPods-ImageNotification.a */; }; 6630540924A38A1900E5B030 /* RainbowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 6630540824A38A1900E5B030 /* RainbowText.m */; }; 6635730624939991006ACFA6 /* SafeStoreReview.m in Sources */ = {isa = PBXBuildFile; fileRef = 6635730524939991006ACFA6 /* SafeStoreReview.m */; }; 6655FFB425BB2B0700642961 /* ThemeModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 6655FFB325BB2B0700642961 /* ThemeModule.m */; }; @@ -36,8 +34,8 @@ 66A1FEB624AB641100C3F539 /* RNCMScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEB324AB641100C3F539 /* RNCMScreen.m */; }; 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */; }; 66A28EB024CAF1B500410A88 /* TestFlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A28EAF24CAF1B500410A88 /* TestFlight.m */; }; - 7B365337B1158EFB22E8A246 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2661F23C3FE063ACCFAEC5B3 /* libPods-SelectTokenIntent.a */; }; - 8994809D37451A08EECE482F /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2979DA25954E3ABDF7273A3C /* libPods-Rainbow.a */; }; + 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */; }; + 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -70,6 +68,7 @@ B5CE8FFF29A5758100EB1EFA /* pooly@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */; }; B5D7F2F029E8D41E003D6A54 /* finiliar@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */; }; B5D7F2F129E8D41E003D6A54 /* finiliar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */; }; + B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */; }; C04D10F025AFC8C1003BEF7A /* Extras.json in Resources */ = {isa = PBXBuildFile; fileRef = C04D10EF25AFC8C1003BEF7A /* Extras.json */; }; C1038325273C2D0C00B18210 /* PriceWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF75272BA7AA00FF5C78 /* PriceWidgetView.swift */; }; C1038337273C5C4200B18210 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF62272BA6EF00FF5C78 /* PriceWidget.swift */; }; @@ -141,6 +140,7 @@ C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; + F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -208,10 +208,8 @@ 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0816126943C8455F30E8448D /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; - 0C6C13C5908C1B18435143AC /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; - 0EB7CA4ECFC333B7C7A48AD9 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; - 11D0375900A9028A9F7EA9DB /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; + 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -237,8 +235,9 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; - 186C275A20FCD306F193BE60 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; - 213B5FD39B63BB9CE422ADD3 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; + 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; + 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -251,16 +250,16 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; - 2661F23C3FE063ACCFAEC5B3 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2979DA25954E3ABDF7273A3C /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; + 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 42DA97054E4E7DF632F81278 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 46A8801576AD0CD3513F574D /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; + 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; - 5761180F78BF553728B9DCBB /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; - 629F2E0B67C5753B2E2F6E9E /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; + 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; @@ -276,13 +275,10 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 6779C3FF1BF8C15142AFFD22 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; - 76FB27FEEA59A31AFE1D0226 /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; - 87F1A57070D7F48604294186 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; - 8F0E8ED7C4052BAB36D82F60 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; - 9FB3DFE566589A30DC84BA41 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -344,6 +340,7 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; + C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -356,12 +353,15 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D328B3108296A42AC1DC2A73 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; + D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - E1E7E9698C0B62C844370E79 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; + E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - FF6AE9A50C7483A450DD765F /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; + F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; + FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5066D7571247D8748033AE88 /* libPods-ImageNotification.a in Frameworks */, + 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - 8994809D37451A08EECE482F /* libPods-Rainbow.a in Frameworks */, + B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 13464CF42E86A8FABAB93E7C /* libPods-PriceWidgetExtension.a in Frameworks */, + 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - 7B365337B1158EFB22E8A246 /* libPods-SelectTokenIntent.a in Frameworks */, + F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - E1E7E9698C0B62C844370E79 /* libPods-ImageNotification.a */, - 42DA97054E4E7DF632F81278 /* libPods-PriceWidgetExtension.a */, - 2979DA25954E3ABDF7273A3C /* libPods-Rainbow.a */, - 2661F23C3FE063ACCFAEC5B3 /* libPods-SelectTokenIntent.a */, + E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */, + 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */, + 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */, + 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - 213B5FD39B63BB9CE422ADD3 /* Pods-ImageNotification.debug.xcconfig */, - 629F2E0B67C5753B2E2F6E9E /* Pods-ImageNotification.release.xcconfig */, - 76FB27FEEA59A31AFE1D0226 /* Pods-ImageNotification.localrelease.xcconfig */, - 5761180F78BF553728B9DCBB /* Pods-ImageNotification.staging.xcconfig */, - 9FB3DFE566589A30DC84BA41 /* Pods-PriceWidgetExtension.debug.xcconfig */, - 186C275A20FCD306F193BE60 /* Pods-PriceWidgetExtension.release.xcconfig */, - 0C6C13C5908C1B18435143AC /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - 8F0E8ED7C4052BAB36D82F60 /* Pods-PriceWidgetExtension.staging.xcconfig */, - D328B3108296A42AC1DC2A73 /* Pods-Rainbow.debug.xcconfig */, - 11D0375900A9028A9F7EA9DB /* Pods-Rainbow.release.xcconfig */, - 6779C3FF1BF8C15142AFFD22 /* Pods-Rainbow.localrelease.xcconfig */, - 46A8801576AD0CD3513F574D /* Pods-Rainbow.staging.xcconfig */, - 0EB7CA4ECFC333B7C7A48AD9 /* Pods-SelectTokenIntent.debug.xcconfig */, - 0816126943C8455F30E8448D /* Pods-SelectTokenIntent.release.xcconfig */, - 87F1A57070D7F48604294186 /* Pods-SelectTokenIntent.localrelease.xcconfig */, - FF6AE9A50C7483A450DD765F /* Pods-SelectTokenIntent.staging.xcconfig */, + F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */, + 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */, + C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */, + 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */, + E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */, + FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */, + 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */, + 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */, + E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */, + 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */, + 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */, + 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */, + 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */, + 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */, + D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 4CA40467B0395F0E1F3CC6E7 /* [CP] Check Pods Manifest.lock */, + 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - 3E1A26037639D0D9AB90C859 /* [CP] Copy Pods Resources */, + DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - 605107FCC961D702B2A841C2 /* [CP] Check Pods Manifest.lock */, + F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - 9E3904B3298264D287106F53 /* [CP] Embed Pods Frameworks */, - 3FD47662803BEDBEFC9C3572 /* [CP] Copy Pods Resources */, - 49E7EB5802976D9F463CD204 /* [CP-User] [RNFB] Core Configuration */, + 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */, + B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */, + 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - 91963BA9905C6B7442C15355 /* [CP] Check Pods Manifest.lock */, + B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - 65499A95C1427AEC9F66A430 /* [CP] Copy Pods Resources */, + 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - 0C3F4689BCA806F06BA96914 /* [CP] Check Pods Manifest.lock */, + EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - 65BA76948289F039CD850652 /* [CP] Copy Pods Resources */, + E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1098,7 +1098,20 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 0C3F4689BCA806F06BA96914 /* [CP] Check Pods Manifest.lock */ = { + 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + name = "[CP-User] [RNFB] Core Configuration"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + }; + 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1113,20 +1126,38 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3E1A26037639D0D9AB90C859 /* [CP] Copy Pods Resources */ = { + 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1137,7 +1168,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1151,14 +1181,50 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Upload Debug Symbols to Sentry"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; + }; + B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3FD47662803BEDBEFC9C3572 /* [CP] Copy Pods Resources */ = { + B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1214,70 +1280,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 49E7EB5802976D9F463CD204 /* [CP-User] [RNFB] Core Configuration */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", - ); - name = "[CP-User] [RNFB] Core Configuration"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; - }; - 4CA40467B0395F0E1F3CC6E7 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 605107FCC961D702B2A841C2 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 65499A95C1427AEC9F66A430 /* [CP] Copy Pods Resources */ = { + DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1288,6 +1297,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1301,13 +1311,14 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 65BA76948289F039CD850652 /* [CP] Copy Pods Resources */ = { + E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1343,7 +1354,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 91963BA9905C6B7442C15355 /* [CP] Check Pods Manifest.lock */ = { + EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1358,45 +1369,34 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9E3904B3298264D287106F53 /* [CP] Embed Pods Frameworks */ = { + F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( + inputFileListPaths = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Upload Debug Symbols to Sentry"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 213B5FD39B63BB9CE422ADD3 /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1633,7 +1633,7 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 629F2E0B67C5753B2E2F6E9E /* Pods-ImageNotification.release.xcconfig */; + baseConfigurationReference = 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1681,7 +1681,7 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 76FB27FEEA59A31AFE1D0226 /* Pods-ImageNotification.localrelease.xcconfig */; + baseConfigurationReference = C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1729,7 +1729,7 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5761180F78BF553728B9DCBB /* Pods-ImageNotification.staging.xcconfig */; + baseConfigurationReference = 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1777,7 +1777,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D328B3108296A42AC1DC2A73 /* Pods-Rainbow.debug.xcconfig */; + baseConfigurationReference = 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1854,7 +1854,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 11D0375900A9028A9F7EA9DB /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 46A8801576AD0CD3513F574D /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2087,7 +2087,7 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6779C3FF1BF8C15142AFFD22 /* Pods-Rainbow.localrelease.xcconfig */; + baseConfigurationReference = 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2260,7 +2260,7 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9FB3DFE566589A30DC84BA41 /* Pods-PriceWidgetExtension.debug.xcconfig */; + baseConfigurationReference = E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2309,7 +2309,7 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 186C275A20FCD306F193BE60 /* Pods-PriceWidgetExtension.release.xcconfig */; + baseConfigurationReference = FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2356,7 +2356,7 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0C6C13C5908C1B18435143AC /* Pods-PriceWidgetExtension.localrelease.xcconfig */; + baseConfigurationReference = 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2403,7 +2403,7 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F0E8ED7C4052BAB36D82F60 /* Pods-PriceWidgetExtension.staging.xcconfig */; + baseConfigurationReference = 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2450,7 +2450,7 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0EB7CA4ECFC333B7C7A48AD9 /* Pods-SelectTokenIntent.debug.xcconfig */; + baseConfigurationReference = 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2497,7 +2497,7 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0816126943C8455F30E8448D /* Pods-SelectTokenIntent.release.xcconfig */; + baseConfigurationReference = 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2542,7 +2542,7 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 87F1A57070D7F48604294186 /* Pods-SelectTokenIntent.localrelease.xcconfig */; + baseConfigurationReference = 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2587,7 +2587,7 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FF6AE9A50C7483A450DD765F /* Pods-SelectTokenIntent.staging.xcconfig */; + baseConfigurationReference = D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 2c2bcc372ca..02e439c90db 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -137,7 +137,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps } address={address} mainnetAddress={mainnetAddress} large - network={ethereumUtils.getNetworkFromChainId(chainId)} + chainId={chainId} symbol={symbol || ''} color={colors?.primary} /> @@ -198,8 +198,8 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) ...(network ? { blockExplorer: { - title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer(network)) }), - action: () => ethereumUtils.openAddressInBlockExplorer(address, network), + title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)) }), + action: () => ethereumUtils.openAddressInBlockExplorer(address, chainId), }, } : {}), diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index ae3d9243882..44ba80067dc 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -32,9 +32,8 @@ import { useAccountSettings } from '@/hooks'; import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { swapsStore, useSwapsStore } from '@/state/swaps/swapsStore'; -import { ethereumUtils } from '@/utils'; import { getNativeAssetForNetwork } from '@/utils/ethereumUtils'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; import React, { useCallback } from 'react'; @@ -142,7 +141,7 @@ function FlashbotsToggle() { const { SwapSettings } = useSwapContext(); const inputAssetChainId = swapsStore(state => state.inputAsset?.chainId) ?? ChainId.mainnet; - const isFlashbotsEnabledForNetwork = getNetworkObj(ethereumUtils.getNetworkFromChainId(inputAssetChainId)).features.flashbots; + const isFlashbotsEnabledForNetwork = getNetworkObject({ chainId: inputAssetChainId }).features.flashbots; const flashbotsToggleValue = useDerivedValue(() => isFlashbotsEnabledForNetwork && SwapSettings.flashbots.value); return ( @@ -365,9 +364,7 @@ export function ReviewPanel() { }); const openGasExplainer = useCallback(async () => { - const nativeAsset = await getNativeAssetForNetwork( - ethereumUtils.getNetworkFromChainId(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet) - ); + const nativeAsset = await getNativeAssetForNetwork(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet); navigate(Routes.EXPLAIN_SHEET, { network: chainNameFromChainId(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet), diff --git a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx index 6f55bbb09a2..e206a0d0a85 100644 --- a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx +++ b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx @@ -4,13 +4,13 @@ import { StyleSheet, View } from 'react-native'; import EthIcon from '@/assets/eth-icon.png'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { globalColors } from '@/design-system'; -import { Network } from '@/networks/types'; import { borders, fonts } from '@/styles'; import { useTheme } from '@/theme'; import { FallbackIcon as CoinIconTextFallback, isETH } from '@/utils'; import { FastFallbackCoinIconImage } from '@/components/asset-list/RecyclerAssetList2/FastComponents/FastFallbackCoinIconImage'; import Animated from 'react-native-reanimated'; import FastImage, { Source } from 'react-native-fast-image'; +import { ChainId } from '@/__swaps__/types/chains'; // TODO: Delete this and replace with RainbowCoinIcon // ⚠️ When replacing this component with RainbowCoinIcon, make sure @@ -45,17 +45,15 @@ const smallFallbackIconStyle = { * @param param0 - optional mainnetAddress, address and network * @returns a proper type and address to use for the url */ -function resolveNetworkAndAddress({ address, mainnetAddress, network }: { mainnetAddress?: string; address: string; network: Network }) { +function resolveChainIdAndAddress({ address, mainnetAddress }: { mainnetAddress?: string; address: string }) { if (mainnetAddress) { return { resolvedAddress: mainnetAddress, - resolvedNetwork: Network.mainnet, }; } return { resolvedAddress: address, - resolvedNetwork: network, }; } @@ -67,7 +65,7 @@ export const SwapCoinIcon = React.memo(function FeedCoinIcon({ forceDarkMode, large, mainnetAddress, - network, + chainId, small, symbol, }: { @@ -78,16 +76,15 @@ export const SwapCoinIcon = React.memo(function FeedCoinIcon({ forceDarkMode?: boolean; large?: boolean; mainnetAddress?: string; - network: Network; + chainId: ChainId; small?: boolean; symbol: string; }) { const theme = useTheme(); - const { resolvedNetwork, resolvedAddress } = resolveNetworkAndAddress({ + const { resolvedAddress } = resolveChainIdAndAddress({ address, mainnetAddress, - network, }); const fallbackIconColor = color ?? theme.colors.purpleUniswap; @@ -115,7 +112,6 @@ export const SwapCoinIcon = React.memo(function FeedCoinIcon({ )} - {network && network !== Network.mainnet && !small && ( + {chainId && chainId !== ChainId.mainnet && !small && ( - + )} diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index 8700f9408a1..5a4ade623e2 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -17,7 +17,7 @@ import { useAccountAccentColor } from '@/hooks'; import { useSharedValueState } from '@/hooks/reanimated/useSharedValueState'; import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; +import { showActionSheetWithOptions } from '@/utils'; import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; type ChainSelectionProps = { @@ -195,12 +195,9 @@ const ChainButtonIcon = ({ output }: { output: boolean | undefined }) => { return ( {output ? ( - + ) : userAssetsFilter && userAssetsFilter !== 'all' ? ( - + ) : ( <> )} diff --git a/src/__swaps__/screens/Swap/components/TokenList/ListEmpty.tsx b/src/__swaps__/screens/Swap/components/TokenList/ListEmpty.tsx index 0c1b2250263..9753a28cbb5 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ListEmpty.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ListEmpty.tsx @@ -4,11 +4,11 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { Box, Stack, Text } from '@/design-system'; import * as i18n from '@/languages'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { isL2Chain } from '@/__swaps__/utils/chains'; import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants'; import { useSwapContext } from '../../providers/swap-provider'; import { BUY_LIST_HEADER_HEIGHT } from './TokenToBuyList'; import { SELL_LIST_HEADER_HEIGHT } from './TokenToSellList'; +import { isL2Chain } from '@/handlers/web3'; type ListEmptyProps = { action?: 'swap' | 'bridge'; @@ -19,7 +19,7 @@ export const ListEmpty = memo(function ListEmpty({ action = 'swap', output = fal const { inputProgress, outputProgress } = useSwapContext(); const isL2 = useMemo(() => { - return output ? isL2Chain(swapsStore.getState().selectedOutputChainId) : false; + return output ? isL2Chain({ chainId: swapsStore.getState().selectedOutputChainId }) : false; }, [output]); const containerHeight = useAnimatedStyle(() => { diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index 88aa5a99ca7..ab3729c448c 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -1,7 +1,7 @@ import { ChainId } from '@/__swaps__/types/chains'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { add, convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/__swaps__/utils/numbers'; -import ethereumUtils, { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { useMemo } from 'react'; import { formatUnits } from 'viem'; @@ -33,8 +33,7 @@ export function useEstimatedGasFee({ gasLimit: string | undefined; gasSettings: GasSettings | undefined; }) { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const nativeNetworkAsset = useNativeAssetForNetwork(network); + const nativeNetworkAsset = useNativeAsset({ chainId }); const { nativeCurrency } = useAccountSettings(); return useMemo(() => { diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index c5d6d84072f..1f03494ed2c 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -9,14 +9,11 @@ import { ethereumUtils } from '@/utils'; export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? ChainId.mainnet); - const nativeAsset = useSharedValue( - ethereumUtils.getNetworkNativeAsset(ethereumUtils.getNetworkFromChainId(chainId.value)) - ); + const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset(chainId.value)); const getNativeAssetForNetwork = useCallback( (chainId: ChainId) => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const asset = ethereumUtils.getNetworkNativeAsset(network); + const asset = ethereumUtils.getNetworkNativeAsset(chainId); nativeAsset.value = asset; }, [nativeAsset] diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 03214c85123..db861ca9401 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -27,7 +27,6 @@ import { } from '@/resources/assets/externalAssetsQuery'; import { triggerHapticFeedback } from '@/screens/points/constants'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { ethereumUtils } from '@/utils'; import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; import { useCallback } from 'react'; import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; @@ -349,31 +348,31 @@ export function useSwapInputsController({ if (!asset) return null; const address = asset.address; - const network = ethereumUtils.getNetworkFromChainId(asset.chainId); + const chainId = asset.chainId; const currency = store.getState().settings.nativeCurrency; try { const tokenData = await fetchExternalToken({ address, - network, + chainId, currency, }); if (tokenData?.price.value) { - queryClient.setQueryData(externalTokenQueryKey({ address, network, currency }), tokenData); + queryClient.setQueryData(externalTokenQueryKey({ address, chainId, currency }), tokenData); return tokenData.price.value; } } catch (error) { logger.error(new RainbowError('[useSwapInputsController]: get asset prices failed')); const now = Date.now(); - const state = queryClient.getQueryState(externalTokenQueryKey({ address, network, currency })); + const state = queryClient.getQueryState(externalTokenQueryKey({ address, chainId, currency })); const price = state?.data?.price.value; if (price) { const updatedAt = state.dataUpdatedAt; // NOTE: if the data is older than 60 seconds, we need to invalidate it and not use it if (now - updatedAt > EXTERNAL_TOKEN_STALE_TIME) { - queryClient.invalidateQueries(externalTokenQueryKey({ address, network, currency })); + queryClient.invalidateQueries(externalTokenQueryKey({ address, chainId, currency })); return null; } return price; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 6368bc161b9..5ae640688a2 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -29,7 +29,7 @@ import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; import { getDefaultSlippageWorklet, isUnwrapEthWorklet, isWrapEthWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { getFlashbotsProvider, getIsHardhatConnected, getProviderForNetwork, isHardHat } from '@/handlers/web3'; +import { getFlashbotsProvider, getIsHardhatConnected, getProvider, isHardHat } from '@/handlers/web3'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; @@ -38,14 +38,14 @@ import { RainbowError, logger } from '@/logger'; import { loadWallet } from '@/model/wallet'; import { Navigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; -import { RainbowNetworkByChainId, getNetworkObj } from '@/networks'; +import { RainbowNetworkByChainId, getNetworkObject } from '@/networks'; import { walletExecuteRap } from '@/raps/execute'; import { QuoteTypeMap, RapSwapActionParameters } from '@/raps/references'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { ethereumUtils, haptics } from '@/utils'; +import { haptics } from '@/utils'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; import { IS_IOS } from '@/env'; @@ -195,9 +195,10 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const NotificationManager = IS_IOS ? NativeModules.NotificationManager : null; NotificationManager?.postNotification('rapInProgress'); - const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); const provider = - parameters.flashbots && getNetworkObj(network).features.flashbots ? await getFlashbotsProvider() : getProviderForNetwork(network); + parameters.flashbots && getNetworkObject({ chainId: parameters.chainId }).features.flashbots + ? await getFlashbotsProvider() + : getProvider({ chainId: parameters.chainId }); const providerUrl = provider?.connection?.url; const connectedToHardhat = !!providerUrl && isHardHat(providerUrl); diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts index c61c29adbdd..98105ad5628 100644 --- a/src/__swaps__/types/chains.ts +++ b/src/__swaps__/types/chains.ts @@ -2,10 +2,6 @@ import * as chain from 'viem/chains'; import type { Chain } from 'viem/chains'; const HARDHAT_CHAIN_ID = 1337; -const BLAST_CHAIN_ID = 81457; -const BLAST_SEPOLIA_CHAIN_ID = 168587773; -const POLYGON_AMOY_CHAIN_ID = 80002; - const HARDHAT_OP_CHAIN_ID = 1338; export const chainHardhat: Chain = { @@ -38,55 +34,6 @@ export const chainHardhatOptimism: Chain = { testnet: true, }; -export const chainBlast: Chain = { - id: BLAST_CHAIN_ID, - name: 'Blast', - rpcUrls: { - public: { http: [process.env.BLAST_MAINNET_RPC as string] }, - default: { - http: [process.env.BLAST_MAINNET_RPC as string], - }, - }, - blockExplorers: { - default: { name: 'Blastscan', url: 'https://blastscan.io/' }, - }, - nativeCurrency: { - name: 'Blast', - symbol: 'BLAST', - decimals: 18, - }, -}; - -export const chainBlastSepolia: Chain = { - id: BLAST_SEPOLIA_CHAIN_ID, - name: 'Blast Sepolia', - nativeCurrency: { - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - rpcUrls: { - public: { http: ['https://sepolia.blast.io'] }, - default: { http: ['https://sepolia.blast.io'] }, - }, - testnet: true, -}; - -export const chainPolygonAmoy: Chain = { - id: POLYGON_AMOY_CHAIN_ID, - name: 'Polygon Amoy', - nativeCurrency: { - decimals: 18, - name: 'MATIC', - symbol: 'MATIC', - }, - rpcUrls: { - public: { http: ['https://rpc-amoy.polygon.technology'] }, - default: { http: ['https://rpc-amoy.polygon.technology'] }, - }, - testnet: true, -}; - export enum ChainName { arbitrum = 'arbitrum', arbitrumNova = 'arbitrum-nova', @@ -129,13 +76,14 @@ export enum ChainId { avalancheFuji = chain.avalancheFuji.id, base = chain.base.id, baseSepolia = chain.baseSepolia.id, - blast = BLAST_CHAIN_ID, - blastSepolia = BLAST_SEPOLIA_CHAIN_ID, + blast = chain.blast.id, + blastSepolia = chain.blastSepolia.id, bsc = chain.bsc.id, bscTestnet = chain.bscTestnet.id, celo = chain.celo.id, degen = chain.degen.id, gnosis = chain.gnosis.id, + goerli = chain.goerli.id, hardhat = HARDHAT_CHAIN_ID, hardhatOptimism = chainHardhatOptimism.id, holesky = chain.holesky.id, @@ -145,7 +93,7 @@ export enum ChainId { optimism = chain.optimism.id, optimismSepolia = chain.optimismSepolia.id, polygon = chain.polygon.id, - polygonAmoy = chainPolygonAmoy.id, + polygonAmoy = chain.polygonAmoy.id, polygonMumbai = chain.polygonMumbai.id, polygonZkEvm = chain.polygonZkEvm.id, rari = 1380012617, diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts index 71cbee651ed..1b9ca2bd496 100644 --- a/src/__swaps__/utils/chains.ts +++ b/src/__swaps__/utils/chains.ts @@ -49,33 +49,6 @@ export const getChainName = ({ chainId }: { chainId: number }) => { return ChainNameDisplay[chainId] || network; }; -/** - * @desc Checks if the given chain is a Layer 2. - * @param chain The chain name to check. - * @return Whether or not the chain is an L2 network. - */ -export const isL2Chain = (chain: ChainName | ChainId): boolean => { - switch (chain) { - case ChainName.arbitrum: - case ChainName.base: - case ChainName.bsc: - case ChainName.optimism: - case ChainName.polygon: - case ChainName.zora: - case ChainName.avalanche: - case ChainId.arbitrum: - case ChainId.base: - case ChainId.bsc: - case ChainId.optimism: - case ChainId.polygon: - case ChainId.zora: - case ChainId.avalanche: - return true; - default: - return false; - } -}; - export function isNativeAsset(address: AddressOrEth, chainId: ChainId) { return isLowerCaseMatch(NATIVE_ASSETS_PER_CHAIN[chainId], address); } diff --git a/src/components/ContactRowInfoButton.js b/src/components/ContactRowInfoButton.js index 96d63ddb2bc..60c58c64a07 100644 --- a/src/components/ContactRowInfoButton.js +++ b/src/components/ContactRowInfoButton.js @@ -67,9 +67,9 @@ const ContactRowActions = { }, }; -const buildBlockExplorerAction = type => { +const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(type)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { @@ -93,7 +93,7 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.network))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( { @@ -114,7 +114,7 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { }, [item?.network, item?.name, item?.address, handleCopyAddress, network]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(item?.network); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); return { menuItems: [ blockExplorerAction, diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index 4f0b56593b2..c02f018636d 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -63,6 +63,7 @@ import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; +import { ChainId } from '@/__swaps__/types/chains'; const PAGES = { HOME: 'home', @@ -187,7 +188,7 @@ export const ControlPanel = () => { ({ networkType, features: { walletconnect } }) => walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => { return { - IconComponent: , + IconComponent: , label: network.name, secondaryLabel: i18n.t( isConnected && network.value === currentNetwork @@ -196,6 +197,7 @@ export const ControlPanel = () => { ), uniqueId: network.value, selected: network.value === currentNetwork, + chainId: network.id, }; }); }, [currentNetwork, isConnected, testnetsEnabled]); @@ -392,7 +394,7 @@ const HomePanel = ({ const walletSecondaryLabel = selectedWallet?.secondaryLabel || ''; const network = allNetworkItems.find(item => item.uniqueId === selectedNetwork); - const networkIcon = ; + const networkIcon = ; const networkLabel = network?.label || ''; const networkSecondaryLabel = network?.secondaryLabel || ''; @@ -458,7 +460,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(Network.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -486,7 +488,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(Network.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -735,6 +737,7 @@ interface ControlPanelMenuItemProps { label: string; labelColor?: TextColor; imageUrl?: string; + chainId?: ChainId; color?: string; onPress?: () => void; secondaryLabel?: string; diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index 8f18b142f03..c730eadafdb 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -6,7 +6,7 @@ import { Provider } from '@ethersproject/providers'; import { RainbowNetworks, getNetworkObj } from '@/networks'; import { getProviderForNetwork } from '@/handlers/web3'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import ethereumUtils, { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { UserRejectedRequestError } from 'viem'; import { convertHexToString } from '@/helpers/utilities'; import { logger } from '@/logger'; @@ -17,6 +17,7 @@ import { Tab } from '@rainbow-me/provider/dist/references/messengers'; import { getDappMetadata } from '@/resources/metadata/dapp'; import { useAppSessionsStore } from '@/state/appSessions'; import { BigNumber } from '@ethersproject/bignumber'; +import { ChainId } from '@/__swaps__/types/chains'; export type ProviderRequestPayload = RequestArguments & { id: number; @@ -157,9 +158,9 @@ const messengerProviderRequestFn = async (messenger: Messenger, request: Provide dappName: dappData?.appName || request.meta?.sender.title || request.meta?.sender.url || '', imageUrl: dappData?.appLogo || '', address: appSession?.address || '', - network: appSession?.network || Network.mainnet, dappUrl: request.meta?.sender.url || '', payload: request, + chainId: appSession?.network ? ethereumUtils.getChainIdFromNetwork(appSession.network) : ChainId.mainnet, }); } diff --git a/src/components/L2Disclaimer.js b/src/components/L2Disclaimer.js index 3c791c74f7b..162084f1dec 100644 --- a/src/components/L2Disclaimer.js +++ b/src/components/L2Disclaimer.js @@ -9,8 +9,9 @@ import { padding, position } from '@/styles'; import { darkModeThemeColors } from '@/styles/colors'; import { getNetworkObj } from '@/networks'; import * as lang from '@/languages'; -import { isL2Network } from '@/handlers/web3'; +import { isL2Chain } from '@/handlers/web3'; import { EthCoinIcon } from './coin-icon/EthCoinIcon'; +import { ethereumUtils } from '@/utils'; const L2Disclaimer = ({ network, @@ -36,7 +37,8 @@ const L2Disclaimer = ({ }, }; - const isL2 = isL2Network(network); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + const isL2 = isL2Chain({ chainId }); return ( <> @@ -44,7 +46,7 @@ const L2Disclaimer = ({ - {isL2 ? : } + {isL2 ? : } @@ -98,7 +101,7 @@ const MemoizedBalanceCoinRow = React.memo( @@ -126,7 +121,7 @@ export default React.memo(function FastCurrencySelectionRow({ {isInfoButtonVisible && } {showFavoriteButton && - network === Network.mainnet && + chainId === ChainId.mainnet && (ios ? ( // @ts-ignore React.ReactNode; icon?: string; - network: Network; shadowColor: string; size?: number; symbol: string; diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index 723fef826aa..2378b7c6f5d 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -18,9 +18,9 @@ import { useRecoilState } from 'recoil'; import { useRemoteConfig } from '@/model/remoteConfig'; import { useAccountAccentColor } from '@/hooks/useAccountAccentColor'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; -import { Network } from '@/networks/types'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { ChainId } from '@/__swaps__/types/chains'; export const ProfileActionButtonsRowHeight = 80; @@ -191,7 +191,7 @@ function SwapButton() { android && delayNext(); - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(Network.mainnet, accountAddress); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, accountAddress); navigate(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { diff --git a/src/components/cards/EthCard.tsx b/src/components/cards/EthCard.tsx index 47d912c4e4b..0e11d03b334 100644 --- a/src/components/cards/EthCard.tsx +++ b/src/components/cards/EthCard.tsx @@ -25,6 +25,7 @@ import assetTypes from '@/entities/assetTypes'; import { Network } from '@/networks/types'; import { getUniqueId } from '@/utils/ethereumUtils'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; export const ETH_CARD_HEIGHT = 284.3; @@ -35,7 +36,7 @@ export const EthCard = () => { const { isDamaged } = useWallets(); const { data: externalEthAsset } = useExternalToken({ address: ETH_ADDRESS, - network: Network.mainnet, + chainId: ChainId.mainnet, currency: nativeCurrency, }); @@ -44,7 +45,7 @@ export const EthCard = () => { ...externalEthAsset, address: ETH_ADDRESS, network: Network.mainnet, - uniqueId: getUniqueId(ETH_ADDRESS, Network.mainnet), + uniqueId: getUniqueId(ETH_ADDRESS, ChainId.mainnet), }), [externalEthAsset] ); @@ -133,12 +134,7 @@ export const EthCard = () => { const { f2c_enabled: addCashEnabled } = useRemoteConfig(); return ( - + diff --git a/src/components/cards/MintsCard/CollectionCell.tsx b/src/components/cards/MintsCard/CollectionCell.tsx index d8529db99a9..01053b1a852 100644 --- a/src/components/cards/MintsCard/CollectionCell.tsx +++ b/src/components/cards/MintsCard/CollectionCell.tsx @@ -6,7 +6,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { useTheme } from '@/theme'; import { View } from 'react-native'; import { MintableCollection } from '@/graphql/__generated__/arc'; -import ethereumUtils, { getNetworkFromChainId, useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { IS_IOS } from '@/env'; @@ -41,7 +41,7 @@ export function CollectionCell({ collection }: { collection: MintableCollection const [mediaRendered, setMediaRendered] = useState(false); - const currency = useNativeAssetForNetwork(getNetworkFromChainId(collection.chainId)); + const currency = useNativeAsset({ chainId: collection.chainId }); const amount = convertRawAmountToRoundedDecimal(collection.mintStatus.price, 18, 6); @@ -131,7 +131,7 @@ export function CollectionCell({ collection }: { collection: MintableCollection { const { colorMode } = useColorMode(); const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); + const offerChainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ address: offer.paymentToken.address, - network: offer.network as Network, + chainId: offerChainId, currency: nativeCurrency, }); @@ -238,7 +239,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { containerSize, @@ -60,7 +61,7 @@ const IndicatorIconContainer = styled(Centered)(({ marginBottom, iconSize, badge })); export default function ChainBadge({ - network, + chainId, badgeXPosition = -7, badgeYPosition = 0, marginBottom = 0, @@ -76,48 +77,48 @@ export default function ChainBadge({ const source = useMemo(() => { let val = null; if (size === 'large') { - if (network === Network.arbitrum) { + if (chainId === ChainId.arbitrum) { val = isDarkMode ? ArbitrumBadgeLargeDark : ArbitrumBadgeLarge; - } else if (network === Network.optimism) { + } else if (chainId === ChainId.optimism) { val = isDarkMode ? OptimismBadgeLargeDark : OptimismBadgeLarge; - } else if (network === Network.polygon) { + } else if (chainId === ChainId.polygon) { val = isDarkMode ? PolygonBadgeLargeDark : PolygonBadgeLarge; - } else if (network === Network.bsc) { + } else if (chainId === ChainId.bsc) { val = isDarkMode ? BscBadgeLargeDark : BscBadgeLarge; - } else if (network === Network.zora) { + } else if (chainId === ChainId.zora) { val = isDarkMode ? ZoraBadgeLargeDark : ZoraBadgeLarge; - } else if (network === Network.base) { + } else if (chainId === ChainId.base) { val = isDarkMode ? BaseBadgeLargeDark : BaseBadgeLarge; - } else if (network === Network.avalanche) { + } else if (chainId === ChainId.avalanche) { val = isDarkMode ? AvalancheBadgeLargeDark : AvalancheBadgeLarge; - } else if (network === Network.blast) { + } else if (chainId === ChainId.blast) { val = isDarkMode ? BlastBadgeLargeDark : BlastBadgeLarge; - } else if (network === Network.degen) { + } else if (chainId === ChainId.degen) { val = isDarkMode ? DegenBadgeLargeDark : DegenBadgeLarge; } } else { - if (network === Network.arbitrum) { + if (chainId === ChainId.arbitrum) { val = isDarkMode ? ArbitrumBadgeDark : ArbitrumBadge; - } else if (network === Network.optimism) { + } else if (chainId === ChainId.optimism) { val = isDarkMode ? OptimismBadgeDark : OptimismBadge; - } else if (network === Network.polygon) { + } else if (chainId === ChainId.polygon) { val = isDarkMode ? PolygonBadgeDark : PolygonBadge; - } else if (network === Network.bsc) { + } else if (chainId === ChainId.bsc) { val = isDarkMode ? BscBadgeDark : BscBadge; - } else if (network === Network.zora) { + } else if (chainId === ChainId.zora) { val = isDarkMode ? ZoraBadgeDark : ZoraBadge; - } else if (network === Network.base) { + } else if (chainId === ChainId.base) { val = isDarkMode ? BaseBadgeDark : BaseBadge; - } else if (network === Network.avalanche) { + } else if (chainId === ChainId.avalanche) { val = isDarkMode ? AvalancheBadgeDark : AvalancheBadge; - } else if (network === Network.blast) { + } else if (chainId === ChainId.blast) { val = isDarkMode ? BlastBadgeDark : BlastBadge; - } else if (network === Network.degen) { + } else if (chainId === ChainId.degen) { val = isDarkMode ? DegenBadgeDark : DegenBadge; } } return val; - }, [network, isDarkMode, size]); + }, [chainId, isDarkMode, size]); if (!source) return null; diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx index 404129008f1..844b96a2d18 100644 --- a/src/components/coin-icon/ChainImage.tsx +++ b/src/components/coin-icon/ChainImage.tsx @@ -12,38 +12,39 @@ import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; import FastImage, { Source } from 'react-native-fast-image'; +import { ChainId } from '@/__swaps__/types/chains'; -export function ChainImage({ chain, size = 20 }: { chain: Network | null | undefined; size?: number }) { +export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) { const source = useMemo(() => { - switch (chain) { - case Network.arbitrum: + switch (chainId) { + case ChainId.arbitrum: return ArbitrumBadge; - case Network.base: + case ChainId.base: return BaseBadge; - case Network.bsc: + case ChainId.bsc: return BscBadge; - case Network.mainnet: + case ChainId.mainnet: return EthereumBadge; - case Network.optimism: + case ChainId.optimism: return OptimismBadge; - case Network.polygon: + case ChainId.polygon: return PolygonBadge; - case Network.zora: + case ChainId.zora: return ZoraBadge; - case Network.avalanche: + case ChainId.avalanche: return AvalancheBadge; - case Network.blast: + case ChainId.blast: return BlastBadge; - case Network.degen: + case ChainId.degen: return DegenBadge; default: return { uri: '' }; } - }, [chain]); + }, [chainId]); - if (!chain) return null; + if (!chainId) return null; return ( - + ); } diff --git a/src/components/coin-icon/EthCoinIcon.tsx b/src/components/coin-icon/EthCoinIcon.tsx index ab85e04a8bb..4207a9e6333 100644 --- a/src/components/coin-icon/EthCoinIcon.tsx +++ b/src/components/coin-icon/EthCoinIcon.tsx @@ -1,22 +1,22 @@ import React from 'react'; -import { Network } from '@/networks/types'; -import { ThemeContextProps, useTheme } from '@/theme'; -import { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import { useTheme } from '@/theme'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import RainbowCoinIcon from './RainbowCoinIcon'; import { ETH_SYMBOL } from '@/references'; +import { ChainId } from '@/__swaps__/types/chains'; type EthCoinIconProps = { size?: number; }; export const EthCoinIcon = ({ size = 40 }: EthCoinIconProps) => { - const ethAsset = useNativeAssetForNetwork(Network.mainnet); + const ethAsset = useNativeAsset({ chainId: ChainId.mainnet }); const theme = useTheme(); return ( ({ export default React.memo(function RainbowCoinIcon({ size = 40, icon, - network, + chainId, symbol, theme, colors, ignoreBadge, - badgeXPosition, - badgeYPosition, }: { size?: number; icon?: string; - network: Network; + chainId: ChainId; symbol: string; theme: ThemeContextProps; colors?: TokenColors; ignoreBadge?: boolean; - badgeXPosition?: number; - badgeYPosition?: number; }) { const fallbackIconColor = colors?.primary || colors?.fallback || theme.colors.purpleUniswap; @@ -49,7 +45,7 @@ export default React.memo(function RainbowCoinIcon({ return ( - + {() => ( - {!ignoreBadge && network && } + {!ignoreBadge && chainId && } ); }); diff --git a/src/components/coin-icon/RequestVendorLogoIcon.js b/src/components/coin-icon/RequestVendorLogoIcon.js index 9458f625936..d858f7522dd 100644 --- a/src/components/coin-icon/RequestVendorLogoIcon.js +++ b/src/components/coin-icon/RequestVendorLogoIcon.js @@ -1,8 +1,8 @@ import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { useTheme } from '../../theme/ThemeContext'; -import { initials } from '../../utils'; -import ChainBadge from '../coin-icon/ChainBadge'; +import { ethereumUtils, initials } from '../../utils'; +import ChainBadge from './ChainBadge'; import { Centered } from '../layout'; import { Text } from '../text'; import { CoinIconSize } from './CoinIcon'; @@ -71,7 +71,7 @@ export default function RequestVendorLogoIcon({ )} - + ); } diff --git a/src/components/coin-icon/TwoCoinsIcon.tsx b/src/components/coin-icon/TwoCoinsIcon.tsx index 4e561cc3e27..a6537bb131c 100644 --- a/src/components/coin-icon/TwoCoinsIcon.tsx +++ b/src/components/coin-icon/TwoCoinsIcon.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { Box } from '@/design-system'; import { ParsedAddressAsset } from '@/entities'; import { useTheme } from '@/theme'; -import { ImgixImage } from '@/components/images'; import ChainBadge from './ChainBadge'; import RainbowCoinIcon from './RainbowCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; export function TwoCoinsIcon({ size = 45, @@ -45,7 +45,7 @@ export function TwoCoinsIcon({ icon={under?.icon_url} theme={theme} size={underSize} - network={under.network} + chainId={under.chainId as ChainId} symbol={under.symbol} ignoreBadge /> @@ -54,9 +54,16 @@ export function TwoCoinsIcon({ borderRadius={100} style={{ zIndex: 10, position: 'absolute', top: 0, right: 0, borderRadius: 99, borderColor: theme.colors.white, borderWidth: 2 }} > - + - {badge && } + {badge && } ); diff --git a/src/components/coin-row/CoinRowInfoButton.js b/src/components/coin-row/CoinRowInfoButton.js index eb82785df37..63a58a9aa5b 100644 --- a/src/components/coin-row/CoinRowInfoButton.js +++ b/src/components/coin-row/CoinRowInfoButton.js @@ -67,9 +67,9 @@ const CoinRowActions = { }, }; -const buildBlockExplorerAction = type => { +const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(type)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { actionKey: CoinRowActionsEnum.blockExplorer, @@ -93,7 +93,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.network))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -115,7 +115,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) }, [item, handleCopyContractAddress]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(item?.network); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); return { menuItems: [ blockExplorerAction, diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index 4840564c295..9ee6cb3bb10 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -26,6 +26,7 @@ import Spinner from '../Spinner'; import * as lang from '@/languages'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { checkForPendingSwap } from '@/screens/transaction-details/helpers/checkForPendingSwap'; +import { ChainId } from '@/__swaps__/types/chains'; export const getApprovalLabel = ({ approvalAmount, asset, type }: Pick) => { if (!approvalAmount || !asset) return; @@ -294,7 +295,7 @@ export const ActivityIcon = ({ }} /> - {transaction.network !== Network.mainnet && } + {transaction.chainId !== ChainId.mainnet && } ); } @@ -367,7 +368,7 @@ export const ActivityIcon = ({ )} - {transaction.network !== Network.mainnet && } + {transaction.chainId !== ChainId.mainnet && } ); } @@ -377,7 +378,7 @@ export const ActivityIcon = ({ { - return isL2Network(item?.network); + return isL2Chain({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }); }, [item?.network]); const containerSelectedStyles = { diff --git a/src/components/context-menu-buttons/ChainContextMenu.tsx b/src/components/context-menu-buttons/ChainContextMenu.tsx index cce2522b299..09828c27eb0 100644 --- a/src/components/context-menu-buttons/ChainContextMenu.tsx +++ b/src/components/context-menu-buttons/ChainContextMenu.tsx @@ -4,7 +4,7 @@ import { ContextMenuButton } from '@/components/context-menu'; import { Bleed, Box, Inline, Text, TextProps } from '@/design-system'; import * as i18n from '@/languages'; import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; -import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; +import { showActionSheetWithOptions } from '@/utils'; import { userAssetsStore } from '@/state/assets/userAssets'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; @@ -147,7 +147,7 @@ export const ChainContextMenu = ({ )} {selectedChainId && ( - + )} diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index c9ac185cef6..d8bd9130d39 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -12,7 +12,7 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils } from '@/utils'; import { formatAddressForDisplay } from '@/utils/abbreviations'; -import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -112,7 +112,7 @@ export default function MoreButton({ address, ensName }: { address?: string; ens setClipboard(address!); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, Network.mainnet); + ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/components/exchange/CurrencySelectModalHeader.tsx b/src/components/exchange/CurrencySelectModalHeader.tsx index 410c3af81c4..6b08a46d040 100644 --- a/src/components/exchange/CurrencySelectModalHeader.tsx +++ b/src/components/exchange/CurrencySelectModalHeader.tsx @@ -8,6 +8,7 @@ import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; +import { ethereumUtils } from '@/utils'; export const CurrencySelectModalHeaderHeight = 59; @@ -58,7 +59,7 @@ export default function CurrencySelectModalHeader({ items.map(({ data, ...item }) => ({ diff --git a/src/components/exchange/ExchangeField.tsx b/src/components/exchange/ExchangeField.tsx index e65d52b443a..a31dc8e749b 100644 --- a/src/components/exchange/ExchangeField.tsx +++ b/src/components/exchange/ExchangeField.tsx @@ -11,6 +11,7 @@ import { useTheme } from '@/theme'; import { AccentColorProvider, Box, Space } from '@/design-system'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; const ExchangeFieldHeight = android ? 64 : 38; const ExchangeFieldPadding: Space = android ? '15px (Deprecated)' : '19px (Deprecated)'; @@ -33,7 +34,7 @@ interface ExchangeFieldProps { editable: boolean; loading: boolean; type?: string; - network: Network; + chainId: ChainId; onBlur?: (event: FocusEvent) => void; onFocus: (event: FocusEvent) => void; onPressSelectCurrency: (chainId: any) => void; @@ -55,7 +56,7 @@ const ExchangeField: ForwardRefRenderFunction = ( disableCurrencySelection, editable, loading, - network, + chainId, onBlur, onFocus, onPressSelectCurrency, @@ -126,7 +127,7 @@ const ExchangeField: ForwardRefRenderFunction = ( paddingRight={ExchangeFieldPadding} width="full" style={style} - testID={`${testID}-${symbol || 'empty'}-${network || 'empty'}`} + testID={`${testID}-${symbol || 'empty'}-${chainId || 'empty'}`} > = ( > {symbol ? ( - + ) : ( - + )} diff --git a/src/components/exchange/ExchangeInputField.tsx b/src/components/exchange/ExchangeInputField.tsx index 7eb359cd267..490cc202a04 100644 --- a/src/components/exchange/ExchangeInputField.tsx +++ b/src/components/exchange/ExchangeInputField.tsx @@ -7,6 +7,7 @@ import ExchangeNativeField from './ExchangeNativeField'; import { Network } from '@/helpers'; import styled from '@/styled-thing'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; const Container = styled(ColumnWithMargins).attrs({ margin: 5 })({ paddingTop: android ? 0 : 6, @@ -30,10 +31,10 @@ interface ExchangeInputFieldProps { nativeAmount: string | null; nativeCurrency: string; nativeFieldRef: MutableRefObject; - network: Network; + chainId: ChainId; onFocus: ({ target }: { target: Element }) => void; onPressMaxBalance: () => void; - onPressSelectInputCurrency: (chainId: any) => void; + onPressSelectInputCurrency: (chainId: ChainId) => void; inputAmount: string | null; inputCurrencyIcon?: string; inputCurrencyColors?: TokenColors; @@ -63,7 +64,7 @@ export default function ExchangeInputField({ nativeAmount, nativeCurrency, nativeFieldRef, - network, + chainId, inputCurrencyNetwork, onFocus, onPressMaxBalance, @@ -85,7 +86,7 @@ export default function ExchangeInputField({ editable={editable} loading={loading} mainnetAddress={inputCurrencyMainnetAddress} - network={network} + chainId={chainId} onFocus={onFocus} onPressSelectCurrency={onPressSelectInputCurrency} ref={inputFieldRef} diff --git a/src/components/exchange/ExchangeOutputField.tsx b/src/components/exchange/ExchangeOutputField.tsx index c81b23ef0a2..479862c16f6 100644 --- a/src/components/exchange/ExchangeOutputField.tsx +++ b/src/components/exchange/ExchangeOutputField.tsx @@ -2,14 +2,14 @@ import React, { MutableRefObject } from 'react'; import { TextInput } from 'react-native'; import ExchangeField from './ExchangeField'; import { Box } from '@rainbow-me/design-system'; -import { Network } from '@rainbow-me/helpers'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; interface ExchangeOutputFieldProps { color: string; + chainId: ChainId; editable: boolean; loading: boolean; - network: Network; onFocus: ({ target }: { target: Element }) => void; onPressSelectOutputCurrency: () => void; onTapWhileDisabled?: () => void; @@ -30,7 +30,7 @@ export default function ExchangeOutputField({ color, editable, loading, - network, + chainId, onFocus, onPressSelectOutputCurrency, onTapWhileDisabled, @@ -62,7 +62,7 @@ export default function ExchangeOutputField({ editable={editable} loading={loading} mainnetAddress={outputCurrencyMainnetAddress} - network={network} + chainId={chainId} onFocus={onFocus} onPressSelectCurrency={onPressSelectOutputCurrency} onTapWhileDisabled={onTapWhileDisabled} diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index 7ec55d01060..b1280c79fcc 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -2,7 +2,6 @@ import React from 'react'; import isEqual from 'react-fast-compare'; import { Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/networks/types'; import { useAsset, useDimensions } from '@/hooks'; import { ButtonPressAnimation } from '../animations'; import { FloatingEmojis } from '../floating-emojis'; @@ -10,6 +9,7 @@ import { IS_IOS } from '@/env'; import { FavStar, Info } from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; import { View } from 'react-native'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; interface ExchangeTokenRowProps { item: any; @@ -17,6 +17,7 @@ interface ExchangeTokenRowProps { export default React.memo(function ExchangeTokenRow({ item: { + chainId, showBalance, showFavoriteButton, onPress, @@ -29,19 +30,18 @@ export default React.memo(function ExchangeTokenRow({ address, name, testID, - network, disabled, }, }: ExchangeTokenRowProps) { const { width: deviceWidth } = useDimensions(); const item = useAsset({ address, - network, + chainId, }); - const rowTestID = `${testID}-exchange-coin-row-${symbol ?? item?.symbol ?? ''}-${network || Network.mainnet}`; + const rowTestID = `${testID}-exchange-coin-row-${symbol ?? item?.symbol ?? ''}-${chainId || ChainId.mainnet}`; - const isInfoButtonVisible = !item?.isNativeAsset || (!isNativeAsset(address ?? item?.address, network) && !showBalance); + const isInfoButtonVisible = !item?.isNativeAsset || (!isNativeAsset(address ?? item?.address, chainId) && !showBalance); return ( @@ -60,7 +60,7 @@ export default React.memo(function ExchangeTokenRow({ - {currentChainId !== 1 ? ( - - ) : ( - - )} + {currentChainId !== 1 ? : } ) : ( - + )} { +const buildBlockExplorerAction = (chainId: ChainId) => { const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(type)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { actionKey: CoinRowActionsEnum.blockExplorer, @@ -43,7 +43,7 @@ export default function contextMenuProps(item: any, onCopySwapDetailsText: (addr }; const onPressAndroid = () => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.network))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -64,7 +64,7 @@ export default function contextMenuProps(item: any, onCopySwapDetailsText: (addr ); }; - const blockExplorerAction = buildBlockExplorerAction(item?.network); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); const menuConfig = { menuItems: [ blockExplorerAction, diff --git a/src/components/expanded-state/AvailableNetworks.js b/src/components/expanded-state/AvailableNetworks.js index b54bf9d66b7..72cba5b9315 100644 --- a/src/components/expanded-state/AvailableNetworks.js +++ b/src/components/expanded-state/AvailableNetworks.js @@ -69,7 +69,11 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, width={{ custom: 22 }} zIndex={availableNetworks?.length - index} > - {network !== 'mainnet' ? : } + {network !== 'mainnet' ? ( + + ) : ( + + )} ); })} diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index f4fcfa4952e..496a8bacd3a 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -25,6 +25,7 @@ import { AddressOrEth, AssetType } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; +import { ChainId } from '@/__swaps__/types/chains'; const NOOP = () => null; @@ -208,6 +209,7 @@ const AvailableNetworksv2 = ({ {availableNetworks?.map((network, index) => { + const chainId = ethereumUtils.getChainIdFromNetwork(network); return ( - {network !== Network.mainnet ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( )} diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index 89167a18a08..eed7e54ba95 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -93,7 +93,7 @@ export default function CustomGasState({ asset }) { isL2Network(assetWithPrice.network), [assetWithPrice.network]); + const isL2 = useMemo(() => isL2Chain({ chainId: assetChainId }), [assetChainId]); const isTestnet = isTestnetNetwork(currentNetwork); const { data, isLoading: additionalAssetDataLoading } = useAdditionalAssetData({ @@ -374,7 +375,7 @@ export default function ChartExpandedState({ asset }) { isNativeAsset={assetWithPrice?.isNativeAsset} links={data?.links} marginTop={!delayedDescriptions && 19} - type={asset?.network} + chainId={ethereumUtils.getChainIdFromNetwork(asset?.network)} /> diff --git a/src/components/expanded-state/asset/SocialLinks.js b/src/components/expanded-state/asset/SocialLinks.js index 2723fab8050..79d2706a75a 100644 --- a/src/components/expanded-state/asset/SocialLinks.js +++ b/src/components/expanded-state/asset/SocialLinks.js @@ -27,9 +27,9 @@ const CommunityLink = styled(Link).attrs({ paddingTop: ios ? 9.5 : 5, }); -export default function SocialLinks({ address, color, isNativeAsset, links, marginTop, type }) { - const etherscanURL = ethereumUtils.getEtherscanHostForNetwork(type); - const blockExplorerName = ethereumUtils.getBlockExplorer(type); +export default function SocialLinks({ address, color, isNativeAsset, links, marginTop, chainId }) { + const etherscanURL = ethereumUtils.getEtherscanHostForNetwork(chainId); + const blockExplorerName = ethereumUtils.getBlockExplorer(chainId); return ( <> diff --git a/src/components/expanded-state/chart/ChartContextButton.js b/src/components/expanded-state/chart/ChartContextButton.js index d29ef3f039e..8ce84794e84 100644 --- a/src/components/expanded-state/chart/ChartContextButton.js +++ b/src/components/expanded-state/chart/ChartContextButton.js @@ -46,7 +46,7 @@ export default function ChartContextButton({ asset, color }) { ? [] : [ `🔍 ${emojiSpacing}${lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(asset?.network)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), })}`, ]), ...(ios ? [lang.t('wallet.action.cancel')] : []), diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 18bbb2b9890..624255db57e 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -11,6 +11,7 @@ import { useAccountSettings, useBooleanState } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { ethereumUtils } from '@/utils'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -113,7 +114,7 @@ export default function ChartExpandedStateHeader({ )} {tradeDetails.feePercentageBasisPoints !== 0 && ( - + )} {hasReward && } - {flashbotsEnabled && inputCurrencyNetwork === Network.mainnet && ( + {flashbotsEnabled && inputChainId === ChainId.mainnet && ( navigate(Routes.EXPLAIN_SHEET, { @@ -112,10 +112,10 @@ export default function SwapDetailsContent({ isHighPriceImpact, isRefuelTx, onCo - {!isNativeAsset(inputCurrency?.address, inputCurrencyNetwork) && ( + {!isNativeAsset(inputCurrency?.address, inputChainId) && ( )} - {!isNativeAsset(outputCurrency?.address, inputCurrencyNetwork) && ( + {!isNativeAsset(outputCurrency?.address, outputChainId) && ( )} diff --git a/src/components/expanded-state/swap-details/SwapDetailsContractRow.js b/src/components/expanded-state/swap-details/SwapDetailsContractRow.js index 5b30c9ab5ce..13d27012d16 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsContractRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsContractRow.js @@ -38,9 +38,9 @@ const ContractActions = { }, }; -const buildBlockExplorerAction = type => { +const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(type)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { actionKey: ContractActionsEnum.blockExplorer, @@ -105,7 +105,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const [menuVisible, setMenuVisible] = useState(false); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(asset?.network); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(asset?.network)); return { menuItems: [ blockExplorerAction, @@ -131,7 +131,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const onPressAndroid = useCallback(() => { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(asset?.network)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), }); const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( diff --git a/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js b/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js index 6f812645963..675acd503c8 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsFeeRow.js @@ -7,11 +7,11 @@ import { useAccountSettings, useStepper } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; -export default function SwapDetailsFeeRow({ tradeDetails, network, testID }) { +export default function SwapDetailsFeeRow({ tradeDetails, chainId, testID }) { const { navigate } = useNavigation(); const { nativeCurrency } = useAccountSettings(); const { rainbowFeeNative, rainbowFeePercentage } = useRainbowFee({ - network, + chainId, tradeDetails, }); const rainbowFeeNativeDisplay = rainbowFeeNative && convertAmountToNativeDisplay(rainbowFeeNative, nativeCurrency); diff --git a/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js b/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js index 7c9dc8c9458..f93113e0fb6 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsRefuelRow.js @@ -9,7 +9,7 @@ import { ImgixImage } from '@/components/images'; import CaretImageSource from '@/assets/family-dropdown-arrow.png'; import Spinner from '@/components/Spinner'; import { useTheme } from '@/theme'; -import { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; const CaretIcon = styled(ImgixImage).attrs(({ theme: { colors } }) => ({ @@ -34,11 +34,8 @@ export default function SwapDetailsRefuelRow({ tradeDetails, testID }) { }); const toSymbol = tradeDetails?.refuel?.toAsset?.symbol; - const fromNetwork = ethereumUtils.getNetworkFromChainId(fromAsset?.chainId); - const toNetwork = ethereumUtils.getNetworkFromChainId(toAsset?.chainId); - - const fromNativeAsset = useNativeAssetForNetwork(fromNetwork); - const toNativeAsset = useNativeAssetForNetwork(toNetwork); + const fromNativeAsset = useNativeAsset({ chainId: fromAsset?.chainId }); + const toNativeAsset = useNativeAsset({ chainId: toAsset?.chainId }); return ( @@ -53,7 +50,7 @@ export default function SwapDetailsRefuelRow({ tradeDetails, testID }) { - + {roundedAmount || '<0.001'} {reward.token.symbol} diff --git a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx index 8bbc04af099..98f5ceeb63d 100644 --- a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx +++ b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx @@ -12,6 +12,7 @@ import { useMagicAutofocus, useSwapSettings } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; +import { ethereumUtils } from '@/utils'; const convertBipsToPercent = (bips: number) => (bips / 100).toString(); const convertPercentToBips = (percent: number) => (percent * 100).toString(); @@ -41,7 +42,7 @@ export const MaxToleranceInput = forwardRef( slippageRef?.current?.blur(); }, reset: () => { - const slippage = getDefaultSlippageFromConfig(currentNetwork) as unknown as number; + const slippage = getDefaultSlippageFromConfig(ethereumUtils.getChainIdFromNetwork(currentNetwork)) as unknown as number; onSlippageChange(convertBipsToPercent(slippage)); }, })); diff --git a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx index 5e6a1b5a8b3..08fb711b91e 100644 --- a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx +++ b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx @@ -57,7 +57,7 @@ const getAssetActions = (network: Network) => [AssetActionsEnum.etherscan]: { actionKey: AssetActionsEnum.etherscan, actionTitle: lang.t('expanded_state.unique_expanded.view_on_block_explorer', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(network)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(network))), }), icon: { iconType: 'SYSTEM', diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 4bf620800e9..8ba909abe83 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -15,8 +15,7 @@ import { Centered, Column, Row } from '../layout'; import { Text } from '../text'; import { GasSpeedLabelPager } from '.'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { isL2Network } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; +import { isL2Chain } from '@/handlers/web3'; import { add, greaterThan, toFixedDecimals } from '@/helpers/utilities'; import { getCrossChainTimeEstimate } from '@/utils/crossChainTimeEstimates'; import { useAccountSettings, useColorForAsset, useGas, usePrevious, useSwapCurrencies } from '@/hooks'; @@ -25,10 +24,11 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { fonts, fontWithWidth, margin, padding } from '@/styles'; import { ethereumUtils, gasUtils } from '@/utils'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; const { GAS_EMOJIS, GAS_ICONS, GasSpeedOrder, CUSTOM, URGENT, NORMAL, FAST, getGasLabel } = gasUtils; @@ -123,7 +123,7 @@ const TransactionTimeLabel = ({ formatter, isLongWait, theme }) => { const GasSpeedButton = ({ asset, - currentNetwork, + chainId, horizontalPadding = 19, fallbackColor, marginBottom = 20, @@ -168,8 +168,10 @@ const GasSpeedButton = ({ .trim(); }, [nativeCurrencySymbol, selectedGasFee]); - const isL2 = useMemo(() => isL2Network(currentNetwork), [currentNetwork]); - const isLegacyGasNetwork = getNetworkObj(currentNetwork).gas.gasType === 'legacy'; + const isL2 = useMemo(() => isL2Chain({ chainId }), [chainId]); + const networkObject = useMemo(() => getNetworkObject({ chainId }), [chainId]); + + const isLegacyGasNetwork = networkObject.gas.gasType === 'legacy'; const gasIsNotReady = useMemo( () => isNil(price) || isEmpty(gasFeeParamsBySpeed) || isEmpty(selectedGasFee?.gasFee), @@ -291,12 +293,16 @@ const GasSpeedButton = ({ return ''; } return `${timeSymbol}${time} ${estimatedTimeUnit}`; - }, [crossChainServiceTime, currentNetwork, gasPriceReady, selectedGasFee?.estimatedTime?.amount, selectedGasFee?.estimatedTime?.display]); + }, [ + crossChainServiceTime, + gasPriceReady, + isLegacyGasNetwork, + selectedGasFee?.estimatedTime?.amount, + selectedGasFee?.estimatedTime?.display, + ]); const openGasHelper = useCallback(async () => { Keyboard.dismiss(); - const networkObj = getNetworkObj(currentNetwork); - const networkName = networkObj.name; if (crossChainServiceTime) { navigate(Routes.EXPLAIN_SHEET, { inputCurrency, @@ -304,14 +310,14 @@ const GasSpeedButton = ({ type: 'crossChainGas', }); } else { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(currentNetwork); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); navigate(Routes.EXPLAIN_SHEET, { - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), type: 'gas', nativeAsset, }); } - }, [crossChainServiceTime, currentNetwork, inputCurrency, navigate, outputCurrency]); + }, [chainId, crossChainServiceTime, inputCurrency, navigate, outputCurrency]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { @@ -341,8 +347,8 @@ const GasSpeedButton = ({ const speedOptions = useMemo(() => { if (speeds) return speeds; - return getNetworkObj(currentNetwork).gas.speeds; - }, [currentNetwork, speeds]); + return networkObject.gas.speeds; + }, [networkObject.gas.speeds, speeds]); const menuConfig = useMemo(() => { const menuOptions = speedOptions.map(gasOption => { @@ -351,7 +357,7 @@ const GasSpeedButton = ({ const totalGwei = add(gasFeeParamsBySpeed[gasOption]?.maxBaseFee?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei); const estimatedGwei = add(currentBlockParams?.baseFeePerGas?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei); - const shouldRoundGwei = getNetworkObj(currentNetwork).gas.roundGasDisplay; + const shouldRoundGwei = networkObject.gas.roundGasDisplay; const gweiDisplay = !shouldRoundGwei ? gasFeeParamsBySpeed[gasOption]?.gasPrice?.display : gasOption === 'custom' && selectedGasFeeOption !== 'custom' @@ -373,7 +379,14 @@ const GasSpeedButton = ({ menuItems: menuOptions, menuTitle: '', }; - }, [currentBlockParams?.baseFeePerGas?.gwei, currentNetwork, gasFeeParamsBySpeed, selectedGasFeeOption, speedOptions, isL2]); + }, [ + speedOptions, + gasFeeParamsBySpeed, + currentBlockParams?.baseFeePerGas?.gwei, + networkObject.gas.roundGasDisplay, + selectedGasFeeOption, + isL2, + ]); const gasOptionsAvailable = useMemo(() => speedOptions.length > 1, [speedOptions.length]); @@ -395,7 +408,6 @@ const GasSpeedButton = ({ ? makeColorMoreChill(rawColorForAsset || colors.appleBlue, colors.shadowBlack) : colors.alpha(colors.blueGreyDark, 0.12) } - currentNetwork={currentNetwork} dropdownEnabled={gasOptionsAvailable} label={label} showGasOptions={showGasOptions} @@ -438,9 +450,9 @@ const GasSpeedButton = ({ ); }, [ colors, - currentNetwork, gasIsNotReady, gasOptionsAvailable, + handlePressActionSheet, handlePressMenuItem, menuConfig, rawColorForAsset, @@ -476,7 +488,7 @@ const GasSpeedButton = ({ - {!!currentNetwork && ( + {!!chainId && ( - {currentNetwork === Network.mainnet ? ( + {chainId === ChainId.mainnet ? ( ) : ( - + )} )} @@ -535,7 +547,7 @@ const GasSpeedButton = ({ {isLegacyGasNetwork ? ( - + ) : showGasOptions ? ( { parseInt(chain.split(':')[1]))?.filter(isSupportedChain) ?? []; + const chainIds = (chains?.map(chain => parseInt(chain.split(':')[1]))?.filter(isSupportedChain) ?? []) as ChainId[]; if (!address) { const e = new RainbowError(`WalletConnectV2ListItem: could not parse address`); @@ -92,11 +92,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT }; }, [session]); - const availableNetworks = useMemo(() => { - return chainIds - .map(network => ethereumUtils.getNetworkFromChainId(Number(network))) - .sort(network => (network === Network.mainnet ? -1 : 1)); - }, [chainIds]); + const availableNetworksChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); const approvalAccountInfo = useMemo(() => { const selectedWallet = findWalletWithAccount(wallets!, address); @@ -116,7 +112,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT }, watchOnly: true, }); - }, [session, address, dappUrl, goBack]); + }, [address, session, reload, goBack]); const onPressAndroid = useCallback(() => { showActionSheetWithOptions( @@ -138,7 +134,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT } } ); - }, [session, address, dappName, dappUrl, handlePressChangeWallet]); + }, [dappName, handlePressChangeWallet, session, reload, dappUrl]); const handleOnPressMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly @@ -154,7 +150,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT handlePressChangeWallet(); } }, - [address, dappName, dappUrl, handlePressChangeWallet] + [dappName, dappUrl, handlePressChangeWallet, reload, session] ); return ( @@ -206,7 +202,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT - {!!availableNetworks?.length && ( + {!!availableNetworksChainIds?.length && ( - {availableNetworks?.map((network, index) => { + {availableNetworksChainIds?.map((chainId, index) => { return ( 0 ? -4 : 0 }} style={{ backgroundColor: colors.transparent, - zIndex: availableNetworks?.length - index, + zIndex: availableNetworksChainIds?.length - index, borderRadius: 30, }} > - {network !== Network.mainnet ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( )} diff --git a/src/entities/tokens.ts b/src/entities/tokens.ts index 045a00dfa1c..3de13f36507 100644 --- a/src/entities/tokens.ts +++ b/src/entities/tokens.ts @@ -1,8 +1,8 @@ -import { ChainId } from '@rainbow-me/swaps'; import { EthereumAddress } from '.'; import { Chain } from '@wagmi/chains'; import { Network } from '@/networks/types'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; export interface ZerionAssetPrice { value: number; @@ -108,6 +108,7 @@ export interface TokenSearchToken { } export interface RainbowToken extends Asset { + chainId: ChainId; color?: string; highLiquidity?: boolean; totalLiquidity?: number; diff --git a/src/entities/transactions/transaction.ts b/src/entities/transactions/transaction.ts index d95869313ba..9736d25b06f 100644 --- a/src/entities/transactions/transaction.ts +++ b/src/entities/transactions/transaction.ts @@ -4,11 +4,12 @@ import { ParsedAddressAsset } from '../tokens'; import { EthereumAddress } from '../wallet'; import { Network } from '@/helpers/networkTypes'; import { AddCashCurrencyAsset } from '@/references'; -import { ChainId, SwapType } from '@rainbow-me/swaps'; +import { SwapType } from '@rainbow-me/swaps'; import { SwapMetadata } from '@/raps/references'; import { UniqueAsset } from '../uniqueAssets'; import { ParsedAsset } from '@/resources/assets/types'; import { TransactionStatus, TransactionType } from '@/resources/transactions/types'; +import { ChainId } from '@/__swaps__/types/chains'; export type TransactionDirection = 'in' | 'out' | 'self'; @@ -25,6 +26,7 @@ export interface RainbowTransaction { amount: string; display: string; } | null; + chainId: ChainId; changes?: Array< | { asset: ParsedAddressAsset; diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 326f8edc077..48044fac0a0 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -2,20 +2,22 @@ import { Contract } from '@ethersproject/contracts'; import { erc20ABI } from '@/references'; import { convertAmountToBalanceDisplay, convertRawAmountToDecimalFormat } from '@/helpers/utilities'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObj, getNetworkObject } from '@/networks'; import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; +import { ethereumUtils } from '@/utils'; export function isL2Asset(network: Network) { return getNetworkObj(network).networkType === 'layer2'; } -export function isNativeAsset(address: string, network: string) { - return getNetworkObj(network as Network).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); +export function isNativeAsset(address: string, chainId: ChainId) { + return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, network: any, provider: any) { // Check if it's the native chain asset - if (isNativeAsset(address, network)) { + if (isNativeAsset(address, ethereumUtils.getChainIdFromNetwork(network))) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); } return getOnchainTokenBalance({ address, decimals, symbol }, userAddress, provider); diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index bb35bf34d82..55a9dd87b5a 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -2,7 +2,6 @@ import { BigNumberish } from '@ethersproject/bignumber'; import { Block, StaticJsonRpcProvider } from '@ethersproject/providers'; import { ALLOWS_PERMIT, - ChainId, CrosschainQuote, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS, getQuoteExecutionDetails, @@ -22,6 +21,7 @@ import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; import { erc20ABI, ethUnits } from '@/references'; import { ethereumUtils, logger } from '@/utils'; +import { ChainId } from '@/__swaps__/types/chains'; export enum Field { INPUT = 'INPUT', @@ -251,7 +251,7 @@ export const estimateSwapGasLimit = async ({ from: tradeDetails.from, value: isWrapNativeAsset ? tradeDetails.buyAmount : '0', }, - getWrappedAssetMethod(isWrapNativeAsset ? 'deposit' : 'withdraw', provider, chainId), + getWrappedAssetMethod(isWrapNativeAsset ? 'deposit' : 'withdraw', provider, chainId as number), // @ts-ignore isUnwrapNativeAsset ? [tradeDetails.buyAmount] : null, provider, @@ -377,5 +377,5 @@ export const computeSlippageAdjustedAmounts = (trade: any, allowedSlippageInBlip }; export const getTokenForCurrency = (currency: Asset, chainId: ChainId): Token => { - return { ...currency, chainId } as Token; + return { ...currency, chainId: chainId as number } as Token; }; diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index 6f5d6160a52..023e7100617 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -50,7 +50,7 @@ export const swapSearch = async (searchParams: { } const url = `/${searchParams.chainId}/?${qs.stringify(queryParams)}`; const tokenSearch = await tokenSearchApi.get(url); - return tokenSearch.data?.data; + return { ...tokenSearch.data?.data, chainId: searchParams.chainId }; } catch (e: any) { logger.error(new RainbowError(`An error occurred while searching for query`), { query: searchParams.query, @@ -83,7 +83,7 @@ export const tokenSearch = async (searchParams: { try { if (isAddress(searchParams.query)) { // @ts-ignore - params.keys = `networks.${params.chainId}.address`; + params.keys = `networks.${searchParams.chainId}.address`; } const url = `/?${qs.stringify(queryParams)}`; const tokenSearch = await tokenSearchApi.get(url); @@ -98,6 +98,7 @@ export const tokenSearch = async (searchParams: { ...token, address: token.networks['1']?.address || token.networks[Number(networkKeys[0])]?.address, network, + chainId: searchParams.chainId, mainnet_address: token.networks['1']?.address, }; }); diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 0fc3572046e..95822333253 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -36,8 +36,10 @@ import { import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; import { IS_IOS, RPC_PROXY_API_KEY, RPC_PROXY_BASE_URL } from '@/env'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObj, getNetworkObject } from '@/networks'; import store from '@/redux/store'; +import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; export enum TokenStandard { ERC1155 = 'ERC1155', @@ -170,11 +172,11 @@ export const web3SetHttpProvider = async (network: Network | string): Promise { - return getNetworkObj(network as Network).networkType === 'layer2'; +export const isL2Chain = ({ chainId }: { chainId: ChainId }): boolean => { + return getNetworkObject({ chainId }).networkType === 'layer2'; }; /** @@ -234,6 +236,26 @@ export const getProviderForNetwork = (network: Network | string = Network.mainne } }; +export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { + const network = getNetworkFromChainId(chainId); + const isSupportedNetwork = isNetworkEnum(network); + const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; + + if (isSupportedNetwork && cachedProvider) { + return cachedProvider; + } + + if (!isSupportedNetwork) { + const provider = new StaticJsonRpcProvider(network, Network.mainnet); + networkProviders.set(Network.mainnet, provider); + return provider; + } else { + const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); + networkProviders.set(network, provider); + return provider; + } +}; + /** * @desc Checks if the active network is Hardhat. * @returns boolean: `true` if connected to Hardhat. @@ -755,7 +777,7 @@ export const buildTransaction = async ( from: address, to: contractAddress, }; - } else if (!isNativeAsset(asset.address, network)) { + } else if (!isNativeAsset(asset.address, ethereumUtils.getChainIdFromNetwork(network))) { const transferData = getDataForTokenTransfer(value, _recipient); txData = { data: transferData, diff --git a/src/hooks/useAsset.ts b/src/hooks/useAsset.ts index 27f61aeee84..9a5e3180397 100644 --- a/src/hooks/useAsset.ts +++ b/src/hooks/useAsset.ts @@ -1,20 +1,20 @@ import { useMemo } from 'react'; import useAccountAsset from './useAccountAsset'; -import { Network } from '@/networks/types'; -import ethereumUtils, { getUniqueId } from '@/utils/ethereumUtils'; +import { getUniqueId } from '@/utils/ethereumUtils'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useSelector } from 'react-redux'; import { AppState } from '@/redux/store'; +import { ChainId } from '@/__swaps__/types/chains'; // To fetch an asset from account assets, // generic assets, and uniqueTokens -export default function useAsset({ address, network }: { address: string; network: Network }) { +export default function useAsset({ address, chainId }: { address: string; chainId: ChainId }) { const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); - const uniqueId = getUniqueId(address, network); + const uniqueId = getUniqueId(address, chainId); const accountAsset = useAccountAsset(uniqueId); const { data: externalAsset } = useExternalToken({ address, - network, + chainId, currency: nativeCurrency, }); diff --git a/src/hooks/useENSRegistrationCosts.ts b/src/hooks/useENSRegistrationCosts.ts index dd291856be1..d1143975b3e 100644 --- a/src/hooks/useENSRegistrationCosts.ts +++ b/src/hooks/useENSRegistrationCosts.ts @@ -14,7 +14,6 @@ import { estimateENSSetRecordsGasLimit, fetchReverseRecord, } from '@/handlers/ens'; -import { NetworkTypes } from '@/helpers'; import { ENS_DOMAIN, formatEstimatedNetworkFee, @@ -29,6 +28,7 @@ import { Network } from '@/helpers/networkTypes'; import { add, addBuffer, addDisplay, fromWei, greaterThanOrEqualTo, multiply } from '@/helpers/utilities'; import { ethUnits, timeUnits } from '@/references'; import { ethereumUtils, gasUtils } from '@/utils'; +import { ChainId } from '@/__swaps__/types/chains'; enum QUERY_KEYS { GET_COMMIT_GAS_LIMIT = 'GET_COMMIT_GAS_LIMIT', @@ -93,7 +93,7 @@ export default function useENSRegistrationCosts({ const rentPriceInWei = rentPrice?.wei?.toString(); const checkIfSufficientEth = useCallback((wei: string) => { - const nativeAsset = ethereumUtils.getNetworkNativeAsset(NetworkTypes.mainnet); + const nativeAsset = ethereumUtils.getNetworkNativeAsset(ChainId.mainnet); const balanceAmount = nativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(wei); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); diff --git a/src/hooks/useGas.ts b/src/hooks/useGas.ts index 943cd7a84e5..224e25ebc8e 100644 --- a/src/hooks/useGas.ts +++ b/src/hooks/useGas.ts @@ -28,11 +28,13 @@ import { getNetworkObj } from '@/networks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { BNB_MAINNET_ADDRESS, ETH_ADDRESS, MATIC_MAINNET_ADDRESS } from '@/references'; import useAccountSettings from './useAccountSettings'; +import { ChainId } from '@/__swaps__/types/chains'; const checkSufficientGas = (txFee: LegacyGasFee | GasFee, network: Network, nativeAsset?: ParsedAddressAsset) => { const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; - const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset(network); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset(chainId); const balanceAmount = networkNativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(txFeeValue?.value?.amount); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); @@ -64,17 +66,17 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss // keep native assets up to date useExternalToken({ address: BNB_MAINNET_ADDRESS, - network: Network.mainnet, + chainId: ChainId.mainnet, currency: nativeCurrency, }); useExternalToken({ address: ETH_ADDRESS, - network: Network.mainnet, + chainId: ChainId.mainnet, currency: nativeCurrency, }); useExternalToken({ address: MATIC_MAINNET_ADDRESS, - network: Network.mainnet, + chainId: ChainId.mainnet, currency: nativeCurrency, }); diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts index d359fff1a88..f6789c38b76 100644 --- a/src/hooks/usePriceImpactDetails.ts +++ b/src/hooks/usePriceImpactDetails.ts @@ -14,8 +14,9 @@ import { } from '@/helpers/utilities'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; -import ethereumUtils, { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; +import { ChainId } from '@/__swaps__/types/chains'; export enum SwapPriceImpactType { none = 'none', @@ -31,34 +32,33 @@ export default function usePriceImpactDetails( inputCurrency: SwappableAsset | null, outputCurrency: SwappableAsset | null, tradeDetails: CrosschainQuote | Quote | null, - currentNetwork = Network.mainnet + chainId: ChainId = ChainId.mainnet ) { const { nativeCurrency } = useAccountSettings(); const { colors } = useTheme(); - const sellNetwork = (tradeDetails as CrosschainQuote)?.fromChainId - ? ethereumUtils.getNetworkFromChainId((tradeDetails as CrosschainQuote)?.fromChainId) - : currentNetwork; - const buyNetwork = outputCurrency?.network || currentNetwork; - const sellNativeAsset = useNativeAssetForNetwork(sellNetwork); - const buyNativeAsset = useNativeAssetForNetwork(buyNetwork); + const sellChainId = ( + (tradeDetails as CrosschainQuote)?.fromChainId ? (tradeDetails as CrosschainQuote)?.fromChainId : chainId + ) as ChainId; + const buyChainId = (outputCurrency?.chainId || chainId) as ChainId; + const sellNativeAsset = useNativeAsset({ chainId: sellChainId }); + const buyNativeAsset = useNativeAsset({ chainId: buyChainId }); const isWrapOrUnwrap = useMemo(() => { if (!tradeDetails) return false; - const chainId = ethereumUtils.getChainIdFromNetwork(buyNetwork); return ( isWrapNative({ buyTokenAddress: tradeDetails?.buyTokenAddress, sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId, + chainId: buyChainId, }) || isUnwrapNative({ buyTokenAddress: tradeDetails?.buyTokenAddress, sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId, + chainId: buyChainId, }) ); - }, [buyNetwork, tradeDetails]); + }, [buyChainId, tradeDetails]); const inputNativeAmount = useMemo(() => { if (isWrapOrUnwrap) { diff --git a/src/hooks/useRainbowFee.js b/src/hooks/useRainbowFee.js index c56ad9e7ef8..b80d489a6be 100644 --- a/src/hooks/useRainbowFee.js +++ b/src/hooks/useRainbowFee.js @@ -4,7 +4,7 @@ import { convertRawAmountToDecimalFormat, divide, multiply, subtract } from '@/h import { useAccountSettings, useSwapCurrencies } from '@/hooks'; import { ethereumUtils } from '@/utils'; -export default function useRainbowFee({ tradeDetails, network }) { +export default function useRainbowFee({ tradeDetails, chainId }) { const { inputCurrency, outputCurrency } = useSwapCurrencies(); const { accountAddress } = useAccountSettings(); const [nativeAsset, setNativeAsset] = useState(null); @@ -47,11 +47,11 @@ export default function useRainbowFee({ tradeDetails, network }) { useEffect(() => { const getNativeAsset = async () => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(network, accountAddress); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); setNativeAsset(nativeAsset); }; !nativeAsset && getNativeAsset(); - }, [nativeAsset, network, accountAddress]); + }, [nativeAsset, chainId, accountAddress]); return { rainbowFeeNative, rainbowFeePercentage }; } diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index bc5f0250ca6..554fe14cfb6 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -6,20 +6,19 @@ import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; -import { RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; +import { RainbowToken, TokenSearchTokenListId } from '@/entities'; import { tokenSearch } from '@/handlers/tokenSearch'; -import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; +import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; -import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; -const MAINNET_CHAINID = 1; type swapCurrencyListType = | 'verifiedAssets' | 'highLiquidityAssets' @@ -28,13 +27,10 @@ type swapCurrencyListType = | 'curatedAssets' | 'importedAssets'; -type CrosschainVerifiedAssets = { - [Network.mainnet]: RT[]; - [Network.optimism]: RT[]; - [Network.polygon]: RT[]; - [Network.bsc]: RT[]; - [Network.arbitrum]: RT[]; -}; +type CrosschainVerifiedAssets = Record< + ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.bsc | ChainId.arbitrum, + RainbowToken[] +>; const abcSort = (list: any[], key?: string) => { return list.sort((a, b) => { @@ -45,25 +41,26 @@ const abcSort = (list: any[], key?: string) => { const searchCurrencyList = async (searchParams: { chainId: number; fromChainId?: number | ''; - searchList: RT[] | TokenSearchTokenListId; + searchList: RainbowToken[] | TokenSearchTokenListId; query: string; }) => { const { searchList, query, chainId } = searchParams; const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - const keys: (keyof RT)[] = isAddress ? ['address'] : ['symbol', 'name']; + const keys: (keyof RainbowToken)[] = isAddress ? ['address'] : ['symbol', 'name']; const formattedQuery = isAddress ? addHexPrefix(query).toLowerCase() : query; if (typeof searchList === 'string') { const threshold = isAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'; - if (chainId === MAINNET_CHAINID && !formattedQuery && searchList !== 'verifiedAssets') { + if (chainId === ChainId.mainnet && !formattedQuery && searchList !== 'verifiedAssets') { return []; } - return tokenSearch({ + const ts = await tokenSearch({ chainId, keys, list: searchList, threshold, query: formattedQuery, }); + return ts; } else { return ( filterList(searchList, formattedQuery, keys, { @@ -73,36 +70,36 @@ const searchCurrencyList = async (searchParams: { } }; -const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINID, isDiscover = false) => { +const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.mainnet, isDiscover = false) => { const previousChainId = usePrevious(searchChainId); - const searching = useMemo(() => searchQuery !== '' || MAINNET_CHAINID !== searchChainId, [searchChainId, searchQuery]); + const searching = useMemo(() => searchQuery !== '' || ChainId.mainnet !== searchChainId, [searchChainId, searchQuery]); const { favorites: favoriteAddresses, favoritesMetadata: favoriteMap } = useFavorites(); - const curatedMap = rainbowTokenList.CURATED_TOKENS; const unfilteredFavorites = Object.values(favoriteMap).filter(token => token.networks[searchChainId]); const [loading, setLoading] = useState(true); - const [favoriteAssets, setFavoriteAssets] = useState([]); - const [importedAssets, setImportedAssets] = useState([]); - const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); - const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); - const [verifiedAssets, setVerifiedAssets] = useState([]); + const [favoriteAssets, setFavoriteAssets] = useState([]); + const [importedAssets, setImportedAssets] = useState([]); + const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); + const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); + const [verifiedAssets, setVerifiedAssets] = useState([]); + const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ - [Network.mainnet]: [], - [Network.optimism]: [], - [Network.polygon]: [], - [Network.bsc]: [], - [Network.arbitrum]: [], + [ChainId.mainnet]: [], + [ChainId.optimism]: [], + [ChainId.polygon]: [], + [ChainId.bsc]: [], + [ChainId.arbitrum]: [], }); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyNetwork = usePrevious(inputCurrency?.network); - const inputChainId = useMemo(() => ethereumUtils.getChainIdFromNetwork(inputCurrency?.network), [inputCurrency?.network]); + const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); + const inputChainId = inputCurrency?.chainId; const isCrosschainSearch = useMemo(() => { if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { return true; @@ -114,14 +111,14 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI [favoriteAddresses] ); const handleSearchResponse = useCallback( - (tokens: RT[]): RT[] => { + (tokens: RainbowToken[]): RainbowToken[] => { // These transformations are necessary for L2 tokens to match our spec return (tokens || []) .map(token => { - const t: RT = { + const t: RainbowToken = { ...token, address: token?.address || token.uniqueId.toLowerCase(), - } as RT; + } as RainbowToken; return t; }) @@ -163,15 +160,14 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI }, [searchChainId, searchQuery, searching, unfilteredFavorites]); const getImportedAsset = useCallback( - async (searchQuery: string, chainId: number): Promise => { + async (searchQuery: string, chainId: number): Promise => { if (searching) { if (isAddress(searchQuery)) { const tokenListEntry = rainbowTokenList.RAINBOW_TOKEN_LIST[searchQuery.toLowerCase()]; if (tokenListEntry) { return [tokenListEntry]; } - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); const tokenContract = new Contract(searchQuery, erc20ABI, provider); try { const [name, symbol, decimals, address] = await Promise.all([ @@ -180,10 +176,11 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI tokenContract.decimals(), getAddress(searchQuery), ]); - const uniqueId = getUniqueId(address, network); + const uniqueId = getUniqueId(address, chainId); return [ { + chainId, address, decimals, favorite: false, @@ -198,7 +195,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI }, }, symbol, - network, + network: ethereumUtils.getNetworkFromChainId(chainId), uniqueId, } as RainbowToken, ]; @@ -215,18 +212,17 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI ); const getCrosschainVerifiedAssetsForNetwork = useCallback( - async (network: Network) => { - const crosschainId = ethereumUtils.getChainIdFromNetwork(network); - const fromChainId = inputChainId !== crosschainId ? inputChainId : ''; + async (chainId: ChainId) => { + const fromChainId = inputChainId !== chainId ? inputChainId : ''; const results = await searchCurrencyList({ searchList: 'verifiedAssets', query: '', - chainId: crosschainId, + chainId, fromChainId, }); setCrosschainVerifiedAssets(state => ({ ...state, - [network]: handleSearchResponse(results || []), + [chainId]: handleSearchResponse(results || []), })); }, [handleSearchResponse, inputChainId] @@ -234,8 +230,8 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI const getCrosschainVerifiedAssets = useCallback(async () => { const crosschainAssetRequests: Promise[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(network as Network)); + Object.keys(crosschainVerifiedAssets).forEach(chainId => { + crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(Number(chainId))); }); await Promise.all(crosschainAssetRequests); }, [crosschainVerifiedAssets, getCrosschainVerifiedAssetsForNetwork]); @@ -296,7 +292,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI const search = useCallback(async () => { const categories: swapCurrencyListType[] = - searchChainId === MAINNET_CHAINID + searchChainId === ChainId.mainnet ? ['favoriteAssets', 'highLiquidityAssets', 'verifiedAssets', 'importedAssets'] : ['verifiedAssets', 'importedAssets']; setLoading(true); @@ -341,9 +337,9 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI (searching && !wasSearching) || (searching && previousSearchQuery !== searchQuery) || searchChainId !== previousChainId || - inputCurrency?.network !== previousInputCurrencyNetwork + inputCurrency?.chainId !== previousInputCurrencyChainId ) { - if (searchChainId === MAINNET_CHAINID) { + if (searchChainId === ChainId.mainnet) { search(); slowSearch(); } else { @@ -358,7 +354,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI }; doSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.network]); + }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); const { colors } = useTheme(); @@ -366,7 +362,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI const list = []; let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.network !== inputCurrency?.network) + ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) : null; if (searching) { const importedAsset = importedAssets?.[0]; @@ -398,7 +394,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI }); } } - if (favoriteAssets?.length && searchChainId === MAINNET_CHAINID) { + if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { list.push({ color: colors.yellowFavorite, data: abcSort(favoriteAssets, 'name'), @@ -429,7 +425,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI }); } } else { - const curatedAssets = searchChainId === MAINNET_CHAINID && getCurated(); + const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { @@ -460,39 +456,39 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAI } return list; }, [ + isCrosschainSearch, + verifiedAssets, searching, + inputCurrency?.name, + inputCurrency?.chainId, importedAssets, - favoriteAssets, - verifiedAssets, highLiquidityAssets, lowLiquidityAssets, - colors.yellowFavorite, - unfilteredFavorites, - searchChainId, - getCurated, isFavorite, - inputCurrency?.name, + favoriteAssets, + searchChainId, colors.networkColors, - isCrosschainSearch, - inputCurrency?.network, + colors.yellowFavorite, + getCurated, + unfilteredFavorites, ]); const crosschainExactMatches = useMemo(() => { if (currencyList.length) return []; if (!searchQuery) return []; - const exactMatches: RT[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - const currentNetworkChainId = ethereumUtils.getChainIdFromNetwork(network as Network); + const exactMatches: RainbowToken[] = []; + Object.keys(crosschainVerifiedAssets).forEach(chainId => { + const currentNetworkChainId = Number(chainId); if (currentNetworkChainId !== searchChainId) { // including goerli in our networks type is causing this type issue // @ts-ignore - const exactMatch = crosschainVerifiedAssets[network as Network].find((asset: RT) => { + const exactMatch = crosschainVerifiedAssets[currentNetworkChainId].find((asset: RainbowToken) => { const symbolMatch = isLowerCaseMatch(asset?.symbol, searchQuery); const nameMatch = isLowerCaseMatch(asset?.name, searchQuery); return symbolMatch || nameMatch; }); if (exactMatch) { - exactMatches.push({ ...exactMatch, network }); + exactMatches.push({ ...exactMatch, chainId: currentNetworkChainId }); } } }); diff --git a/src/hooks/useSwapCurrencyHandlers.ts b/src/hooks/useSwapCurrencyHandlers.ts index e030608e205..3fce074a3cb 100644 --- a/src/hooks/useSwapCurrencyHandlers.ts +++ b/src/hooks/useSwapCurrencyHandlers.ts @@ -4,18 +4,15 @@ import { useDispatch } from 'react-redux'; import { delayNext } from './useMagicAutofocus'; import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; import { updatePrecisionToDisplay } from '@/helpers/utilities'; -import { useAccountSettings, useSwapDerivedValues, useSwapInputHandlers } from '@/hooks'; +import { useSwapDerivedValues, useSwapInputHandlers } from '@/hooks'; import { useNavigation } from '@/navigation'; import { flipSwapCurrencies, updateSwapInputAmount, updateSwapInputCurrency, updateSwapOutputCurrency } from '@/redux/swap'; import Routes from '@/navigation/routesNames'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { queryClient } from '@/react-query'; -import { prefetchExternalToken } from '@/resources/assets/externalAssetsQuery'; const { currentlyFocusedInput, focusTextInput } = TextInput.State; export default function useSwapCurrencyHandlers({ - currentNetwork, inputNetwork, outputNetwork, defaultInputAsset, @@ -30,7 +27,6 @@ export default function useSwapCurrencyHandlers({ title, type, }: any = {}) { - const { nativeCurrency } = useAccountSettings(); const dispatch = useDispatch(); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); const { navigate, setParams, getParent: dangerouslyGetParent } = useNavigation(); @@ -102,7 +98,6 @@ export default function useSwapCurrencyHandlers({ flipSwapCurrenciesWithTimeout(inputFieldRef, false, derivedValues?.outputAmount); } }, [ - currentNetwork, inputNetwork, outputNetwork, nativeFieldRef, @@ -130,7 +125,7 @@ export default function useSwapCurrencyHandlers({ setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newInputCurrency); }, - [crosschainSwapsEnabled, dispatch, inputFieldRef, nativeCurrency, setLastFocusedInputHandle] + [crosschainSwapsEnabled, dispatch, inputFieldRef, setLastFocusedInputHandle] ); const updateOutputCurrency = useCallback( diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index 11bf2594836..a987758763e 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -1,12 +1,12 @@ import lang from 'i18n-js'; import { getAddress, isAddress } from '@ethersproject/address'; -import { ChainId, EthereumAddress } from '@rainbow-me/swaps'; +import { EthereumAddress } from '@rainbow-me/swaps'; import { Contract } from '@ethersproject/contracts'; import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; -import { AssetType, RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; +import { RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; import { swapSearch } from '@/handlers/tokenSearch'; import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; @@ -18,6 +18,7 @@ import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; const MAINNET_CHAINID = 1; type swapCurrencyListType = @@ -124,7 +125,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI if (token.networks[MAINNET_CHAINID]) { token.mainnet_address = token.networks[MAINNET_CHAINID].address; } - token.uniqueId = getUniqueId(token.address, network); + token.uniqueId = getUniqueId(token.address, activeChainId); return token; }) @@ -157,7 +158,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI return { ...token, network: Network.mainnet, - uniqueId: getUniqueId(token.address, Network.mainnet), + uniqueId: getUniqueId(token.address, ChainId.mainnet), }; }); }, [curatedMap, favoriteAddresses]); diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index 34738fe1681..a19abf3c5aa 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -66,16 +66,14 @@ const getInputAmount = async ( try { const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken?.network); - const outputNetwork = ethereumUtils.getNetworkFromChainId(outputChainId); const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken?.network); - const inputNetwork = ethereumUtils.getNetworkFromChainId(inputChainId); - const inputTokenAddress = isNativeAsset(inputToken?.address, inputNetwork) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; + const inputTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; - const outputTokenAddress = isNativeAsset(outputToken?.address, outputNetwork) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; + const outputTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const isCrosschainSwap = inputNetwork !== outputNetwork; + const isCrosschainSwap = inputChainId !== outputChainId; if (isCrosschainSwap) return null; const buyAmount = convertAmountToRawAmount(convertNumberToString(outputAmount), outputToken.decimals); @@ -83,11 +81,11 @@ const getInputAmount = async ( logger.info(`[getInputAmount]: `, { outputToken, outputChainId, - outputNetwork, + outputNetwork: outputToken?.network, outputTokenAddress, inputToken, inputChainId, - inputNetwork, + inputNetwork: inputToken?.network, inputTokenAddress, isCrosschainSwap, }); @@ -167,15 +165,13 @@ const getOutputAmount = async ( try { const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken.network); - const outputNetwork = outputToken.network; - const buyTokenAddress = isNativeAsset(outputToken?.address, outputNetwork) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken.network); - const inputNetwork = inputToken.network; + const buyTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const sellTokenAddress = isNativeAsset(inputToken?.address, inputNetwork) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; + const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken.network); + const sellTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); - const isCrosschainSwap = outputNetwork !== inputNetwork; + const isCrosschainSwap = outputChainId !== inputChainId; // logger.info(`[getOutputAmount]: `, { // outputToken, diff --git a/src/hooks/useSwapInputHandlers.ts b/src/hooks/useSwapInputHandlers.ts index 99bac072ada..b6c4478bae2 100644 --- a/src/hooks/useSwapInputHandlers.ts +++ b/src/hooks/useSwapInputHandlers.ts @@ -24,7 +24,7 @@ export default function useSwapInputHandlers() { const accountAsset = ethereumUtils.getAccountAsset(inputCurrencyUniqueId); const oldAmount = accountAsset?.balance?.amount ?? '0'; let newAmount = oldAmount; - if (isNativeAsset(inputCurrencyAddress, inputCurrencyNetwork) && accountAsset) { + if (isNativeAsset(inputCurrencyAddress, ethereumUtils.getChainIdFromNetwork(inputCurrencyNetwork)) && accountAsset) { // this subtracts gas from the balance of the asset newAmount = toFixedDecimals(ethereumUtils.getBalanceAmount(selectedGasFee, accountAsset, l1GasFeeOptimism), 6); diff --git a/src/hooks/useSwapRefuel.ts b/src/hooks/useSwapRefuel.ts index 45a1e804a57..488edca8bdc 100644 --- a/src/hooks/useSwapRefuel.ts +++ b/src/hooks/useSwapRefuel.ts @@ -51,8 +51,8 @@ export default function useSwapRefuel({ const { data: minRefuelAmount } = useMinRefuelAmount( { - chainId, - toChainId, + chainId: chainId as number, + toChainId: toChainId as number, }, { enabled: isCrosschainSwap } ); @@ -60,16 +60,16 @@ export default function useSwapRefuel({ useEffect(() => { const getNativeInputOutputAssets = async () => { if (!outputNetwork || !inputNetwork || !accountAddress) return; - const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(outputNetwork, accountAddress); - const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(inputNetwork, accountAddress); + const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(toChainId, accountAddress); + const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); setOutputNativeAsset(outputNativeAsset); setInputNativeAsset(inputNativeAsset); }; getNativeInputOutputAssets(); - }, [outputNetwork, inputNetwork, accountAddress]); + }, [outputNetwork, inputNetwork, accountAddress, toChainId, chainId]); const { showRefuelSheet, refuelState } = useMemo(() => { - const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, outputNetwork); + const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, toChainId); // // If the swap is going into a native token on the destination chain, in which case we can ignore all refuel functionality if (swappingToNativeAsset) { return { showRefuelSheet: false, refuelState: null }; @@ -95,7 +95,7 @@ export default function useSwapRefuel({ // Check the existing source native asset balance const inputNativeAssetAmount = toWei(inputNativeAsset?.balance?.amount || '0'); // Check if swapping from native asset - const swappingFromNativeAsset = isNativeAsset(inputCurrency?.address, inputNetwork); + const swappingFromNativeAsset = isNativeAsset(inputCurrency?.address, chainId); const gasFeesPlusRefuelAmount = add(gasFee, refuelAmount.toString()); // - If they wont have enough after the swap of the source native asset then we should offer to deduct some of the input amount into the refuel amount @@ -123,15 +123,16 @@ export default function useSwapRefuel({ return { showRefuelSheet: true, refuelState: RefuelState.Notice }; }, [ + chainId, inputCurrency?.address, inputNativeAsset?.balance?.amount, - inputNetwork, isCrosschainSwap, minRefuelAmount, outputCurrency?.address, outputNativeAsset?.balance?.amount, outputNetwork, selectedGasFee?.gasFee?.estimatedFee?.value?.amount, + toChainId, tradeDetails?.sellAmount, ]); diff --git a/src/migrations/migrations/migratePinnedAndHiddenTokenUniqueIds.ts b/src/migrations/migrations/migratePinnedAndHiddenTokenUniqueIds.ts index 5bd8fe4a2a9..1784613c564 100644 --- a/src/migrations/migrations/migratePinnedAndHiddenTokenUniqueIds.ts +++ b/src/migrations/migrations/migratePinnedAndHiddenTokenUniqueIds.ts @@ -2,7 +2,6 @@ import { BooleanMap } from '@/hooks/useCoinListEditOptions'; import { Migration, MigrationName } from '@/migrations/types'; import { loadAddress } from '@/model/wallet'; import { Network } from '@/networks/types'; -import { getUniqueId } from '@/utils/ethereumUtils'; import { MMKV } from 'react-native-mmkv'; const mmkv = new MMKV(); @@ -20,7 +19,7 @@ export function migratePinnedAndHiddenTokenUniqueIds(): Migration { const pinnedCoinsKeys = Object.keys(pinnedCoinsString ? JSON.parse(pinnedCoinsString) : {}); const newHiddenCoins = hiddenCoinsKeys.reduce((acc, curr) => { if (!curr.includes('_')) { - acc[getUniqueId(curr, Network.mainnet)] = true; + acc[`${curr}_${Network.mainnet}`] = true; return acc; } acc[curr] = true; @@ -29,7 +28,7 @@ export function migratePinnedAndHiddenTokenUniqueIds(): Migration { const newPinnedCoins = pinnedCoinsKeys.reduce((acc, curr) => { if (!curr.includes('_')) { - acc[getUniqueId(curr, Network.mainnet)] = true; + acc[`${curr}_${Network.mainnet}`] = true; return acc; } acc[curr] = true; diff --git a/src/model/migrations.ts b/src/model/migrations.ts index c79715a0db9..5026391f774 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -36,9 +36,9 @@ import logger from '@/utils/logger'; import { queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; -import { getUniqueId } from '@/utils/ethereumUtils'; import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps'; +import { getUniqueIdNetwork } from '@/utils/ethereumUtils'; export default async function runMigrations() { // get current version @@ -463,12 +463,12 @@ export default async function runMigrations() { const pinnedCoinsMigrated = pinnedCoins.map((address: string) => { const asset = assets?.find((asset: any) => asset.address === address.toLowerCase()); - return getUniqueId(asset?.address, network); + return getUniqueIdNetwork(asset?.address, network); }); const hiddenCoinsMigrated = hiddenCoins.map((address: string) => { const asset = ethereumUtils.getAsset(assets, address); - return getUniqueId(asset?.address, network); + return getUniqueIdNetwork(asset?.address, network); }); logger.log(JSON.stringify({ pinnedCoinsMigrated }, null, 2)); diff --git a/src/networks/blast.ts b/src/networks/blast.ts index 44c63c24fb3..f7b168d3e72 100644 --- a/src/networks/blast.ts +++ b/src/networks/blast.ts @@ -7,8 +7,6 @@ import { getRemoteConfig } from '@/model/remoteConfig'; import { BLAST_MAINNET_RPC } from 'react-native-dotenv'; import { BLAST_ETH_ADDRESS } from '@/references'; -const BLAST_CHAIN_ID = 81457; - export const getBlastNetworkObject = (): NetworkProperties => { const { blast_enabled, blast_tx_enabled } = getRemoteConfig(); return { @@ -30,7 +28,7 @@ export const getBlastNetworkObject = (): NetworkProperties => { }, balanceCheckerAddress: '', - rpc: () => proxyRpcEndpoint(BLAST_CHAIN_ID), + rpc: () => proxyRpcEndpoint(blast.id), getProvider: () => getProviderForNetwork(Network.blast), // features diff --git a/src/networks/index.ts b/src/networks/index.ts index e5cb731e66d..79c9089f23a 100644 --- a/src/networks/index.ts +++ b/src/networks/index.ts @@ -74,6 +74,43 @@ export function getNetworkObj(network: Network): NetworkProperties { } } +export function getNetworkObject({ chainId }: { chainId: ChainId }): NetworkProperties { + switch (chainId) { + // Mainnet + case ChainId.mainnet: + return getMainnetNetworkObject(); + + // L2s + case ChainId.arbitrum: + return getArbitrumNetworkObject(); + case ChainId.base: + return getBaseNetworkObject(); + case ChainId.bsc: + return getBSCNetworkObject(); + case ChainId.optimism: + return getOptimismNetworkObject(); + case ChainId.polygon: + return getPolygonNetworkObject(); + case ChainId.zora: + return getZoraNetworkObject(); + case ChainId.gnosis: + return getGnosisNetworkObject(); + case ChainId.avalanche: + return getAvalancheNetworkObject(); + case ChainId.blast: + return getBlastNetworkObject(); + case ChainId.degen: + return getDegenNetworkObject(); + // Testnets + case ChainId.goerli: + return getGoerliNetworkObject(); + + // Fallback + default: + return getMainnetNetworkObject(); + } +} + /** * Sorts Networks based on addresses assets */ diff --git a/src/parsers/accounts.js b/src/parsers/accounts.js index a9e44c86c6d..aa31cce971b 100644 --- a/src/parsers/accounts.js +++ b/src/parsers/accounts.js @@ -1,12 +1,12 @@ import isNil from 'lodash/isNil'; import toUpper from 'lodash/toUpper'; import { isNativeAsset } from '@/handlers/assets'; -import networkTypes from '@/helpers/networkTypes'; import * as i18n from '@/languages'; import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; import { getTokenMetadata, isLowerCaseMatch } from '@/utils'; import { memoFn } from '@/utils/memoFn'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; // eslint-disable-next-line no-useless-escape const sanitize = memoFn(s => s.replace(/[^a-z0-9áéíóúñü \.,_@:-]/gim, '')); @@ -35,10 +35,10 @@ export const parseAsset = ({ asset_code: address, ...asset } = {}) => { ...asset, ...metadata, address, - isNativeAsset: isNativeAsset(address, asset.network || networkTypes.mainnet), + isNativeAsset: isNativeAsset(address, asset.chain_id || ChainId.mainnet), name, symbol, - uniqueId: getUniqueId(address, asset.network), + uniqueId: getUniqueId(address, asset.chain_id), }; return parsedAsset; diff --git a/src/parsers/newTransaction.ts b/src/parsers/newTransaction.ts index 14f7e6a8a50..6111b7faa2b 100644 --- a/src/parsers/newTransaction.ts +++ b/src/parsers/newTransaction.ts @@ -1,5 +1,5 @@ import { NativeCurrencyKey, NewTransactionOrAddCashTransaction, RainbowTransaction } from '@/entities'; -import { isL2Network } from '@/handlers/web3'; +import { isL2Chain } from '@/handlers/web3'; import { ETH_ADDRESS } from '@/references'; import { convertAmountAndPriceToNativeDisplay, convertAmountToBalanceDisplay } from '@/helpers/utilities'; import { ethereumUtils } from '@/utils'; @@ -28,6 +28,7 @@ export const parseNewTransaction = async ( maxFeePerGas, maxPriorityFeePerGas, network, + chainId, nft, nonce, hash, @@ -52,12 +53,13 @@ export const parseNewTransaction = async ( const assetPrice = asset?.price?.value ?? ethereumUtils.getAssetPrice(asset?.address); const native = - network && isL2Network(network) + chainId && isL2Chain({ chainId }) ? { amount: '', display: '' } : convertAmountAndPriceToNativeDisplay(amount ?? 0, assetPrice, nativeCurrency); return { address: asset?.address ?? ETH_ADDRESS, + chainId, balance, data, ensCommitRegistrationName, diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 89518524707..80c13845313 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -9,7 +9,7 @@ import { isSignTypedData, SIGN, PERSONAL_SIGN, SEND_TRANSACTION, SIGN_TRANSACTIO import { isAddress } from '@ethersproject/address'; import { toUtf8String } from '@ethersproject/strings'; -export const getRequestDisplayDetails = (payload, nativeCurrency, dappNetwork) => { +export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { const timestampInMs = Date.now(); if (payload.method === SEND_TRANSACTION || payload.method === SIGN_TRANSACTION) { const transaction = Object.assign(payload?.params?.[0] ?? null); @@ -29,7 +29,7 @@ export const getRequestDisplayDetails = (payload, nativeCurrency, dappNetwork) = transaction.data = '0x'; } - return getTransactionDisplayDetails(transaction, nativeCurrency, timestampInMs, dappNetwork); + return getTransactionDisplayDetails(transaction, nativeCurrency, timestampInMs, chainId); } if (payload.method === SIGN) { const message = payload?.params?.find(p => !isAddress(p)); @@ -71,9 +71,9 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ timestampInMs, }); -const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, dappNetwork) => { +const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork(dappNetwork); + const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; @@ -95,7 +95,7 @@ const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs } if (transaction.data.startsWith(tokenTransferHash)) { const contractAddress = transaction.to; - const accountAssetUniqueId = ethereumUtils.getUniqueId(contractAddress, dappNetwork); + const accountAssetUniqueId = ethereumUtils.getUniqueId(contractAddress, chainId); const asset = ethereumUtils.getAccountAsset(accountAssetUniqueId); const dataPayload = transaction.data.replace(tokenTransferHash, ''); const toAddress = `0x${dataPayload.slice(0, 64).replace(/^0+/, '')}`; diff --git a/src/parsers/transactions.ts b/src/parsers/transactions.ts index ec0e06bc652..495ff7ba0c4 100644 --- a/src/parsers/transactions.ts +++ b/src/parsers/transactions.ts @@ -20,6 +20,7 @@ import { TransactionType, TransactionWithChangesType, } from '@/resources/transactions/types'; +import { ChainId } from '@/__swaps__/types/chains'; const LAST_TXN_HASH_BUFFER = 20; @@ -64,7 +65,8 @@ export const getAssetFromChanges = (changes: TransactionChanges, type: Transacti export const parseTransaction = async ( transaction: TransactionApiResponse, - nativeCurrency: NativeCurrencyKey + nativeCurrency: NativeCurrencyKey, + chainId: ChainId ): Promise => { const { status, hash, meta, nonce, protocol } = transaction; @@ -112,6 +114,7 @@ export const parseTransaction = async ( }; return { + chainId, from: txn.address_from, to: txn.address_to, title: `${type}.${status}`, diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 0e0f1bf747a..208f337024f 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -168,6 +168,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // 5 - if the swap was successful we add the transaction to the store const transaction = { + chainId, data: bridgeQuote.data, value: bridgeQuote.value?.toString(), asset: typedAssetToBuy, diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index 8e94fc05d68..be6a0df7c9a 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -180,6 +180,7 @@ export const crosschainSwap = async ({ : parameters.assetToSell.price; const transaction = { + chainId, data: parameters.quote.data, from: parameters.quote.from as Address, to: parameters.quote.to as Address, diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts index cecdb88653a..6c804fad086 100644 --- a/src/raps/actions/ens.ts +++ b/src/raps/actions/ens.ts @@ -25,7 +25,7 @@ import { } from '../registerENS'; import { Logger } from '@ethersproject/logger'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import Routes from '@/navigation/routesNames'; +import { ChainId } from '@/__swaps__/types/chains'; export interface ENSRapActionResponse { baseNonce?: number | null; @@ -465,6 +465,7 @@ const ensAction = async ( logger.log(`[${actionName}] response`, tx); const newTransaction: NewTransaction = { + chainId: ChainId.mainnet, data: tx.data, ensCommitRegistrationName: type === ENSRegistrationTransactionType.COMMIT ? name : undefined, ensRegistration: true, diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index f2f17a032b9..5dd61e809f2 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -225,14 +225,14 @@ export const executeSwap = async ({ }; // Wrap Eth - if (isWrapNative({ buyTokenAddress, sellTokenAddress, chainId: chainId as unknown as SwapChainId })) { + if (isWrapNative({ buyTokenAddress, sellTokenAddress, chainId })) { return wrapNativeAsset(quote.buyAmount, wallet, chainId as unknown as SwapChainId, transactionParams); // Unwrap Weth - } else if (isUnwrapNative({ buyTokenAddress, sellTokenAddress, chainId: chainId as unknown as SwapChainId })) { + } else if (isUnwrapNative({ buyTokenAddress, sellTokenAddress, chainId })) { return unwrapNativeAsset(quote.sellAmount, wallet, chainId as unknown as SwapChainId, transactionParams); // Swap } else { - return fillQuote(quote, transactionParams, wallet, permit, chainId as unknown as SwapChainId, REFERRER); + return fillQuote(quote, transactionParams, wallet, permit, chainId as number, REFERRER); } }; @@ -319,6 +319,7 @@ export const swap = async ({ : parameters.assetToSell.price; const transaction = { + chainId: parameters.chainId, data: swap.data, from: swap.from as Address, to: swap.to as Address, @@ -367,8 +368,8 @@ export const swap = async ({ type: 'swap', swap: { type: SwapType.normal, - fromChainId: parameters.assetToSell.chainId as unknown as SwapChainId, - toChainId: parameters.assetToBuy.chainId as unknown as SwapChainId, + fromChainId: parameters.assetToSell.chainId, + toChainId: parameters.assetToBuy.chainId, // TODO: Is this right? isBridge: diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index 30107f755e4..d2c95ea1c00 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -282,7 +282,7 @@ export const unlock = async ({ hash: approval.hash as TxHash, // TODO: MARK - Replace this once we migrate network => chainId network: ethereumUtils.getNetworkFromChainId(chainId), - // chainId: approval.chainId, + chainId: approval.chainId, nonce: approval.nonce, status: 'pending', type: 'approve', diff --git a/src/raps/unlockAndCrosschainSwap.ts b/src/raps/unlockAndCrosschainSwap.ts index 0a41ef235a8..d1ff8fa64f3 100644 --- a/src/raps/unlockAndCrosschainSwap.ts +++ b/src/raps/unlockAndCrosschainSwap.ts @@ -3,7 +3,7 @@ import { Address } from 'viem'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; -import { ethereumUtils, isLowerCaseMatch } from '@/utils'; +import { isLowerCaseMatch } from '@/utils'; import { ETH_ADDRESS } from '../references'; import { assetNeedsUnlocking, estimateApprove } from './actions'; @@ -38,10 +38,8 @@ export const estimateUnlockAndCrosschainSwap = async ({ let gasLimits: (string | number)[] = []; let swapAssetNeedsUnlocking = false; - // TODO: MARK - Replace this once we migrate network => chainId - const network = ethereumUtils.getNetworkFromChainId(chainId); // Aggregators represent native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(assetToSell.address, network); + const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(assetToSell.address, chainId); const shouldNotHaveApproval = no_approval !== undefined && no_approval; diff --git a/src/raps/unlockAndSwap.ts b/src/raps/unlockAndSwap.ts index 94788237308..614e66b992c 100644 --- a/src/raps/unlockAndSwap.ts +++ b/src/raps/unlockAndSwap.ts @@ -10,7 +10,7 @@ import { Address } from 'viem'; import { ChainId } from '@/__swaps__/types/chains'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; -import { ethereumUtils, isLowerCaseMatch } from '@/utils'; +import { isLowerCaseMatch } from '@/utils'; import { ETH_ADDRESS } from '../references'; import { isUnwrapNative } from '@/handlers/swap'; @@ -42,10 +42,7 @@ export const estimateUnlockAndSwap = async ({ let gasLimits: (string | number)[] = []; let swapAssetNeedsUnlocking = false; - // TODO: MARK - replace this when we migrate from network => chainId - const network = ethereumUtils.getNetworkFromChainId(chainId); - - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, network); + const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, chainId); if (!isNativeAssetUnwrapping && !nativeAsset) { swapAssetNeedsUnlocking = await assetNeedsUnlocking({ @@ -114,11 +111,8 @@ export const createUnlockAndSwapRap = async (swapParameters: RapSwapActionParame buyTokenAddress, }); - // TODO: MARK - replace this when we migrate from network => chainId - const network = ethereumUtils.getNetworkFromChainId(chainId); - // Aggregators represent native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, network); + const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, chainId); let swapAssetNeedsUnlocking = false; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index 4dba3b81dda..d583e40cae1 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -8,6 +8,7 @@ import { getRequestDisplayDetails } from '@/parsers'; import { ethereumUtils } from '@/utils'; import logger from '@/utils/logger'; import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; // -- Constants --------------------------------------- // @@ -23,7 +24,7 @@ export interface RequestData { dappName: string; imageUrl: string | undefined; address: string; - network: Network; + chainId: ChainId; dappUrl: string; payload: any; displayDetails: RequestDisplayDetails | null | Record; @@ -162,11 +163,9 @@ export const addRequestToApprove = const walletConnector = walletConnectors[peerId]; // @ts-expect-error "_chainId" is private. const chainId = walletConnector._chainId; - const requestNetwork = ethereumUtils.getNetworkFromChainId(Number(chainId)); // @ts-expect-error "_accounts" is private. const address = walletConnector._accounts[0]; - const dappNetwork = ethereumUtils.getNetworkFromChainId(Number(chainId)); - const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, dappNetwork); + const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, chainId); const oneHourAgoTs = Date.now() - EXPIRATION_THRESHOLD_IN_MS; // @ts-expect-error This fails to compile as `displayDetails` does not // always return an object with `timestampInMs`. Still, the error thrown @@ -184,7 +183,7 @@ export const addRequestToApprove = const request: WalletconnectRequestData = { address, - network: requestNetwork, + chainId, clientId, dappName, dappScheme, diff --git a/src/references/rainbow-token-list/index.ts b/src/references/rainbow-token-list/index.ts index db6f14a6190..845443a6cc3 100644 --- a/src/references/rainbow-token-list/index.ts +++ b/src/references/rainbow-token-list/index.ts @@ -8,6 +8,7 @@ import { RainbowToken } from '@/entities'; import { STORAGE_IDS } from '@/model/mmkv'; import logger from '@/utils/logger'; import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const rainbowListStorage = new MMKV({ id: STORAGE_IDS.RAINBOW_TOKEN_LIST, @@ -25,6 +26,7 @@ const ethWithAddress: RainbowToken = { isVerified: true, name: 'Ethereum', symbol: 'ETH', + chainId: ChainId.mainnet, network: Network.mainnet, uniqueId: 'eth', }; @@ -42,6 +44,7 @@ function generateDerivedData(tokenListData: TokenListData) { name, symbol, network: Network.mainnet, + chainId: ChainId.mainnet, uniqueId: address, ...extensions, }; diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index 8ae7bf99443..48cf161d614 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -41,22 +41,21 @@ export const filterPositionsData = ( }; export function parseAsset({ address, asset }: { address: string; asset: AddysAsset }): ParsedAsset { - const chainName = asset?.network; - const network = chainName; - const chainId = ethereumUtils.getChainIdFromNetwork(chainName); + const network = asset?.network; + const chainId = ethereumUtils.getChainIdFromNetwork(network); const mainnetAddress = asset?.networks?.[MAINNET_CHAIN_ID]?.address; - const uniqueId = getUniqueId(address, network); + const uniqueId = getUniqueId(address, chainId); const parsedAsset = { address, color: asset?.colors?.primary, colors: asset.colors, chainId, - chainName, + chainName: network, decimals: asset?.decimals, id: address, icon_url: asset?.icon_url, - isNativeAsset: isNativeAsset(address, chainName), + isNativeAsset: isNativeAsset(address, chainId), name: asset?.name || lang.t('account.unknown_token'), mainnet_address: mainnetAddress, mainnetAddress, diff --git a/src/resources/assets/externalAssetsQuery.ts b/src/resources/assets/externalAssetsQuery.ts index b68a8e7029d..0092abdeebc 100644 --- a/src/resources/assets/externalAssetsQuery.ts +++ b/src/resources/assets/externalAssetsQuery.ts @@ -5,7 +5,7 @@ import { convertAmountAndPriceToNativeDisplay, convertAmountToPercentageDisplay import { NativeCurrencyKey } from '@/entities'; import { Token } from '@/graphql/__generated__/metadata'; import { ethereumUtils } from '@/utils'; -import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute @@ -32,13 +32,13 @@ export type FormattedExternalAsset = ExternalToken & { // Query Types for External Token type ExternalTokenArgs = { address: string; - network: Network; + chainId: ChainId; currency: NativeCurrencyKey; }; // Query Key for Token Price -export const externalTokenQueryKey = ({ address, network, currency }: ExternalTokenArgs) => - createQueryKey('externalToken', { address, network, currency }, { persisterVersion: 1 }); +export const externalTokenQueryKey = ({ address, chainId, currency }: ExternalTokenArgs) => + createQueryKey('externalToken', { address, chainId, currency }, { persisterVersion: 1 }); type externalTokenQueryKey = ReturnType; @@ -55,8 +55,7 @@ const formatExternalAsset = (asset: ExternalToken, nativeCurrency: NativeCurrenc }; // Query Function for Token Price -export async function fetchExternalToken({ address, network, currency }: ExternalTokenArgs) { - const chainId = ethereumUtils.getChainIdFromNetwork(network); +export async function fetchExternalToken({ address, chainId, currency }: ExternalTokenArgs) { const response = await metadataClient.externalToken({ address, chainId, @@ -70,19 +69,19 @@ export async function fetchExternalToken({ address, network, currency }: Externa } export async function externalTokenQueryFunction({ - queryKey: [{ address, network, currency }], + queryKey: [{ address, chainId, currency }], }: QueryFunctionArgs): Promise { - if (!address || !network) return null; - return await fetchExternalToken({ address, network, currency }); + if (!address || !chainId) return null; + return await fetchExternalToken({ address, chainId, currency }); } export type ExternalTokenQueryFunctionResult = QueryFunctionResult; // Prefetch function for Token Price -export async function prefetchExternalToken({ address, network, currency }: ExternalTokenArgs) { +export async function prefetchExternalToken({ address, chainId, currency }: ExternalTokenArgs) { await queryClient.prefetchQuery( - externalTokenQueryKey({ address, network, currency }), - async () => await fetchExternalToken({ address, network, currency }), + externalTokenQueryKey({ address, chainId, currency }), + async () => await fetchExternalToken({ address, chainId, currency }), { staleTime: EXTERNAL_TOKEN_STALE_TIME, cacheTime: EXTERNAL_TOKEN_CACHE_TIME, @@ -92,13 +91,13 @@ export async function prefetchExternalToken({ address, network, currency }: Exte // Query Hook for Token Price export function useExternalToken( - { address, network, currency }: ExternalTokenArgs, + { address, chainId, currency }: ExternalTokenArgs, config: QueryConfig = {} ) { - return useQuery(externalTokenQueryKey({ address, network, currency }), externalTokenQueryFunction, { + return useQuery(externalTokenQueryKey({ address, chainId, currency }), externalTokenQueryFunction, { staleTime: EXTERNAL_TOKEN_STALE_TIME, cacheTime: EXTERNAL_TOKEN_CACHE_TIME, - enabled: !!address && !!network, + enabled: !!address && !!chainId, ...config, }); } diff --git a/src/resources/assets/useUserAsset.ts b/src/resources/assets/useUserAsset.ts index 247b3bba9ac..b94d22c15ec 100644 --- a/src/resources/assets/useUserAsset.ts +++ b/src/resources/assets/useUserAsset.ts @@ -1,10 +1,10 @@ import { ChainId } from '@/__swaps__/types/chains'; import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; import { selectUserAssetWithUniqueId } from '@/resources/assets/assetSelectors'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { getUniqueId } from '@/utils/ethereumUtils'; export function useUserAsset(uniqueId: string) { const { accountAddress, nativeCurrency } = useAccountSettings(); @@ -23,9 +23,8 @@ export function useUserAsset(uniqueId: string) { } export function useUserNativeNetworkAsset(chainId: ChainId) { - const network = getNetworkFromChainId(chainId); - const { nativeCurrency } = getNetworkObj(network); + const { nativeCurrency } = getNetworkObject({ chainId }); const { address } = nativeCurrency; - const uniqueId = `${address}_${network}`; + const uniqueId = getUniqueId(address, chainId); return useUserAsset(uniqueId); } diff --git a/src/resources/defi/utils.ts b/src/resources/defi/utils.ts index 87510df68a6..ac6062ec0dd 100644 --- a/src/resources/defi/utils.ts +++ b/src/resources/defi/utils.ts @@ -1,4 +1,4 @@ -import { NativeCurrencyKey, ZerionAsset } from '@/entities'; +import { NativeCurrencyKey } from '@/entities'; import { AddysPositionsResponse, Borrow, @@ -17,7 +17,6 @@ import { import { add, convertAmountToNativeDisplay, convertRawAmountToNativeDisplay, subtract } from '@/helpers/utilities'; import { maybeSignUri } from '@/handlers/imgix'; import { ethereumUtils } from '@/utils'; -import { Network } from '@/networks/types'; export const parsePosition = (position: Position, currency: NativeCurrencyKey): RainbowPosition => { let totalDeposits = '0'; @@ -146,7 +145,7 @@ export const parsePositions = (data: AddysPositionsResponse, currency: NativeCur parsedPositions.forEach(({ deposits }) => { deposits.forEach(({ asset }) => { - const uniqueId = ethereumUtils.getUniqueId(asset.asset_code.toLowerCase(), asset.network); + const uniqueId = ethereumUtils.getUniqueId(asset.asset_code.toLowerCase(), ethereumUtils.getChainIdFromNetwork(asset.network)); positionTokens.push(uniqueId); }); }); diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index f220740b06c..6c47d8f0da3 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -11,7 +11,7 @@ import { useQuery } from '@tanstack/react-query'; import { omit } from 'lodash'; import { externalTokenQueryKey, fetchExternalToken } from './assets/externalAssetsQuery'; -export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 3 }); +export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 4 }); const DEFAULT_FAVORITES = [DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS]; @@ -23,14 +23,13 @@ const getUniqueId = (address: AddressOrEth, chainId: ChainId) => getStandardized async function fetchMetadata(addresses: string[], chainId = ChainId.mainnet) { const favoritesMetadata: Record = {}; const newFavoritesMeta: Record = {}; - const network = ethereumUtils.getNetworkFromChainId(chainId); // Map addresses to an array of promises returned by fetchExternalToken const fetchPromises: Promise[] = addresses.map(async address => { const externalAsset = await queryClient.fetchQuery( - externalTokenQueryKey({ address, network, currency: NativeCurrencyKeys.USD }), - async () => fetchExternalToken({ address, network, currency: NativeCurrencyKeys.USD }), + externalTokenQueryKey({ address, chainId, currency: NativeCurrencyKeys.USD }), + async () => fetchExternalToken({ address, chainId, currency: NativeCurrencyKeys.USD }), { staleTime: Infinity, } @@ -40,6 +39,7 @@ async function fetchMetadata(addresses: string[], chainId = ChainId.mainnet) { const uniqueId = getUniqueId(externalAsset?.networks[chainId]?.address, chainId); newFavoritesMeta[uniqueId] = { ...externalAsset, + chainId, network, address, networks: externalAsset.networks, @@ -157,6 +157,8 @@ export function useFavorites(): { const favoritesMetadata = query.data ?? {}; const favorites = Object.keys(favoritesMetadata); + console.log('favoritesMetadata', favoritesMetadata); + return { favorites, favoritesMetadata, diff --git a/src/resources/reservoir/utils.ts b/src/resources/reservoir/utils.ts index a4c3635ee21..ec4dcf2140c 100644 --- a/src/resources/reservoir/utils.ts +++ b/src/resources/reservoir/utils.ts @@ -1,4 +1,4 @@ -import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const RAINBOW_FEE_ADDRESS_MAINNET = '0x69d6d375de8c7ade7e44446df97f49e661fdad7d'; const RAINBOW_FEE_ADDRESS_POLYGON = '0xfb9af3db5e19c4165f413f53fe3bbe6226834548'; @@ -8,21 +8,21 @@ const RAINBOW_FEE_ADDRESS_BASE = '0x1bbe055ad3204fa4468b4e6d3a3c59b9d9ac8c19'; const RAINBOW_FEE_ADDRESS_BSC = '0x9670271ec2e2937a2e9df536784344bbff2bbea6'; const RAINBOW_FEE_ADDRESS_ZORA = '0x7a3d05c70581bd345fe117c06e45f9669205384f'; -export function getRainbowFeeAddress(network: Network) { - switch (network) { - case Network.mainnet: +export function getRainbowFeeAddress(chainId: ChainId) { + switch (chainId) { + case ChainId.mainnet: return RAINBOW_FEE_ADDRESS_MAINNET; - case Network.polygon: + case ChainId.polygon: return RAINBOW_FEE_ADDRESS_POLYGON; - case Network.optimism: + case ChainId.optimism: return RAINBOW_FEE_ADDRESS_OPTIMISM; - case Network.arbitrum: + case ChainId.arbitrum: return RAINBOW_FEE_ADDRESS_ARBITRUM; - case Network.base: + case ChainId.base: return RAINBOW_FEE_ADDRESS_BASE; - case Network.bsc: + case ChainId.bsc: return RAINBOW_FEE_ADDRESS_BSC; - case Network.zora: + case ChainId.zora: return RAINBOW_FEE_ADDRESS_ZORA; default: return undefined; diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts index 32bb45672f0..b1324ca1e4b 100644 --- a/src/resources/transactions/consolidatedTransactions.ts +++ b/src/resources/transactions/consolidatedTransactions.ts @@ -7,6 +7,7 @@ import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { RainbowNetworks } from '@/networks'; import { parseTransaction } from '@/parsers/transactions'; +import { ethereumUtils } from '@/utils'; const CONSOLIDATED_TRANSACTIONS_INTERVAL = 30000; const CONSOLIDATED_TRANSACTIONS_TIMEOUT = 20000; @@ -107,7 +108,9 @@ async function parseConsolidatedTransactions( ): Promise { const data = message?.payload?.transactions || []; - const parsedTransactionPromises = data.map((tx: TransactionApiResponse) => parseTransaction(tx, currency)); + const parsedTransactionPromises = data.map((tx: TransactionApiResponse) => + parseTransaction(tx, currency, ethereumUtils.getChainIdFromNetwork(tx.network)) + ); // Filter out undefined values immediately const parsedConsolidatedTransactions = (await Promise.all(parsedTransactionPromises)).flat(); // Filter out any remaining undefined values diff --git a/src/resources/transactions/transaction.ts b/src/resources/transactions/transaction.ts index c2253efbb74..b4210f302bc 100644 --- a/src/resources/transactions/transaction.ts +++ b/src/resources/transactions/transaction.ts @@ -53,7 +53,7 @@ export const fetchTransaction = async ({ if (!tx) { return null; } - const parsedTx = await parseTransaction(tx, currency); + const parsedTx = await parseTransaction(tx, currency, chainId); if (!parsedTx) throw new Error('Failed to parse transaction'); return parsedTx; } catch (e) { diff --git a/src/screens/AddCash/components/ProviderCard.tsx b/src/screens/AddCash/components/ProviderCard.tsx index fc59984cc6a..cbe8ffcb9a1 100644 --- a/src/screens/AddCash/components/ProviderCard.tsx +++ b/src/screens/AddCash/components/ProviderCard.tsx @@ -18,6 +18,7 @@ import { convertAPINetworkToInternalNetwork } from '@/screens/AddCash/utils'; import { ProviderConfig, CalloutType, PaymentMethod } from '@/screens/AddCash/types'; import * as i18n from '@/languages'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; +import { ethereumUtils } from '@/utils'; type PaymentMethodConfig = { name: string; @@ -95,7 +96,11 @@ function NetworkIcons({ networks }: { networks: Network[] }) { borderRadius: 30, }} > - {network !== Network.mainnet ? : } + {network !== Network.mainnet ? ( + + ) : ( + + )} ); })} diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx index d6ac2404fa1..25abf58d032 100644 --- a/src/screens/CurrencySelectModal.tsx +++ b/src/screens/CurrencySelectModal.tsx @@ -6,7 +6,6 @@ import React, { Fragment, ReactElement, useCallback, useEffect, useMemo, useRef, import { DefaultSectionT, InteractionManager, Keyboard, Linking, SectionList, TextInput } from 'react-native'; import { MMKV } from 'react-native-mmkv'; import Animated from 'react-native-reanimated'; -import { useDispatch } from 'react-redux'; import { useDebounce } from 'use-debounce'; import GestureBlocker from '../components/GestureBlocker'; import { CurrencySelectionList, CurrencySelectModalHeader, ExchangeSearch } from '../components/exchange'; @@ -15,7 +14,7 @@ import { KeyboardFixedOpenLayout } from '../components/layout'; import { Modal } from '../components/modal'; import { STORAGE_IDS } from '../model/mmkv'; import { analytics } from '@/analytics'; -import { addHexPrefix, isL2Network } from '@/handlers/web3'; +import { addHexPrefix, isL2Chain } from '@/handlers/web3'; import { CurrencySelectionTypes, Network, TokenSectionTypes } from '@/helpers'; import { useAccountSettings, @@ -40,8 +39,8 @@ import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import DiscoverSearchInput from '@/components/discover/DiscoverSearchInput'; import { externalTokenQueryKey, fetchExternalToken } from '@/resources/assets/externalAssetsQuery'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; -import { getNetworkObj } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; +import { ChainId } from '@/__swaps__/types/chains'; export interface EnrichedExchangeAsset extends SwappableAsset { ens: boolean; @@ -100,7 +99,6 @@ export default function CurrencySelectModal() { const { goBack, navigate, getState: dangerouslyGetState } = useNavigation(); const { nativeCurrency } = useAccountSettings(); const { colors } = useTheme(); - const dispatch = useDispatch(); const { params: { defaultOutputAsset, @@ -318,7 +316,7 @@ export default function CurrencySelectModal() { ); const checkForRequiredAssets = useCallback( (item: any) => { - if (type === CurrencySelectionTypes.output && currentChainId && currentChainId !== getNetworkObj(Network.mainnet).id) { + if (type === CurrencySelectionTypes.output && currentChainId && currentChainId !== ChainId.mainnet) { const currentL2Name = ethereumUtils.getNetworkNameFromChainId(currentChainId); const currentL2WalletAssets = assetsInWallet.filter( ({ network }) => network && network?.toLowerCase() === currentL2Name?.toLowerCase() @@ -350,18 +348,16 @@ export default function CurrencySelectModal() { const selectAsset = async () => { if (!item?.balance) { - const network = getNetworkFromChainId(currentChainId); - const externalAsset = await queryClient.fetchQuery( externalTokenQueryKey({ address: item.address, - network, + chainId: currentChainId, currency: nativeCurrency, }), async () => fetchExternalToken({ address: item.address, - network, + chainId: currentChainId, currency: nativeCurrency, }), { @@ -407,15 +403,14 @@ export default function CurrencySelectModal() { ); const itemProps = useMemo(() => { - const isMainnet = currentChainId === getNetworkObj(Network.mainnet).id; return { onPress: handleSelectAsset, showBalance: type === CurrencySelectionTypes.input, - showFavoriteButton: type === CurrencySelectionTypes.output && isMainnet, + showFavoriteButton: type === CurrencySelectionTypes.output && currentChainId === ChainId.mainnet, }; }, [handleSelectAsset, type, currentChainId]); - const searchingOnL2Network = useMemo(() => isL2Network(ethereumUtils.getNetworkFromChainId(currentChainId)), [currentChainId]); + const searchingOnL2Network = useMemo(() => isL2Chain({ chainId: currentChainId }), [currentChainId]); const [startInteraction] = useInteraction(); useEffect(() => { diff --git a/src/screens/ENSConfirmRegisterSheet.tsx b/src/screens/ENSConfirmRegisterSheet.tsx index 5850616770d..beb2f56167c 100644 --- a/src/screens/ENSConfirmRegisterSheet.tsx +++ b/src/screens/ENSConfirmRegisterSheet.tsx @@ -40,6 +40,7 @@ import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDomina import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { ActionTypes } from '@/hooks/useENSRegistrationActionHandler'; +import { ChainId } from '@/__swaps__/types/chains'; export const ENSConfirmRegisterSheetHeight = 600; export const ENSConfirmRenewSheetHeight = 560; @@ -84,7 +85,7 @@ function TransactionActionRow({ {/* @ts-expect-error JavaScript component */} diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index de78160a168..7398cd95bb7 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -29,7 +29,7 @@ import { Box, Row, Rows } from '@/design-system'; import { GasFee, LegacyGasFee, LegacyGasFeeParams, SwappableAsset } from '@/entities'; import { ExchangeModalTypes, isKeyboardOpen, Network } from '@/helpers'; import { KeyboardType } from '@/helpers/keyboardTypes'; -import { getProviderForNetwork, getFlashbotsProvider } from '@/handlers/web3'; +import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; import { delay, greaterThan } from '@/helpers/utilities'; import { useAccountSettings, @@ -56,7 +56,7 @@ import { ethereumUtils, gasUtils } from '@/utils'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; import logger from '@/utils/logger'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { ChainId, CrosschainQuote, Quote } from '@rainbow-me/swaps'; +import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; import store from '@/redux/store'; import { getCrosschainSwapServiceTime, isUnwrapNative, isWrapNative } from '@/handlers/swap'; import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; @@ -64,14 +64,14 @@ import { Wallet } from '@ethersproject/wallet'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { useTheme } from '@/theme'; import { logger as loggr } from '@/logger'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import Animated from 'react-native-reanimated'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; import { getNextNonce } from '@/state/nonces'; import { getChainName } from '@/__swaps__/utils/chains'; -import { ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { AddressOrEth, ParsedAsset } from '@/__swaps__/types/assets'; import { TokenColors } from '@/graphql/__generated__/metadata'; import { estimateSwapGasLimit } from '@/raps/actions'; @@ -79,25 +79,26 @@ import { estimateCrosschainSwapGasLimit } from '@/raps/actions/crosschainSwap'; import { parseGasParamAmounts } from '@/parsers'; export const DEFAULT_SLIPPAGE_BIPS = { - [Network.mainnet]: 100, - [Network.polygon]: 200, - [Network.base]: 200, - [Network.bsc]: 200, + [ChainId.mainnet]: 100, + [ChainId.polygon]: 200, + [ChainId.base]: 200, + [ChainId.bsc]: 200, [Network.optimism]: 200, - [Network.arbitrum]: 200, - [Network.goerli]: 100, - [Network.gnosis]: 200, - [Network.zora]: 200, - [Network.avalanche]: 200, - [Network.blast]: 200, - [Network.degen]: 200, + [ChainId.arbitrum]: 200, + [ChainId.goerli]: 100, + [ChainId.gnosis]: 200, + [ChainId.zora]: 200, + [ChainId.avalanche]: 200, + [ChainId.blast]: 200, + [ChainId.degen]: 200, }; -export const getDefaultSlippageFromConfig = (network: Network) => { +export const getDefaultSlippageFromConfig = (chainId: ChainId) => { const configSlippage = getRemoteConfig().default_slippage_bips as unknown as { [network: string]: number; }; - const slippage = configSlippage?.[network] ?? DEFAULT_SLIPPAGE_BIPS[network] ?? 100; + const network = ethereumUtils.getNetworkFromChainId(chainId); + const slippage = configSlippage?.[network] ?? DEFAULT_SLIPPAGE_BIPS[chainId] ?? 100; return slippage; }; const NOOP = () => null; @@ -114,7 +115,7 @@ interface ExchangeModalProps { typeSpecificParams: TypeSpecificParameters; } -export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, type, typeSpecificParams }: ExchangeModalProps) { +export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, type, typeSpecificParams }: ExchangeModalProps) { const { isHardwareWallet } = useWallets(); const dispatch = useDispatch(); const { slippageInBips, maxInputUpdate, flipCurrenciesUpdate } = useSwapSettings(); @@ -146,13 +147,14 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te updateGasFeeOption, updateTxFee, txNetwork, + isGasReady, } = useGas(); - const { accountAddress, flashbotsEnabled, nativeCurrency, network } = useAccountSettings(); + const { accountAddress, flashbotsEnabled, nativeCurrency } = useAccountSettings(); const [isAuthorizing, setIsAuthorizing] = useState(false); const prevGasFeesParamsBySpeed = usePrevious(gasFeeParamsBySpeed); - const prevTxNetwork = usePrevious(txNetwork); + const prevChainId = usePrevious(ethereumUtils.getChainIdFromNetwork(txNetwork)); const keyboardListenerSubscription = useRef(); @@ -172,64 +174,64 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te const { updateInputAmount, updateMaxInputAmount, updateNativeAmount, updateOutputAmount } = useSwapInputHandlers(); - const { inputNetwork, outputNetwork, chainId, currentNetwork, isCrosschainSwap, isBridgeSwap } = useMemo(() => { - const inputNetwork = inputCurrency?.network || Network.mainnet; - const outputNetwork = outputCurrency?.network || Network.mainnet; - const chainId = ethereumUtils.getChainIdFromNetwork(inputNetwork || outputNetwork); + const { inputChainId, outputChainId, currentChainId, isCrosschainSwap, isBridgeSwap } = useMemo(() => { + const inputChainId = inputCurrency?.chainId || ChainId.mainnet; + const outputChainId = outputCurrency?.chainId || ChainId.mainnet; + const chainId: ChainId = inputChainId || outputChainId; - const currentNetwork = ethereumUtils.getNetworkFromChainId(chainId); - const isCrosschainSwap = crosschainSwapsEnabled && inputNetwork !== outputNetwork; + const isCrosschainSwap = crosschainSwapsEnabled && inputChainId !== outputChainId; const isBridgeSwap = inputCurrency?.symbol === outputCurrency?.symbol; return { - inputNetwork, - outputNetwork, - chainId, - currentNetwork, + inputChainId, + outputChainId, + currentChainId: chainId, isCrosschainSwap, isBridgeSwap, }; - }, [crosschainSwapsEnabled, inputCurrency?.symbol, inputCurrency?.network, outputCurrency?.symbol, outputCurrency?.network]); - - const { flipCurrencies, navigateToSelectInputCurrency, navigateToSelectOutputCurrency, updateAndFocusInputAmount } = - useSwapCurrencyHandlers({ - currentNetwork, - inputNetwork, - outputNetwork, - defaultInputAsset, - defaultOutputAsset, - fromDiscover, - ignoreInitialTypeCheck, - inputFieldRef, - lastFocusedInputHandle, - nativeFieldRef, - outputFieldRef, - setLastFocusedInputHandle, - title, - type, - }); + }, [inputCurrency?.chainId, inputCurrency?.symbol, outputCurrency?.chainId, outputCurrency?.symbol, crosschainSwapsEnabled]); + + const { flipCurrencies, navigateToSelectInputCurrency, navigateToSelectOutputCurrency } = useSwapCurrencyHandlers({ + inputChainId, + outputChainId, + defaultInputAsset, + defaultOutputAsset, + fromDiscover, + ignoreInitialTypeCheck, + inputFieldRef, + lastFocusedInputHandle, + nativeFieldRef, + outputFieldRef, + setLastFocusedInputHandle, + title, + type, + }); const speedUrgentSelected = useRef(false); useEffect(() => { - if (!speedUrgentSelected.current && !isEmpty(gasFeeParamsBySpeed) && getNetworkObj(currentNetwork).swaps?.defaultToFastGas) { + if ( + !speedUrgentSelected.current && + !isEmpty(gasFeeParamsBySpeed) && + getNetworkObject({ chainId: currentChainId }).swaps?.defaultToFastGas + ) { // Default to fast for networks with speed options updateGasFeeOption(gasUtils.FAST); speedUrgentSelected.current = true; } - }, [currentNetwork, gasFeeParamsBySpeed, selectedGasFee, updateGasFeeOption, updateTxFee]); + }, [currentChainId, gasFeeParamsBySpeed, selectedGasFee, updateGasFeeOption, updateTxFee]); useEffect(() => { - if (currentNetwork !== prevTxNetwork) { + if (currentChainId !== prevChainId) { speedUrgentSelected.current = false; } - }, [currentNetwork, prevTxNetwork]); + }, [currentChainId, prevChainId, txNetwork]); const defaultGasLimit = useMemo(() => { - return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); - }, [chainId]); + return ethereumUtils.getBasicSwapGasLimit(Number(currentChainId)); + }, [currentChainId]); const { result: { - derivedValues: { inputAmount, nativeAmount, outputAmount }, + derivedValues: { inputAmount, outputAmount }, displayValues: { inputAmountDisplay, outputAmountDisplay, nativeAmountDisplay }, tradeDetails, }, @@ -241,11 +243,11 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te const lastTradeDetails = usePrevious(tradeDetails); const isSufficientBalance = useSwapIsSufficientBalance(inputAmount); - const { priceImpact, outputNativeAmount } = usePriceImpactDetails(inputCurrency, outputCurrency, tradeDetails, currentNetwork); + const { priceImpact, outputNativeAmount } = usePriceImpactDetails(inputCurrency, outputCurrency, tradeDetails, currentChainId); const [debouncedIsHighPriceImpact] = useDebounce(priceImpact.type !== SwapPriceImpactType.none, 1000); // For a limited period after the merge we need to block the use of flashbots. // This line should be removed after reenabling flashbots in remote config. - const swapSupportsFlashbots = getNetworkObj(currentNetwork).features.flashbots; + const swapSupportsFlashbots = getNetworkObject({ chainId: currentChainId }).features.flashbots; const flashbots = swapSupportsFlashbots && flashbotsEnabled; const isDismissing = useRef(false); @@ -275,15 +277,9 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te }, [addListener, dangerouslyGetParent, lastFocusedInputHandle]); useEffect(() => { - let slippage = DEFAULT_SLIPPAGE_BIPS?.[currentNetwork]; - const configSlippage = default_slippage_bips as unknown as { - [network: string]: number; - }; - if (configSlippage?.[currentNetwork]) { - slippage = configSlippage?.[currentNetwork]; - } + const slippage = getDefaultSlippageFromConfig(currentChainId); slippage && dispatch(updateSwapSlippage(slippage)); - }, [currentNetwork, default_slippage_bips, dispatch]); + }, [currentChainId, default_slippage_bips, dispatch]); useEffect(() => { return () => { @@ -295,17 +291,19 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te const updateGasLimit = useCallback(async () => { try { - const provider = getProviderForNetwork(currentNetwork); + const provider = getProvider({ chainId: currentChainId }); const quote = isCrosschainSwap ? (tradeDetails as CrosschainQuote) : (tradeDetails as Quote); const gasLimit = await (isCrosschainSwap ? estimateCrosschainSwapGasLimit : estimateSwapGasLimit)({ - chainId, - quote: quote as any, // this is a temporary fix until we have the correct type coersion here + chainId: currentChainId, + quote: quote as CrosschainQuote, }); + if (gasLimit) { - if (getNetworkObj(currentNetwork).gas?.OptimismTxFee) { + if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { if (tradeDetails) { const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { data: tradeDetails.data, @@ -326,7 +324,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te } catch (error) { updateTxFee(defaultGasLimit, null); } - }, [chainId, currentNetwork, defaultGasLimit, isCrosschainSwap, tradeDetails, updateTxFee]); + }, [currentChainId, defaultGasLimit, isCrosschainSwap, tradeDetails, updateTxFee]); useEffect(() => { if (tradeDetails && !equal(tradeDetails, lastTradeDetails)) { @@ -345,24 +343,24 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te useEffect(() => { if ( !isGasReady || - (!prevTxNetwork && txNetwork !== prevTxNetwork) || + (!prevChainId && currentChainId !== prevChainId) || (!isEmpty(gasFeeParamsBySpeed) && !isEqual(gasFeeParamsBySpeed, prevGasFeesParamsBySpeed)) ) { updateGasLimit(); } - }, [gasFeeParamsBySpeed, isGasReady, prevGasFeesParamsBySpeed, prevTxNetwork, txNetwork, updateGasLimit]); + }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, txNetwork, updateGasLimit]); // Listen to gas prices, Uniswap reserves updates useEffect(() => { updateDefaultGasLimit(defaultGasLimit); InteractionManager.runAfterInteractions(() => { // Start polling in the current network - startPollingGasFees(currentNetwork, flashbots); + startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId), flashbots); }); return () => { stopPollingGasFees(); }; - }, [defaultGasLimit, currentNetwork, startPollingGasFees, stopPollingGasFees, updateDefaultGasLimit, flashbots]); + }, [defaultGasLimit, currentChainId, startPollingGasFees, stopPollingGasFees, updateDefaultGasLimit, flashbots]); const checkGasVsOutput = async (gasPrice: string, outputPrice: string) => { if (greaterThan(outputPrice, 0) && greaterThan(gasPrice, outputPrice) && !(IS_ANDROID && IS_TEST)) { @@ -396,12 +394,12 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te }); const submit = useCallback( - async (amountInUSD: any): Promise => { + async (amountInUSD: string): Promise => { setIsAuthorizing(true); const NotificationManager = ios ? NativeModules.NotificationManager : null; try { // load the correct network provider for the wallet - const provider = getProviderForNetwork(currentNetwork); + const provider = getProvider({ chainId: currentChainId }); let wallet = await loadWallet({ address: accountAddress, showErrorIfNotLoaded: false, @@ -416,7 +414,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te // Switch to the flashbots provider if enabled // TODO(skylarbarrera): need to check if ledger and handle differently here - if (flashbots && getNetworkObj(currentNetwork).features?.flashbots && wallet instanceof Wallet) { + if (flashbots && getNetworkObject({ chainId: currentChainId }).features?.flashbots && wallet instanceof Wallet) { logger.debug('flashbots provider being set on mainnet'); const flashbotsProvider = await getFlashbotsProvider(); wallet = new Wallet(wallet.privateKey, flashbotsProvider); @@ -435,22 +433,22 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te } logger.log('[exchange - handle submit] rap'); - const currentNonce = await getNextNonce({ address: accountAddress, network: currentNetwork }); + const currentNonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); const { independentField, independentValue, slippageInBips, source } = store.getState().swap; const transformedAssetToSell = { ...inputCurrency, - chainName: getChainName({ chainId: inputCurrency.chainId! }) as ChainName, + chainName: getChainName({ chainId: inputCurrency.chainId as number }) as ChainName, address: inputCurrency.address as AddressOrEth, - chainId: inputCurrency.chainId!, + chainId: inputCurrency.chainId, colors: inputCurrency.colors as TokenColors, } as ParsedAsset; const transformedAssetToBuy = { ...outputCurrency, - chainName: getChainName({ chainId: outputCurrency.chainId! }) as ChainName, + chainName: getChainName({ chainId: outputCurrency.chainId as number }) as ChainName, address: outputCurrency.address as AddressOrEth, - chainId: outputCurrency.chainId!, + chainId: outputCurrency.chainId, colors: outputCurrency.colors as TokenColors, } as ParsedAsset; @@ -469,8 +467,8 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te ); }; - const { nonce, errorMessage } = await walletExecuteRap(wallet, isCrosschainSwap ? 'crosschainSwap' : 'swap', { - chainId, + const { errorMessage } = await walletExecuteRap(wallet, isCrosschainSwap ? 'crosschainSwap' : 'swap', { + chainId: currentChainId, flashbots, nonce: currentNonce, assetToSell: transformedAssetToSell, @@ -523,7 +521,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te legacyGasPrice: (selectedGasFee?.gasFeeParams as unknown as LegacyGasFeeParams)?.gasPrice?.amount || '', liquiditySources: JSON.stringify(tradeDetails?.protocols || []), maxNetworkFee: (selectedGasFee?.gasFee as GasFee)?.maxFee?.value?.amount || '', - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), networkFee: selectedGasFee?.gasFee?.estimatedFee?.value?.amount || '', outputTokenAddress: outputCurrency?.address || '', outputTokenName: outputCurrency?.name || '', @@ -562,10 +560,10 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te }, [ accountAddress, - chainId, - currentNetwork, + currentChainId, debouncedIsHighPriceImpact, flashbots, + gasFeeParamsBySpeed, goBack, inputAmount, inputCurrency, @@ -576,9 +574,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te outputAmount, outputCurrency, priceImpact.percentDisplay, - selectedGasFee?.gasFee, - selectedGasFee?.gasFeeParams, - selectedGasFee?.option, + selectedGasFee, setParams, tradeDetails, type, @@ -606,7 +602,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te legacyGasPrice: (selectedGasFee?.gasFeeParams as unknown as LegacyGasFeeParams)?.gasPrice?.amount || '', liquiditySources: JSON.stringify(tradeDetails?.protocols || []), maxNetworkFee: (selectedGasFee?.gasFee as GasFee)?.maxFee?.value?.amount || '', - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), networkFee: selectedGasFee?.gasFee?.estimatedFee?.value?.amount || '', outputTokenAddress: outputCurrency?.address || '', outputTokenName: outputCurrency?.name || '', @@ -636,26 +632,26 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te selectedGasFee?.gasFee, selectedGasFee?.option, selectedGasFee?.gasFeeParams, - inputCurrency?.address, - inputCurrency?.name, - inputCurrency?.symbol, - outputCurrency?.address, - outputCurrency?.name, - outputCurrency?.symbol, slippageInBips, type, tradeDetails?.source, tradeDetails?.protocols, + inputCurrency?.address, + inputCurrency?.name, + inputCurrency?.symbol, isHardwareWallet, debouncedIsHighPriceImpact, - currentNetwork, + currentChainId, + outputCurrency?.address, + outputCurrency?.name, + outputCurrency?.symbol, priceImpact.percentDisplay, submit, ]); const confirmButtonProps = useMemoOne( () => ({ - currentNetwork, + currentNetwork: ethereumUtils.getNetworkFromChainId(currentChainId), disabled: !Number(inputAmount) || (!loading && !tradeDetails), inputAmount, isAuthorizing, @@ -670,7 +666,6 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te isBridgeSwap, }), [ - currentNetwork, loading, handleSubmit, inputAmount, @@ -698,7 +693,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te setParams({ focused: false }); navigate(Routes.SWAP_SETTINGS_SHEET, { asset: outputCurrency, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), restoreFocusOnSwapModal: () => { android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); setParams({ focused: true }); @@ -721,7 +716,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te setParams, navigate, outputCurrency, - currentNetwork, + currentChainId, swapSupportsFlashbots, ]); @@ -738,7 +733,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te setParams({ focused: false }); navigate(Routes.SWAP_DETAILS_SHEET, { confirmButtonProps, - currentNetwork, + currentNetwork: ethereumUtils.getNetworkFromChainId(currentChainId), flashbotTransaction: flashbots, isRefuelTx, restoreFocusOnSwapModal: () => { @@ -765,7 +760,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te }, [ confirmButtonProps, - currentNetwork, + currentChainId, flashbots, inputCurrency?.address, inputCurrency?.name, @@ -788,8 +783,8 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te lastFocusedInput?.blur(); navigate(Routes.EXPLAIN_SHEET, { inputToken: inputCurrency?.symbol, - fromNetwork: inputNetwork, - toNetwork: outputNetwork, + fromNetwork: ethereumUtils.getNetworkFromChainId(inputChainId), + toNetwork: ethereumUtils.getNetworkFromChainId(outputChainId), isCrosschainSwap, isBridgeSwap, onClose: () => { @@ -804,13 +799,13 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te }); }, [ inputCurrency?.symbol, - inputNetwork, + inputChainId, isBridgeSwap, isCrosschainSwap, lastFocusedInputHandle, navigate, outputCurrency?.symbol, - outputNetwork, + outputChainId, ]); const showConfirmButton = !!inputCurrency && !!outputCurrency; @@ -842,6 +837,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te { @@ -869,11 +864,12 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te { - navigateToSelectOutputCurrency(chainId); + navigateToSelectOutputCurrency(currentChainId); }} + // eslint-disable-next-line react/jsx-props-no-spreading {...(isCrosschainSwap && !!outputCurrency && { onTapWhileDisabled: handleTapWhileDisabled, @@ -915,6 +911,7 @@ export default function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, te {showConfirmButton && ( { const colors = theme?.colors; + const fromNetworkObject = getNetworkObj(params?.fromNetwork); + const toNetworkObject = getNetworkObj(params?.toNetwork); return { op_rewards_airdrop_timing: { emoji: '📦', @@ -207,7 +210,7 @@ export const explainers = (params, theme) => { title: params?.inputToken ? lang.t(`explain.output_disabled.${params?.isCrosschainSwap ? 'title_crosschain' : 'title'}`, { inputToken: params?.inputToken, - fromNetwork: getNetworkObj(params?.fromNetwork).name, + fromNetwork: fromNetworkObject.name, }) : lang.t('explain.output_disabled.title_empty'), @@ -215,18 +218,18 @@ export const explainers = (params, theme) => { ? lang.t(`explain.output_disabled.${params?.isBridgeSwap ? 'text_bridge' : 'text_crosschain'}`, { inputToken: params?.inputToken, outputToken: params?.outputToken, - fromNetwork: getNetworkObj(params?.fromNetwork).name, - toNetwork: getNetworkObj(params?.toNetwork).name, + fromNetwork: fromNetworkObject.name, + toNetwork: toNetworkObject.name, }) : lang.t('explain.output_disabled.text', { - fromNetwork: getNetworkObj(params?.fromNetwork)?.name, + fromNetwork: fromNetworkObject?.name, inputToken: params?.inputToken, outputToken: params?.outputToken, }), - logo: !isL2Network(params?.fromNetwork) ? ( + logo: !isL2Chain({ chainId: fromNetworkObject.id }) ? ( ) : ( - + ), }, floor_price: { @@ -241,7 +244,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - network={params?.network?.toLowerCase()} + chainId={ethereumUtils.getChainIdFromNetwork(params?.network)} colors={params?.nativeAsset?.colors} theme={theme} /> @@ -376,7 +379,7 @@ export const explainers = (params, theme) => { optimism: { emoji: '⛽️', extraHeight: 150, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -389,7 +392,7 @@ export const explainers = (params, theme) => { arbitrum: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -402,7 +405,7 @@ export const explainers = (params, theme) => { polygon: { emoji: '⛽️', extraHeight: 120, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -415,7 +418,7 @@ export const explainers = (params, theme) => { bsc: { emoji: '⛽️', extraHeight: IS_ANDROID ? 120 : 160, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -428,7 +431,7 @@ export const explainers = (params, theme) => { zora: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -441,7 +444,7 @@ export const explainers = (params, theme) => { base: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -454,7 +457,7 @@ export const explainers = (params, theme) => { avalanche: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -467,7 +470,7 @@ export const explainers = (params, theme) => { degen: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -480,7 +483,7 @@ export const explainers = (params, theme) => { blast: { emoji: '⛽️', extraHeight: 144, - logo: , + logo: , readMoreLink: buildRainbowLearnUrl({ url: 'https://learn.rainbow.me/layer-2-and-layer-3-networks', query: { @@ -540,7 +543,12 @@ export const explainers = (params, theme) => { title: `Switching to ${getNetworkObj(params?.network)?.name}`, logo: params?.network !== 'mainnet' ? ( - + ) : ( ), @@ -588,7 +596,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - network={params?.inputCurrency?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -644,7 +652,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - network={params?.inputCurrency?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -653,7 +661,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - network={params?.outputCurrency?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -670,7 +678,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - network={params?.inputCurrency?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -679,7 +687,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - network={params?.outputCurrency?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -718,7 +726,7 @@ export const explainers = (params, theme) => { zIndex={params?.networks?.length - index} > {network !== 'mainnet' ? ( - + ) : ( @@ -771,7 +779,7 @@ export const explainers = (params, theme) => { {lang.t('explain.obtain_l2_asset.fragment3')} ), - logo: , + logo: , title: lang.t('explain.obtain_l2_asset.title', { networkName: params?.networkName, }), @@ -871,7 +879,7 @@ export const explainers = (params, theme) => { size={30} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - network={params?.nativeAsset?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.nativeAsset?.network)} colors={params?.nativeAssety?.colors} theme={theme} ignoreBadge @@ -916,7 +924,7 @@ export const explainers = (params, theme) => { size={30} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - network={params?.nativeAsset?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.nativeAsset?.network)} colors={params?.nativeAsset?.colors} theme={theme} ignoreBadge @@ -962,7 +970,7 @@ export const explainers = (params, theme) => { size={30} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - network={params?.nativeAsset?.network} + chainId={ethereumUtils.getChainIdFromNetwork(params?.nativeAsset?.network)} colors={params?.nativeAsset?.colors} theme={theme} /> diff --git a/src/screens/MintsSheet/card/Card.tsx b/src/screens/MintsSheet/card/Card.tsx index 2e67519d839..1870ce8454e 100644 --- a/src/screens/MintsSheet/card/Card.tsx +++ b/src/screens/MintsSheet/card/Card.tsx @@ -12,9 +12,9 @@ import { useTheme } from '@/theme'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import ChainBadge from '@/components/coin-icon/ChainBadge'; -import { Network } from '@/helpers'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; export const NUM_NFTS = 3; @@ -56,8 +56,8 @@ export function Card({ collection }: { collection: MintableCollection }) { - {network !== Network.mainnet ? ( - + {collection.chainId !== ChainId.mainnet ? ( + ) : ( )} diff --git a/src/screens/NFTOffersSheet/OfferRow.tsx b/src/screens/NFTOffersSheet/OfferRow.tsx index 9c29c3ac80b..2fb228536a2 100644 --- a/src/screens/NFTOffersSheet/OfferRow.tsx +++ b/src/screens/NFTOffersSheet/OfferRow.tsx @@ -17,6 +17,7 @@ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { Network } from '@/networks/types'; import { useAccountSettings } from '@/hooks'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { ethereumUtils } from '@/utils'; const NFT_SIZE = 50; const MARKETPLACE_ORB_SIZE = 18; @@ -97,9 +98,10 @@ export const OfferRow = ({ offer }: { offer: NftOffer }) => { const { colorMode } = useColorMode(); const theme = useTheme(); const bgColor = useBackgroundColor('surfaceSecondaryElevated'); + const chainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ address: offer.paymentToken.address, - network: offer.network as Network, + chainId, currency: nativeCurrency, }); @@ -213,7 +215,7 @@ export const OfferRow = ({ offer }: { offer: NftOffer }) => { { try { - const networkObj = getNetworkObj(network); + const networkObj = getNetworkObject({ chainId: offerChainId }); const signer = createWalletClient({ // @ts-ignore account: accountAddress, @@ -243,18 +242,18 @@ export function NFTSingleOfferSheet() { } catch { logger.error(new RainbowError('NFT Offer: Failed to estimate gas')); } - }, [accountAddress, feeParam, network, offer, updateTxFee]); + }, [accountAddress, feeParam, offerChainId, offer, updateTxFee]); // estimate gas useEffect(() => { if (!isReadOnlyWallet && !isExpired) { - startPollingGasFees(network); + startPollingGasFees(offer?.network as Network); estimateGas(); } return () => { stopPollingGasFees(); }; - }, [estimateGas, isExpired, isReadOnlyWallet, network, startPollingGasFees, stopPollingGasFees, updateTxFee]); + }, [estimateGas, isExpired, isReadOnlyWallet, offer?.network, startPollingGasFees, stopPollingGasFees, updateTxFee]); const acceptOffer = useCallback(async () => { logger.info(`Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); @@ -281,13 +280,14 @@ export function NFTSingleOfferSheet() { const privateKey = await loadPrivateKey(accountAddress, false); // @ts-ignore const account = privateKeyToAccount(privateKey); - const networkObj = getNetworkObj(network); + const networkObj = getNetworkObject({ chainId: offerChainId }); + const signer = createWalletClient({ account, chain: networkObj, transport: http(networkObj.rpc()), }); - const nonce = await getNextNonce({ address: accountAddress, network }); + const nonce = await getNextNonce({ address: accountAddress, network: networkObj.value }); try { let errorMessage = ''; let didComplete = false; @@ -325,6 +325,7 @@ export function NFTSingleOfferSheet() { if (step.id === 'sale') { tx = { status: 'pending', + chainId: offerChainId, to: item.data?.to, from: item.data?.from, hash: item.txHashes[0].txHash, @@ -333,7 +334,7 @@ export function NFTSingleOfferSheet() { asset: { ...offer.paymentToken, network: offer.network as Network, - uniqueId: getUniqueId(offer.paymentToken.address, offer.network as Network), + uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, changes: [ { @@ -346,7 +347,7 @@ export function NFTSingleOfferSheet() { asset: { ...offer.paymentToken, network: offer.network as Network, - uniqueId: getUniqueId(offer.paymentToken.address, offer.network as Network), + uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, value: offer.grossAmount.raw, }, @@ -356,6 +357,7 @@ export function NFTSingleOfferSheet() { } else if (step.id === 'nft-approval') { tx = { status: 'pending', + chainId: offerChainId, to: item.data?.to, from: item.data?.from, hash: item.txHashes[0].txHash, @@ -421,13 +423,13 @@ export function NFTSingleOfferSheet() { } finally { setIsAccepting(false); } - }, [accountAddress, feeParam, navigate, network, nft, offer, rainbowFeeDecimal]); + }, [accountAddress, feeParam, navigate, offerChainId, nft, offer, rainbowFeeDecimal]); let buttonLabel = ''; if (!isAccepting) { if (insufficientEth) { buttonLabel = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: getNetworkObj(offer.network as Network).nativeCurrency.symbol, + tokenName: getNetworkObject({ chainId: offerChainId }).nativeCurrency.symbol, }); } else { buttonLabel = i18n.t(i18n.l.nft_offers.single_offer_sheet.hold_to_sell); @@ -521,7 +523,7 @@ export function NFTSingleOfferSheet() { diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 5af9ffd573b..dcb196b20f1 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -55,7 +55,7 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { useTheme } from '@/theme'; -import { getUniqueTokenType, promiseUtils } from '@/utils'; +import { ethereumUtils, getUniqueTokenType, promiseUtils } from '@/utils'; import logger from '@/utils/logger'; import { getNetworkObj } from '@/networks'; import { IS_ANDROID } from '@/env'; @@ -508,7 +508,7 @@ export const SendConfirmationSheet = () => { { {isENS && ( /* @ts-expect-error JavaScript component */ - + )} diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 2f05d484060..17a7cfe86dd 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -20,8 +20,8 @@ import { buildTransaction, createSignableTransaction, estimateGasLimit, - getProviderForNetwork, - isL2Network, + getProvider, + isL2Chain, resolveNameOrAddress, web3Provider, } from '@/handlers/web3'; @@ -60,12 +60,13 @@ import { NoResults } from '@/components/list'; import { NoResultsType } from '@/components/list/NoResults'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { Wallet } from '@ethersproject/wallet'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNextNonce } from '@/state/nonces'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { REGISTRATION_STEPS } from '@/helpers/ens'; +import { ChainId } from '@/__swaps__/types/chains'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; @@ -133,8 +134,8 @@ export default function SendSheet(props) { isSufficientBalance: false, nativeAmount: '', }); - const [currentNetwork, setCurrentNetwork] = useState(); - const prevNetwork = usePrevious(currentNetwork); + const [currentChainId, setCurrentChainId] = useState(); + const prevChainId = usePrevious(currentChainId); const [currentInput, setCurrentInput] = useState(''); const { params } = useRoute(); @@ -182,8 +183,8 @@ export default function SendSheet(props) { }); const isL2 = useMemo(() => { - return isL2Network(currentNetwork); - }, [currentNetwork]); + return isL2Chain({ chainId: currentChainId }); + }, [currentChainId]); const sendUpdateAssetAmount = useCallback( newAssetAmount => { @@ -264,12 +265,12 @@ export default function SendSheet(props) { // We can start fetching gas prices // after we know the network that the asset // belongs to - if (prevNetwork !== currentNetwork) { + if (prevChainId !== currentChainId) { InteractionManager.runAfterInteractions(() => { - startPollingGasFees(currentNetwork); + startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId)); }); } - }, [prevNetwork, startPollingGasFees, selected?.network, currentNetwork]); + }, [startPollingGasFees, selected.network, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -281,21 +282,21 @@ export default function SendSheet(props) { }, [stopPollingGasFees]); useEffect(() => { - const assetNetwork = selected?.network; - if (assetNetwork && (assetNetwork !== currentNetwork || !currentNetwork || prevNetwork !== currentNetwork)) { + const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); + const networkChainId = ethereumUtils.getChainIdFromNetwork(network); + if (assetChainId && (assetChainId !== currentChainId || !currentChainId || prevChainId !== currentChainId)) { let provider = web3Provider; - const selectedNetwork = selected?.network; - if (network === Network.goerli) { - setCurrentNetwork(Network.goerli); - provider = getProviderForNetwork(Network.goerli); + if (networkChainId === ChainId.goerli) { + setCurrentChainId(ChainId.goerli); + provider = getProvider({ chainId: ChainId.goerli }); setCurrentProvider(provider); } else { - setCurrentNetwork(selectedNetwork); - provider = getProviderForNetwork(selectedNetwork); + setCurrentChainId(assetChainId); + provider = getProvider({ chainId: currentChainId }); setCurrentProvider(provider); } } - }, [currentNetwork, isNft, network, prevNetwork, selected?.network, sendUpdateSelected]); + }, [currentChainId, isNft, network, prevChainId, selected?.network, sendUpdateSelected]); const onChangeNativeAmount = useCallback( newNativeAmount => { @@ -372,12 +373,12 @@ export default function SendSheet(props) { recipient: toAddress, }, currentProvider, - currentNetwork + ethereumUtils.getNetworkFromChainId(currentChainId) ); const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txData, currentProvider); updateTxFee(updatedGasLimit, null, l1GasFeeOptimism); }, - [accountAddress, amountDetails.assetAmount, currentNetwork, currentProvider, selected, toAddress, updateTxFee] + [accountAddress, amountDetails.assetAmount, currentChainId, currentProvider, selected, toAddress, updateTxFee] ); const onSubmit = useCallback( @@ -395,6 +396,8 @@ export default function SendSheet(props) { }); if (!wallet) return; + const currentChainIdNetwork = ethereumUtils.getNetworkFromChainId(currentChainId); + const validTransaction = isValidAddress && amountDetails.isSufficientBalance && isSufficientGas && isValidGas; if (!selectedGasFee?.gasFee?.estimatedFee || !validTransaction) { logger.sentry('preventing tx submit for one of the following reasons:'); @@ -410,7 +413,7 @@ export default function SendSheet(props) { let updatedGasLimit = null; // Attempt to update gas limit before sending ERC20 / ERC721 - if (!isNativeAsset(selected.address, currentNetwork)) { + if (!isNativeAsset(selected.address, currentChainId)) { try { // Estimate the tx with gas limit padding before sending updatedGasLimit = await estimateGasLimit( @@ -422,11 +425,11 @@ export default function SendSheet(props) { }, true, currentProvider, - currentNetwork + currentChainIdNetwork ); if (!lessThan(updatedGasLimit, gasLimit)) { - if (getNetworkObj(currentNetwork).gas?.OptimismTxFee) { + if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { updateTxFeeForOptimism(updatedGasLimit); } else { updateTxFee(updatedGasLimit, null); @@ -463,8 +466,8 @@ export default function SendSheet(props) { asset: selected, from: accountAddress, gasLimit: gasLimitToUse, - network: currentNetwork, - nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, network: currentNetwork })), + network: currentChainIdNetwork, + nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, network: currentChainIdNetwork })), to: toAddress, ...gasParams, }; @@ -504,7 +507,8 @@ export default function SendSheet(props) { submitSuccess = true; txDetails.hash = hash; txDetails.nonce = nonce; - txDetails.network = currentNetwork; + txDetails.network = currentChainIdNetwork; + txDetails.chainId = currentChainId; txDetails.data = data; txDetails.value = value; txDetails.txTo = signableTransaction.to; @@ -513,7 +517,7 @@ export default function SendSheet(props) { txDetails.status = 'pending'; addNewTransaction({ address: accountAddress, - network: currentNetwork, + network: currentChainIdNetwork, transaction: txDetails, }); } @@ -537,7 +541,7 @@ export default function SendSheet(props) { accountAddress, amountDetails.assetAmount, amountDetails.isSufficientBalance, - currentNetwork, + currentChainId, currentProvider, ensName, ensProfile?.data?.coinAddresses, @@ -594,7 +598,7 @@ export default function SendSheet(props) { const { buttonDisabled, buttonLabel } = useMemo(() => { const isZeroAssetAmount = Number(amountDetails.assetAmount) <= 0; - + const currentNetworkObject = getNetworkObject({ chainId: currentChainId }); let disabled = true; let label = lang.t('button.confirm_exchange.enter_amount'); @@ -606,14 +610,14 @@ export default function SendSheet(props) { !selectedGasFee || isEmpty(selectedGasFee?.gasFee) || !toAddress || - (getNetworkObj(currentNetwork).gas?.OptimismTxFee && l1GasFeeOptimism === null) + (currentNetworkObject.gas?.OptimismTxFee && l1GasFeeOptimism === null) ) { label = lang.t('button.confirm_exchange.loading'); disabled = true; } else if (!isZeroAssetAmount && !isSufficientGas) { disabled = true; label = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: getNetworkObj(currentNetwork).nativeCurrency.symbol, + tokenName: currentNetworkObject.nativeCurrency.symbol, }); } else if (!isValidGas) { disabled = true; @@ -635,7 +639,7 @@ export default function SendSheet(props) { gasFeeParamsBySpeed, selectedGasFee, toAddress, - currentNetwork, + currentChainId, l1GasFeeOptimism, isSufficientGas, isValidGas, @@ -683,7 +687,7 @@ export default function SendSheet(props) { isENS, isL2, isNft, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), profilesEnabled, to: recipient, toAddress, @@ -692,7 +696,7 @@ export default function SendSheet(props) { amountDetails, assetInputRef, buttonDisabled, - currentNetwork, + currentChainId, ensProfile, isL2, isNft, @@ -760,10 +764,11 @@ export default function SendSheet(props) { useEffect(() => { if (!currentProvider?._network?.chainId) return; - const currentProviderNetwork = ethereumUtils.getNetworkFromChainId(Number(currentProvider._network.chainId)); - const assetNetwork = selected?.network; - if (assetNetwork === currentNetwork && currentProviderNetwork === currentNetwork && isValidAddress && !isEmpty(selected)) { + const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); + const currentProviderChainId = currentProvider._network.chainId; + + if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { estimateGasLimit( { address: accountAddress, @@ -773,10 +778,10 @@ export default function SendSheet(props) { }, false, currentProvider, - currentNetwork + ethereumUtils.getNetworkFromChainId(currentChainId) ) .then(async gasLimit => { - if (getNetworkObj(currentNetwork).gas?.OptimismTxFee) { + if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { updateTxFeeForOptimism(gasLimit); } else { updateTxFee(gasLimit, null); @@ -790,7 +795,6 @@ export default function SendSheet(props) { }, [ accountAddress, amountDetails.assetAmount, - currentNetwork, currentProvider, isValidAddress, recipient, @@ -800,6 +804,7 @@ export default function SendSheet(props) { updateTxFeeForOptimism, network, isNft, + currentChainId, ]); const sendContactListDataKey = useMemo(() => `${ensSuggestions?.[0]?.address || '_'}`, [ensSuggestions]); @@ -904,7 +909,7 @@ export default function SendSheet(props) { [name, emoji]); @@ -24,8 +24,8 @@ const currencyListItems = Object.values(supportedNativeCurrencies).map(({ curren const CurrencySection = () => { const { nativeCurrency, settingsChangeNativeCurrency } = useAccountSettings(); const theme = useTheme(); - const { data: WBTC } = useExternalToken({ address: WBTC_ADDRESS, network: Network.mainnet, currency: nativeCurrency }); - const { data: ETH } = useExternalToken({ address: ETH_ADDRESS, network: Network.mainnet, currency: nativeCurrency }); + const { data: WBTC } = useExternalToken({ address: WBTC_ADDRESS, chainId: ChainId.mainnet, currency: nativeCurrency }); + const { data: ETH } = useExternalToken({ address: ETH_ADDRESS, chainId: ChainId.mainnet, currency: nativeCurrency }); const onSelectCurrency = useCallback( (currency: any) => { @@ -54,7 +54,7 @@ const CurrencySection = () => { icon={currency === ETH?.symbol ? ETH?.icon_url : WBTC?.icon_url} size={23} symbol={currency} - network={Network.mainnet} + chainId={ChainId.mainnet} theme={theme} /> diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index d4b8b505431..c3e68c1c306 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -61,10 +61,10 @@ import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWalle import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; -import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProviderForNetwork, toHex } from '@/handlers/web3'; +import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { GasSpeedButton } from '@/components/gas'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObj, getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; import { PERSONAL_SIGN, @@ -97,6 +97,7 @@ import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; import { getOnchainAssetBalance } from '@/handlers/assets'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; +import { ChainId } from '@/__swaps__/types/chains'; const COLLAPSED_CARD_HEIGHT = 56; const MAX_CARD_HEIGHT = 176; @@ -142,6 +143,7 @@ type SignTransactionSheetParams = { onCancel: (error?: Error) => void; onCloseScreen: (canceled: boolean) => void; network: Network; + chainId: ChainId; address: string; source: RequestSource; }; @@ -169,7 +171,7 @@ export const SignTransactionSheet = () => { onSuccess: onSuccessCallback, onCancel: onCancelCallback, onCloseScreen: onCloseScreenCallback, - network: currentNetwork, + chainId: currentChainId, address: currentAddress, // for request type specific handling source, @@ -236,7 +238,7 @@ export const SignTransactionSheet = () => { // use the default let gas = txPayload.gasLimit || txPayload.gas; - const provider = getProviderForNetwork(currentNetwork); + const provider = getProvider({ chainId: currentChainId }); try { // attempt to re-run estimation logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); @@ -251,21 +253,21 @@ export const SignTransactionSheet = () => { logger.error(new RainbowError('WC: error estimating gas'), { error }); } finally { logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); - - if (currentNetwork && getNetworkObj(currentNetwork).gas.OptimismTxFee) { + const networkObject = getNetworkObject({ chainId: currentChainId }); + if (networkObject && networkObject.gas.OptimismTxFee) { const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); updateTxFee(gas, null, l1GasFeeOptimism); } else { updateTxFee(gas, null); } } - }, [currentNetwork, req, updateTxFee]); + }, [currentChainId, req, updateTxFee]); const fetchMethodName = useCallback( async (data: string) => { const methodSignaturePrefix = data.substr(0, 10); try { - const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, getNetworkObj(currentNetwork).id); + const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); if (name) { setMethodName(name); } @@ -273,15 +275,16 @@ export const SignTransactionSheet = () => { setMethodName(data); } }, - [currentNetwork] + [currentChainId] ); // start polling for gas and get fn name useEffect(() => { InteractionManager.runAfterInteractions(() => { - if (currentNetwork) { + if (currentChainId) { if (!isMessageRequest) { - startPollingGasFees(currentNetwork); + const network = ethereumUtils.getNetworkFromChainId(currentChainId); + startPollingGasFees(network); fetchMethodName(transactionDetails?.payload?.params[0].data); } else { setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); @@ -289,7 +292,7 @@ export const SignTransactionSheet = () => { analytics.track(event.txRequestShownSheet), { source }; } }); - }, [isMessageRequest, currentNetwork, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source]); + }, [isMessageRequest, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source, currentChainId]); // get gas limit useEffect(() => { @@ -317,7 +320,7 @@ export const SignTransactionSheet = () => { } const { gasFee } = selectedGasFee; - if (!walletBalance?.isLoaded || !currentNetwork || !gasFee?.estimatedFee) { + if (!walletBalance?.isLoaded || !currentChainId || !gasFee?.estimatedFee) { return; } @@ -336,10 +339,10 @@ export const SignTransactionSheet = () => { const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); setIsBalanceEnough(isEnough); - }, [isMessageRequest, isSufficientGas, currentNetwork, selectedGasFee, walletBalance, req]); + }, [isMessageRequest, isSufficientGas, selectedGasFee, walletBalance, req, currentChainId]); const accountInfo = useMemo(() => { - const selectedWallet = findWalletWithAccount(wallets!, currentAddress); + const selectedWallet = wallets ? findWalletWithAccount(wallets, currentAddress) : undefined; const profileInfo = getAccountProfileInfo(selectedWallet, walletNames, currentAddress); return { ...profileInfo, @@ -352,22 +355,27 @@ export const SignTransactionSheet = () => { const initProvider = async () => { let p; // check on this o.O - if (currentNetwork === Network.mainnet) { + if (currentChainId === ChainId.mainnet) { p = await getFlashbotsProvider(); } else { - p = getProviderForNetwork(currentNetwork); + p = getProvider({ chainId: currentChainId }); } setProvider(p); }; initProvider(); - }, [currentNetwork, setProvider]); + }, [currentChainId, setProvider]); useEffect(() => { (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork(currentNetwork, accountInfo.address); + const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); if (asset && provider) { - const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentNetwork, provider); + const balance = await getOnchainAssetBalance( + asset, + accountInfo.address, + ethereumUtils.getNetworkFromChainId(currentChainId), + provider + ); if (balance) { const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; setNativeAsset(assetWithOnchainBalance); @@ -376,13 +384,13 @@ export const SignTransactionSheet = () => { } } })(); - }, [accountInfo.address, currentNetwork, provider]); + }, [accountInfo.address, currentChainId, provider]); useEffect(() => { (async () => { if (!isMessageRequest && !nonceForDisplay) { try { - const nonce = await getNextNonce({ address: currentAddress, network: currentNetwork }); + const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); if (nonce || nonce === 0) { const nonceAsString = nonce.toString(); setNonceForDisplay(nonceAsString); @@ -393,18 +401,17 @@ export const SignTransactionSheet = () => { } })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accountInfo.address, currentNetwork, getNextNonce, isMessageRequest]); + }, [accountInfo.address, currentChainId, getNextNonce, isMessageRequest]); useEffect(() => { const timeout = setTimeout(async () => { try { - const chainId = ethereumUtils.getChainIdFromNetwork(currentNetwork); let simulationData; if (isMessageRequest) { // Message Signing simulationData = await metadataPOSTClient.simulateMessage({ address: accountAddress, - chainId: chainId, + chainId: currentChainId, message: { method: transactionDetails?.payload?.method, params: [request.message], @@ -426,7 +433,7 @@ export const SignTransactionSheet = () => { } else { // TX Signing simulationData = await metadataPOSTClient.simulateTransactions({ - chainId: chainId, + chainId: currentChainId, currency: nativeCurrency?.toLowerCase(), transactions: [ { @@ -463,7 +470,7 @@ export const SignTransactionSheet = () => { }; }, [ accountAddress, - currentNetwork, + currentChainId, isMessageRequest, isPersonalSign, nativeCurrency, @@ -523,7 +530,7 @@ export const SignTransactionSheet = () => { const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); let response = null; - const provider = getProviderForNetwork(currentNetwork); + const provider = getProvider({ chainId: currentChainId }); if (!provider) { return; } @@ -571,7 +578,7 @@ export const SignTransactionSheet = () => { dappName: transactionDetails?.dappName, dappUrl: transactionDetails?.dappUrl, isHardwareWallet: accountInfo.isHardwareWallet, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), }); onSuccessCallback?.(response.result); @@ -584,7 +591,7 @@ export const SignTransactionSheet = () => { transactionDetails?.payload?.method, transactionDetails?.dappName, transactionDetails?.dappUrl, - currentNetwork, + currentChainId, accountInfo.address, accountInfo.isHardwareWallet, source, @@ -596,8 +603,9 @@ export const SignTransactionSheet = () => { const handleConfirmTransaction = useCallback(async () => { const sendInsteadOfSign = transactionDetails.payload.method === SEND_TRANSACTION; const txPayload = req; - let { gas, gasLimit: gasLimitFromPayload } = txPayload; - if (!currentNetwork) return; + let { gas } = txPayload; + const gasLimitFromPayload = txPayload?.gasLimit; + if (!currentChainId) return; try { logger.debug( 'WC: gas suggested by dapp', @@ -632,7 +640,7 @@ export const SignTransactionSheet = () => { const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, network: currentNetwork }); + const nonce = await getNextNonce({ address: accountInfo.address, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -648,10 +656,10 @@ export const SignTransactionSheet = () => { let response = null; try { - if (!currentNetwork) { + if (!currentChainId) { return; } - const provider = getProviderForNetwork(currentNetwork); + const provider = getProvider({ chainId: currentChainId }); if (!provider) { return; } @@ -708,6 +716,7 @@ export const SignTransactionSheet = () => { if (sendInsteadOfSign && sendResult?.hash) { txDetails = { status: 'pending', + chainId: currentChainId, asset: displayDetails?.request?.asset || nativeAsset, contract: { name: transactionDetails.dappName, @@ -717,7 +726,7 @@ export const SignTransactionSheet = () => { from: displayDetails?.request?.from, gasLimit, hash: sendResult.hash, - network: currentNetwork || Network.mainnet, + network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, nonce: sendResult.nonce, to: displayDetails?.request?.to, value: sendResult.value.toString(), @@ -727,7 +736,7 @@ export const SignTransactionSheet = () => { if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - network: currentNetwork || Network.mainnet, + network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, address: accountAddress, }); txSavedInCurrentWallet = true; @@ -739,7 +748,7 @@ export const SignTransactionSheet = () => { dappName: transactionDetails.dappName, dappUrl: transactionDetails.dappUrl, isHardwareWallet: accountInfo.isHardwareWallet, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), }); if (!sendInsteadOfSign) { @@ -759,7 +768,7 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - network: currentNetwork || Network.mainnet, + network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, address: txDetails?.from as string, }); }); @@ -770,7 +779,7 @@ export const SignTransactionSheet = () => { dappUrl: transactionDetails?.dappUrl, formattedDappUrl, rpcMethod: req?.method, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(currentChainId), }); // If the user is using a hardware wallet, we don't want to close the sheet on an error if (!accountInfo.isHardwareWallet) { @@ -784,7 +793,7 @@ export const SignTransactionSheet = () => { transactionDetails.dappUrl, transactionDetails.imageUrl, req, - currentNetwork, + currentChainId, selectedGasFee, gasLimit, accountInfo.address, @@ -843,7 +852,7 @@ export const SignTransactionSheet = () => { const expandedCardBottomInset = EXPANDED_CARD_BOTTOM_INSET + (isMessageRequest ? 0 : GAS_BUTTON_SPACE); - const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!currentNetwork && !!selectedGasFee?.gasFee?.estimatedFee); + const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!currentChainId && !!selectedGasFee?.gasFee?.estimatedFee); return ( @@ -915,7 +924,7 @@ export const SignTransactionSheet = () => { { /> ) : ( { ) : ( - {!!currentNetwork && walletBalance?.isLoaded && ( + {!!currentChainId && walletBalance?.isLoaded && ( - + {`${walletBalance?.display} ${i18n.t(i18n.l.walletconnect.simulation.profile_section.on_network, { - network: getNetworkObj(currentNetwork)?.name, + network: getNetworkObject({ chainId: currentChainId })?.name, })}`} @@ -1020,6 +1029,7 @@ export const SignTransactionSheet = () => { disabled={!canPressConfirm} size="big" weight="heavy" + // eslint-disable-next-line react/jsx-props-no-spreading {...((simulationError || (simulationScanResult && simulationScanResult !== TransactionScanResultType.Ok)) && { color: simulationScanResult === TransactionScanResultType.Warning ? 'orange' : colors.red, })} @@ -1051,7 +1061,7 @@ export const SignTransactionSheet = () => { ethereumUtils.openAddressInBlockExplorer( - meta?.to?.address! || toAddress || meta?.transferTo?.address || '', - currentNetwork + meta?.to?.address || toAddress || meta?.transferTo?.address || '', + ethereumUtils.getChainIdFromNetwork(currentNetwork) ) } value={ @@ -1547,7 +1557,7 @@ const SimulatedEventRow = ({ const { nativeCurrency } = useAccountSettings(); const { data: externalAsset } = useExternalToken({ address: asset?.assetCode || '', - network: (asset?.network as Network) || Network.mainnet, + chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), currency: nativeCurrency, }); @@ -1602,7 +1612,7 @@ const SimulatedEventRow = ({ )} - {detailType === 'chain' && currentNetwork && } + {detailType === 'chain' && currentNetwork && ( + + )} {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( {value} diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index 02010500e23..dc3f25f771b 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -17,7 +17,7 @@ import { Emoji, Text } from '../components/text'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { removeRegistrationByName, saveCommitRegistrationParameters } from '@/redux/ensRegistration'; import { GasFeeTypes } from '@/entities'; -import { getFlashbotsProvider, getProviderForNetwork, isL2Network, toHex } from '@/handlers/web3'; +import { getFlashbotsProvider, getProviderForNetwork, isL2Chain, toHex } from '@/handlers/web3'; import { greaterThan } from '@/helpers/utilities'; import { useAccountSettings, useDimensions, useGas, useWallets } from '@/hooks'; import { sendTransaction } from '@/model/wallet'; @@ -27,7 +27,7 @@ import { updateGasFeeForSpeed } from '@/redux/gas'; import { ethUnits } from '@/references'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { gasUtils, safeAreaInsetValues } from '@/utils'; +import { ethereumUtils, gasUtils, safeAreaInsetValues } from '@/utils'; import logger from '@/utils/logger'; import { getNetworkObj } from '@/networks'; import * as i18n from '@/languages'; @@ -125,7 +125,7 @@ export default function SpeedUpAndCancelSheet() { const [nonce, setNonce] = useState(null); const [to, setTo] = useState(tx.to); const [value, setValue] = useState(null); - const isL2 = isL2Network(tx?.network || null); + const isL2 = isL2Chain({ chainId: tx?.chainId }); const getNewTransactionGasParams = useCallback(() => { const gasParams = parseGasParamsForTransaction(selectedGasFee); @@ -475,6 +475,7 @@ export default function SpeedUpAndCancelSheet() { ({ color: colors.alpha(colors.blueGreyDark, 0.3), @@ -65,12 +66,7 @@ const SwitchText = ({ children, ...props }) => { const NetworkPill = ({ chainIds }) => { const { colors } = useTheme(); - const availableNetworks = useMemo(() => { - // we dont want to show mainnet - return chainIds - .map(network => ethereumUtils.getNetworkFromChainId(Number(network))) - .sort(network => (network === Network.mainnet ? -1 : 1)); - }, [chainIds]); + const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); const networkMenuItems = useMemo(() => { RainbowNetworks.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ @@ -83,7 +79,7 @@ const NetworkPill = ({ chainIds }) => { })); }, [chainIds]); - if (availableNetworks.length === 0) return null; + if (availableNetworkChainIds.length === 0) return null; return ( { marginRight={{ custom: -2 }} > - {availableNetworks.length > 1 ? ( + {availableNetworkChainIds.length > 1 ? ( <> - {availableNetworks.map((network, index) => { + {availableNetworkChainIds.map((chainId, index) => { return ( 0 ? -6 : 0 }} style={{ position: 'relative', backgroundColor: colors.transparent, - zIndex: availableNetworks.length - index, + zIndex: availableNetworkChainIds.length - index, borderRadius: 30, borderWidth: 2, borderColor: colors.white, }} > - {network !== Network.mainnet ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( )} @@ -132,15 +128,15 @@ const NetworkPill = ({ chainIds }) => { ) : ( - {availableNetworks[0] !== Network.mainnet ? ( - + {availableNetworkChainIds[0] !== Network.mainnet ? ( + ) : ( )} - {getNetworkObj(availableNetworks[0]).name} + {getNetworkObj(availableNetworkChainIds[0]).name} diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 2cdb1afb412..74233112e6b 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -23,6 +23,7 @@ import { Network } from '@/helpers'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; +import { ChainId } from '@/__swaps__/types/chains'; export const SearchContainer = styled(Row)({ height: '100%', @@ -50,11 +51,7 @@ export default function DiscoverSearch() { const currencySelectionListRef = useRef(); const [searchQueryForSearch] = useDebounce(searchQuery, 350); const [ensResults, setEnsResults] = useState([]); - const { swapCurrencyList, swapCurrencyListLoading } = useSearchCurrencyList( - searchQueryForSearch, - ethereumUtils.getChainIdFromNetwork(Network.mainnet), - true - ); + const { swapCurrencyList, swapCurrencyListLoading } = useSearchCurrencyList(searchQueryForSearch, ChainId.mainnet, true); // we want to debounce the poap search further const [searchQueryForPoap] = useDebounce(searchQueryForSearch, 800); diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index 1a429d370db..2a58d15fcf7 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -31,8 +31,7 @@ import * as i18n from '@/languages'; import { analyticsV2 } from '@/analytics'; import { event } from '@/analytics/event'; import { ETH_ADDRESS, ETH_SYMBOL } from '@/references'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { Network } from '@/networks/types'; +import { getNetworkObject } from '@/networks'; import { fetchReverseRecord } from '@/handlers/ens'; import { ContactAvatar } from '@/components/contacts'; import { addressHashedColorIndex } from '@/utils/profileUtils'; @@ -57,6 +56,7 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/__swaps__/types/chains'; const NFT_IMAGE_HEIGHT = 250; // inset * 2 -> 28 *2 @@ -127,6 +127,7 @@ const getFormattedDate = (date: string) => { const MintSheet = () => { const params = useRoute(); const { collection: mintCollection, pricePerMint } = params.params as MintSheetProps; + const chainId = mintCollection.chainId; const { accountAddress } = useAccountProfile(); const { nativeCurrency } = useAccountSettings(); const { height: deviceHeight, width: deviceWidth } = useDimensions(); @@ -136,7 +137,6 @@ const MintSheet = () => { const [insufficientEth, setInsufficientEth] = useState(false); const [showNativePrice, setShowNativePrice] = useState(false); const [gasError, setGasError] = useState(false); - const currentNetwork = RainbowNetworks.find(({ id }) => id === mintCollection.chainId)?.value || Network.mainnet; const [ensName, setENSName] = useState(''); const [mintStatus, setMintStatus] = useState<'none' | 'minting' | 'minted' | 'error'>('none'); const txRef = useRef(); @@ -200,7 +200,7 @@ const MintSheet = () => { // check address balance useEffect(() => { const checkInsufficientEth = async () => { - const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork(currentNetwork, accountAddress))?.balance?.amount ?? 0; + const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress))?.balance?.amount ?? 0; const totalMintPrice = multiply(price.amount, quantity); if (greaterThanOrEqualTo(totalMintPrice, nativeBalance)) { @@ -215,10 +215,10 @@ const MintSheet = () => { checkInsufficientEth(); }, [ accountAddress, - currentNetwork, + chainId, getTotalGasPrice, - mintCollection.publicMintInfo?.price?.currency?.decimals, - mintCollection.publicMintInfo?.price?.currency?.symbol, + mintCollection?.publicMintInfo?.price?.currency?.decimals, + mintCollection?.publicMintInfo?.price?.currency?.symbol, price, quantity, ]); @@ -237,17 +237,18 @@ const MintSheet = () => { // start poll gas price useEffect(() => { - startPollingGasFees(currentNetwork); + const network = ethereumUtils.getNetworkFromChainId(chainId); + startPollingGasFees(network); return () => { stopPollingGasFees(); }; - }, [currentNetwork, startPollingGasFees, stopPollingGasFees]); + }, [chainId, startPollingGasFees, stopPollingGasFees]); // estimate gas limit useEffect(() => { const estimateMintGas = async () => { - const networkObj = getNetworkObj(currentNetwork); + const networkObj = getNetworkObject({ chainId }); const signer = createWalletClient({ account: accountAddress, chain: networkObj, @@ -299,27 +300,27 @@ const MintSheet = () => { } }; estimateMintGas(); - }, [accountAddress, currentNetwork, mintCollection.id, quantity, updateTxFee]); + }, [accountAddress, chainId, mintCollection.id, quantity, updateTxFee]); const deployerDisplay = abbreviations.address(mintCollection.creator || '', 4, 6); const contractAddressDisplay = `${abbreviations.address(mintCollection.id || '', 4, 6)} 􀄯`; - const buildMintDotFunUrl = (contract: string, network: Network) => { - const MintDotFunNetworks = [Network.mainnet, Network.optimism, Network.base, Network.zora]; - if (!MintDotFunNetworks.includes(network)) { + const buildMintDotFunUrl = (contract: string, chainId: ChainId) => { + const MintDotFunNetworks = [ChainId.mainnet, ChainId.optimism, ChainId.base, ChainId.zora]; + if (!MintDotFunNetworks.includes(chainId)) { Alert.alert(i18n.t(i18n.l.minting.mintdotfun_unsupported_network)); } let chainSlug = 'ethereum'; - switch (network) { - case Network.optimism: + switch (chainId) { + case ChainId.optimism: chainSlug = 'op'; break; - case Network.base: + case ChainId.base: chainSlug = 'base'; break; - case Network.zora: + case ChainId.zora: chainSlug = 'zora'; break; } @@ -339,7 +340,7 @@ const MintSheet = () => { contract: mintCollection.id || '', chainId: mintCollection.chainId, }); - Linking.openURL(buildMintDotFunUrl(mintCollection.id!, currentNetwork)); + Linking.openURL(buildMintDotFunUrl(mintCollection.id!, chainId)); return; } @@ -356,16 +357,17 @@ const MintSheet = () => { const privateKey = await loadPrivateKey(accountAddress, false); // @ts-ignore const account = privateKeyToAccount(privateKey); - const networkObj = getNetworkObj(currentNetwork); + const networkObj = getNetworkObject({ chainId }); const signer = createWalletClient({ account, chain: networkObj, transport: http(networkObj.rpc()), }); - const feeAddress = getRainbowFeeAddress(currentNetwork); - const nonce = await getNextNonce({ address: accountAddress, network: currentNetwork }); + const feeAddress = getRainbowFeeAddress(chainId); + const nonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(chainId) }); try { + const currentNetwork = ethereumUtils.getNetworkFromChainId(chainId); await getClient()?.actions.mintToken({ items: [ { @@ -403,10 +405,11 @@ const MintSheet = () => { name: mintCollection.publicMintInfo?.price?.currency?.name || 'Ethereum', decimals: mintCollection.publicMintInfo?.price?.currency?.decimals || 18, symbol: ETH_SYMBOL, - uniqueId: getUniqueId(ETH_ADDRESS, currentNetwork), + uniqueId: getUniqueId(ETH_ADDRESS, chainId), }; const tx: NewTransaction = { + chainId, status: 'pending', to: item.data?.to, from: item.data?.from, @@ -463,7 +466,7 @@ const MintSheet = () => { } }, [ accountAddress, - currentNetwork, + chainId, imageUrl, isMintingAvailable, isReadOnlyWallet, @@ -630,7 +633,7 @@ const MintSheet = () => { fallbackColor={imageColor} marginTop={0} horizontalPadding={0} - currentNetwork={currentNetwork} + chainId={chainId} theme={'dark'} loading={!isGasReady} marginBottom={0} @@ -680,7 +683,7 @@ const MintSheet = () => { symbol="􀉆" label={i18n.t(i18n.l.minting.contract)} value={ - ethereumUtils.openAddressInBlockExplorer(mintCollection.id!, currentNetwork)}> + ethereumUtils.openAddressInBlockExplorer(mintCollection.id!, chainId)}> {contractAddressDisplay} @@ -695,13 +698,13 @@ const MintSheet = () => { value={ - {currentNetwork === Network.mainnet ? ( + {chainId === ChainId.mainnet ? ( ) : ( - + )} - {`${getNetworkObj(currentNetwork).name}`} + {`${getNetworkObject({ chainId }).name}`} diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index c06ed2a84a1..5bee2b44794 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -6,7 +6,7 @@ import * as i18n from '@/languages'; import { ListHeader, ListPanel, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; -import ethereumUtils, { getNetworkFromChainId, useNativeAssetForNetwork } from '@/utils/ethereumUtils'; +import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { useAccountAccentColor, useAccountProfile, useAccountSettings } from '@/hooks'; import { safeAreaInsetValues } from '@/utils'; import { NanoXDeviceAnimation } from '@/screens/hardware-wallets/components/NanoXDeviceAnimation'; @@ -93,7 +93,7 @@ export const ClaimRewardsPanel = () => { const NETWORK_LIST_ITEMS = CLAIM_NETWORKS.map(chainId => { return { - IconComponent: , + IconComponent: , label: ChainNameDisplay[chainId], uniqueId: chainId.toString(), selected: false, @@ -188,7 +188,7 @@ const ClaimingRewards = ({ decimals: 18, symbol: 'ETH', }); - const eth = useNativeAssetForNetwork(Network.mainnet); + const eth = useNativeAsset({ chainId: ChainId.mainnet }); const unclaimedRewardsNativeCurrency = convertAmountAndPriceToNativeDisplay( claimableBalance.amount, eth?.price?.value || 0, @@ -211,7 +211,7 @@ const ClaimingRewards = ({ }>({ mutationFn: async () => { // Fetch the native asset from the origin chain - const opEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.optimism)); + const opEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.optimism); const opEth = { ...opEth_, chainName: chainNameFromChainId(ChainId.optimism), @@ -220,9 +220,9 @@ const ClaimingRewards = ({ // Fetch the native asset from the destination chain let destinationEth_; if (chainId === ChainId.base) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.base)); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.base); } else if (chainId === ChainId.zora) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(getNetworkFromChainId(ChainId.zora)); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.zora); } else { destinationEth_ = opEth; } diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 54fe8b6a0eb..60951803f17 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -20,6 +20,7 @@ import { useTheme } from '@/theme'; import LinearGradient from 'react-native-linear-gradient'; import { ButtonPressAnimation } from '@/components/animations'; import { noop } from 'lodash'; +import { ChainId } from '@/__swaps__/types/chains'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -128,7 +129,7 @@ export const LeaderboardRow = memo(function LeaderboardRow({ setClipboard(address); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, Network.mainnet); + ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/screens/points/content/PointsContent.tsx b/src/screens/points/content/PointsContent.tsx index fd61b4ef6e6..e6e6012c8ed 100644 --- a/src/screens/points/content/PointsContent.tsx +++ b/src/screens/points/content/PointsContent.tsx @@ -57,12 +57,12 @@ import EthIcon from '@/assets/eth-icon.png'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import Animated, { runOnUI, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; -import { useNativeAssetForNetwork } from '@/utils/ethereumUtils'; -import { Network } from '@/helpers'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { format, intervalToDuration, isToday } from 'date-fns'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ETH_REWARDS, useExperimentalFlag } from '@/config'; import { RewardsActionButton } from '../components/RewardsActionButton'; +import { ChainId } from '@/__swaps__/types/chains'; const InfoCards = ({ points }: { points: GetPointsDataForWalletQuery | undefined }) => { const labelSecondary = useForegroundColor('labelSecondary'); @@ -673,7 +673,7 @@ export function PointsContent() { const rank = points?.points?.user.stats.position.current; const isUnranked = !!points?.points?.user?.stats?.position?.unranked; - const eth = useNativeAssetForNetwork(Network.mainnet); + const eth = useNativeAsset({ chainId: ChainId.mainnet }); const rewards = points?.points?.user?.rewards; const { claimed, claimable } = rewards || {}; const showClaimYourPoints = claimable && claimable !== '0'; diff --git a/src/screens/positions/SubPositionListItem.tsx b/src/screens/positions/SubPositionListItem.tsx index 8ab85b77a7c..fce137b804d 100644 --- a/src/screens/positions/SubPositionListItem.tsx +++ b/src/screens/positions/SubPositionListItem.tsx @@ -10,6 +10,7 @@ import { NativeDisplay, PositionAsset } from '@/resources/defi/types'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useAccountSettings } from '@/hooks'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { ethereumUtils } from '@/utils'; type Props = { asset: PositionAsset; @@ -22,7 +23,8 @@ type Props = { export const SubPositionListItem: React.FC = ({ asset, apy, quantity, native, positionColor }) => { const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); - const { data: externalAsset } = useExternalToken({ address: asset.asset_code, network: asset.network, currency: nativeCurrency }); + const chainId = ethereumUtils.getChainIdFromNetwork(asset.network); + const { data: externalAsset } = useExternalToken({ address: asset.asset_code, chainId, currency: nativeCurrency }); const priceChangeColor = (asset.price?.relative_change_24h || 0) < 0 ? theme.colors.blueGreyDark60 : theme.colors.green; @@ -31,7 +33,7 @@ export const SubPositionListItem: React.FC = ({ asset, apy, quantity, nat = ({ trans const { colors } = useTheme(); const hash = useMemo(() => ethereumUtils.getHash(transaction), [transaction]); const { network, status } = transaction; + const chainId = ethereumUtils.getChainIdFromNetwork(network); const isReadOnly = useSelector((state: AppState) => state.wallets.selected?.type === WalletTypes.readOnly ?? true); // Retry swap related data const retrySwapMetadata = useMemo(() => { @@ -100,7 +101,7 @@ export const TransactionDetailsHashAndActionsSection: React.FC = ({ trans weight="heavy" onPress={onViewOnBlockExplorerPress} label={i18n.t(i18n.l.wallet.action.view_on, { - blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer(network)), + blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer(chainId)), })} lightShadows /> diff --git a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx index 67b5b94937f..d0790ebe0e6 100644 --- a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx @@ -17,6 +17,7 @@ import ImgixImage from '@/components/images/ImgixImage'; import { View } from 'react-native'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; +import { ChainId } from '@/__swaps__/types/chains'; type Props = { transaction: RainbowTransaction; @@ -84,13 +85,13 @@ export const TransactionDetailsValueAndFeeSection: React.FC = ({ transact }} /> - {transaction.network !== Network.mainnet && } + {transaction.chainId !== ChainId.mainnet && } ) : ( { - const nativeAssetAddress = getNetworkObj(network).nativeCurrency.address; - const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, network); +const getNetworkNativeAsset = (chainId: ChainId): ParsedAddressAsset | undefined => { + const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; + const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId); return getAccountAsset(nativeAssetUniqueId); }; -export const getNativeAssetForNetwork = async (network: Network, address?: EthereumAddress): Promise => { - const networkNativeAsset = getNetworkNativeAsset(network); +export const getNativeAssetForNetwork = async (chainId: ChainId, address?: EthereumAddress): Promise => { + const network = getNetworkFromChainId(chainId); + const networkNativeAsset = getNetworkNativeAsset(chainId); const { accountAddress, nativeCurrency } = store.getState().settings; const differentWallet = address?.toLowerCase() !== accountAddress?.toLowerCase(); let nativeAsset = differentWallet ? undefined : networkNativeAsset; // If the asset is on a different wallet, or not available in this wallet if (differentWallet || !nativeAsset) { - const mainnetAddress = getNetworkObj(network)?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; - const nativeAssetAddress = getNetworkObj(network).nativeCurrency.address; + const mainnetAddress = getNetworkObject({ chainId })?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; + const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; const externalAsset = await queryClient.fetchQuery( - externalTokenQueryKey({ address: nativeAssetAddress, network, currency: nativeCurrency }), - async () => fetchExternalToken({ address: nativeAssetAddress, network, currency: nativeCurrency }), + externalTokenQueryKey({ address: nativeAssetAddress, chainId, currency: nativeCurrency }), + async () => fetchExternalToken({ address: nativeAssetAddress, chainId, currency: nativeCurrency }), { staleTime: 60000, } @@ -80,17 +82,17 @@ export const getNativeAssetForNetwork = async (network: Network, address?: Ether nativeAsset = { ...externalAsset, network, - uniqueId: getUniqueId(getNetworkObj(network).nativeCurrency.address, network), - address: getNetworkObj(network).nativeCurrency.address, - decimals: getNetworkObj(network).nativeCurrency.decimals, - symbol: getNetworkObj(network).nativeCurrency.symbol, + uniqueId: getUniqueId(getNetworkObject({ chainId }).nativeCurrency.address, chainId), + address: getNetworkObject({ chainId }).nativeCurrency.address, + decimals: getNetworkObject({ chainId }).nativeCurrency.decimals, + symbol: getNetworkObject({ chainId }).nativeCurrency.symbol, }; } const provider = getProviderForNetwork(network); if (nativeAsset) { nativeAsset.mainnet_address = mainnetAddress; - nativeAsset.address = getNetworkObj(network).nativeCurrency.address; + nativeAsset.address = getNetworkObject({ chainId }).nativeCurrency.address; const balance = await getOnchainAssetBalance(nativeAsset, address, network, provider); @@ -129,14 +131,14 @@ const getUserAssetFromCache = (uniqueId: string) => { const getExternalAssetFromCache = (uniqueId: string) => { const { nativeCurrency } = store.getState().settings; - const { network, address } = getAddressAndNetworkFromUniqueId(uniqueId); + const { address, chainId } = getAddressAndChainIdFromUniqueId(uniqueId); try { const cachedExternalAsset = queryClient.getQueryData( externalTokenQueryKey({ address, currency: nativeCurrency, - network, + chainId, }) ); @@ -165,17 +167,17 @@ const getAssetPrice = (address: EthereumAddress = ETH_ADDRESS): number => { return genericPrice || getAccountAsset(address)?.price?.value || 0; }; -export const useNativeAssetForNetwork = (network: Network) => { - let address = getNetworkObj(network).nativeCurrency?.mainnetAddress || ETH_ADDRESS; - let theNetwork = Network.mainnet; +export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { + let address = getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS; + let internalChainId = ChainId.mainnet; const { nativeCurrency } = store.getState().settings; - if (network === Network.avalanche || network === Network.degen) { - address = getNetworkObj(network).nativeCurrency?.address; - theNetwork = network; + if (chainId === ChainId.avalanche || chainId === ChainId.degen) { + address = getNetworkObject({ chainId }).nativeCurrency?.address; + internalChainId = chainId; } const { data: nativeAsset } = useExternalToken({ address, - network: theNetwork, + chainId: internalChainId, currency: nativeCurrency, }); @@ -200,8 +202,8 @@ const getEthPriceUnit = () => getAssetPrice(); const getMaticPriceUnit = () => getAssetPrice(MATIC_MAINNET_ADDRESS); const getBnbPriceUnit = () => getAssetPrice(BNB_MAINNET_ADDRESS); -const getAvaxPriceUnit = () => getAssetPrice(getUniqueId(AVAX_AVALANCHE_ADDRESS, Network.avalanche)); -const getDegenPriceUnit = () => getAssetPrice(getUniqueId(DEGEN_CHAIN_DEGEN_ADDRESS, Network.degen)); +const getAvaxPriceUnit = () => getAssetPrice(getUniqueId(AVAX_AVALANCHE_ADDRESS, ChainId.avalanche)); +const getDegenPriceUnit = () => getAssetPrice(getUniqueId(DEGEN_CHAIN_DEGEN_ADDRESS, ChainId.degen)); const getBalanceAmount = ( selectedGasFee: SelectedGasFee | LegacySelectedGasFee, @@ -271,33 +273,35 @@ const getDataString = (func: string, arrVals: string[]) => { * @desc get network string from chainId * @param {Number} chainId */ -export const getNetworkFromChainId = (chainId: number): Network => { - return RainbowNetworks.find(network => network.id === chainId)?.value || getNetworkObj(Network.mainnet).value; +export const getNetworkFromChainId = (chainId: ChainId): Network => { + return RainbowNetworks.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; }; /** * @desc get network string from chainId * @param {Number} chainId */ -const getNetworkNameFromChainId = (chainId: number): string => { - return RainbowNetworks.find(network => network.id === chainId)?.name || getNetworkObj(Network.mainnet).name; +const getNetworkNameFromChainId = (chainId: ChainId): string => { + return RainbowNetworks.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; }; /** * @desc get chainId from network string * @param {String} network */ -const getChainIdFromNetwork = (network: Network): number => { - return getNetworkObj(network).id; +const getChainIdFromNetwork = (network?: Network): ChainId => { + return network ? getNetworkObj(network).id : ChainId.mainnet; }; /** * @desc get etherscan host from network string * @param {String} network */ -function getEtherscanHostForNetwork(network?: Network): string { +function getEtherscanHostForNetwork(chainId: ChainId): string { const base_host = 'etherscan.io'; - const blockExplorer = getNetworkObj(network || Network.mainnet).blockExplorers?.default?.url; + const networkObject = getNetworkObject({ chainId }); + const blockExplorer = networkObject.blockExplorers?.default?.url; + const network = networkObject.network as Network; if (network && isTestnetNetwork(network)) { return `${network}.${base_host}`; @@ -371,30 +375,33 @@ export const getFirstTransactionTimestamp = async (address: EthereumAddress): Pr return timestamp ? timestamp * 1000 : undefined; }; -function getBlockExplorer(network: Network) { - return getNetworkObj(network).blockExplorers?.default.name || 'etherscan'; +function getBlockExplorer(chainId: ChainId) { + return getNetworkObject({ chainId }).blockExplorers?.default.name || 'etherscan'; } -function openAddressInBlockExplorer(address: EthereumAddress, network: Network) { - const explorer = getNetworkObj(network)?.blockExplorers?.default?.url; +function openAddressInBlockExplorer(address: EthereumAddress, chainId: ChainId) { + const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/address/${address}`); } function openTokenEtherscanURL(address: EthereumAddress, network: Network) { if (!isString(address)) return; - const explorer = getNetworkObj(network)?.blockExplorers?.default?.url; + const chainId = getChainIdFromNetwork(network); + const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/token/${address}`); } function openNftInBlockExplorer(contractAddress: string, tokenId: string, network: Network) { - const explorer = getNetworkObj(network)?.blockExplorers?.default?.url; + const chainId = getChainIdFromNetwork(network); + const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/token/${contractAddress}?a=${tokenId}`); } function openTransactionInBlockExplorer(hash: string, network: Network) { const normalizedHash = hash.replace(/-.*/g, ''); if (!isString(hash)) return; - const explorer = getNetworkObj(network)?.blockExplorers?.default?.url; + const chainId = getChainIdFromNetwork(network); + const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/tx/${normalizedHash}`); } @@ -409,14 +416,16 @@ async function parseEthereumUrl(data: string) { const functionName = ethUrl.function_name; let asset = null; - const network = getNetworkFromChainId(Number(ethUrl.chain_id || 1)); + const chainId = (ethUrl.chain_id as ChainId) || ChainId.mainnet; + const network = getNetworkFromChainId(chainId); let address: any = null; let nativeAmount: any = null; const { nativeCurrency } = store.getState().settings; if (!functionName) { // Send native asset - asset = getNetworkNativeAsset(network); + const chainId = getChainIdFromNetwork(network); + asset = getNetworkNativeAsset(chainId); // @ts-ignore if (!asset || asset?.balance.amount === 0) { @@ -427,7 +436,7 @@ async function parseEthereumUrl(data: string) { nativeAmount = ethUrl.parameters?.value && fromWei(ethUrl.parameters.value); } else if (functionName === 'transfer') { // Send ERC-20 - const targetUniqueId = getUniqueId(ethUrl.target_address, network); + const targetUniqueId = getUniqueId(ethUrl.target_address, chainId); asset = getAccountAsset(targetUniqueId); // @ts-ignore if (!asset || asset?.balance.amount === 0) { @@ -456,21 +465,24 @@ async function parseEthereumUrl(data: string) { }); } -export const getUniqueId = (address: EthereumAddress, network: Network) => `${address}_${network}`; +export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) => `${address}_${network}`; + +export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => `${address}_${chainId}`; -export const getAddressAndNetworkFromUniqueId = (uniqueId: string): { address: EthereumAddress; network: Network } => { +export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: EthereumAddress; chainId: ChainId } => { const parts = uniqueId.split('_'); // If the unique ID does not contain '_', it's a mainnet address if (parts.length === 1) { - return { address: parts[0], network: Network.mainnet }; + return { address: parts[0], chainId: ChainId.mainnet }; } // If the unique ID contains '_', the last part is the network and the rest is the address const network = parts[1] as Network; // Assuming the last part is a valid Network enum value const address = parts[0]; + const chainId = getChainIdFromNetwork(network); - return { address, network }; + return { address, chainId }; }; const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider): Promise => { @@ -516,7 +528,7 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider } }; -const getBasicSwapGasLimit = (chainId: number) => { +const getBasicSwapGasLimit = (chainId: ChainId) => { switch (chainId) { case getChainIdFromNetwork(Network.arbitrum): return ethUnits.basic_swap_arbitrum; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 77674147db7..8a6f193a480 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -86,7 +86,7 @@ export const handleDappBrowserRequest = async (request: Omit Date: Mon, 19 Aug 2024 14:49:21 -0400 Subject: [PATCH 37/78] fix beforeSend event filtration (#6012) --- src/logger/sentry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index ba9cb80d46f..b6d06abb9b5 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -16,10 +16,10 @@ export const defaultOptions: Sentry.ReactNativeOptions = { enableTracing: false, environment: isTestFlight ? 'Testflight' : SENTRY_ENVIRONMENT, integrations: [], - maxBreadcrumbs: 5, + maxBreadcrumbs: 10, tracesSampleRate: 0, beforeSend(event) { - if (!event.tags?.['device.family']) { + if (!event.contexts?.device?.family) { return null; } From fcba32cc7929031d0b609095551af682a163d06f Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 19 Aug 2024 16:24:41 -0400 Subject: [PATCH 38/78] Bump App Version to 1.9.36 (#6013) * bump 1.9.36 * fix changelog --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ android/app/build.gradle | 4 ++-- ios/Rainbow.xcodeproj/project.pbxproj | 8 ++++---- package.json | 2 +- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b31b387ed40..4f2c3056133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,32 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.35] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.35) + +### Added + +- Added translations for swaps v2, gas, backups and ETH rewards (#5980) +- Added a popular tokens section in swaps token search list (#5990) + +### Changed + +- Bump fast-xml-parser from 4.4.0 to 4.4.1 (#5965) +- Convert App.js => App.tsx (#5792) +- No longer have the restricted codeowners for reviews (#5991) +- Changed the trending Dapps section to backend endpoint (#5974) +- The default swap input currency is now the network with the most ETH (#5994) +- Design system improvements (#5984) +- Dapp browser improvements (#5978) +- Updated swaps SDK (#5996) +- Changed the fee domination from USD to actual payment token (#6000) +- Sentry bot resilience updates (#5995) + +### Fixed + +- Fixed a crash on an empty profile screen along with adding a placeholder for no transactions on activity screen (#5975) +- Fixed a bug where some tokens had a missing asset balance (#5998) +- Fixed account balance discrepancies in different places (#5959) + ## [1.9.34] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.34) ### Added diff --git a/android/app/build.gradle b/android/app/build.gradle index f2510af5934..5cca331f4c6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -139,8 +139,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 226 - versionName "1.9.35" + versionCode 227 + versionName "1.9.36" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index ab496e61acf..fe4d0b3f373 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1833,7 +1833,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.35; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1895,7 +1895,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.35; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2011,7 +2011,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.35; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2127,7 +2127,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.35; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index ebd6edf061c..e81d44d32d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.35-1", + "version": "1.9.36-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", From e7cc7f7998602b971ee9c3202c26261282d719b7 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 20 Aug 2024 13:40:05 -0300 Subject: [PATCH 39/78] fix sdk bump broke swaps (#6017) * test * fix --- .../Swap/hooks/useSwapInputsController.ts | 128 ++++++------------ src/hooks/useSwapDerivedOutputs.ts | 2 +- 2 files changed, 39 insertions(+), 91 deletions(-) diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index db861ca9401..f78e76daf78 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -15,16 +15,9 @@ import { analyticsV2 } from '@/analytics'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; -import { RainbowError, logger } from '@/logger'; +import { logger } from '@/logger'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { queryClient } from '@/react-query'; import store from '@/redux/store'; -import { - EXTERNAL_TOKEN_STALE_TIME, - ExternalTokenQueryFunctionResult, - externalTokenQueryKey, - fetchExternalToken, -} from '@/resources/assets/externalAssetsQuery'; import { triggerHapticFeedback } from '@/screens/points/constants'; import { swapsStore } from '@/state/swaps/swapsStore'; import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; @@ -344,67 +337,6 @@ export function useSwapInputsController({ ] ); - const getAssetNativePrice = useCallback(async ({ asset }: { asset: ExtendedAnimatedAssetWithColors | null }) => { - if (!asset) return null; - - const address = asset.address; - const chainId = asset.chainId; - const currency = store.getState().settings.nativeCurrency; - - try { - const tokenData = await fetchExternalToken({ - address, - chainId, - currency, - }); - - if (tokenData?.price.value) { - queryClient.setQueryData(externalTokenQueryKey({ address, chainId, currency }), tokenData); - return tokenData.price.value; - } - } catch (error) { - logger.error(new RainbowError('[useSwapInputsController]: get asset prices failed')); - - const now = Date.now(); - const state = queryClient.getQueryState(externalTokenQueryKey({ address, chainId, currency })); - const price = state?.data?.price.value; - if (price) { - const updatedAt = state.dataUpdatedAt; - // NOTE: if the data is older than 60 seconds, we need to invalidate it and not use it - if (now - updatedAt > EXTERNAL_TOKEN_STALE_TIME) { - queryClient.invalidateQueries(externalTokenQueryKey({ address, chainId, currency })); - return null; - } - return price; - } - } - return null; - }, []); - - const fetchAssetPrices = useCallback( - async ({ - inputAsset, - outputAsset, - }: { - inputAsset: ExtendedAnimatedAssetWithColors | null; - outputAsset: ExtendedAnimatedAssetWithColors | null; - }) => { - return Promise.all( - [ - { - asset: inputAsset, - type: 'inputAsset', - }, - { - asset: outputAsset, - type: 'outputAsset', - }, - ].map(getAssetNativePrice) - ).then(([inputPrice, outputPrice]) => ({ inputPrice, outputPrice })); - }, - [getAssetNativePrice] - ); - const fetchAndUpdateQuote = async ({ inputAmount, lastTypedInput: lastTypedInputParam, outputAmount }: RequestNewQuoteParams) => { const originalInputAssetUniqueId = internalSelectedInputAsset.value?.uniqueId; const originalOutputAssetUniqueId = internalSelectedOutputAsset.value?.uniqueId; @@ -431,18 +363,48 @@ export function useSwapInputsController({ return; } + const originalQuoteParams = { + assetToBuyUniqueId: originalOutputAssetUniqueId, + assetToSellUniqueId: originalInputAssetUniqueId, + inputAmount: inputAmount, + lastTypedInput: lastTypedInputParam, + outputAmount: outputAmount, + }; + try { const quoteResponse = await (params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params)); - if (!quoteResponse || 'error' in quoteResponse) throw ''; + + const inputAsset = internalSelectedInputAsset.value; + const outputAsset = internalSelectedOutputAsset.value; + + analyticsV2.track(analyticsV2.event.swapsReceivedQuote, { + inputAsset, + outputAsset, + quote: quoteResponse, + }); + + if (!quoteResponse || 'error' in quoteResponse) { + runOnUI(() => { + setQuote({ + data: quoteResponse, + inputAmount: undefined, + inputPrice: undefined, + outputAmount: undefined, + outputPrice: undefined, + originalQuoteParams, + quoteFetchingInterval, + }); + })(); + + return; + } const quotedInputAmount = lastTypedInputParam === 'outputAmount' ? Number( convertRawAmountToDecimalFormat( quoteResponse.sellAmount.toString(), - internalSelectedInputAsset.value?.networks[internalSelectedInputAsset.value.chainId]?.decimals || - internalSelectedInputAsset.value?.decimals || - 18 + inputAsset?.networks[inputAsset.chainId]?.decimals || inputAsset?.decimals || 18 ) ) : undefined; @@ -452,37 +414,23 @@ export function useSwapInputsController({ ? Number( convertRawAmountToDecimalFormat( quoteResponse.buyAmountMinusFees.toString(), - internalSelectedOutputAsset.value?.networks[internalSelectedOutputAsset.value.chainId]?.decimals || - internalSelectedOutputAsset.value?.decimals || - 18 + outputAsset?.networks[outputAsset.chainId]?.decimals || outputAsset?.decimals || 18 ) ) : undefined; - analyticsV2.track(analyticsV2.event.swapsReceivedQuote, { - inputAsset: internalSelectedInputAsset.value, - outputAsset: internalSelectedOutputAsset.value, - quote: quoteResponse, - }); - runOnUI(() => { setQuote({ data: quoteResponse, inputAmount: quotedInputAmount, inputPrice: quoteResponse.sellTokenAsset.price.value, - originalQuoteParams: { - assetToBuyUniqueId: originalOutputAssetUniqueId, - assetToSellUniqueId: originalInputAssetUniqueId, - inputAmount: inputAmount, - lastTypedInput: lastTypedInputParam, - outputAmount: outputAmount, - }, outputAmount: quotedOutputAmount, outputPrice: quoteResponse.buyTokenAsset.price.value, + originalQuoteParams, quoteFetchingInterval, }); })(); - } catch (error) { + } catch { runOnUI(resetFetchingStatus)({ fromError: true, quoteFetchingInterval }); } }; diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index a19abf3c5aa..fa8db8c1b63 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -13,7 +13,7 @@ import { useQuery } from '@tanstack/react-query'; import { useCallback, useMemo, useState } from 'react'; // DO NOT REMOVE THESE COMMENTED ENV VARS // eslint-disable-next-line @typescript-eslint/no-unused-vars -import { IS_TESTING } from 'react-native-dotenv'; +import { IS_APK_BUILD, IS_TESTING } from 'react-native-dotenv'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { analytics } from '@/analytics'; import { EthereumAddress } from '@/entities'; From 0defc362d28c30955156855f7ce43ab068ccb311 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 20 Aug 2024 13:09:10 -0400 Subject: [PATCH 40/78] fix device family not working (#6018) --- src/logger/sentry.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index b6d06abb9b5..5111c0e2f13 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -8,7 +8,6 @@ import isTestFlight from '@/helpers/isTestFlight'; export const defaultOptions: Sentry.ReactNativeOptions = { attachStacktrace: true, - defaultIntegrations: false, dsn: SENTRY_ENDPOINT, enableAppHangTracking: false, enableAutoPerformanceTracing: false, From 742b1f0b159064a5af50865e4e39a7ec1a01c8a8 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 20 Aug 2024 15:09:19 -0400 Subject: [PATCH 41/78] remove filter (#6019) * remove filter * fix --- src/logger/sentry.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index 5111c0e2f13..53d5160db83 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -17,13 +17,6 @@ export const defaultOptions: Sentry.ReactNativeOptions = { integrations: [], maxBreadcrumbs: 10, tracesSampleRate: 0, - beforeSend(event) { - if (!event.contexts?.device?.family) { - return null; - } - - return event; - }, }; export function initSentry() { From eb095edf9323ec1f9dd3baad12277b93c7007848 Mon Sep 17 00:00:00 2001 From: Jin Date: Tue, 20 Aug 2024 17:02:30 -0700 Subject: [PATCH 42/78] WETH to ETH endpoints do not always return the same asset info (#6022) --- src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index f78e76daf78..93524aca55f 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -423,9 +423,9 @@ export function useSwapInputsController({ setQuote({ data: quoteResponse, inputAmount: quotedInputAmount, - inputPrice: quoteResponse.sellTokenAsset.price.value, + inputPrice: quoteResponse?.sellTokenAsset?.price?.value, outputAmount: quotedOutputAmount, - outputPrice: quoteResponse.buyTokenAsset.price.value, + outputPrice: quoteResponse?.buyTokenAsset?.price?.value, originalQuoteParams, quoteFetchingInterval, }); From fe10fd71d65130bc4775dcf86730e3dc35c98fb1 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 21 Aug 2024 01:23:01 -0400 Subject: [PATCH 43/78] Fix missing pricing swaps (#6023) * fetch prices again until BE returns it * clean log --- .../Swap/hooks/useSwapInputsController.ts | 83 ++++++++++++++++++- src/resources/favorites.ts | 2 - 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 93524aca55f..2d1944a544b 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -15,9 +15,16 @@ import { analyticsV2 } from '@/analytics'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; -import { logger } from '@/logger'; +import { logger, RainbowError } from '@/logger'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { queryClient } from '@/react-query'; import store from '@/redux/store'; +import { + EXTERNAL_TOKEN_STALE_TIME, + ExternalTokenQueryFunctionResult, + externalTokenQueryKey, + fetchExternalToken, +} from '@/resources/assets/externalAssetsQuery'; import { triggerHapticFeedback } from '@/screens/points/constants'; import { swapsStore } from '@/state/swaps/swapsStore'; import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; @@ -337,6 +344,68 @@ export function useSwapInputsController({ ] ); + const getAssetNativePrice = useCallback(async ({ asset }: { asset: ExtendedAnimatedAssetWithColors | null }) => { + if (!asset) return null; + + const address = asset.address; + const chainId = asset.chainId; + const currency = store.getState().settings.nativeCurrency; + + try { + const tokenData = await fetchExternalToken({ + address, + chainId, + currency, + }); + + if (tokenData?.price.value) { + queryClient.setQueryData(externalTokenQueryKey({ address, chainId, currency }), tokenData); + return tokenData.price.value; + } + } catch (error) { + logger.error(new RainbowError('[useSwapInputsController]: get asset prices failed')); + + const now = Date.now(); + const state = queryClient.getQueryState(externalTokenQueryKey({ address, chainId, currency })); + const price = state?.data?.price.value; + if (price) { + const updatedAt = state.dataUpdatedAt; + // NOTE: if the data is older than 60 seconds, we need to invalidate it and not use it + if (now - updatedAt > EXTERNAL_TOKEN_STALE_TIME) { + queryClient.invalidateQueries(externalTokenQueryKey({ address, chainId, currency })); + return null; + } + return price; + } + } + return null; + }, []); + + const fetchAssetPrices = useCallback( + async ({ + inputAsset, + outputAsset, + }: { + inputAsset: ExtendedAnimatedAssetWithColors | null; + outputAsset: ExtendedAnimatedAssetWithColors | null; + }) => { + const assetsToFetch = []; + + assetsToFetch.push({ + asset: inputAsset, + type: 'inputAsset', + }); + + assetsToFetch.push({ + asset: outputAsset, + type: 'outputAsset', + }); + + return Promise.all(assetsToFetch.map(getAssetNativePrice)).then(([inputPrice, outputPrice]) => ({ inputPrice, outputPrice })); + }, + [getAssetNativePrice] + ); + const fetchAndUpdateQuote = async ({ inputAmount, lastTypedInput: lastTypedInputParam, outputAmount }: RequestNewQuoteParams) => { const originalInputAssetUniqueId = internalSelectedInputAsset.value?.uniqueId; const originalOutputAssetUniqueId = internalSelectedOutputAsset.value?.uniqueId; @@ -372,7 +441,13 @@ export function useSwapInputsController({ }; try { - const quoteResponse = await (params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params)); + const [quoteResponse, fetchedPrices] = await Promise.all([ + params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params), + fetchAssetPrices({ + inputAsset: internalSelectedInputAsset.value, + outputAsset: internalSelectedOutputAsset.value, + }), + ]); const inputAsset = internalSelectedInputAsset.value; const outputAsset = internalSelectedOutputAsset.value; @@ -423,9 +498,9 @@ export function useSwapInputsController({ setQuote({ data: quoteResponse, inputAmount: quotedInputAmount, - inputPrice: quoteResponse?.sellTokenAsset?.price?.value, + inputPrice: quoteResponse?.sellTokenAsset?.price?.value || fetchedPrices.inputPrice, outputAmount: quotedOutputAmount, - outputPrice: quoteResponse?.buyTokenAsset?.price?.value, + outputPrice: quoteResponse?.buyTokenAsset?.price?.value || fetchedPrices.outputPrice, originalQuoteParams, quoteFetchingInterval, }); diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 6c47d8f0da3..3bb6096f8aa 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -157,8 +157,6 @@ export function useFavorites(): { const favoritesMetadata = query.data ?? {}; const favorites = Object.keys(favoritesMetadata); - console.log('favoritesMetadata', favoritesMetadata); - return { favorites, favoritesMetadata, From b711f17e1bc55ecfb179285085ac0ca2bd727296 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 21 Aug 2024 10:32:59 -0400 Subject: [PATCH 44/78] crash fix (#6025) * fix * rename --- src/hooks/useWalletBalances.ts | 2 +- src/resources/summary/summary.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index c4c4e818d76..71ff139cc6b 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -62,7 +62,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { for (const address of allAddresses) { const lowerCaseAddress = address.toLowerCase() as Address; - const assetBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.asset_value.toString() || '0'; + const assetBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.asset_value?.toString() || '0'; const positionData = queryClient.getQueryData(positionsQueryKey({ address, currency: nativeCurrency })); const positionsBalance = positionData?.totals?.total?.amount || '0'; diff --git a/src/resources/summary/summary.ts b/src/resources/summary/summary.ts index 90705878f46..6f902a391f0 100644 --- a/src/resources/summary/summary.ts +++ b/src/resources/summary/summary.ts @@ -12,7 +12,7 @@ const addysHttp = new RainbowFetchClient({ }, }); -export interface AddySummary { +interface AddysSummary { data: { addresses: { [key: Address]: { @@ -26,7 +26,7 @@ export interface AddySummary { }; num_erc20s: number; last_activity: number; - asset_value: number; + asset_value: number | null; }; }; summary_by_chain: { @@ -38,7 +38,7 @@ export interface AddySummary { }; num_erc20s: number; last_activity: number; - asset_value: number; + asset_value: number | null; }; }; }; @@ -72,7 +72,7 @@ async function addysSummaryQueryFunction({ queryKey: [{ addresses, currency }] } addresses, }) ); - return data as AddySummary; + return data as AddysSummary; } type AddysSumaryResult = QueryFunctionResult; From 79be98ce15192e45ff9a28254d1b6e71c5cdba90 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:33:39 -0400 Subject: [PATCH 45/78] Fix Wrap / Unwrap (#6026) * bump swaps sdk * feeTokenAsset is optional --- package.json | 2 +- src/__swaps__/screens/Swap/components/ReviewPanel.tsx | 6 +++--- yarn.lock | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index e81d44d32d8..e95e1737304 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@notifee/react-native": "7.8.2", "@rainbow-me/provider": "0.0.12", "@rainbow-me/react-native-animated-number": "0.0.2", - "@rainbow-me/swaps": "0.23.0", + "@rainbow-me/swaps": "0.24.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-camera-roll/camera-roll": "7.7.0", "@react-native-clipboard/clipboard": "1.13.2", diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 44ba80067dc..9441ba77056 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -90,13 +90,13 @@ const RainbowFee = () => { }).amount; const { display: feeDisplay } = convertRawAmountToBalance(quote.fee.toString(), { - decimals: quote.feeTokenAsset.decimals, - symbol: quote.feeTokenAsset.symbol, + decimals: quote?.feeTokenAsset?.decimals || 18, + symbol: quote?.feeTokenAsset?.symbol || 'ETH', }); rainbowFee.value = [feeDisplay, `${handleSignificantDecimals(multiply(feePercentage, 100), 2)}%`]; }, - [nativeAsset?.value?.decimals, nativeAsset?.value?.price?.value, nativeCurrency, rainbowFee] + [rainbowFee] ); useAnimatedReaction( diff --git a/yarn.lock b/yarn.lock index 2a2f3946569..94b70d87693 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4299,9 +4299,9 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/swaps@npm:0.23.0": - version: 0.23.0 - resolution: "@rainbow-me/swaps@npm:0.23.0" +"@rainbow-me/swaps@npm:0.24.0": + version: 0.24.0 + resolution: "@rainbow-me/swaps@npm:0.24.0" dependencies: "@ethereumjs/util": "npm:9.0.0" "@ethersproject/abi": "npm:5.7.0" @@ -4316,7 +4316,7 @@ __metadata: "@ethersproject/transactions": "npm:5.7.0" "@ethersproject/wallet": "npm:5.7.0" "@metamask/eth-sig-util": "npm:7.0.0" - checksum: 10c0/3029ca9ed16a45af1961d2fdc79c545ae20a55212b38c8d3daba707617f8630f5328bc52e0e4732d97bffdbda62408093b3c871ef696322f1853d71231561a0f + checksum: 10c0/c04cdd4f8dca5c2d6f8e371dca88e6359387dea9c9241a98fcacb334cd2016ba985ae70e412bc8983f7074c17a61f80edef1d367222defceb2666c750386f8b9 languageName: node linkType: hard @@ -7811,7 +7811,7 @@ __metadata: "@notifee/react-native": "npm:7.8.2" "@rainbow-me/provider": "npm:0.0.12" "@rainbow-me/react-native-animated-number": "npm:0.0.2" - "@rainbow-me/swaps": "npm:0.23.0" + "@rainbow-me/swaps": "npm:0.24.0" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-camera-roll/camera-roll": "npm:7.7.0" "@react-native-clipboard/clipboard": "npm:1.13.2" From c4b67d6fd35d0840a9ffa7871beeca650519a529 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 21 Aug 2024 15:12:31 -0400 Subject: [PATCH 46/78] limit to 3 (#6028) --- src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts | 3 ++- src/hooks/useWalletBalances.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index 5d37829f703..bb6a5c2ff3b 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -27,6 +27,7 @@ export interface AssetToBuySection { const MAX_UNVERIFIED_RESULTS = 8; const MAX_VERIFIED_RESULTS = 48; +const MAX_POPULAR_RESULTS = 3; const mergeAssetsFavoriteStatus = ({ assets, @@ -177,7 +178,7 @@ const buildListSectionsData = ({ assets: combinedData.popularAssets, recentSwaps: combinedData.recentSwaps, filteredBridgeAssetAddress, - }).slice(0, 6); + }).slice(0, MAX_POPULAR_RESULTS); addSection( 'popular', mergeAssetsFavoriteStatus({ diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 71ff139cc6b..5fbffcede7a 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -79,7 +79,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { } return result; - }, [allAddresses, summaryData, nativeCurrency]); + }, [isLoading, allAddresses, summaryData?.data?.addresses, nativeCurrency]); return { balances, From 46110780cf8c4faf7e2890354e67df351514b98b Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:41:20 -0400 Subject: [PATCH 47/78] fix ci scripts (#6030) --- .github/workflows/macstadium-e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-e2e.yml index 0ae2e69dd9d..fd826523eaa 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-e2e.yml @@ -23,12 +23,12 @@ jobs: - name: Set up ENV vars & scripts env: - CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} + CI_SCRIPTS: ${{ secrets.CI_SCRIPTS }} run: | source ~/.zshrc git clone git@github.com:rainbow-me/rainbow-env.git mv rainbow-env/dotenv .env && rm -rf rainbow-env - eval $CI_SCRIPTS_RN_UPGRADE + eval $CI_SCRIPTS sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - name: Get Yarn cache directory path From 8cdeac807db1a3d1feaa85d18ce9c0e2f26973d8 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:41:49 -0600 Subject: [PATCH 48/78] Fix browser favorites migration (#6029) --- src/model/migrations.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 5026391f774..dcf2f73d979 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -641,9 +641,19 @@ export default async function runMigrations() { /** *************** Migration v19 ****************** - * Migrates dapp browser favorites store from createStore to createRainbowStore + * Deleted migration */ const v19 = async () => { + return; + }; + + migrations.push(v19); + + /** + *************** Migration v20 ****************** + * Migrates dapp browser favorites store from createStore to createRainbowStore + */ + const v20 = async () => { const initializeLegacyStore = () => { return new Promise(resolve => { // Give the async legacy store a moment to initialize @@ -666,7 +676,7 @@ export default async function runMigrations() { } }; - migrations.push(v19); + migrations.push(v20); logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); From 0caf458dfd83a80dd0a486ebe4bdf88e70e52acc Mon Sep 17 00:00:00 2001 From: Ibrahim Taveras Date: Wed, 21 Aug 2024 20:50:42 -0400 Subject: [PATCH 49/78] bump iOS & Android version to v1.9.37 (#6032) --- CHANGELOG.md | 10 ++++++++++ android/app/build.gradle | 4 ++-- ios/Rainbow.xcodeproj/project.pbxproj | 8 ++++---- package.json | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f2c3056133..45ffe98480f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.36] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.36) + +### Fixed + +- Fixed Sentry logging issues (#6012, #6018, #6019) +- Fixed issue in swaps where certain errors were not being handled (#6017) +- Fixed a bug with wrapping and unwrapping ETH (#6022, #6026) +- Fixed a crash that was happening on asset balance (#6025) +- Fixed missing pricing on swaps (#6023) + ## [1.9.35] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.35) ### Added diff --git a/android/app/build.gradle b/android/app/build.gradle index 5cca331f4c6..a999c66a916 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -139,8 +139,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 227 - versionName "1.9.36" + versionCode 228 + versionName "1.9.37" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index fe4d0b3f373..fbb316edc0e 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1833,7 +1833,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1895,7 +1895,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2011,7 +2011,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2127,7 +2127,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index e95e1737304..f04e88d3afa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.36-1", + "version": "1.9.37-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", From db2e757ea05af84a13cc274753d98fa26a03e71a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 22 Aug 2024 14:22:08 -0400 Subject: [PATCH 50/78] spindl data layer (#6031) * spindl data layer * rename folder * remove store * add selectors * add cacheTime * add uil to take locale to country code --- src/graphql/queries/arc.graphql | 40 +++++++++++++++++++ .../_selectors/getFeaturedResultById.ts | 6 +++ .../_selectors/getFeaturedResultIds.ts | 5 +++ .../getFeaturedResultsForPlacement.ts | 6 +++ .../getFeaturedResultsForPlacementWithIds.ts | 13 ++++++ .../featuredResults/getFeaturedResults.ts | 30 ++++++++++++++ .../featuredResults/trackFeaturedResult.ts | 25 ++++++++++++ src/state/featuredResults/featuredResults.ts | 0 src/utils/languageLocaleToCountry.ts | 16 ++++++++ 9 files changed, 141 insertions(+) create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultById.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultIds.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts create mode 100644 src/resources/featuredResults/getFeaturedResults.ts create mode 100644 src/resources/featuredResults/trackFeaturedResult.ts create mode 100644 src/state/featuredResults/featuredResults.ts create mode 100644 src/utils/languageLocaleToCountry.ts diff --git a/src/graphql/queries/arc.graphql b/src/graphql/queries/arc.graphql index 4b8d55ebb58..69648355a7c 100644 --- a/src/graphql/queries/arc.graphql +++ b/src/graphql/queries/arc.graphql @@ -473,3 +473,43 @@ query getNFTs($walletAddress: String!, $sortBy: NFTCollectionSortCriterion, $sor } } } + +query getFeaturedResults($placementId: String!, $walletAddress: String!, $country: String, $limit: Int) { + featuredResults(placementId: $placementId, walletAddress: $walletAddress, country: $country, limit: $limit) { + items { + id + type + impressionId + advertiserId + placementSlug + title + context { + text + } + description + imageUrl + category + imageAltText + ctas { + title + href + } + } + } +} + +mutation trackFeaturedResult( + $type: TrackFeaturedResultType! + $placementId: String! + $impressionId: String! + $featuredResultCreativeId: String! +) { + trackFeaturedResult( + type: $type + placementId: $placementId + impressionId: $impressionId + featuredResultCreativeId: $featuredResultCreativeId + ) { + message + } +} diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultById.ts b/src/resources/featuredResults/_selectors/getFeaturedResultById.ts new file mode 100644 index 00000000000..7e4a79456ec --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultById.ts @@ -0,0 +1,6 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultById = (data: FeaturedResults, id: FeaturedResult['id']): FeaturedResult | undefined => { + return data.featuredResults.items?.find(item => item.id === id); +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts new file mode 100644 index 00000000000..429bc501e42 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts @@ -0,0 +1,5 @@ +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsById = (data: FeaturedResults) => { + return data.featuredResults.items?.map(item => item.id) ?? []; +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts new file mode 100644 index 00000000000..a4e6a04ee67 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts @@ -0,0 +1,6 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsForPlacement = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { + return data.featuredResults.items?.filter(item => item.placementSlug === placement) ?? []; +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts new file mode 100644 index 00000000000..9ee209fab76 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts @@ -0,0 +1,13 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsForPlacementWithIds = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { + return ( + data.featuredResults.items?.reduce((acc, item) => { + if (item.placementSlug === placement) { + acc.push(item.id); + } + return acc; + }, [] as string[]) ?? [] + ); +}; diff --git a/src/resources/featuredResults/getFeaturedResults.ts b/src/resources/featuredResults/getFeaturedResults.ts new file mode 100644 index 00000000000..99e80c3ef33 --- /dev/null +++ b/src/resources/featuredResults/getFeaturedResults.ts @@ -0,0 +1,30 @@ +import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { arcClient } from '@/graphql'; + +const defaultStaleTime = 60_000; // 1 minute +const defaultCacheTime = 1000 * 60 * 60 * 24; // 1 day + +export type FeaturedResultsVariables = Parameters['0']; +export type FeaturedResults = Awaited>; + +// /////////////////////////////////////////////// +// Query Key +export const featuredResultsQueryKey = (props: FeaturedResultsVariables) => + createQueryKey('featured-results', props, { persisterVersion: 1 }); + +export type FeaturedResultsQueryKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Hook + +export function useFeaturedResults( + props: FeaturedResultsVariables, + config: QueryConfigWithSelect = {} +) { + return useQuery(featuredResultsQueryKey(props), () => arcClient.getFeaturedResults(props), { + ...config, + staleTime: defaultStaleTime, + cacheTime: defaultCacheTime, + }); +} diff --git a/src/resources/featuredResults/trackFeaturedResult.ts b/src/resources/featuredResults/trackFeaturedResult.ts new file mode 100644 index 00000000000..7209cc38dae --- /dev/null +++ b/src/resources/featuredResults/trackFeaturedResult.ts @@ -0,0 +1,25 @@ +import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { arcClient } from '@/graphql'; + +export type TrackFeaturedResultVariables = Parameters['0']; +export type TrackFeaturedResultResult = Awaited>; + +// /////////////////////////////////////////////// +// Mutation Key +export const trackFeaturedResultMutationKey = (props: TrackFeaturedResultVariables) => + createQueryKey('track-featured-result', props, { persisterVersion: 1 }); + +export type TrackFeaturedResultMutationKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Hook + +export function useTrackFeaturedResult( + props: TrackFeaturedResultVariables, + config: QueryConfigWithSelect = {} +) { + return useMutation(trackFeaturedResultMutationKey(props), () => arcClient.trackFeaturedResult(props), { + ...config, + }); +} diff --git a/src/state/featuredResults/featuredResults.ts b/src/state/featuredResults/featuredResults.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/utils/languageLocaleToCountry.ts b/src/utils/languageLocaleToCountry.ts new file mode 100644 index 00000000000..b8edace143b --- /dev/null +++ b/src/utils/languageLocaleToCountry.ts @@ -0,0 +1,16 @@ +import { Language } from '@/languages'; + +/** + * Converts a language locale to a country code. + * @param languageLocale - The language locale to convert. + * @returns The country code. + */ +export const languageLocaleToCountry = (languageLocale: keyof typeof Language) => { + const [languageCode, countryCode] = languageLocale.split('_'); + + // e.g. - ES_419 we want to return ES instead of 419 + if (Number(countryCode)) { + return languageCode; + } + return countryCode; +}; From 1773a35249371a169f067573080da0349255def4 Mon Sep 17 00:00:00 2001 From: gregs Date: Thu, 22 Aug 2024 16:01:30 -0300 Subject: [PATCH 51/78] fix android deps errors (#6027) * fix * patch react-native-input-mask resolution * fix other deps * bump dep again --------- Co-authored-by: Bruno Barbieri --- android/app/build.gradle | 10 +- package.json | 2 +- .../react-native-text-input-mask+2.0.0.patch | 310 +++++++++++++++++- yarn.lock | 12 +- 4 files changed, 321 insertions(+), 13 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a999c66a916..09503d90d47 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -208,7 +208,15 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation 'io.github.novacrypto:BIP39:2019.01.27' + implementation("com.github.NovaCrypto:BIP39:0e7fa95f80") { + exclude group: "io.github.novacrypto", module: "ToRuntime" + exclude group: "io.github.novacrypto", module: "SHA256" + } + implementation("com.github.NovaCrypto:Sha256:57bed72da5") { + exclude group: "io.github.novacrypto", module: "ToRuntime" + } + implementation "com.github.NovaCrypto:ToRuntime:c3ae3080eb" + implementation 'com.google.android.play:review:2.0.1' implementation 'com.google.android.play:app-update:2.1.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' diff --git a/package.json b/package.json index f04e88d3afa..e0c896fe89a 100644 --- a/package.json +++ b/package.json @@ -265,7 +265,7 @@ "react-native-text-input-mask": "2.0.0", "react-native-text-size": "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a", "react-native-tooltip": "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590", - "react-native-tooltips": "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3", + "react-native-tooltips": "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa", "react-native-udp": "2.7.0", "react-native-url-polyfill": "2.0.0", "react-native-version-number": "0.3.6", diff --git a/patches/react-native-text-input-mask+2.0.0.patch b/patches/react-native-text-input-mask+2.0.0.patch index b0dc705aa3c..9ebb2b5fb33 100644 --- a/patches/react-native-text-input-mask+2.0.0.patch +++ b/patches/react-native-text-input-mask+2.0.0.patch @@ -1,7 +1,310 @@ diff --git a/node_modules/react-native-text-input-mask/.DS_Store b/node_modules/react-native-text-input-mask/.DS_Store new file mode 100644 -index 0000000..5902d8c -Binary files /dev/null and b/node_modules/react-native-text-input-mask/.DS_Store differ +index 0000000..e69de29 +diff --git a/node_modules/react-native-text-input-mask/android/build.gradle b/node_modules/react-native-text-input-mask/android/build.gradle +index ba1e645..18630dc 100644 +--- a/node_modules/react-native-text-input-mask/android/build.gradle ++++ b/node_modules/react-native-text-input-mask/android/build.gradle +@@ -26,6 +26,6 @@ android { + + dependencies { + implementation 'com.facebook.react:react-native:+' +- implementation 'com.redmadrobot:inputmask:4.1.0' ++ implementation 'com.github.RedMadRobot:input-mask-android:4.1.0' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' + } +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin +new file mode 100644 +index 0000000..0d259dd +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin +@@ -0,0 +1 @@ ++o/classes +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex +new file mode 100644 +index 0000000..b937d77 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin +new file mode 100644 +index 0000000..5ff383e +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin +@@ -0,0 +1 @@ ++o/debug +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex +new file mode 100644 +index 0000000..530bd9a +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex +new file mode 100644 +index 0000000..b42ab6e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex +new file mode 100644 +index 0000000..5b4c31c +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex +new file mode 100644 +index 0000000..e4d045f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex +new file mode 100644 +index 0000000..6f7f156 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin +new file mode 100644 +index 0000000..5cb003f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin differ +diff --git a/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java +new file mode 100644 +index 0000000..5187c84 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java +@@ -0,0 +1,10 @@ ++/** ++ * Automatically generated file. DO NOT MODIFY ++ */ ++package com.RNTextInputMask; ++ ++public final class BuildConfig { ++ public static final boolean DEBUG = Boolean.parseBoolean("true"); ++ public static final String LIBRARY_PACKAGE_NAME = "com.RNTextInputMask"; ++ public static final String BUILD_TYPE = "debug"; ++} +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +new file mode 100644 +index 0000000..32b79dc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +new file mode 100644 +index 0000000..97e5bd9 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +@@ -0,0 +1,18 @@ ++{ ++ "version": 3, ++ "artifactType": { ++ "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", ++ "kind": "Directory" ++ }, ++ "applicationId": "com.RNTextInputMask", ++ "variantName": "debug", ++ "elements": [ ++ { ++ "type": "SINGLE", ++ "filters": [], ++ "attributes": [], ++ "outputFile": "AndroidManifest.xml" ++ } ++ ], ++ "elementType": "File" ++} +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +new file mode 100644 +index 0000000..1211b1e +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +@@ -0,0 +1,6 @@ ++aarFormatVersion=1.0 ++aarMetadataVersion=1.0 ++minCompileSdk=1 ++minCompileSdkExtension=0 ++minAndroidGradlePluginVersion=1.0.0 ++coreLibraryDesugaringEnabled=false +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +new file mode 100644 +index 0000000..9e26dfe +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +@@ -0,0 +1 @@ ++{} +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +new file mode 100644 +index 0000000..1a5b0f8 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar +new file mode 100644 +index 0000000..82ba69f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +new file mode 100644 +index 0000000..e40d27b +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +@@ -0,0 +1 @@ ++#Wed Aug 21 15:12:29 EDT 2024 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +new file mode 100644 +index 0000000..87dfc96 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +new file mode 100644 +index 0000000..b65d916 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +new file mode 100644 +index 0000000..85962a0 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml +new file mode 100644 +index 0000000..4f8b0d8 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class +new file mode 100644 +index 0000000..c38e5af +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class +new file mode 100644 +index 0000000..7aa7e43 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class +new file mode 100644 +index 0000000..55f4795 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class +new file mode 100644 +index 0000000..2a9601e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class +new file mode 100644 +index 0000000..a7a3f00 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +new file mode 100644 +index 0000000..78ac5b8 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +@@ -0,0 +1,2 @@ ++R_DEF: Internal format may change without notice ++local +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +new file mode 100644 +index 0000000..fbfd233 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +@@ -0,0 +1,7 @@ ++1 ++2 ++4 ++5 ++6 ++7 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +new file mode 100644 +index 0000000..32b79dc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json +new file mode 100644 +index 0000000..0637a08 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json +@@ -0,0 +1 @@ ++[] +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class +new file mode 100644 +index 0000000..c38e5af +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class +new file mode 100644 +index 0000000..7aa7e43 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class +new file mode 100644 +index 0000000..55f4795 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class +new file mode 100644 +index 0000000..2a9601e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class +new file mode 100644 +index 0000000..a7a3f00 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +new file mode 100644 +index 0000000..c69e81e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +new file mode 100644 +index 0000000..345473c +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +@@ -0,0 +1 @@ ++com.RNTextInputMask +diff --git a/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt +new file mode 100644 +index 0000000..3c35afc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt +@@ -0,0 +1,17 @@ ++-- Merging decision tree log --- ++manifest ++ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 ++ package ++ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:2:11-40 ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ xmlns:android ++ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:11-69 ++uses-sdk ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml reason: use-sdk injection requested ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ android:targetSdkVersion ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ android:minSdkVersion ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml +diff --git a/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +new file mode 100644 +index 0000000..dc8c43a +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin differ diff --git a/node_modules/react-native-text-input-mask/index.js b/node_modules/react-native-text-input-mask/index.js index 408edad..3905a6f 100644 --- a/node_modules/react-native-text-input-mask/index.js @@ -61,5 +364,4 @@ index 408edad..3905a6f 100644 +export default forwardRef(ForwardedTextInputMask); diff --git a/node_modules/react-native-text-input-mask/ios/.DS_Store b/node_modules/react-native-text-input-mask/ios/.DS_Store new file mode 100644 -index 0000000..66750e8 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/ios/.DS_Store differ +index 0000000..e69de29 diff --git a/yarn.lock b/yarn.lock index 94b70d87693..37b92dd5fd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8016,7 +8016,7 @@ __metadata: react-native-text-input-mask: "npm:2.0.0" react-native-text-size: "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a" react-native-tooltip: "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590" - react-native-tooltips: "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3" + react-native-tooltips: "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" react-native-udp: "npm:2.7.0" react-native-url-polyfill: "npm:2.0.0" react-native-version-number: "npm:0.3.6" @@ -21347,12 +21347,10 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-tooltips@rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3": - version: 1.0.3 - resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=77338abadbef8225870aea5cfc0dacf94a1448e3" - dependencies: - deprecated-react-native-prop-types: "npm:2.2.0" - checksum: 10c0/b97f5891e583b15a37362f0b0bb2657a41728347bbf1bb4dc692a620db0ea87b02c11c633594e2c6dddcc3b116f6d186616761352f8f803768af3a9b98f9caab +"react-native-tooltips@rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa": + version: 1.0.4 + resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" + checksum: 10c0/7a1f79d8d381532b41eff2b6e8dbd818a4cbd05c21c28419cba8bfb63047555d5e10ee93687a56c2e093eff7921503971942f14a9b5584e6be93cd21ab334805 languageName: node linkType: hard From 3d5a7704133816e701c3a7a7478ccf179c89a1b9 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 22 Aug 2024 20:58:02 -0400 Subject: [PATCH 52/78] [chore]: Remove old logger and cleanup logging (#6021) * remove old logger and rework logger to include file name * code review suggestions --- package.json | 4 +- scripts/codegen-translations.js | 2 +- shim.js | 8 +- src/App.tsx | 24 ++-- .../screens/Swap/resources/assets/assets.ts | 2 +- .../Swap/resources/assets/userAssets.ts | 4 +- .../resources/assets/userAssetsByChain.ts | 2 +- .../Swap/resources/search/discovery.ts | 2 +- .../screens/Swap/resources/search/search.ts | 2 +- src/__swaps__/utils/decimalFormatter.ts | 2 +- src/analytics/index.ts | 10 +- src/analytics/utils.ts | 13 +- .../DappBrowser/handleProviderRequest.ts | 2 +- .../hooks/useScreenshotAndScrollTriggers.ts | 2 +- src/components/DappBrowser/screenshots.ts | 10 +- src/components/DappBrowser/utils.ts | 2 +- .../core/ExternalScrollView.tsx | 2 +- .../avatar-builder/EmojiContent.tsx | 6 +- .../backup/BackupChooseProviderStep.tsx | 4 +- src/components/backup/CloudBackupProvider.tsx | 12 +- src/components/backup/RestoreCloudStep.tsx | 7 +- src/components/cards/NFTOffersCard/Offer.tsx | 2 +- src/components/error-boundary/Fallback.tsx | 2 +- .../unique-token/ZoomableWrapper.android.js | 4 +- src/components/investment-cards/PoolValue.js | 4 +- src/components/list/NoResults.tsx | 2 +- src/components/qr-code/QRCode.js | 2 +- .../remote-promo-sheet/checkForCampaign.ts | 22 ++-- .../remote-promo-sheet/localCampaignChecks.ts | 2 +- .../notificationsPromoCampaign.ts | 10 +- .../remote-promo-sheet/runChecks.ts | 2 +- .../secret-display/SecretDisplaySection.tsx | 5 +- src/components/svg/SvgImage.js | 10 +- src/components/value-chart/Chart.js | 2 +- src/components/video/SimpleVideo.tsx | 4 +- .../WalletConnectV2ListItem.tsx | 4 +- src/debugging/network.js | 36 +++--- src/debugging/useDependencyDebugger.ts | 4 +- src/ens-avatar/src/utils.ts | 2 +- .../unlockableAppIconCheck.ts | 10 +- src/handlers/LedgerSigner.ts | 6 +- src/handlers/authentication.ts | 6 +- src/handlers/cloudBackup.ts | 12 +- src/handlers/cloudinary.ts | 2 +- src/handlers/deeplinks.ts | 42 +++--- src/handlers/dispersion.ts | 8 +- src/handlers/ens.ts | 19 ++- src/handlers/imgix.ts | 9 +- src/handlers/localstorage/common.ts | 10 +- src/handlers/localstorage/removeWallet.ts | 8 +- src/handlers/swap.ts | 37 ++++-- src/handlers/tokenSearch.ts | 6 +- src/handlers/transactions.ts | 4 +- src/handlers/walletReadyEvents.ts | 10 +- src/handlers/web3.ts | 18 +-- src/helpers/RainbowContext.tsx | 20 +-- src/helpers/signingWallet.ts | 15 +-- src/hooks/useAppVersion.ts | 2 +- src/hooks/useCloudBackups.ts | 12 +- src/hooks/useENSRegistrationActionHandler.ts | 7 +- src/hooks/useENSSearch.ts | 2 +- src/hooks/useEffectDebugger.ts | 14 +- src/hooks/useHideSplashScreen.ts | 4 +- src/hooks/useImportingWallet.ts | 18 +-- src/hooks/useInitializeAccountData.ts | 6 +- src/hooks/useInitializeWallet.ts | 32 ++--- src/hooks/useLedgerConnect.ts | 8 +- src/hooks/useLedgerImport.ts | 21 ++- src/hooks/useLoadAccountData.ts | 4 +- src/hooks/useLoadAccountLateData.ts | 4 +- src/hooks/useLoadGlobalEarlyData.ts | 4 +- src/hooks/useLoadGlobalLateData.ts | 4 +- src/hooks/useManageCloudBackups.ts | 4 +- src/hooks/useRefreshAccountData.ts | 10 +- src/hooks/useSafeImageUri.ts | 2 +- src/hooks/useScanner.ts | 14 +- src/hooks/useSearchCurrencyList.ts | 6 +- src/hooks/useSendableUniqueTokens.ts | 2 +- src/hooks/useStepper.ts | 2 +- src/hooks/useSwapCurrencyList.ts | 6 +- src/hooks/useSwapDerivedOutputs.ts | 32 +++-- src/hooks/useWalletCloudBackup.ts | 18 ++- src/hooks/useWalletManualBackup.ts | 8 +- src/hooks/useWatchPendingTxs.ts | 7 +- src/hooks/useWatchWallet.ts | 7 +- src/hooks/useWebData.ts | 12 +- src/keychain/index.ts | 54 ++++---- src/logger/sentry.ts | 6 +- src/migrations/index.ts | 14 +- .../migrateRemotePromoSheetsToZustand.ts | 2 +- src/model/backup.ts | 30 ++--- src/model/migrations.ts | 99 ++++++-------- src/model/preferences.ts | 11 +- src/model/remoteConfig.ts | 6 +- src/model/wallet.ts | 121 +++++++++--------- src/navigation/HardwareWalletTxNavigator.tsx | 6 +- .../views/BottomSheetBackground.tsx | 8 +- .../views/BottomSheetNavigatorView.tsx | 12 +- src/notifications/NotificationsHandler.tsx | 6 +- src/notifications/foregroundHandler.ts | 4 +- src/notifications/permissions.ts | 6 +- src/notifications/settings/firebase.ts | 8 +- src/notifications/settings/initialization.ts | 4 +- src/notifications/tokens.ts | 4 +- src/parsers/requests.js | 6 +- src/raps/actions/crosschainSwap.ts | 8 +- src/raps/actions/ens.ts | 48 +++---- src/raps/actions/swap.ts | 4 +- src/raps/actions/unlock.ts | 14 +- src/raps/common.ts | 2 +- src/raps/execute.ts | 2 +- .../interpolations/bSplineInterpolation.js | 14 +- .../createNativeStackNavigator.js | 4 +- src/redux/gas.ts | 9 +- src/redux/requests.ts | 6 +- src/redux/settings.ts | 20 +-- src/redux/walletconnect.ts | 20 +-- src/redux/wallets.ts | 52 ++++---- src/references/rainbow-token-list/index.ts | 16 +-- src/references/shitcoins.ts | 2 +- src/resources/assets/hardhatAssets.ts | 6 +- src/resources/metadata/dapps.tsx | 2 +- src/resources/nfts/simplehash/index.ts | 14 +- src/resources/nfts/utils.ts | 2 +- src/resources/reservoir/mints.ts | 8 +- .../transactions/consolidatedTransactions.ts | 2 +- src/resources/transactions/transaction.ts | 2 +- src/screens/AddCash/index.tsx | 2 +- .../AddCash/providers/Coinbase/index.tsx | 4 +- .../AddCash/providers/Moonpay/index.tsx | 4 +- src/screens/AddCash/providers/Ramp/index.tsx | 4 +- src/screens/AddWalletSheet.tsx | 18 +-- src/screens/ChangeWalletSheet.tsx | 6 +- src/screens/CheckIdentifierScreen.tsx | 6 +- .../Diagnostics/DiagnosticsItemRow.tsx | 2 +- .../helpers/createAndShareStateDumpFile.ts | 2 +- src/screens/Diagnostics/index.tsx | 2 +- src/screens/ExchangeModal.tsx | 24 ++-- src/screens/NFTSingleOfferSheet/index.tsx | 8 +- src/screens/NotificationsPromoSheet/index.tsx | 6 +- src/screens/SendConfirmationSheet.tsx | 6 +- src/screens/SendSheet.js | 37 +++--- .../components/AppIconSection.tsx | 8 +- .../components/Backups/ViewWalletBackup.tsx | 16 +-- .../components/Backups/WalletsAndBackup.tsx | 10 +- .../SettingsSheet/components/DevSection.tsx | 6 +- .../components/GoogleAccountSection.tsx | 4 +- .../components/PrivacySection.tsx | 4 +- src/screens/SignTransactionSheet.tsx | 30 +++-- src/screens/SpeedUpAndCancelSheet.js | 23 ++-- src/screens/WelcomeScreen/index.tsx | 4 +- .../PairHardwareWalletSigningSheet.tsx | 7 +- src/screens/mints/MintSheet.tsx | 10 +- .../points/claim-flow/ClaimRewardsPanel.tsx | 2 +- .../points/content/ReferralContent.tsx | 2 +- .../points/contexts/PointsProfileContext.tsx | 4 +- src/state/assets/userAssets.ts | 10 +- src/state/browser/browserStore.ts | 10 +- src/state/internal/createRainbowStore.ts | 6 +- src/state/remoteCards/remoteCards.ts | 12 +- .../remotePromoSheets/remotePromoSheets.ts | 10 +- src/state/swaps/swapsStore.ts | 8 +- src/storage/legacy.ts | 2 +- src/utils/actionsheet.ts | 4 +- src/utils/bluetoothPermissions.ts | 8 +- src/utils/branch.ts | 24 ++-- src/utils/contenthash.ts | 2 +- src/utils/ethereumUtils.ts | 2 +- src/utils/index.ts | 1 - src/utils/ledger.ts | 8 +- src/utils/logger.ts | 117 ----------------- src/utils/memoFn.ts | 8 +- src/utils/poaps.ts | 4 +- src/utils/reviewAlert.ts | 8 +- src/utils/simplifyChartData.ts | 16 +-- src/walletConnect/index.tsx | 100 ++++++++------- 176 files changed, 955 insertions(+), 1085 deletions(-) delete mode 100644 src/utils/logger.ts diff --git a/package.json b/package.json index e0c896fe89a..3dddbc815ca 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", - "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js --quiet", - "lint:js": "eslint --cache .", + "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js", + "lint:js": "eslint --cache . --quiet", "lint:ts": "yarn tsc --skipLibCheck --noEmit", "postinstall": "./scripts/postinstall.sh", "start": "react-native start", diff --git a/scripts/codegen-translations.js b/scripts/codegen-translations.js index 3f18125d4dc..56631ab0f7f 100644 --- a/scripts/codegen-translations.js +++ b/scripts/codegen-translations.js @@ -97,7 +97,7 @@ type ValidScope =${validTagsAsArrays.map(generateTypeForTag).join('')}; * keys. */ function pushNestedKeysAsArrays(keysArray, object, prefixArray) { - for (let key in object) { + for (const key in object) { const keyRepresentation = prefixArray.concat([key]); keysArray.push(keyRepresentation); diff --git a/shim.js b/shim.js index 6a079f429a3..bc700f0239f 100644 --- a/shim.js +++ b/shim.js @@ -6,7 +6,7 @@ import ReactNative from 'react-native'; import Storage from 'react-native-storage'; // import { debugLayoutAnimations } from './src/config/debug'; import { mmkvStorageBackend } from '@/handlers/localstorage/mmkvStorageBackend'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import 'fast-text-encoding'; import globalVariables from './globalVariables'; @@ -47,7 +47,7 @@ for (const [key, value] of Object.entries(globalVariables)) { Object.defineProperty(global, key, { get: () => value, set: () => { - logger.sentry(`Trying to override internal Rainbow var ${key}`); + logger.debug(`[shim]: Trying to override internal Rainbow var ${key}`); }, }); } @@ -108,7 +108,7 @@ ReactNative.LayoutAnimation.configureNext = () => null; // debugLayoutAnimations // ) { // ReactNative.LayoutAnimation.configureNext = (...args) => { -// logger.sentry('LayoutAnimation.configureNext', args); +// logger.debug('[shim]: LayoutAnimation.configureNext', args); // oldConfigureNext(...args); // }; // ReactNative.LayoutAnimation.configureNext.__shimmed = true; @@ -122,7 +122,7 @@ if (!ReactNative.InteractionManager._shimmed) { if (finishAutomatically) { setTimeout(() => { ReactNative.InteractionManager.clearInteractionHandle(handle); - logger.sentry(`Interaction finished automatically`); + logger.debug(`[shim]: Interaction finished automatically`); }, 3000); } return handle; diff --git a/src/App.tsx b/src/App.tsx index 93ec4a4f782..02c836a846e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -79,17 +79,17 @@ function App({ walletReady }: AppProps) { const initialUrl = await Linking.getInitialURL(); branchListenerRef.current = await branchListener(url => { - logger.debug(`Branch: listener called`, {}, logger.DebugContext.deeplinks); + logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); try { handleDeeplink(url, initialRoute); } catch (error) { if (error instanceof Error) { - logger.error(new RainbowError('Error opening deeplink'), { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { message: error.message, url, }); } else { - logger.error(new RainbowError('Error opening deeplink'), { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { message: 'Unknown error', url, }); @@ -98,7 +98,7 @@ function App({ walletReady }: AppProps) { }); if (initialUrl) { - logger.debug(`App: has initial URL, opening with Branch`, { initialUrl }); + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); branch.openURL(initialUrl); } }, [initialRoute]); @@ -140,7 +140,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (!__DEV__ && isTestFlight) { - logger.info(`Test flight usage - ${isTestFlight}`); + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); } identifyFlow(); eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); @@ -162,7 +162,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (walletReady) { - logger.info('✅ Wallet ready!'); + logger.debug(`[App]: ✅ Wallet ready!`); runWalletBackupStatusChecks(); } }, [walletReady]); @@ -249,7 +249,7 @@ function Root() { */ if (deviceIdWasJustCreated && !isReturningUser) { // on very first open, set some default data and fire event - logger.info(`User opened application for the first time`); + logger.debug(`[App]: User opened application for the first time`); const { width: screenWidth, height: screenHeight, scale: screenScale } = Dimensions.get('screen'); @@ -271,13 +271,17 @@ function Root() { initializeApplication() .then(() => { - logger.debug(`Application initialized with Sentry and analytics`); + logger.debug(`[App]: Application initialized with Sentry and analytics`); // init complete, load the rest of the app setInitializing(false); }) - .catch(() => { - logger.error(new RainbowError(`initializeApplication failed`)); + .catch(error => { + logger.error(new RainbowError(`[App]: initializeApplication failed`), { + data: { + error, + }, + }); // for failure, continue to rest of the app for now setInitializing(false); diff --git a/src/__swaps__/screens/Swap/resources/assets/assets.ts b/src/__swaps__/screens/Swap/resources/assets/assets.ts index 81108a88786..070c11577ee 100644 --- a/src/__swaps__/screens/Swap/resources/assets/assets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/assets.ts @@ -50,7 +50,7 @@ export async function assetsQueryFunction({ const parsedAssets = parseAssets(results, chainId, currency); return parsedAssets; } catch (e) { - logger.error(new RainbowError('assetsQueryFunction: '), { + logger.error(new RainbowError('[assetsQueryFunction]: Failed to fetch assets'), { message: (e as Error)?.message, }); return {}; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index cd0e6abdca8..1408eec1fb1 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -123,7 +123,7 @@ async function userAssetsQueryFunction({ queryKey: [{ address, currency, testnet } return cachedUserAssets; } catch (e) { - logger.error(new RainbowError('userAssetsQueryFunction: '), { + logger.error(new RainbowError('[userAssetsQueryFunction]: Failed to fetch user assets'), { message: (e as Error)?.message, }); return cachedUserAssets; @@ -169,7 +169,7 @@ async function userAssetsQueryFunctionRetryByChain({ } queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), cachedUserAssets); } catch (e) { - logger.error(new RainbowError('userAssetsQueryFunctionRetryByChain: '), { + logger.error(new RainbowError('[userAssetsQueryFunctionRetryByChain]: Failed to retry fetching user assets'), { message: (e as Error)?.message, }); } diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts index 9652ecf2ae4..cb5a12e1e45 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts @@ -82,7 +82,7 @@ export async function userAssetsByChainQueryFunction({ return cachedDataForChain; } } catch (e) { - logger.error(new RainbowError(`userAssetsByChainQueryFunction - chainId = ${chainId}:`), { + logger.error(new RainbowError(`[userAssetsByChainQueryFunction]: Failed to fetch user assets for ${chainId}`), { message: (e as Error)?.message, }); return cachedDataForChain; diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts index b64c680ba04..f3b0a9ae468 100644 --- a/src/__swaps__/screens/Swap/resources/search/discovery.ts +++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts @@ -28,7 +28,7 @@ async function tokenSearchQueryFunction({ queryKey: [{ chainId }] }: QueryFuncti const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); return parseTokenSearch(tokenSearch.data.data, chainId); } catch (e) { - logger.error(new RainbowError('Token discovery failed'), { url }); + logger.error(new RainbowError('[tokenSearchQueryFunction]: Token discovery failed'), { url }); return []; } } diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts index 86c45602cdd..9ac2e5b678e 100644 --- a/src/__swaps__/screens/Swap/resources/search/search.ts +++ b/src/__swaps__/screens/Swap/resources/search/search.ts @@ -90,7 +90,7 @@ async function tokenSearchQueryFunction({ return parseTokenSearch(tokenSearch.data.data, chainId); } } catch (e) { - logger.error(new RainbowError('Token search failed'), { url }); + logger.error(new RainbowError('[tokenSearchQueryFunction]: Token search failed'), { url }); return []; } } diff --git a/src/__swaps__/utils/decimalFormatter.ts b/src/__swaps__/utils/decimalFormatter.ts index afe646b1653..5b047b8c677 100644 --- a/src/__swaps__/utils/decimalFormatter.ts +++ b/src/__swaps__/utils/decimalFormatter.ts @@ -36,7 +36,7 @@ export function valueBasedDecimalFormatter({ maximumDecimalPlaces: number; } { const orderOfMagnitude = orderOfMagnitudeWorklet(amount); - let minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; + const minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; const significantDecimals = significantDecimalsWorklet(amount); diff --git a/src/analytics/index.ts b/src/analytics/index.ts index d0800a9cb67..6bbd722edb8 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -19,9 +19,9 @@ export class Analytics { this.client = rudderClient; this.disabled = isTesting || !!device.get(['doNotTrack']); if (isTesting) { - logger.debug('Analytics is disabled for testing'); + logger.debug('[Analytics]: disabled for testing'); } else { - logger.debug('Analytics client initialized'); + logger.debug('[Analytics]: client initialized'); } } @@ -71,7 +71,7 @@ export class Analytics { dataPlaneUrl: RUDDERSTACK_DATA_PLANE_URL, }); } catch (error) { - logger.error(new RainbowError('Unable to initialize Rudderstack'), { error }); + logger.error(new RainbowError('[Analytics]: Unable to initialize Rudderstack'), { error }); } } @@ -80,7 +80,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setDeviceId(deviceId: string) { - logger.debug(`Set deviceId on analytics instance`); + logger.debug(`[Analytics]: Set deviceId on analytics instance`); this.deviceId = deviceId; } @@ -89,7 +89,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setCurrentWalletAddressHash(currentWalletAddressHash: string) { - logger.debug(`Set currentWalletAddressHash on analytics instance`); + logger.debug(`[Analytics]: Set currentWalletAddressHash on analytics instance`); this.currentWalletAddressHash = currentWalletAddressHash; } diff --git a/src/analytics/utils.ts b/src/analytics/utils.ts index 28f474d7faa..450ed5666ff 100644 --- a/src/analytics/utils.ts +++ b/src/analytics/utils.ts @@ -35,7 +35,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { const deviceIdFromStorage = ls.device.get(['id']); if (deviceIdFromStorage) { - logger.debug(`getOrCreateDeviceId using existing deviceId from storage`); + logger.debug(`[getOrCreateDeviceId]: using existing deviceId from storage ${deviceIdFromStorage}`); // if we have a ID in storage, we've already migrated return [deviceIdFromStorage, false]; } else { @@ -47,12 +47,11 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { // set ID ls.device.set(['id'], deviceId); - // log to Sentry if (hasExistingDeviceId) { - logger.info(`getOrCreateDeviceId migrating device ID from keychain to local storage`); + logger.debug(`[getOrCreateDeviceId]: migrating device ID from keychain to local storage`); } - logger.debug(`getOrCreateDeviceId returned new deviceId`); + logger.debug(`[getOrCreateDeviceId]: returned new deviceId ${deviceId}`); // if we had an old device id in keychain, `wasCreated` should be false return [deviceId, !hasExistingDeviceId]; @@ -61,7 +60,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { export function securelyHashWalletAddress(walletAddress: `0x${string}`): string | undefined { if (!SECURE_WALLET_HASH_KEY) { - logger.error(new RainbowError(`Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); + logger.error(new RainbowError(`[securelyHashWalletAddress]: Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); } try { @@ -73,11 +72,11 @@ export function securelyHashWalletAddress(walletAddress: `0x${string}`): string walletAddress ); - logger.debug(`Wallet address securely hashed`); + logger.debug(`[securelyHashWalletAddress]: Wallet address securely hashed`); return hmac; } catch (e) { // could be an invalid hashing key, or trying to hash an ENS - logger.error(new RainbowError(`Wallet address hashing failed`)); + logger.error(new RainbowError(`[securelyHashWalletAddress]: Wallet address hashing failed`)); } } diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index c730eadafdb..3554f3ce19f 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -290,7 +290,7 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: // TODO - Open add / switch ethereum chain return { chainAlreadyAdded: true }; } else { - logger.info('[DAPPBROWSER]: NOT SUPPORTED CHAIN'); + logger.debug(`[handleProviderRequestApp]: Dapp requested unsupported chain ${chainId}`); return { chainAlreadyAdded: false }; } }; diff --git a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts index 787ba5db987..841ea58aab7 100644 --- a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts +++ b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts @@ -51,7 +51,7 @@ export function useScreenshotAndScrollTriggers() { saveScreenshotToFileSystem(uri, tabId, timestamp, pageUrl); }) .catch(error => { - logger.error(new RainbowError('Failed to capture tab screenshot'), { + logger.error(new RainbowError('[DappBrowser]: Failed to capture tab screenshot'), { error: error.message, }); }); diff --git a/src/components/DappBrowser/screenshots.ts b/src/components/DappBrowser/screenshots.ts index 89a56b359c2..42fe8082aa7 100644 --- a/src/components/DappBrowser/screenshots.ts +++ b/src/components/DappBrowser/screenshots.ts @@ -20,11 +20,11 @@ export const findTabScreenshot = (id: string, url?: string): ScreenshotType | nu if (!Array.isArray(screenshots)) { try { - logger.error(new RainbowError('Screenshot data is malformed — expected array'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — expected array'), { screenshots: JSON.stringify(screenshots, null, 2), }); } catch (e: any) { - logger.error(new RainbowError('Screenshot data is malformed — error stringifying'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — error stringifying'), { message: e.message, }); } @@ -79,7 +79,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] const deletePromises = screenshotsToDelete.map(screenshot => { const filePath = `${RNFS.DocumentDirectoryPath}/${screenshot.uri}`; return RNFS.unlink(filePath).catch(e => { - logger.error(new RainbowError('Error deleting screenshot file'), { + logger.error(new RainbowError('[DappBrowser]: Error deleting screenshot file'), { message: e.message, filePath, screenshot: JSON.stringify(screenshot, null, 2), @@ -88,7 +88,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] }); await Promise.all(deletePromises); } catch (e: any) { - logger.error(new RainbowError('Screenshot file pruning operation failed to complete'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot file pruning operation failed to complete'), { message: e.message, }); } @@ -118,7 +118,7 @@ export const saveScreenshot = async (tempUri: string, tabId: string, timestamp: // Set screenshot for display return screenshotWithRNFSPath; } catch (e: any) { - logger.error(new RainbowError('Error saving tab screenshot to file system'), { + logger.error(new RainbowError('[DappBrowser]: Error saving tab screenshot to file system'), { message: e.message, screenshotData: { tempUri, diff --git a/src/components/DappBrowser/utils.ts b/src/components/DappBrowser/utils.ts index 22c38a979fb..7d1d885311b 100644 --- a/src/components/DappBrowser/utils.ts +++ b/src/components/DappBrowser/utils.ts @@ -120,7 +120,7 @@ export async function handleShareUrl(url: string): Promise { await Share.share({ message: url }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - logger.error(new RainbowError('Error sharing browser URL'), { + logger.error(new RainbowError('[DappBrowser]: Error sharing browser URL'), { message: e.message, url, }); diff --git a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx index 821f6be0487..1bf25cbc586 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx @@ -42,7 +42,7 @@ const ExternalScrollViewWithRef = React.forwardRef ); diff --git a/src/components/avatar-builder/EmojiContent.tsx b/src/components/avatar-builder/EmojiContent.tsx index 0e7d75f1aa8..da1cafba6de 100644 --- a/src/components/avatar-builder/EmojiContent.tsx +++ b/src/components/avatar-builder/EmojiContent.tsx @@ -18,10 +18,10 @@ const EmojiContent = ({ data, columns, onEmojiSelect, cellSize, fontSize }: Prop const { colors } = useTheme(); const categoryEmojis = useMemo(() => { - let categoryEmojis = []; + const categoryEmojis = []; for (let i = 0; i < data.length; i += columns) { - let rowContent = []; - let touchableNet = []; + const rowContent = []; + const touchableNet = []; for (let j = 0; j < columns; j++) { if (i + j < data.length) { rowContent.push(charFromEmojiObject(data[i + j].emoji)); diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupChooseProviderStep.tsx index d18dbeb68f5..9e75ac5af13 100644 --- a/src/components/backup/BackupChooseProviderStep.tsx +++ b/src/components/backup/BackupChooseProviderStep.tsx @@ -55,8 +55,10 @@ export default function BackupSheetSectionNoProvider() { } }); } catch (e) { + logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), { + error: e, + }); Alert.alert(lang.t(lang.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); diff --git a/src/components/backup/CloudBackupProvider.tsx b/src/components/backup/CloudBackupProvider.tsx index 4819f22bf6a..377e9d13a83 100644 --- a/src/components/backup/CloudBackupProvider.tsx +++ b/src/components/backup/CloudBackupProvider.tsx @@ -32,7 +32,7 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { setIsFetching(true); const isAvailable = await isCloudBackupAvailable(); if (!isAvailable) { - logger.log('Cloud backup is not available'); + logger.debug('[CloudBackupProvider]: Cloud backup is not available'); setIsFetching(false); return; } @@ -44,20 +44,20 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { } } - logger.log('Syncing with cloud'); + logger.debug('[CloudBackupProvider]: Syncing with cloud'); await syncCloud(); - logger.log('Fetching user data'); + logger.debug('[CloudBackupProvider]: Fetching user data'); const userData = await fetchUserDataFromCloud(); setUserData(userData); - logger.log('Fetching all backups'); + logger.debug('[CloudBackupProvider]: Fetching all backups'); const backups = await fetchAllBackups(); - logger.log(`Retrieved ${backups.files.length} backup files`); + logger.debug(`[CloudBackupProvider]: Retrieved ${backups.files.length} backup files`); setBackups(backups); } catch (e) { - logger.error(new RainbowError('Failed to fetch all backups'), { + logger.error(new RainbowError('[CloudBackupProvider]: Failed to fetch all backups'), { error: e, }); } diff --git a/src/components/backup/RestoreCloudStep.tsx b/src/components/backup/RestoreCloudStep.tsx index cfbeea0072e..898533eb7c6 100644 --- a/src/components/backup/RestoreCloudStep.tsx +++ b/src/components/backup/RestoreCloudStep.tsx @@ -151,14 +151,15 @@ export default function RestoreCloudStep() { filename = normalizeAndroidBackupFilename(filename); } - logger.info('Done updating backup state'); + logger.debug('[RestoreCloudStep]: Done updating backup state'); // NOTE: Marking the restored wallets as backed up // @ts-expect-error TypeScript doesn't play nicely with Redux types here const walletIdsToUpdate = Object.keys(newWalletsState || {}).filter(walletId => !(prevWalletsState || {})[walletId]); - logger.log('updating backup state of wallets with ids', { + + logger.debug('[RestoreCloudStep]: Updating backup state of wallets with ids', { walletIds: JSON.stringify(walletIdsToUpdate), }); - logger.log('backupSelected.name', { + logger.debug('[RestoreCloudStep]: Selected backup name', { fileName: selectedBackup.name, }); diff --git a/src/components/cards/NFTOffersCard/Offer.tsx b/src/components/cards/NFTOffersCard/Offer.tsx index 42bde292b39..dd2bcbc943f 100644 --- a/src/components/cards/NFTOffersCard/Offer.tsx +++ b/src/components/cards/NFTOffersCard/Offer.tsx @@ -140,7 +140,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { default: secondaryTextColor = 'labelTertiary'; secondaryText = ''; - logger.error(new RainbowError('NFTOffersCard: invalid sort criterion')); + logger.error(new RainbowError('[NFTOffersCard]: invalid sort criterion')); break; } diff --git a/src/components/error-boundary/Fallback.tsx b/src/components/error-boundary/Fallback.tsx index ad57442ebc3..0ca291fddeb 100644 --- a/src/components/error-boundary/Fallback.tsx +++ b/src/components/error-boundary/Fallback.tsx @@ -36,7 +36,7 @@ export default function Fallback({ resetError: () => void; }) { const handleRestart = () => { - logger.error(new RainbowError('RainbowAppRestartFromErrorBoundary'), { + logger.error(new RainbowError('[ErrorBoundary]: RainbowAppRestartFromErrorBoundary'), { data: { error: error.toString(), componentStack, diff --git a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js index 3b74a5f5a1d..b81a35f88c0 100644 --- a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js +++ b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js @@ -186,8 +186,8 @@ export const ZoomableWrapper = ({ let targetScale = Math.min(scale.value, MAX_IMAGE_SCALE); // determine whether to snap to screen edges - let breakingScaleX = deviceWidth / fullSizeWidth; - let breakingScaleY = deviceHeight / fullSizeHeight; + const breakingScaleX = deviceWidth / fullSizeWidth; + const breakingScaleY = deviceHeight / fullSizeHeight; const maxDisplacementX = (deviceWidth * (Math.max(1, targetScale / breakingScaleX) - 1)) / 2 / zooming; const maxDisplacementY = (deviceHeight * (Math.max(1, targetScale / breakingScaleY) - 1)) / 2 / zooming; diff --git a/src/components/investment-cards/PoolValue.js b/src/components/investment-cards/PoolValue.js index b382a707989..cf1e6397ee8 100644 --- a/src/components/investment-cards/PoolValue.js +++ b/src/components/investment-cards/PoolValue.js @@ -29,7 +29,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { const { nativeCurrency } = useAccountSettings(); if (type === 'annualized_fees') { - let percent = parseFloat(value); + const percent = parseFloat(value); if (!percent || percent === 0) { formattedValue = '0%'; } @@ -42,7 +42,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { formattedValue = '< 0.0001%'; } - let fixedPercent = percent.toFixed(2); + const fixedPercent = percent.toFixed(2); if (fixedPercent === '0.00') { formattedValue = '0%'; } diff --git a/src/components/list/NoResults.tsx b/src/components/list/NoResults.tsx index 5086163a346..ef610f7b36e 100644 --- a/src/components/list/NoResults.tsx +++ b/src/components/list/NoResults.tsx @@ -41,7 +41,7 @@ export const NoResults = ({ onL2, type }: { onL2?: boolean; type: NoResultsType break; default: title = lang.t('exchange.no_results.nothing_found'); - logger.warn('NoResults: unknown type, falling back to default message'); + logger.warn('[NoResults]: unknown type, falling back to default message'); break; } diff --git a/src/components/qr-code/QRCode.js b/src/components/qr-code/QRCode.js index 4959d8a672d..0c47645e978 100644 --- a/src/components/qr-code/QRCode.js +++ b/src/components/qr-code/QRCode.js @@ -27,7 +27,7 @@ const QRCode = ({ const dots = []; const matrix = generateMatrix(value, ecl); const cellSize = size / matrix.length; - let qrList = [ + const qrList = [ { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, diff --git a/src/components/remote-promo-sheet/checkForCampaign.ts b/src/components/remote-promo-sheet/checkForCampaign.ts index 47b5bf34447..da6a7794a25 100644 --- a/src/components/remote-promo-sheet/checkForCampaign.ts +++ b/src/components/remote-promo-sheet/checkForCampaign.ts @@ -31,21 +31,21 @@ const timeBetweenPromoSheets = () => { }; export const checkForCampaign = async () => { - logger.debug('Campaigns: Running Checks'); + logger.debug('[checkForCampaign]: Running Checks'); if (timeBetweenPromoSheets() < TIMEOUT_BETWEEN_PROMOS) { - logger.debug('Campaigns: Time between promos has not exceeded timeout'); + logger.debug('[checkForCampaign]: Time between promos has not exceeded timeout'); return; } const isShown = remotePromoSheetsStore.getState().isShown; if (isShown) { - logger.debug('Campaigns: Another remote sheet is currently shown'); + logger.debug('[checkForCampaign]: Another remote sheet is currently shown'); return; } const isReturningUser = device.get(['isReturningUser']); if (!isReturningUser) { - logger.debug('Campaigns: First launch, not showing promo sheet'); + logger.debug('[checkForCampaign]: First launch, not showing promo sheet'); return; } @@ -55,10 +55,10 @@ export const checkForCampaign = async () => { for (const promo of promoSheetCollection?.items || []) { if (!promo) continue; - logger.debug(`Campaigns: Checking ${promo.sys.id}`); + logger.debug(`[checkForCampaign]: Checking ${promo.sys.id}`); const result = await shouldPromptCampaign(promo as PromoSheet); - logger.debug(`Campaigns: ${promo.sys.id} will show: ${result}`); + logger.debug(`[checkForCampaign]: ${promo.sys.id} will show: ${result}`); if (result) { const isShown = remotePromoSheetsStore.getState().isShown; if (!isShown) { @@ -69,7 +69,7 @@ export const checkForCampaign = async () => { }; export const triggerCampaign = async ({ campaignKey, sys: { id: campaignId } }: PromoSheet) => { - logger.debug(`Campaigns: Showing ${campaignKey} Promo`); + logger.debug(`[checkForCampaign]: Showing ${campaignKey} Promo`); setTimeout(() => { remotePromoSheetsStore.getState().showSheet(campaignId); @@ -93,14 +93,14 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise action.fn === 'isPreviewing'); const hasShown = remotePromoSheetsStore.getState().getSheet(id)?.hasBeenShown; // If the campaign has been viewed already or it's the first app launch, exit early if (hasShown && !isPreviewing) { - logger.debug(`Campaigns: User has already been shown ${campaignKey}`); + logger.debug(`[checkForCampaign]: User has already been shown ${campaignKey}`); return false; } @@ -113,9 +113,9 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise ${result === outcome}`); + logger.debug(`[checkForCampaign]: [${fn}] matches desired outcome: => ${result === outcome}`); if (result !== outcome) { shouldPrompt = false; diff --git a/src/components/remote-promo-sheet/localCampaignChecks.ts b/src/components/remote-promo-sheet/localCampaignChecks.ts index 9577f3f1bef..f91c7909c7b 100644 --- a/src/components/remote-promo-sheet/localCampaignChecks.ts +++ b/src/components/remote-promo-sheet/localCampaignChecks.ts @@ -31,7 +31,7 @@ export interface Campaign { export const activeCampaigns: Campaign[] = [NotificationsPromoCampaign]; export const runLocalCampaignChecks = async (): Promise => { - logger.debug('Campaigns: Running Checks'); + logger.debug('[runLocalCampaignChecks]: Running Checks'); for (const campaign of activeCampaigns) { InteractionManager.runAfterInteractions(async () => { const response = await campaign.check(); diff --git a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts index 00aa7b65c77..465ea2dddef 100644 --- a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts +++ b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts @@ -10,12 +10,12 @@ import { STORAGE_IDS } from '@/model/mmkv'; const mmkv = new MMKV(); export const notificationsCampaignAction = async () => { - logger.debug('Notifications promo: showing notifications promo'); + logger.debug('[notificationsCampaignAction]: showing notifications promo'); mmkv.set(CampaignKey.notificationsLaunch, true); setTimeout(() => { - logger.debug(`Notifications promo: triggering notifications promo action`); + logger.debug('[notificationsCampaignAction]: triggering notifications promo action'); Navigation.handleAction(Routes.NOTIFICATIONS_PROMO_SHEET, {}); }, 1000); @@ -25,7 +25,7 @@ export const notificationsCampaignCheck = async (): Promise { const runChecks = useCallback(() => { InteractionManager.runAfterInteractions(async () => { if (IS_TEST || !remotePromoSheets) { - logger.debug('Campaigns: remote promo sheets is disabled'); + logger.debug('[useRunChecks]: remote promo sheets is disabled'); return; } diff --git a/src/components/secret-display/SecretDisplaySection.tsx b/src/components/secret-display/SecretDisplaySection.tsx index eaa38f59627..0ef93ba05e6 100644 --- a/src/components/secret-display/SecretDisplaySection.tsx +++ b/src/components/secret-display/SecretDisplaySection.tsx @@ -106,11 +106,10 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: onSecretLoaded?.(!!seedPhrase); } catch (error) { const message = (error as Error)?.message; - logger.error(new RainbowError('Error while trying to reveal secret'), { + logger.error(new RainbowError('[SecretDisplaySection]: Error while trying to reveal secret'), { error: message, }); setSectionState(message === createdWithBiometricError ? SecretDisplayStates.securedWithBiometrics : SecretDisplayStates.noSeed); - captureException(error); onSecretLoaded?.(false); } }, [onSecretLoaded, privateKeyAddress, onWalletTypeIdentified, walletId]); @@ -234,7 +233,7 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: ); default: - logger.error(new RainbowError('Secret display section tries to present an unknown state')); + logger.error(new RainbowError(`[SecretDisplaySection]: Secret display section state unknown ${sectionState}`)); return null; } } diff --git a/src/components/svg/SvgImage.js b/src/components/svg/SvgImage.js index 1960a973590..e55bd0be0b1 100644 --- a/src/components/svg/SvgImage.js +++ b/src/components/svg/SvgImage.js @@ -4,7 +4,7 @@ import { WebView } from 'react-native-webview'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { CardSize } from '../unique-token/CardSize'; const ImageTile = styled(ImgixImage)({ @@ -97,7 +97,7 @@ class SvgImage extends Component { } doFetch = async props => { - let uri = props.source && props.source.uri; + const uri = props.source && props.source.uri; if (uri) { props.onLoadStart && props.onLoadStart(); if (uri.match(/^data:image\/svg/)) { @@ -110,17 +110,17 @@ class SvgImage extends Component { if (text.toLowerCase().indexOf('/)) { - logger.log('foreignObject tag not supported', { text, uri }); + logger.debug('[SvgImage]: foreignObject tag not supported', { text, uri }); // return w/o error so we can fallback to png return; } this.mounted && this.setState({ fetchingUrl: uri, svgContent: text }); } else { - logger.log('invalid svg', { text, uri }); + logger.debug('[SvgImage]: invalid svg', { text, uri }); this.mounted && props.onError && props.onError('invalid svg'); } } catch (err) { - logger.log('error loading remote svg image', err); + logger.debug('[SvgImage]: error loading remote svg image', err); this.mounted && props.onError && props.onError('error loading remote svg image'); } } diff --git a/src/components/value-chart/Chart.js b/src/components/value-chart/Chart.js index e276615a256..a373c5612c1 100644 --- a/src/components/value-chart/Chart.js +++ b/src/components/value-chart/Chart.js @@ -24,7 +24,7 @@ const ChartTimespans = [ ChartTypes.week, ChartTypes.month, ChartTypes.year, - //ChartTypes.max, todo restore after receiving proper data from zerion + // ChartTypes.max, todo restore after receiving proper data from zerion ]; const ChartContainer = styled.View({ diff --git a/src/components/video/SimpleVideo.tsx b/src/components/video/SimpleVideo.tsx index eced8e1d621..52bc4ef49ab 100644 --- a/src/components/video/SimpleVideo.tsx +++ b/src/components/video/SimpleVideo.tsx @@ -4,7 +4,7 @@ import Video, { VideoRef } from 'react-native-video'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; export type SimpleVideoProps = { readonly style?: ViewStyle; @@ -54,7 +54,7 @@ export default function SimpleVideo({ style, uri, posterUri, loading, setLoading try { current?.pause(); } catch (e) { - logger.error(e); + logger.error(new RainbowError(`[SimpleVideo]: Error pausing video: ${e}`)); } }; }, [ref]); diff --git a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx index fec634b9c2b..2b9af1b9700 100644 --- a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx +++ b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx @@ -65,7 +65,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT const eip155Account = namespaces.eip155?.accounts?.[0] || undefined; if (!eip155Account) { - const e = new RainbowError(`WalletConnectV2ListItem: unsupported namespace`); + const e = new RainbowError(`[WalletConnectV2ListItem]: unsupported namespace`); logger.error(e); // defensive, just for types, should never happen @@ -76,7 +76,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT const chainIds = (chains?.map(chain => parseInt(chain.split(':')[1]))?.filter(isSupportedChain) ?? []) as ChainId[]; if (!address) { - const e = new RainbowError(`WalletConnectV2ListItem: could not parse address`); + const e = new RainbowError(`[WalletConnectV2ListItem]: could not parse address`); logger.error(e); // defensive, just for types, should never happen diff --git a/src/debugging/network.js b/src/debugging/network.js index 716faef9a23..989aa3365b2 100644 --- a/src/debugging/network.js +++ b/src/debugging/network.js @@ -1,5 +1,5 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; let internalCounter = 0; @@ -21,10 +21,10 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses }; const emptyLine = () => { - logger.log(''); + logger.debug(''); }; const separator = () => { - logger.log(`----------------------------------------`); + logger.debug(`----------------------------------------`); }; if (showNetworkRequests) { @@ -35,21 +35,21 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.log(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); + logger.debug(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); emptyLine(); if (data) { emptyLine(); - logger.log(' PARAMETERS: '); + logger.debug(' PARAMETERS: '); emptyLine(); try { const dataObj = JSON.parse(data); - logger.log(' {'); + logger.debug(' {'); Object.keys(dataObj).forEach(key => { - logger.log(` ${key} : `, dataObj[key]); + logger.debug(` ${key} : `, dataObj[key]); }); - logger.log(' }'); + logger.debug(' }'); } catch (e) { - logger.log(data); + logger.error(new RainbowError(`Error parsing data: ${e}`), { data }); } } emptyLine(); @@ -72,33 +72,33 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.log(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); + logger.debug(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); emptyLine(); if (timeout && status > 400) { - logger.log(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); + logger.debug(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); } if (status) { - logger.log(` Status: ${status}`); + logger.debug(` Status: ${status}`); } if (time) { - logger.log(` Completed in: ${time / 1000} s`); + logger.debug(` Completed in: ${time / 1000} s`); } if (response) { emptyLine(); - logger.log(' RESPONSE: '); + logger.debug(' RESPONSE: '); emptyLine(); try { const responseObj = JSON.parse(response); - logger.log(' {'); + logger.debug(' {'); Object.keys(responseObj).forEach(key => { - logger.log(` ${key} : `, responseObj[key]); + logger.debug(` ${key} : `, responseObj[key]); }); - logger.log(' }'); + logger.debug(' }'); } catch (e) { - logger.log(response); + logger.error(new RainbowError(`Error parsing response: ${e}`), { data: response }); } } emptyLine(); diff --git a/src/debugging/useDependencyDebugger.ts b/src/debugging/useDependencyDebugger.ts index 4080b3bffc5..b3966e3c1da 100644 --- a/src/debugging/useDependencyDebugger.ts +++ b/src/debugging/useDependencyDebugger.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { logger } from '../utils'; +import { logger } from '@/logger'; const usePrevious = (value: T): T => { const ref = useRef(value); @@ -50,6 +50,6 @@ export const useDependencyDebugger = (dependencies: unknown[] | Record { const nfts = appIcon.unlockingNFTs[network]; if (!nfts) return; - logger.debug(`Checking ${appIconKey} on network ${network}`); + logger.debug(`[unlockableAppIconCheck]: Checking ${appIconKey} on network ${network}`); return await checkIfWalletsOwnNft(nfts, network, walletsToCheck); }) ) ).some(result => !!result); - logger.debug(`${appIconKey} check result: ${found}`); + logger.debug(`[unlockableAppIconCheck]: ${appIconKey} check result: ${found}`); // We open the sheet with a setTimeout 1 sec later to make sure we can return first // so we can abort early if we're showing a sheet to prevent 2+ sheets showing at the same time @@ -46,14 +46,14 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w setTimeout(() => { if (found) { unlockableAppIconStorage.set(appIconKey, true); - logger.debug(`Feature check ${appIconKey} set to true. Wont show up anymore!`); + logger.debug(`[unlockableAppIconCheck]: Feature check ${appIconKey} set to true. Wont show up anymore!`); Navigation.handleAction(Routes.APP_ICON_UNLOCK_SHEET, { appIconKey }); return true; } }, 1000); return found; } catch (e) { - logger.error(new RainbowError('UnlockableAppIconCheck blew up'), { e }); + logger.error(new RainbowError('[unlockableAppIconCheck]: UnlockableAppIconCheck blew up'), { e }); } return false; }; diff --git a/src/handlers/LedgerSigner.ts b/src/handlers/LedgerSigner.ts index 7e86770d4d4..af5a511c81d 100644 --- a/src/handlers/LedgerSigner.ts +++ b/src/handlers/LedgerSigner.ts @@ -57,14 +57,14 @@ export class LedgerSigner extends Signer { return new Promise(async (resolve, reject) => { if (timeout && timeout > 0) { setTimeout(() => { - logger.debug('Ledger: Signer timeout', {}, logger.DebugContext.ledger); + logger.debug('[LedgerSigner]: Signer timeout', {}, logger.DebugContext.ledger); return reject(new RainbowError('Ledger: Signer timeout')); }, timeout); } const eth = await this._eth; if (!eth) { - logger.debug('Ledger: Eth app not open', {}, logger.DebugContext.ledger); + logger.debug('[LedgerSigner]: Eth app not open', {}, logger.DebugContext.ledger); return reject(new Error('Ledger: Eth app not open')); } @@ -74,7 +74,7 @@ export class LedgerSigner extends Signer { const result = await callback(eth); return resolve(result); } catch (error: any) { - logger.error(new RainbowError('Ledger: Transport error'), error); + logger.error(new RainbowError('[LedgerSigner]: Transport error'), error); // blind signing isnt enabled if (error.name === 'EthAppPleaseEnableContractData') diff --git a/src/handlers/authentication.ts b/src/handlers/authentication.ts index df12fa72059..75ddcb3f0ae 100644 --- a/src/handlers/authentication.ts +++ b/src/handlers/authentication.ts @@ -17,7 +17,9 @@ export async function getExistingPIN(): Promise { return userPIN as string; } } catch (error) { - logger.error(new RainbowError('Error while trying to get existing PIN code.'), { message: (error as Error).message }); + logger.error(new RainbowError('[getExistingPIN]: Error while trying to get existing PIN code.'), { + message: (error as Error).message, + }); } return; } @@ -39,7 +41,7 @@ export async function savePIN(pin: string | undefined) { await keychain.saveString(pinKey, encryptedPin); } } catch (error) { - logger.error(new RainbowError('savePin error'), { + logger.error(new RainbowError('[savePIN]: savePin error'), { message: (error as Error).message, }); } diff --git a/src/handlers/cloudBackup.ts b/src/handlers/cloudBackup.ts index 0f500a4bb46..1eb3f5be795 100644 --- a/src/handlers/cloudBackup.ts +++ b/src/handlers/cloudBackup.ts @@ -115,16 +115,14 @@ export async function encryptAndSaveDataToCloud(data: any, password: any, filena ); if (!exists) { - logger.info('Backup doesnt exist after completion'); const error = new Error(CLOUD_BACKUP_ERRORS.INTEGRITY_CHECK_FAILED); - logger.error(new RainbowError(error.message)); throw error; } await RNFS.unlink(path); return filename; } catch (e: any) { - logger.error(new RainbowError('Error during encryptAndSaveDataToCloud'), { + logger.error(new RainbowError('[cloudBackup]: Error during encryptAndSaveDataToCloud'), { message: e.message, }); throw new Error(CLOUD_BACKUP_ERRORS.GENERAL_ERROR); @@ -173,7 +171,7 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n } if (!document) { - logger.error(new RainbowError('No backup found with that name!'), { + logger.error(new RainbowError('[cloudBackup]: No backup found with that name!'), { filename, }); const error = new Error(CLOUD_BACKUP_ERRORS.SPECIFIC_BACKUP_NOT_FOUND); @@ -186,19 +184,19 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n const encryptedData = ios ? await getICloudDocument(filename) : await getGoogleDriveDocument(document.id); if (encryptedData) { - logger.info('Got cloud document ', { filename }); + logger.debug(`[cloudBackup]: Got cloud document ${filename}`); const backedUpDataStringified = await encryptor.decrypt(backupPassword, encryptedData); if (backedUpDataStringified) { const backedUpData = JSON.parse(backedUpDataStringified); return backedUpData; } else { - logger.error(new RainbowError('We couldnt decrypt the data')); + logger.error(new RainbowError('[cloudBackup]: We couldnt decrypt the data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA); throw error; } } - logger.error(new RainbowError('We couldnt get the encrypted data')); + logger.error(new RainbowError('[cloudBackup]: We couldnt get the encrypted data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_GETTING_ENCRYPTED_DATA); throw error; } diff --git a/src/handlers/cloudinary.ts b/src/handlers/cloudinary.ts index 244a4b2575b..5a3521a7d52 100644 --- a/src/handlers/cloudinary.ts +++ b/src/handlers/cloudinary.ts @@ -13,7 +13,7 @@ type CloudinaryConfig = { const PixelRatios = [1, 1.5, 2, 2.625, 2.75, 3, 3.5]; // popular ratios. const IconsSizes = [40, 36]; // Remove 36 with TopMover const allowedIconSizes = PixelRatios.reduce((acc, ratio) => { - for (let size of IconsSizes) { + for (const size of IconsSizes) { acc.push(size * ratio); } return acc; diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index 7c2f95be5fd..c8914e691bd 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -28,7 +28,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; export default async function handleDeeplink(url: string, initialRoute: any = null) { if (!url) { - logger.warn(`handleDeeplink: No url provided`); + logger.warn(`[handleDeeplink]: No url provided`); return; } @@ -36,13 +36,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * We need to wait till the wallet is ready to handle any deeplink */ while (!store.getState().appState.walletReady) { - logger.info(`handleDeeplink: Waiting for wallet to be ready`); + logger.debug(`[handleDeeplink]: Waiting for wallet to be ready`); await delay(50); } const { protocol, host, pathname, query } = new URL(url, true); - logger.info(`handleDeeplink: handling url`, { + logger.debug(`[handleDeeplink]: handling url`, { url, protocol, host, @@ -54,13 +54,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * Handling send deep links */ - logger.info(`handleDeeplink: ethereum:// protocol`); + logger.debug(`[handleDeeplink]: ethereum:// protocol`); ethereumUtils.parseEthereumUrl(url); } else if (protocol === 'https:' || protocol === 'rainbow:') { /** * Any native iOS deep link OR universal links via HTTPS */ - logger.info(`handleDeeplink: https:// or rainbow:// protocol`); + logger.debug(`[handleDeeplink]: https:// or rainbow:// protocol`); /** * The first path following the host (universal link) or protocol @@ -75,7 +75,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * tap "Rainbow" in Web3Modal and it hits this handler */ case 'wc': { - logger.info(`handleDeeplink: wc`); + logger.debug(`[handleDeeplink]: wc`); handleWalletConnect(query.uri, query.connector); break; } @@ -84,7 +84,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Links from website to an individual token */ case 'token': { - logger.info(`handleDeeplink: token`); + logger.debug(`[handleDeeplink]: token`); const { addr } = query; const address = (addr as string)?.toLowerCase() ?? ''; @@ -120,12 +120,12 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * should contain metadata about the transaction, if we have it. */ case 'f2c': { - logger.info(`handleDeeplink: f2c`); + logger.debug(`[handleDeeplink]: f2c`); const { provider, sessionId } = query; if (!provider || !sessionId) { - logger.warn('Received FWC deeplink with invalid params', { + logger.warn(`[handleDeeplink]: Received FWC deeplink with invalid params`, { url, query, }); @@ -166,11 +166,12 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Ratio's onramp SDK. */ case 'plaid': { - logger.log('handleDeeplink: handling Plaid redirect', { url }); + logger.debug(`[handleDeeplink]: handling Plaid redirect`, { url }); break; } case 'poap': { + logger.debug(`[handleDeeplink]: handling POAP`, { url }); const secretWordOrHash = pathname?.split('/')?.[1]; await getPoapAndOpenSheetWithSecretWord(secretWordOrHash, false); await getPoapAndOpenSheetWithQRHash(secretWordOrHash, false); @@ -178,6 +179,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } case 'points': { + logger.debug(`[handleDeeplink]: handling points`, { url }); const referralCode = query?.ref; if (referralCode) { analyticsV2.track(analyticsV2.event.pointsReferralCodeDeeplinkOpened); @@ -191,6 +193,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu case 'dapp': { const { url } = query; + logger.debug(`[handleDeeplink]: handling dapp`, { url }); if (url) { Navigation.handleAction(Routes.DAPP_BROWSER_SCREEN, { url }); } @@ -198,6 +201,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } default: { + logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; /** * This handles ENS profile links on mobile i.e. @@ -215,7 +219,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu fromRoute: 'Deeplink', }); } else { - logger.warn(`handleDeeplink: invalid address or ENS provided`, { + logger.warn(`[handleDeeplink]: invalid address or ENS provided`, { url, protocol, host, @@ -228,7 +232,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * This is a catch-all for any other deep links that we don't handle */ - logger.warn(`handleDeeplink: invalid or unknown deeplink`, { + logger.warn(`[handleDeeplink]: invalid or unknown deeplink`, { url, protocol, host, @@ -240,7 +244,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } // Android uses normal deeplinks } else if (protocol === 'wc:') { - logger.info(`handleDeeplink: wc:// protocol`); + logger.debug(`[handleDeeplink]: wc:// protocol`); handleWalletConnect(url, query.connector); } } @@ -267,21 +271,21 @@ const walletConnectURICache = new Set(); function handleWalletConnect(uri?: string, connector?: string) { if (!uri) { - logger.debug(`handleWalletConnect: skipping uri empty`, {}); + logger.debug(`[handleWalletConnect]: skipping uri empty`); return; } const cacheKey = JSON.stringify({ uri }); if (walletConnectURICache.has(cacheKey)) { - logger.debug(`handleWalletConnect: skipping duplicate event`, {}); + logger.debug(`[handleWalletConnect]: skipping duplicate event`); return; } const { query } = new URL(uri); const parsedUri = uri ? parseUri(uri) : null; - logger.debug(`handleWalletConnect: handling event`, { + logger.debug(`[handleWalletConnect]: handling event`, { uri, query, parsedUri, @@ -295,7 +299,7 @@ function handleWalletConnect(uri?: string, connector?: string) { store.dispatch(walletConnectSetPendingRedirect()); store.dispatch( walletConnectOnSessionRequest(uri, connector, (status: any, dappScheme: any) => { - logger.debug(`walletConnectOnSessionRequest callback`, { + logger.debug(`[walletConnectOnSessionRequest] callback`, { status, dappScheme, }); @@ -304,12 +308,12 @@ function handleWalletConnect(uri?: string, connector?: string) { }) ); } else if (parsedUri.version === 2) { - logger.debug(`handleWalletConnect: handling v2`, { uri }); + logger.debug(`[handleWalletConnect]: handling v2`, { uri }); setHasPendingDeeplinkPendingRedirect(true); pairWalletConnect({ uri, connector }); } } else { - logger.debug(`handleWalletConnect: handling fallback`, { uri }); + logger.debug(`[handleWalletConnect]: handling fallback`, { uri }); // This is when we get focused by WC due to a signing request // Don't add this URI to cache setHasPendingDeeplinkPendingRedirect(true); diff --git a/src/handlers/dispersion.ts b/src/handlers/dispersion.ts index c2281dadf71..f2f1686f794 100644 --- a/src/handlers/dispersion.ts +++ b/src/handlers/dispersion.ts @@ -1,7 +1,7 @@ import { RainbowFetchClient } from '../rainbow-fetch'; import { EthereumAddress, IndexToken, RainbowToken } from '@/entities'; import UniswapAssetsCache from '@/utils/uniswapAssetsCache'; -import { logger } from '@/logger'; +import { logger, RainbowError } from '@/logger'; const dispersionApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -25,7 +25,7 @@ export const getUniswapV2Tokens = async (addresses: EthereumAddress[]): Promise< return res?.data?.tokens ?? null; } } catch (e: any) { - logger.warn(`dispersionApi: error fetching uniswap v2 tokens`, { + logger.error(new RainbowError(`[getUniswapV2Tokens]: error fetching uniswap v2 tokens`), { message: e.message, }); } @@ -37,7 +37,7 @@ export const getTrendingAddresses = async (): Promise const res = await dispersionApi.get('/dispersion/v1/trending'); return res?.data?.data?.trending ?? null; } catch (e: any) { - logger.warn(`dispersionApi: error fetching trending addresses`, { + logger.error(new RainbowError(`[getTrendingAddresses]: error fetching trending addresses`), { message: e.message, }); return null; @@ -49,7 +49,7 @@ export const getAdditionalAssetData = async (address: EthereumAddress, chainId = const res = await dispersionApi.get(`/dispersion/v1/expanded/${chainId}/${address}`); return res?.data?.data ?? null; } catch (e: any) { - logger.warn(`dispersionApi: error fetching additional asset data`, { + logger.error(new RainbowError(`[getAdditionalAssetData]: error fetching additional asset data`), { message: e.message, }); return null; diff --git a/src/handlers/ens.ts b/src/handlers/ens.ts index e29076a878f..5277ac98919 100644 --- a/src/handlers/ens.ts +++ b/src/handlers/ens.ts @@ -1,12 +1,11 @@ import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder'; import { getAddress } from '@ethersproject/address'; import { Resolver } from '@ethersproject/providers'; -import { captureException } from '@sentry/react-native'; import { Duration, sub } from 'date-fns'; import { isValidAddress, isZeroAddress } from 'ethereumjs-util'; import { BigNumber } from '@ethersproject/bignumber'; import { debounce, isEmpty, sortBy } from 'lodash'; -import { fetchENSAvatar, prefetchENSAvatar } from '../hooks/useENSAvatar'; +import { fetchENSAvatar } from '../hooks/useENSAvatar'; import { prefetchENSCover } from '../hooks/useENSCover'; import { prefetchENSRecords } from '../hooks/useENSRecords'; import { ENSActionParameters, ENSRapActionType } from '@/raps/common'; @@ -18,16 +17,14 @@ import { ENS_DOMAIN, ENS_RECORDS, ENSRegistrationTransactionType, generateSalt, import { add } from '@/helpers/utilities'; import { ImgixImage } from '@/components/images'; import { ENS_NFT_CONTRACT_ADDRESS, ethUnits } from '@/references'; -import { labelhash, logger, profileUtils } from '@/utils'; +import { labelhash, profileUtils } from '@/utils'; import { AvatarResolver } from '@/ens-avatar/src'; import { ensClient } from '@/graphql'; import { prefetchFirstTransactionTimestamp } from '@/resources/transactions/firstTransactionTimestampQuery'; import { prefetchENSAddress } from '@/resources/ens/ensAddressQuery'; -import { ENS_MARQUEE_QUERY_KEY } from '@/resources/metadata/ensMarqueeQuery'; -import { queryClient } from '@/react-query'; -import { EnsMarqueeAccount } from '@/graphql/__generated__/metadata'; import { MimeType, handleNFTImages } from '@/utils/handleNFTImages'; import store from '@/redux/store'; +import { logger, RainbowError } from '@/logger'; const DUMMY_RECORDS = { description: 'description', @@ -137,8 +134,9 @@ export const fetchMetadata = async ({ const image_url = `https://metadata.ens.domains/mainnet/${contractAddress}/${tokenId}/image`; return { image_url, name }; } catch (error) { - logger.sentry('ENS: Error getting ENS metadata', error); - captureException(new Error('ENS: Error getting ENS metadata')); + logger.error(new RainbowError(`[ENS]: fetchMetadata failed`), { + error, + }); throw error; } }; @@ -175,8 +173,9 @@ export const fetchEnsTokens = async ({ .filter((token: TToken | null | undefined): token is TToken => !!token) || [] ); } catch (error) { - logger.sentry('ENS: Error getting ENS unique tokens', error); - captureException(new Error('ENS: Error getting ENS unique tokens')); + logger.error(new RainbowError(`[ENS]: fetchEnsTokens failed`), { + error, + }); return []; } }; diff --git a/src/handlers/imgix.ts b/src/handlers/imgix.ts index 5df0d639337..d07591839a8 100644 --- a/src/handlers/imgix.ts +++ b/src/handlers/imgix.ts @@ -6,7 +6,6 @@ import { Source } from 'react-native-fast-image'; import parse from 'url-parse'; import { isCloudinaryStorageIconLink, signCloudinaryIconUrl } from '@/handlers/cloudinary'; import { logger, RainbowError } from '@/logger'; -import { GOOGLE_USER_CONTENT_URL } from '@/utils/getFullResUrl'; const shouldCreateImgixClient = (): ImgixClient | null => { if (typeof domain === 'string' && !!domain.length && typeof secureURLToken === 'string' && !!secureURLToken.length) { @@ -32,7 +31,7 @@ const staticImgixClient = shouldCreateImgixClient(); // This might be conditional based upon either the runtime // hardware or the number of unique tokens a user may have. const capacity = 1024; -export let staticSignatureLRU: LRUCache = new LRUCache(capacity); +export const staticSignatureLRU: LRUCache = new LRUCache(capacity); interface ImgOptions { w?: number; @@ -117,11 +116,7 @@ const isPossibleToSignUri = (externalImageUri: string | undefined): boolean => { return false; }; -export const maybeSignUri = ( - externalImageUri: string | undefined, - options?: ImgOptions, - skipCaching: boolean = false -): string | undefined => { +export const maybeSignUri = (externalImageUri: string | undefined, options?: ImgOptions, skipCaching = false): string | undefined => { // If the image has already been signed, return this quickly. const signature = `${externalImageUri}-${options?.w}-${options?.fm}`; if (typeof externalImageUri === 'string' && staticSignatureLRU.has(signature as string) && !skipCaching) { diff --git a/src/handlers/localstorage/common.ts b/src/handlers/localstorage/common.ts index 12aa8a41735..591b9e47d36 100644 --- a/src/handlers/localstorage/common.ts +++ b/src/handlers/localstorage/common.ts @@ -1,4 +1,4 @@ -/*global storage*/ +/* global storage*/ import { legacy } from '@/storage/legacy'; import { logger, RainbowError } from '@/logger'; @@ -17,7 +17,7 @@ export const saveLocal = (key = '', data = {}) => { try { legacy.set([key], data); } catch (error) { - logger.error(new RainbowError('Legacy Storage: saveLocal error')); + logger.error(new RainbowError('[localstorage/common]: saveLocal error')); } }; @@ -51,7 +51,7 @@ export const deprecatedSaveLocal = async (key = '', data = {}, version = default key, }); } catch (error) { - logger.error(new RainbowError('Storage: deprecatedSaveLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedSaveLocal error')); } }; @@ -90,7 +90,7 @@ export const deprecatedGetLocal = async (key = '', version = defaultVersion) => case 'ExpiredError': break; default: - logger.error(new RainbowError('Storage: deprecatedGetLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedGetLocal error')); } return null; @@ -109,7 +109,7 @@ export const deprecatedRemoveLocal = (key = '') => { // @ts-expect-error ts-migrate(2552) FIXME: Cannot find name 'storage'. Did you mean 'Storage'... Remove this comment to see the full error message storage.remove({ key }); } catch (error) { - logger.error(new RainbowError('Storage: deprecatedRemoveLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedRemoveLocal error')); } }; diff --git a/src/handlers/localstorage/removeWallet.ts b/src/handlers/localstorage/removeWallet.ts index fe450683a61..7364f02e0cc 100644 --- a/src/handlers/localstorage/removeWallet.ts +++ b/src/handlers/localstorage/removeWallet.ts @@ -8,16 +8,18 @@ import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet } from '@/notifications/settings'; export const removeWalletData = async (accountAddress: any) => { - logger.debug('[remove wallet]', { accountAddress }); + logger.debug('[localstorage/removeWallet]: removing wallet data', { accountAddress }); const allPrefixes = accountLocalKeys.concat(walletConnectAccountLocalKeys); - logger.debug('[remove wallet] - all prefixes', { allPrefixes }); + logger.debug('[localstorage/removeWallet]: all prefixes', { allPrefixes }); const networks = keys(NetworkTypes); const allKeysWithNetworks = allPrefixes.map(prefix => networks.map(network => getKey(prefix, accountAddress, network))); const allKeys = allKeysWithNetworks.flat(); try { await AsyncStorage.multiRemove(allKeys); } catch (error) { - logger.error(new RainbowError('Error removing wallet data from storage')); + logger.error(new RainbowError('[localstorage/removeWallet]: Error removing wallet data from storage'), { + error, + }); } removeNotificationSettingsForWallet(accountAddress); }; diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index 55a9dd87b5a..c1dc6f021bd 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -20,8 +20,9 @@ import { getRemoteConfig } from '@/model/remoteConfig'; import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; import { erc20ABI, ethUnits } from '@/references'; -import { ethereumUtils, logger } from '@/utils'; +import { ethereumUtils } from '@/utils'; import { ChainId } from '@/__swaps__/types/chains'; +import { logger, RainbowError } from '@/logger'; export enum Field { INPUT = 'INPUT', @@ -137,7 +138,9 @@ export const getStateDiff = async (provider: StaticJsonRpcProvider, tradeDetails return formattedStateDiff; } } - logger.log('Couldnt get stateDiff...', JSON.stringify(trace, null, 2)); + logger.debug('[swap]: Couldnt get stateDiff...', { + trace, + }); }; export const getSwapGasLimitWithFakeApproval = async ( @@ -168,20 +171,26 @@ export const getSwapGasLimitWithFakeApproval = async ( try { await provider.send('eth_call', [...callParams, stateDiff]); - logger.log(`Estimate worked with gasLimit: `, gas); + logger.debug('[swap]: Estimate worked with gasLimit', { + gas, + }); return true; } catch (e) { - logger.log(`Estimate failed with gasLimit ${gas}. Trying with different amounts...`); + logger.debug('[swap]: Estimate failed with gasLimit', { + gas, + }); return false; } }); if (gasLimit && gasLimit >= ethUnits.basic_swap) { return gasLimit; } else { - logger.log('Could not find a gas estimate'); + logger.debug('[swap]: Could not find a gas estimate'); } } catch (e) { - logger.log(`Blew up trying to get state diff. Falling back to defaults`, e); + logger.error(new RainbowError('[swap]: Blew up trying to get state diff. Falling back to defaults'), { + error: e, + }); } return getDefaultGasLimitForTrade(tradeDetails, chainId); }; @@ -271,10 +280,14 @@ export const estimateSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); + logger.debug('[swap]: Got gasLimitWithFakeApproval!', { + gasLimitWithFakeApproval, + }); return gasLimitWithFakeApproval; } catch (e) { - logger.debug('Error estimating swap gas limit with approval', e); + logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { + error: e, + }); } } @@ -308,10 +321,14 @@ export const estimateCrosschainSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); + logger.debug('[swap]: Got gasLimitWithFakeApproval!', { + gasLimitWithFakeApproval, + }); return gasLimitWithFakeApproval; } catch (e) { - logger.debug('Error estimating swap gas limit with approval', e); + logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { + error: e, + }); } } diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index 023e7100617..848fc0cd770 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -52,7 +52,7 @@ export const swapSearch = async (searchParams: { const tokenSearch = await tokenSearchApi.get(url); return { ...tokenSearch.data?.data, chainId: searchParams.chainId }; } catch (e: any) { - logger.error(new RainbowError(`An error occurred while searching for query`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -103,7 +103,7 @@ export const tokenSearch = async (searchParams: { }; }); } catch (e: any) { - logger.error(new RainbowError(`An error occurred while searching for query`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -121,7 +121,7 @@ export const walletFilter = async (params: { addresses: EthereumAddress[]; fromC }); return filteredAddresses?.data?.data || []; } catch (e: any) { - logger.error(new RainbowError(`An error occurred while filter wallet addresses`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while filter wallet addresses`), { toChainId: params.toChainId, fromChainId: params.fromChainId, message: e.message, diff --git a/src/handlers/transactions.ts b/src/handlers/transactions.ts index 949e03a5990..f2cf11f164e 100644 --- a/src/handlers/transactions.ts +++ b/src/handlers/transactions.ts @@ -128,12 +128,12 @@ export const getTransactionSocketStatus = async (pendingTransaction: RainbowTran pending = false; } } else if (socketResponse.error) { - logger.warn('getTransactionSocketStatus transaction check failed', socketResponse.error); + logger.warn('[getTransactionSocketStatus]: transaction check failed', socketResponse.error); status = TransactionStatus.failed; pending = false; } } catch (e) { - logger.error(new RainbowError('getTransactionSocketStatus transaction check caught')); + logger.error(new RainbowError('[getTransactionSocketStatus]: transaction check caught')); if (IS_TEST) { status = swap?.isBridge ? TransactionStatus.bridged : TransactionStatus.swapped; pending = false; diff --git a/src/handlers/walletReadyEvents.ts b/src/handlers/walletReadyEvents.ts index 9f02a762c36..1cfa62be144 100644 --- a/src/handlers/walletReadyEvents.ts +++ b/src/handlers/walletReadyEvents.ts @@ -53,10 +53,10 @@ export const runWalletBackupStatusChecks = () => { if (!rainbowWalletsNotBackedUp.length) return; - logger.debug('there is a rainbow wallet not backed up'); + logger.debug('[walletReadyEvents]: there is a rainbow wallet not backed up'); const hasSelectedWallet = rainbowWalletsNotBackedUp.find(notBackedUpWallet => notBackedUpWallet.id === selected!.id); - logger.debug('rainbow wallet not backed up that is selected?', { + logger.debug('[walletReadyEvents]: rainbow wallet not backed up that is selected?', { hasSelectedWallet, }); @@ -70,7 +70,7 @@ export const runWalletBackupStatusChecks = () => { } setTimeout(() => { - logger.debug(`showing ${stepType} backup sheet for selected wallet`); + logger.debug(`[walletReadyEvents]: showing ${stepType} backup sheet for selected wallet`); triggerOnSwipeLayout(() => Navigation.handleAction(Routes.BACKUP_SHEET, { step: stepType, @@ -99,11 +99,11 @@ export const runFeatureUnlockChecks = async (): Promise => { } }); - logger.debug('WALLETS TO CHECK', { walletsToCheck }); + logger.debug('[walletReadyEvents]: WALLETS TO CHECK', { walletsToCheck }); if (!walletsToCheck.length) return false; - logger.debug('Feature Unlocks: Running Checks'); + logger.debug('[walletReadyEvents]: Feature Unlocks: Running Checks'); // short circuits once the first feature is unlocked for (const featureUnlockCheck of featureUnlockChecks) { diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 95822333253..23996995cf2 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -414,16 +414,16 @@ export async function estimateGasWithPadding( const code = to ? await p.getCode(to) : undefined; // 2 - if it's not a contract AND it doesn't have any data use the default gas limit if ((!contractCallEstimateGas && !to) || (to && !data && (!code || code === '0x'))) { - logger.debug('⛽ Skipping estimates, using default', { + logger.debug('[web3]: ⛽ Skipping estimates, using default', { ethUnits: ethUnits.basic_tx.toString(), }); return ethUnits.basic_tx.toString(); } - logger.debug('⛽ Calculating safer gas limit for last block'); + logger.debug('[web3]: ⛽ Calculating safer gas limit for last block'); // 3 - If it is a contract, call the RPC method `estimateGas` with a safe value const saferGasLimit = fraction(gasLimit.toString(), 19, 20); - logger.debug('⛽ safer gas limit for last block is', { saferGasLimit }); + logger.debug('[web3]: ⛽ safer gas limit for last block is', { saferGasLimit }); txPayloadToEstimate[contractCallEstimateGas ? 'gasLimit' : 'gas'] = toHex(saferGasLimit); @@ -435,7 +435,7 @@ export async function estimateGasWithPadding( const lastBlockGasLimit = addBuffer(gasLimit.toString(), 0.9); const paddedGas = addBuffer(estimatedGas.toString(), paddingFactor.toString()); - logger.debug('⛽ GAS CALCULATIONS!', { + logger.debug('[web3]: ⛽ GAS CALCULATIONS!', { estimatedGas: estimatedGas.toString(), gasLimit: gasLimit.toString(), lastBlockGasLimit: lastBlockGasLimit, @@ -444,24 +444,24 @@ export async function estimateGasWithPadding( // If the safe estimation is above the last block gas limit, use it if (greaterThan(estimatedGas.toString(), lastBlockGasLimit)) { - logger.debug('⛽ returning orginal gas estimation', { + logger.debug('[web3]: ⛽ returning orginal gas estimation', { esimatedGas: estimatedGas.toString(), }); return estimatedGas.toString(); } // If the estimation is below the last block gas limit, use the padded estimate if (greaterThan(lastBlockGasLimit, paddedGas)) { - logger.debug('⛽ returning padded gas estimation', { paddedGas }); + logger.debug('[web3]: ⛽ returning padded gas estimation', { paddedGas }); return paddedGas; } // otherwise default to the last block gas limit - logger.debug('⛽ returning last block gas limit', { lastBlockGasLimit }); + logger.debug('[web3]: ⛽ returning last block gas limit', { lastBlockGasLimit }); return lastBlockGasLimit; } catch (e) { /* * Reported ~400x per day, but if it's not actionable it might as well be a warning. */ - logger.warn('Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); + logger.warn('[web3]: Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); return null; } } @@ -552,7 +552,7 @@ export const resolveUnstoppableDomain = async (domain: string): Promise { - logger.error(new RainbowError(`resolveUnstoppableDomain error`), { + logger.error(new RainbowError(`[web3]: resolveUnstoppableDomain error`), { message: error.message, }); return null; diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index 055210d6fd8..f38c5ad79c2 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -3,25 +3,13 @@ import { MMKV } from 'react-native-mmkv'; import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; -import { - showReloadButton, - showSwitchModeButton, - // @ts-ignore - showConnectToHardhatButton, -} from '../config/debug'; +import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; import { defaultConfig } from '../config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; import { STORAGE_IDS } from '@/model/mmkv'; -import { - // @ts-ignore - HARDHAT_URL_ANDROID, - // @ts-ignore - HARDHAT_URL_IOS, - // @ts-ignore - IS_TESTING, -} from 'react-native-dotenv'; +import { IS_TESTING } from 'react-native-dotenv'; import { web3SetHttpProvider } from '@/handlers/web3'; import { logger, RainbowError } from '@/logger'; import networkTypes from '@/helpers/networkTypes'; @@ -76,10 +64,10 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { const connectToHardhat = useCallback(async () => { try { const ready = await web3SetHttpProvider('http://127.0.0.1:8545'); - logger.debug('connected to hardhat', { ready }); + logger.debug('[RainbowContext]: connected to hardhat', { ready }); } catch (e: any) { await web3SetHttpProvider(networkTypes.mainnet); - logger.error(new RainbowError('error connecting to hardhat'), { + logger.error(new RainbowError('[RainbowContext]: error connecting to hardhat'), { message: e.message, }); } diff --git a/src/helpers/signingWallet.ts b/src/helpers/signingWallet.ts index 5722ea9c0ee..7d41fc0e074 100644 --- a/src/helpers/signingWallet.ts +++ b/src/helpers/signingWallet.ts @@ -8,9 +8,8 @@ import { signingWalletAddress, signingWallet as signingWalletKeychain } from '.. import { EthereumAddress } from '@/entities'; import AesEncryptor from '@/handlers/aesEncryption'; import { addHexPrefix } from '@/handlers/web3'; -import { logger } from '@/utils'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger as Logger, RainbowError } from '@/logger'; +import { logger, RainbowError } from '@/logger'; export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): Promise { let alreadyExistingWallet = await loadString(signingWalletAddress); @@ -20,7 +19,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P const { wallet, address } = await deriveAccountFromWalletInput(walletSeed); if (!wallet || !address) { - Logger.error(new RainbowError('signingWallet - wallet or address undefined')); + logger.error(new RainbowError('[signingWallet]: wallet or address undefined')); // @ts-ignore need to handle types in case wallet or address are null return null; } @@ -35,7 +34,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P await saveString(signingWalletAddress, address, publicAccessControlOptions); alreadyExistingWallet = address; } - logger.log('Signing wallet already existing'); + logger.debug('[signingWallet]: Signing wallet already existing'); return alreadyExistingWallet; } @@ -48,7 +47,7 @@ export async function getSignatureForSigningWalletAndCreateSignatureIfNeeded(add if (address === verifyMessage(publicKeyForTheSigningWallet, decryptedSignature)) { return decryptedSignature; } else { - logger.log('Signature does not match. Creating a new one.'); + logger.debug('[signingWallet]: Signature does not match. Creating a new one.'); alreadyExistingEncodedSignature = null; return createSignature(address); } @@ -61,14 +60,14 @@ export async function signWithSigningWallet(messageToSign: string): Promise = (...params: P) => Promise; @@ -379,7 +380,9 @@ async function uploadRecordImages(records: Partial | undefined, imageMe }); return url; } catch (error) { - logger.sentry('[uploadRecordImages] Failed to upload image.', error); + logger.error(new RainbowError('[useENSRegistrationActionHandler]: Failed to upload image.'), { + error, + }); return undefined; } } diff --git a/src/hooks/useENSSearch.ts b/src/hooks/useENSSearch.ts index b99378a9f0c..29cc46b6722 100644 --- a/src/hooks/useENSSearch.ts +++ b/src/hooks/useENSSearch.ts @@ -8,7 +8,7 @@ import { Network } from '@/helpers/networkTypes'; import { timeUnits } from '@/references'; import { ethereumUtils, validateENS } from '@/utils'; -const formatTime = (timestamp: string, abbreviated: boolean = true) => { +const formatTime = (timestamp: string, abbreviated = true) => { const style = abbreviated ? 'MMM d, y' : 'MMMM d, y'; return format(new Date(Number(timestamp) * 1000), style); }; diff --git a/src/hooks/useEffectDebugger.ts b/src/hooks/useEffectDebugger.ts index 85e742c5f57..7294d1b1e5d 100644 --- a/src/hooks/useEffectDebugger.ts +++ b/src/hooks/useEffectDebugger.ts @@ -1,12 +1,12 @@ -import logger from '@/utils/logger'; +import { logger } from '@/logger'; const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { // Edge-case: different array lengths if (oldInputs.length !== newInputs.length) { // Not helpful to compare item by item, so just output the whole array - logger.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs); - logger.log('Old inputs:', oldInputs); - logger.log('New inputs:', newInputs); + logger.debug(`[useEffectDebugger]: ${prefix} - Inputs have a different length`, oldInputs, newInputs); + logger.debug(`[useEffectDebugger]: Old inputs:`, oldInputs); + logger.debug(`[useEffectDebugger]: New inputs:`, newInputs); return; } @@ -14,9 +14,9 @@ const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { oldInputs.forEach((oldInput: any, index: any) => { const newInput = newInputs[index]; if (oldInput !== newInput) { - logger.log(`${prefix} - The input changed in position ${index}`); - logger.log('Old value:', oldInput); - logger.log('New value:', newInput); + logger.debug(`[useEffectDebugger]: ${prefix} - The input changed in position ${index}`); + logger.debug(`[useEffectDebugger]: Old value:`, oldInput); + logger.debug(`[useEffectDebugger]: New value:`, newInput); } }); }; diff --git a/src/hooks/useHideSplashScreen.ts b/src/hooks/useHideSplashScreen.ts index d9383a7b482..aff7dd0c43a 100644 --- a/src/hooks/useHideSplashScreen.ts +++ b/src/hooks/useHideSplashScreen.ts @@ -55,12 +55,12 @@ export default function useHideSplashScreen() { if (appIcon === 'poolboy') { const sound = new Sound(require('../assets/sounds/RainbowSega.mp3'), (error: any) => { if (error) { - logger.error(new RainbowError('Error playing poolboy sound')); + logger.error(new RainbowError('[useHideSplashScreen]: Error playing poolboy sound')); return; } sound.play((success: any) => { - logger.debug('playing poolboy sound'); + logger.debug('[useHideSplashScreen]: playing poolboy sound'); }); }); } diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 0e8c5368f5f..828dee3225c 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -25,9 +25,8 @@ import { Navigation, useNavigation } from '@/navigation'; import { walletsLoadState } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; import { sanitizeSeedPhrase } from '@/utils'; -import logger from '@/utils/logger'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger as Logger, RainbowError } from '@/logger'; +import { logger, RainbowError } from '@/logger'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils'; @@ -46,8 +45,8 @@ export default function useImportingWallet({ showImportModal = true } = {}) { const [name, setName] = useState(null); const [image, setImage] = useState(null); const [busy, setBusy] = useState(false); - const [checkedWallet, setCheckedWallet] = useState(null); - const [resolvedAddress, setResolvedAddress] = useState(null); + const [checkedWallet, setCheckedWallet] = useState> | null>(null); + const [resolvedAddress, setResolvedAddress] = useState(null); const wasImporting = usePrevious(isImporting); const { updateWalletENSAvatars } = useWalletENSAvatar(); const profilesEnabled = useExperimentalFlag(PROFILES); @@ -133,7 +132,6 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_ens_name')); return; } - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; avatarUrl = avatarUrl || avatar?.imageUrl; @@ -157,7 +155,6 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_unstoppable_name')); return; } - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; setBusy(false); @@ -187,7 +184,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { input, }); } catch (e) { - logger.log(`Error resolving ENS during wallet import`, e); + logger.error(new RainbowError(`[useImportingWallet]: Error resolving ENS during wallet import: ${e}`)); } setBusy(false); // @ts-expect-error ts-migrate(2554) FIXME: Expected 4 arguments, but got 3. @@ -196,10 +193,9 @@ export default function useImportingWallet({ showImportModal = true } = {}) { try { setTimeout(async () => { const walletResult = await deriveAccountFromWalletInput(input); - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ address: string; isHDWallet: b... Remove this comment to see the full error message setCheckedWallet(walletResult); if (!walletResult.address) { - Logger.error(new RainbowError('useImportingWallet - walletResult address is undefined')); + logger.error(new RainbowError('[useImportingWallet]: walletResult address is undefined')); return null; } const ens = await fetchReverseRecord(walletResult.address); @@ -220,7 +216,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { }); }, 100); } catch (error) { - logger.log('Error looking up ENS for imported HD type wallet', error); + logger.error(new RainbowError(`[useImportingWallet]: Error looking up ENS for imported HD type wallet: ${error}`)); setBusy(false); } } @@ -337,7 +333,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { .catch(error => { handleSetImporting(false); android && handleSetImporting(false); - logger.error('error importing seed phrase: ', error); + logger.error(new RainbowError(`[useImportingWallet]: Error importing seed phrase: ${error}`)); setTimeout(() => { inputRef.current?.focus(); // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 0. diff --git a/src/hooks/useInitializeAccountData.ts b/src/hooks/useInitializeAccountData.ts index 0ac4db6d733..205d8fa42fa 100644 --- a/src/hooks/useInitializeAccountData.ts +++ b/src/hooks/useInitializeAccountData.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { InteractionManager } from 'react-native'; import { useDispatch } from 'react-redux'; import { explorerInit } from '../redux/explorer'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; export default function useInitializeAccountData() { const dispatch = useDispatch(); @@ -11,11 +11,11 @@ export default function useInitializeAccountData() { const initializeAccountData = useCallback(async () => { try { InteractionManager.runAfterInteractions(() => { - logger.sentry('Initialize account data'); + logger.debug('[useInitializeAccountData]: Initialize account data'); dispatch(explorerInit()); }); } catch (error) { - logger.sentry('Error initializing account data'); + logger.error(new RainbowError(`[useInitializeAccountData]: Error initializing account data: ${error}`)); captureException(error); } }, [dispatch]); diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts index c702ac8d61a..4ece4a1d2d5 100644 --- a/src/hooks/useInitializeWallet.ts +++ b/src/hooks/useInitializeWallet.ts @@ -60,19 +60,19 @@ export default function useInitializeWallet() { ) => { try { PerformanceTracking.startMeasuring(PerformanceMetrics.useInitializeWallet); - logger.debug('Start wallet setup'); + logger.debug('[useInitializeWallet]: Start wallet setup'); await resetAccountState(); - logger.debug('resetAccountState ran ok'); + logger.debug('[useInitializeWallet]: resetAccountState ran ok'); const isImporting = !!seedPhrase; - logger.debug('isImporting? ' + isImporting); + logger.debug(`[useInitializeWallet]: isImporting? ${isImporting}`); if (shouldRunMigrations && !seedPhrase) { - logger.debug('shouldRunMigrations && !seedPhrase? => true'); + logger.debug('[useInitializeWallet]: shouldRunMigrations && !seedPhrase? => true'); await dispatch(walletsLoadState(profilesEnabled)); - logger.debug('walletsLoadState call #1'); + logger.debug('[useInitializeWallet]: walletsLoadState call #1'); await runMigrations(); - logger.debug('done with migrations'); + logger.debug('[useInitializeWallet]: done with migrations'); } setIsSmallBalancesOpen(false); @@ -82,7 +82,7 @@ export default function useInitializeWallet() { const { isNew, walletAddress } = await walletInit(seedPhrase, color, name, overwrite, checkedWallet, network, image, silent); - logger.debug('walletInit returned', { + logger.debug('[useInitializeWallet]: walletInit returned', { isNew, walletAddress, }); @@ -94,12 +94,12 @@ export default function useInitializeWallet() { } if (seedPhrase || isNew) { - logger.debug('walletLoadState call #2'); + logger.debug('[useInitializeWallet]: walletsLoadState call #2'); await dispatch(walletsLoadState(profilesEnabled)); } if (isNil(walletAddress)) { - logger.debug('walletAddress is nil'); + logger.debug('[useInitializeWallet]: walletAddress is nil'); Alert.alert(lang.t('wallet.import_failed_invalid_private_key')); if (!isImporting) { dispatch(appStateUpdate({ walletReady: true })); @@ -109,18 +109,18 @@ export default function useInitializeWallet() { if (!(isNew || isImporting)) { await loadGlobalEarlyData(); - logger.debug('loaded global data...'); + logger.debug('[useInitializeWallet]: loaded global data...'); } await dispatch(settingsUpdateAccountAddress(walletAddress)); - logger.debug('updated settings address', { + logger.debug('[useInitializeWallet]: updated settings address', { walletAddress, }); // Newly created / imported accounts have no data in localstorage if (!(isNew || isImporting)) { await loadAccountData(); - logger.debug('loaded account data', { + logger.debug('[useInitializeWallet]: loaded account data', { network, }); } @@ -128,7 +128,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('Error while hiding splash screen'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { error: err, }); } @@ -136,7 +136,7 @@ export default function useInitializeWallet() { initializeAccountData(); dispatch(appStateUpdate({ walletReady: true })); - logger.debug('💰 Wallet initialized'); + logger.debug('[useInitializeWallet]: 💰 Wallet initialized'); PerformanceTracking.finishMeasuring(PerformanceMetrics.useInitializeWallet, { walletStatus: getWalletStatusForPerformanceMetrics(isNew, isImporting), @@ -145,7 +145,7 @@ export default function useInitializeWallet() { return walletAddress; } catch (error) { PerformanceTracking.clearMeasure(PerformanceMetrics.useInitializeWallet); - logger.error(new RainbowError('Error while initializing wallet'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while initializing wallet'), { error, }); // TODO specify error states more granular @@ -156,7 +156,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('Error while hiding splash screen'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { error: err, }); } diff --git a/src/hooks/useLedgerConnect.ts b/src/hooks/useLedgerConnect.ts index 4ac4888839d..103370bacb0 100644 --- a/src/hooks/useLedgerConnect.ts +++ b/src/hooks/useLedgerConnect.ts @@ -33,13 +33,13 @@ export function useLedgerConnect({ if (isReady) return; if (errorType === LEDGER_ERROR_CODES.DISCONNECTED) { setReadyForPolling(false); - logger.info('[LedgerConnect] - Device Disconnected - Attempting Reconnect', {}); + logger.debug('[useLedgerConnect]: Device Disconnected - Attempting Reconnect', {}); transport.current = undefined; try { transport.current = await TransportBLE.open(deviceId); setReadyForPolling(true); } catch (e) { - logger.error(new RainbowError('[LedgerConnect] - Reconnect Error'), { + logger.error(new RainbowError('[useLedgerConnect]: Reconnect Error'), { error: (e as Error).message, }); // temp removing this to see if it fixes an issue @@ -67,7 +67,7 @@ export function useLedgerConnect({ const pollerCleanup = (poller: NodeJS.Timer | undefined) => { try { if (poller) { - logger.debug('[LedgerConnect] - polling tear down', {}); + logger.debug('[useLedgerConnect]: polling tear down', {}); clearInterval(poller); poller?.unref(); timer.current = undefined; @@ -78,7 +78,7 @@ export function useLedgerConnect({ }; useEffect(() => { if (readyForPolling && (!timer.current || triggerPollerCleanup)) { - logger.debug('[LedgerConnect] - init device polling', {}); + logger.debug('[useLedgerConnect]: init device polling', {}); setTriggerPollerCleanup(false); timer.current = setInterval(async () => { if (transport.current) { diff --git a/src/hooks/useLedgerImport.ts b/src/hooks/useLedgerImport.ts index 611e39f0c99..074e4319a96 100644 --- a/src/hooks/useLedgerImport.ts +++ b/src/hooks/useLedgerImport.ts @@ -1,9 +1,8 @@ import TransportBLE from '@ledgerhq/react-native-hw-transport-ble'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { DebugContext } from '@/logger/debugContext'; import { logger, RainbowError } from '@/logger'; import { Subscription } from '@ledgerhq/hw-transport'; -import { Alert } from 'react-native'; import { checkAndRequestAndroidBluetooth, showBluetoothPermissionsAlert, showBluetoothPoweredOffAlert } from '@/utils/bluetoothPermissions'; import { IS_ANDROID, IS_IOS } from '@/env'; import { ledgerErrorHandler, LEDGER_ERROR_CODES } from '@/utils/ledger'; @@ -30,7 +29,7 @@ export function useLedgerImport({ */ const handlePairError = useCallback( (error: Error) => { - logger.error(new RainbowError('[LedgerImport] - Pairing Error'), { + logger.error(new RainbowError('[useLedgerImport]: Pairing Error'), { error, }); errorCallback?.(ledgerErrorHandler(error)); @@ -43,7 +42,7 @@ export function useLedgerImport({ */ const handlePairSuccess = useCallback( (deviceId: string) => { - logger.debug('[LedgerImport] - Pairing Success', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Pairing Success', {}, DebugContext.ledger); successCallback?.(deviceId); handleCleanUp(); }, @@ -59,15 +58,15 @@ export function useLedgerImport({ const newObserver = TransportBLE.observeState({ // havnt seen complete or error fire yet but its in the docs so keeping for reporting purposes complete: (e: any) => { - logger.debug('[LedgerImport] Observer complete', { e }, DebugContext.ledger); + logger.debug('[useLedgerImport]: Observer complete', { e }, DebugContext.ledger); }, error: (e: any) => { - logger.debug('[LedgerImport] Observer error ', { e }, DebugContext.ledger); + logger.debug('[useLedgerImport]: Observer error ', { e }, DebugContext.ledger); }, next: async (e: any) => { // App is not authorized to use Bluetooth if (e.type === 'Unauthorized') { - logger.debug('[LedgerImport] - Bluetooth Unauthorized', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Bluetooth Unauthorized', {}, DebugContext.ledger); if (IS_IOS) { await showBluetoothPermissionsAlert(); } else { @@ -76,14 +75,14 @@ export function useLedgerImport({ } // Bluetooth is turned off if (e.type === 'PoweredOff') { - logger.debug('[LedgerImport] - Bluetooth Powered Off', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Bluetooth Powered Off', {}, DebugContext.ledger); await showBluetoothPoweredOffAlert(); } if (e.available) { const newListener = TransportBLE.listen({ complete: () => {}, error: error => { - logger.error(new RainbowError('[Ledger Import] - Error Pairing'), { errorMessage: (error as Error).message }); + logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (error as Error).message }); }, next: async e => { if (e.type === 'add') { @@ -119,10 +118,10 @@ export function useLedgerImport({ useEffect(() => { const asyncFn = async () => { - logger.debug('[LedgerImport] - init device polling', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: init device polling', {}, DebugContext.ledger); const isBluetoothEnabled = IS_ANDROID ? await checkAndRequestAndroidBluetooth() : true; - logger.debug('[LedgerImport] - bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); + logger.debug('[useLedgerImport]: bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); if (isBluetoothEnabled) { searchAndPair(); diff --git a/src/hooks/useLoadAccountData.ts b/src/hooks/useLoadAccountData.ts index ecfd441cf48..bb74891d0c7 100644 --- a/src/hooks/useLoadAccountData.ts +++ b/src/hooks/useLoadAccountData.ts @@ -5,12 +5,12 @@ import { requestsLoadState } from '../redux/requests'; import { showcaseTokensLoadState } from '../redux/showcaseTokens'; import { walletConnectLoadState } from '../redux/walletconnect'; import { promiseUtils } from '../utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; export default function useLoadAccountData() { const dispatch = useDispatch(); const loadAccountData = useCallback(async () => { - logger.sentry('Load wallet account data'); + logger.debug('[useLoadAccountData]: Load wallet account data'); await dispatch(showcaseTokensLoadState()); await dispatch(hiddenTokensLoadState()); const promises = []; diff --git a/src/hooks/useLoadAccountLateData.ts b/src/hooks/useLoadAccountLateData.ts index 68d84bd34f2..e438b081072 100644 --- a/src/hooks/useLoadAccountLateData.ts +++ b/src/hooks/useLoadAccountLateData.ts @@ -3,7 +3,7 @@ import { promiseUtils } from '../utils'; import { prefetchAccountENSDomains } from './useAccountENSDomains'; import useAccountSettings from './useAccountSettings'; import useWallets from './useWallets'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { ensRegistrationsLoadState } from '@/redux/ensRegistration'; import { useDispatch } from 'react-redux'; import { showcaseTokensUpdateStateFromWeb } from '@/redux/showcaseTokens'; @@ -15,7 +15,7 @@ export default function useLoadAccountLateData() { const dispatch = useDispatch(); const loadAccountLateData = useCallback(async () => { - logger.sentry('Load wallet account late data'); + logger.debug('[useLoadAccountLateData]: Load wallet account late data'); const promises = []; diff --git a/src/hooks/useLoadGlobalEarlyData.ts b/src/hooks/useLoadGlobalEarlyData.ts index 8427c958ef0..fab0b4b3d0c 100644 --- a/src/hooks/useLoadGlobalEarlyData.ts +++ b/src/hooks/useLoadGlobalEarlyData.ts @@ -3,13 +3,13 @@ import { useDispatch } from 'react-redux'; import { settingsLoadLanguage, settingsLoadState } from '@/redux/settings'; import { promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; export default function useLoadGlobalEarlyData() { const dispatch = useDispatch(); const loadGlobalData = useCallback(async () => { - logger.sentry('Load wallet global early data'); + logger.debug('[useLoadGlobalEarlyData]: Load wallet global early data'); const promises = []; // native currency, app icon, testnetsEnabled, flashbotsEnabled diff --git a/src/hooks/useLoadGlobalLateData.ts b/src/hooks/useLoadGlobalLateData.ts index c4d9d41fcfa..7ad42460955 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -6,7 +6,7 @@ import { keyboardHeightsLoadState } from '@/redux/keyboardHeight'; import { AppState } from '@/redux/store'; import { transactionSignaturesLoadState } from '@/redux/transactionSignatures'; import { promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -21,7 +21,7 @@ export default function useLoadGlobalLateData() { if (!walletReady) { return false; } - logger.sentry('Load wallet global late data'); + logger.debug('[useLoadGlobalLateData]: Load wallet global late data'); const promises = []; // mainnet eth balances for all wallets diff --git a/src/hooks/useManageCloudBackups.ts b/src/hooks/useManageCloudBackups.ts index 0d39ca141d5..141f26b7f4e 100644 --- a/src/hooks/useManageCloudBackups.ts +++ b/src/hooks/useManageCloudBackups.ts @@ -20,7 +20,7 @@ export default function useManageCloudBackups() { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`Error Fetching google account data for Backups Section`), { + logger.error(new RainbowError(`[useManageCloudBackups]: Error Fetching google account data for Backups Section`), { error: (error as Error).message, }); }); @@ -54,7 +54,7 @@ export default function useManageCloudBackups() { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`Logging into Google Drive failed.`), { + logger.error(new RainbowError(`[useManageCloudBackups]: Logging into Google Drive failed.`), { error: (error as Error).message, }); } diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index ad94660351a..aa03ef77468 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,4 +1,3 @@ -import { captureException } from '@sentry/react-native'; import delay from 'delay'; import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; @@ -7,7 +6,7 @@ import { walletConnectLoadState } from '../redux/walletconnect'; import { fetchWalletENSAvatars, fetchWalletNames } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; import { PROFILES, useExperimentalFlag } from '@/config'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; @@ -54,8 +53,7 @@ export default function useRefreshAccountData() { wc, ]); } catch (error) { - logger.log('Error refreshing data', error); - captureException(error); + logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`)); throw error; } }, [accountAddress, dispatch, nativeCurrency, profilesEnabled]); @@ -67,8 +65,8 @@ export default function useRefreshAccountData() { try { await fetchAccountData(); - } catch (e) { - logger.error(e); + } catch (error) { + logger.error(new RainbowError(`[useRefreshAccountData]: Error calling fetchAccountData: ${error}`)); } finally { setIsRefreshing(false); } diff --git a/src/hooks/useSafeImageUri.ts b/src/hooks/useSafeImageUri.ts index a08ef6153b9..f76b99e0264 100644 --- a/src/hooks/useSafeImageUri.ts +++ b/src/hooks/useSafeImageUri.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { maybeSignUri } from '../handlers/imgix'; -export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching: boolean = false): string | undefined { +export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching = false): string | undefined { return useMemo(() => { return maybeSignUri(maybeUnsafeUri, {}, skipCaching); }, [maybeUnsafeUri, skipCaching]); diff --git a/src/hooks/useScanner.ts b/src/hooks/useScanner.ts index 02406c9bc24..7a50df6b8f8 100644 --- a/src/hooks/useScanner.ts +++ b/src/hooks/useScanner.ts @@ -16,7 +16,7 @@ import { Navigation } from '@/navigation'; import { POAP_BASE_URL, RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { addressUtils, ethereumUtils, haptics } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { checkPushNotificationPermissions } from '@/notifications/permissions'; import { pair as pairWalletConnect } from '@/walletConnect'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; @@ -28,12 +28,12 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { const enabledVar = useRef(); const enableScanning = useCallback(() => { - logger.log('📠✅ Enabling QR Code Scanner'); + logger.debug('[useScanner]: 📠✅ Enabling QR Code Scanner'); enabledVar.current = true; }, [enabledVar]); const disableScanning = useCallback(() => { - logger.log('📠🚫 Disabling QR Code Scanner'); + logger.debug('[useScanner]: 📠🚫 Disabling QR Code Scanner'); enabledVar.current = false; }, [enabledVar]); @@ -113,8 +113,8 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { } else if (version === 2) { await pairWalletConnect({ uri, connector }); } - } catch (e) { - logger.log('walletConnectOnSessionRequest exception', e); + } catch (error) { + logger.error(new RainbowError(`[useScanner]: Error handling WalletConnect QR code: ${error}`)); } }, [goBack, onSuccess, walletConnectOnSessionRequest] @@ -190,14 +190,14 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { if (lowerCaseData.startsWith(`${RAINBOW_PROFILES_BASE_URL}/poap`)) { const secretWordOrQrHash = lowerCaseData.split(`${RAINBOW_PROFILES_BASE_URL}/poap/`)?.[1]; - logger.log('onScan: handling poap scan', { secretWordOrQrHash }); + logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } if (lowerCaseData.startsWith(`rainbow://poap`)) { const secretWordOrQrHash = lowerCaseData.split(`rainbow://poap/`)?.[1]; - logger.log('onScan: handling poap scan', { secretWordOrQrHash }); + logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 554fe14cfb6..59b1320d29b 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -11,7 +11,8 @@ import { tokenSearch } from '@/handlers/tokenSearch'; import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; +import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; +import { logger } from '@/logger'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; @@ -200,8 +201,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } as RainbowToken, ]; } catch (e) { - logger.log('error getting token data'); - logger.log(e); + logger.warn('[useSearchCurrencyList]: error getting token data', { error: (e as Error).message }); return null; } } diff --git a/src/hooks/useSendableUniqueTokens.ts b/src/hooks/useSendableUniqueTokens.ts index 331f6b2042f..7e9341676df 100644 --- a/src/hooks/useSendableUniqueTokens.ts +++ b/src/hooks/useSendableUniqueTokens.ts @@ -11,7 +11,7 @@ export default function useSendableUniqueTokens() { const sendableUniqueTokens = uniqueTokens?.filter((uniqueToken: any) => uniqueToken.isSendable); const grouped = groupBy(sendableUniqueTokens, token => token.familyName); const families = Object.keys(grouped).sort(); - let sendableTokens = []; + const sendableTokens = []; for (let i = 0; i < families.length; i++) { let newObject = {}; newObject = { diff --git a/src/hooks/useStepper.ts b/src/hooks/useStepper.ts index 672e8e237f5..6ac07f73523 100644 --- a/src/hooks/useStepper.ts +++ b/src/hooks/useStepper.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -export default function useStepper(max: number, initialIndex: number = 0) { +export default function useStepper(max: number, initialIndex = 0) { const [step, setStep] = useState(initialIndex); const nextStep = useCallback(() => setStep(p => (p + 1) % max), [max]); return [step, nextStep, setStep]; diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index a987758763e..12b8cf47df0 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -11,7 +11,7 @@ import { swapSearch } from '@/handlers/tokenSearch'; import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; +import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; @@ -19,6 +19,7 @@ import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; import { ChainId } from '@/__swaps__/types/chains'; +import { logger } from '@/logger'; const MAINNET_CHAINID = 1; type swapCurrencyListType = @@ -213,8 +214,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI } as RainbowToken, ]; } catch (e) { - logger.log('error getting token data'); - logger.log(e); + logger.warn('[useSwapCurrencyList]: error getting token data', { error: (e as Error).message }); return null; } } diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index fa8db8c1b63..72c76856c54 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -78,7 +78,7 @@ const getInputAmount = async ( const buyAmount = convertAmountToRawAmount(convertNumberToString(outputAmount), outputToken.decimals); - logger.info(`[getInputAmount]: `, { + logger.debug('[useSwapDerivedOutputs]: ', { outputToken, outputChainId, outputNetwork: outputToken?.network, @@ -105,7 +105,7 @@ const getInputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[getInputAmount]: Getting quote', { rand, quoteParams }); + logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote @@ -115,7 +115,7 @@ const getInputAmount = async ( if (!quote || (quote as QuoteError).error || !(quote as Quote).sellAmount) { if ((quote as QuoteError).error) { const quoteError = quote as unknown as QuoteError; - logger.error(new RainbowError('[getInputAmount]: Quote error'), { + logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -173,15 +173,13 @@ const getOutputAmount = async ( const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); const isCrosschainSwap = outputChainId !== inputChainId; - // logger.info(`[getOutputAmount]: `, { - // outputToken, - // outputChainId, - // outputNetwork, - // inputToken, - // inputChainId, - // inputNetwork, - // isCrosschainSwap, - // }); + logger.debug(`[useSwapDerivedOutputs]: `, { + outputToken, + outputChainId, + inputToken, + inputChainId, + isCrosschainSwap, + }); const quoteSource = getSource(source); const quoteParams: QuoteParams = { @@ -200,16 +198,16 @@ const getOutputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[getOutputAmount]: Getting quote', { rand, quoteParams }); + logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote const quote: Quote | CrosschainQuote | QuoteError | null = await (isCrosschainSwap ? getCrosschainQuote : getQuote)(quoteParams); - logger.debug('[getOutputAmount]: Got quote', { rand, quote }); + logger.debug('[useSwapDerivedOutputs]: Got quote', { rand, quote }); if (!quote || (quote as QuoteError)?.error || !(quote as Quote)?.buyAmount) { const quoteError = quote as QuoteError; if (quoteError.error) { - logger.error(new RainbowError('[getOutputAmount]: Quote error'), { + logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -329,7 +327,7 @@ export default function useSwapDerivedOutputs(type: string) { }; } - logger.debug('[getTradeDetails]: Getting trade details', { + logger.debug('[useSwapDerivedOutputs]: Getting trade details', { independentField, independentValue, inputCurrency, @@ -450,7 +448,7 @@ export default function useSwapDerivedOutputs(type: string) { tradeDetails, }; - logger.debug('[getTradeDetails]: Got trade details', { + logger.debug('[useSwapDerivedOutputs]: Got trade details', { data, }); diff --git a/src/hooks/useWalletCloudBackup.ts b/src/hooks/useWalletCloudBackup.ts index f0efed48182..57b9caac681 100644 --- a/src/hooks/useWalletCloudBackup.ts +++ b/src/hooks/useWalletCloudBackup.ts @@ -12,7 +12,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { CLOUD_BACKUP_ERRORS, isCloudBackupAvailable } from '@/handlers/cloudBackup'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { getSupportedBiometryType } from '@/keychain'; import { IS_ANDROID } from '@/env'; import { authenticateWithPIN } from '@/handlers/authentication'; @@ -98,19 +98,19 @@ export default function useWalletCloudBackup() { } // We have the password and we need to add it to an existing backup - logger.log('password fetched correctly'); + logger.debug('[useWalletCloudBackup]: password fetched correctly'); let updatedBackupFile = null; try { if (!latestBackup) { - logger.log(`backing up to ${cloudPlatform}`, wallets![walletId]); + logger.debug(`[useWalletCloudBackup]: backing up to ${cloudPlatform}: ${wallets![walletId]}`); updatedBackupFile = await backupWalletToCloud({ password, wallet: wallets![walletId], userPIN, }); } else { - logger.log(`adding wallet to ${cloudPlatform} backup`, wallets![walletId]); + logger.debug(`[useWalletCloudBackup]: adding wallet to ${cloudPlatform} backup: ${wallets![walletId]}`); updatedBackupFile = await addWalletToCloudBackup({ password, wallet: wallets![walletId], @@ -121,8 +121,7 @@ export default function useWalletCloudBackup() { } catch (e: any) { const userError = getUserError(e); !!onError && onError(userError); - logger.sentry(`error while trying to backup wallet to ${cloudPlatform}`); - captureException(e); + logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to backup wallet to ${cloudPlatform}: ${e}`)); analytics.track(`Error during ${cloudPlatform} Backup`, { category: 'backup', error: userError, @@ -132,14 +131,13 @@ export default function useWalletCloudBackup() { } try { - logger.log('backup completed!'); + logger.debug('[useWalletCloudBackup]: backup completed!'); await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.cloud, updatedBackupFile)); - logger.log('backup saved everywhere!'); + logger.debug('[useWalletCloudBackup]: backup saved everywhere!'); !!onSuccess && onSuccess(); return true; } catch (e) { - logger.sentry('error while trying to save wallet backup state'); - captureException(e); + logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to save wallet backup state: ${e}`)); const userError = getUserError(new Error(CLOUD_BACKUP_ERRORS.WALLET_BACKUP_STATUS_UPDATE_FAILED)); !!onError && onError(userError); analytics.track('Error updating Backup status', { diff --git a/src/hooks/useWalletManualBackup.ts b/src/hooks/useWalletManualBackup.ts index 1a5ba71404b..daa83ed1554 100644 --- a/src/hooks/useWalletManualBackup.ts +++ b/src/hooks/useWalletManualBackup.ts @@ -1,9 +1,8 @@ -import { captureException } from '@sentry/react-native'; import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { setWalletBackedUp } from '../redux/wallets'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; export default function useWalletManualBackup() { const dispatch = useDispatch(); @@ -13,8 +12,9 @@ export default function useWalletManualBackup() { try { await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.manual)); } catch (e) { - logger.sentry(`error while trying to set walletId ${walletId} as manually backed up`); - captureException(e); + logger.error( + new RainbowError(`[useWalletManualBackup]: error while trying to set walletId ${walletId} as manually backed up: ${e}`) + ); } }, [dispatch] diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 51a710ff73d..de1c0ca9bf4 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -98,10 +98,9 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => } else { throw new Error('Pending transaction missing chain id'); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(new RainbowError(`useWatchPendingTransaction: Failed to watch transaction`), { - message: e.message, + } catch (e) { + logger.error(new RainbowError(`[useWatchPendingTransaction]: Failed to watch transaction`), { + message: (e as Error)?.message || 'Unknown error', }); } diff --git a/src/hooks/useWatchWallet.ts b/src/hooks/useWatchWallet.ts index 96aae19410f..fa5754ac1a8 100644 --- a/src/hooks/useWatchWallet.ts +++ b/src/hooks/useWatchWallet.ts @@ -6,7 +6,8 @@ import { useAccountProfile, useDeleteWallet, useImportingWallet, useInitializeWa import { cleanUpWalletKeys, RainbowWallet } from '@/model/wallet'; import { addressSetSelected, walletsSetSelected } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import { doesWalletsContainAddress, logger } from '@/utils'; +import { doesWalletsContainAddress } from '@/utils'; +import { RainbowError, logger } from '@/logger'; export default function useWatchWallet({ address: primaryAddress, @@ -42,7 +43,9 @@ export default function useWatchWallet({ // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 7. initializeWallet(null, null, null, false, false, null, true); } catch (e) { - logger.log('error while switching account', e); + logger.error(new RainbowError(`[useWatchWallet]: error while switching account`), { + error: (e as Error)?.message || 'Unknown error', + }); } }, [dispatch, initializeWallet, wallets] diff --git a/src/hooks/useWebData.ts b/src/hooks/useWebData.ts index daa5a80b5ad..f3efe8e9585 100644 --- a/src/hooks/useWebData.ts +++ b/src/hooks/useWebData.ts @@ -10,7 +10,7 @@ import { containsEmoji } from '@/helpers/strings'; import WalletTypes from '@/helpers/walletTypes'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { AppState } from '@/redux/store'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { useTheme } from '@/theme'; const getAccountSymbol = (name: string) => { @@ -98,7 +98,7 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(assetIds); - logger.log('showcase initialized!'); + logger.debug('[useWebData]: showcase initialized!'); return; } @@ -112,7 +112,7 @@ export default function useWebData() { const response = await getPreference('hidden', accountAddress); if (!response || !response.hidden.ids.length) { await setPreference(PreferenceActionType.init, 'hidden', accountAddress, assetIds); - logger.log('hidden initialized!'); + logger.debug('[useWebData]: hidden initialized!'); return; } @@ -130,15 +130,15 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(showcaseTokens); - logger.log('showcase initialized!'); + logger.debug('[useWebData]: showcase initialized!'); return; } - logger.log('showcase already initialized. skipping'); + logger.debug('[useWebData]: showcase already initialized. skipping'); } } } catch (e) { - logger.log('Error trying to initiailze showcase'); + logger.error(new RainbowError(`[useWebData]: error while trying to initialize showcase: ${e}`)); } }, [accountAddress, initWebData, showcaseTokens, webDataEnabled]); diff --git a/src/keychain/index.ts b/src/keychain/index.ts index 9479712c1d9..0ef3d78771a 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -67,11 +67,11 @@ export const publicAccessControlOptions: Options = { * decrypt the data. */ export async function get(key: string, options: KeychainOptions = {}): Promise> { - logger.debug(`keychain: get`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: get`, { key }, logger.DebugContext.keychain); async function _get(attempts = 0): Promise> { if (attempts > 0) { - logger.debug(`keychain: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); } let data = cache.getString(key); @@ -93,24 +93,24 @@ export async function get(key: string, options: KeychainOptions = {}): Promise 2) { return { @@ -170,7 +170,7 @@ export async function get(key: string, options: KeychainOptions = {}): Promise { - logger.debug(`keychain: set`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: set`, { key }, logger.DebugContext.keychain); // only save public data to mmkv // private data has accessControl if (!options.accessControl) { cache.set(key, value); } else if (options.accessControl && IS_ANDROID && !(await getSupportedBiometryType())) { - logger.debug(`keychain: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); + logger.debug(`[keychain]: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); const pin = options.androidEncryptionPin || (await authenticateWithPINAndCreateIfNeeded()); const encryptedValue = await encryptor.encrypt(pin, value); @@ -216,7 +216,7 @@ export async function set(key: string, value: string, options: KeychainOptions = if (encryptedValue) { value = encryptedValue; } else { - throw new Error(`keychain: failed to encrypt value`); + throw new Error(`[keychain]: failed to encrypt value`); } } @@ -231,7 +231,7 @@ export async function getObject = Record> { - logger.debug(`keychain: getObject`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: getObject`, { key }, logger.DebugContext.keychain); const { value, error } = await get(key, options); @@ -250,7 +250,7 @@ export async function getObject = Record, options: KeychainOptions = {}): Promise { - logger.debug(`keychain: setObject`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: setObject`, { key }, logger.DebugContext.keychain); await set(key, JSON.stringify(value), options); } @@ -259,7 +259,7 @@ export async function setObject(key: string, value: Record, options * Check if a value exists on the keychain. */ export async function has(key: string): Promise { - logger.debug(`keychain: has`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: has`, { key }, logger.DebugContext.keychain); return Boolean(await hasInternetCredentials(key)); } @@ -267,7 +267,7 @@ export async function has(key: string): Promise { * Remove a value from the keychain. */ export async function remove(key: string) { - logger.debug(`keychain: remove`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: remove`, { key }, logger.DebugContext.keychain); cache.delete(key); await resetInternetCredentials(key); @@ -281,11 +281,11 @@ export async function remove(key: string) { */ export async function getAllKeys(): Promise { try { - logger.debug(`keychain: getAllKeys`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getAllKeys`, {}, logger.DebugContext.keychain); const res = await getAllInternetCredentials(); return res ? res.results : []; } catch (e: any) { - logger.error(new RainbowError(`keychain: getAllKeys() failed`), { + logger.error(new RainbowError(`[keychain]: getAllKeys() failed`), { message: e.toString(), }); return undefined; @@ -299,7 +299,7 @@ export async function getAllKeys(): Promise { * `getAllKeys`. */ export async function clear() { - logger.debug(`keychain: clear`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: clear`, {}, logger.DebugContext.keychain); cache.clearAll(); @@ -314,7 +314,7 @@ export async function clear() { * Wrapper around the underlying library's method by the same name. */ export async function getSupportedBiometryType(): Promise { - logger.debug(`keychain: getSupportedBiometryType`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getSupportedBiometryType`, {}, logger.DebugContext.keychain); return (await originalGetSupportedBiometryType()) || undefined; } @@ -323,7 +323,7 @@ export async function getSupportedBiometryType(): Promise> { - logger.debug(`keychain: getSharedWebCredentials`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getSharedWebCredentials`, {}, logger.DebugContext.keychain); let data = undefined; @@ -352,7 +352,7 @@ export async function getSharedWebCredentials(): Promise { - logger.debug(`keychain: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); const isSimulator = IS_DEV && (await DeviceInfo.isEmulator()); diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index 53d5160db83..fd44de21771 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -21,7 +21,7 @@ export const defaultOptions: Sentry.ReactNativeOptions = { export function initSentry() { if (IS_TEST) { - logger.debug(`Sentry is disabled for test environment`); + logger.debug(`[sentry]: disabled for test environment`); return; } try { @@ -34,8 +34,8 @@ export function initSentry() { release, // MUST BE A STRING or Sentry will break in native code }); - logger.debug(`Sentry initialized`); + logger.debug(`[sentry]: Successfully initialized`); } catch (e) { - logger.error(new RainbowError(`Sentry initialization failed`)); + logger.error(new RainbowError(`[sentry]: initialization failed`)); } } diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 8abb7f47509..1f2fe83f900 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -55,7 +55,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { * should exit early */ if (debug && env.IS_PROD) { - logger.error(new RainbowError(`Migration is in debug mode`), { + logger.error(new RainbowError(`[migrations]: is in debug mode`), { migration: name, }); return; @@ -66,7 +66,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { if (handler) { try { logger.debug( - `Migrating`, + `[migrations]: Migrating`, { migration: name, }, @@ -75,19 +75,19 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { await handler(); if (!debug) storage.set([name], new Date().toUTCString()); logger.debug( - `Migrating complete`, + `[migrations]: Migrating complete`, { migration: name, }, MIGRATIONS_DEBUG_CONTEXT ); } catch (e) { - logger.error(new RainbowError(`Migration failed`), { + logger.error(new RainbowError(`[migrations]: Migration failed`), { migration: name, }); } } else { - logger.error(new RainbowError(`Migration had no handler`), { + logger.error(new RainbowError(`[migrations]: Migration had no handler`), { migration: name, }); } @@ -112,11 +112,11 @@ export async function runMigrations(migrations: Migration[]) { ranMigrations.push(migration.name); } else { - logger.debug(`Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); + logger.debug(`[migrations]: Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); } } - logger.info(`Ran or scheduled migrations`, { + logger.debug(`[migrations]: Ran or scheduled migrations`, { migrations: ranMigrations, }); } diff --git a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts index df4e1f41baa..4284569d94b 100644 --- a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts +++ b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts @@ -27,7 +27,7 @@ export function migrateRemotePromoSheetsToZustand(): Migration { }); } } catch (error) { - logger.error(new RainbowError(`Failed to migrate remote promo sheets to zustand`), { + logger.error(new RainbowError(`[migrations]: Failed to migrate remote promo sheets to zustand`), { data: error, }); } diff --git a/src/model/backup.ts b/src/model/backup.ts index b5d02bdedac..2eb50a7c297 100644 --- a/src/model/backup.ts +++ b/src/model/backup.ts @@ -11,7 +11,6 @@ import * as keychain from '@/model/keychain'; import * as kc from '@/keychain'; import { AllRainbowWallets, allWalletsVersion, createWallet, RainbowWallet } from './wallet'; import { analytics } from '@/analytics'; -import oldLogger from '@/utils/logger'; import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_DEV } from '@/env'; import AesEncryptor from '../handlers/aesEncryption'; @@ -152,7 +151,7 @@ export async function backupAllWalletsToCloud({ ); const now = Date.now(); - logger.debug(`Creating backup with all wallets to ${cloudPlatform}`, { + logger.debug(`[backup]: Creating backup with all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -204,7 +203,7 @@ export async function backupAllWalletsToCloud({ const walletIdsToUpdate = Object.keys(wallets); await dispatch(setAllWalletsWithIdsAsBackedUp(walletIdsToUpdate, WalletBackupTypes.cloud, updatedBackupFile)); - logger.debug(`Successfully backed up all wallets to ${cloudPlatform}`, { + logger.debug(`[backup]: Successfully backed up all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -430,7 +429,7 @@ export async function restoreCloudBackup({ if (message === CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA) { return RestoreCloudBackupResultStates.incorrectPassword; } - logger.error(new RainbowError('Error while restoring back up'), { + logger.error(new RainbowError(`[backup]: Error while restoring back up`), { message, }); return RestoreCloudBackupResultStates.failedWhenRestoring; @@ -521,8 +520,7 @@ async function restoreSpecificBackupIntoKeychain(backedUpData: BackedUpData, use } return true; } catch (e) { - oldLogger.sentry('error in restoreSpecificBackupIntoKeychain'); - captureException(e); + logger.error(new RainbowError(`[backup]: Error restoring specific backup into keychain: ${e}`)); return false; } } @@ -590,8 +588,7 @@ async function restoreCurrentBackupIntoKeychain(backedUpData: BackedUpData, newP return true; } catch (e) { - oldLogger.sentry('error in restoreBackupIntoKeychain'); - captureException(e); + logger.error(new RainbowError(`[backup]: Error restoring current backup into keychain: ${e}`)); return false; } } @@ -620,7 +617,7 @@ async function decryptSecretFromBackupPin({ secret, backupPIN }: { secret?: stri } processedSecret = decryptedSecretToUse; } else { - logger.error(new RainbowError('Failed to decrypt backed up seed phrase using backup PIN.')); + logger.error(new RainbowError(`[backup]: Failed to decrypt backed up seed phrase using backup PIN.`)); return processedSecret; } } @@ -673,8 +670,7 @@ export async function fetchBackupPassword(): Promise { } return null; } catch (e) { - oldLogger.sentry('Error while fetching backup password', e); - captureException(e); + logger.error(new RainbowError(`[backup]: Error while fetching backup password: ${e}`)); return null; } } @@ -687,7 +683,7 @@ export async function getDeviceUUID(): Promise { return new Promise(resolve => { DeviceUUID.getUUID((error: unknown, uuid: string[]) => { if (error) { - logger.error(new RainbowError('Received error when trying to get uuid from Native side'), { + logger.error(new RainbowError(`[backup]: Received error when trying to get uuid from Native side`), { error, }); resolve(null); @@ -739,7 +735,7 @@ export async function checkIdentifierOnLaunch() { if (currentIdentifier.error) { switch (currentIdentifier.error) { case kc.ErrorType.Unavailable: { - logger.debug('Value for current identifier not found, setting it to new UUID...', { + logger.debug(`[backup]: Value for current identifier not found, setting it to new UUID...`, { uuid, error: currentIdentifier.error, }); @@ -748,7 +744,7 @@ export async function checkIdentifierOnLaunch() { } default: - logger.error(new RainbowError('Error while checking identifier on launch'), { + logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { error: currentIdentifier.error, }); break; @@ -798,10 +794,8 @@ export async function checkIdentifierOnLaunch() { }); }); } catch (error) { - logger.error(new RainbowError('Error while checking identifier on launch'), { - extra: { - error, - }, + logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { + error, }); } diff --git a/src/model/migrations.ts b/src/model/migrations.ts index dcf2f73d979..d38340607ff 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -32,7 +32,7 @@ import { returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { ethereumUtils, profileUtils } from '@/utils'; import { review } from '@/storage'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; @@ -52,13 +52,11 @@ export default async function runMigrations() { * using the updated Keychain settings (THIS_DEVICE_ONLY) */ const v0 = async () => { - logger.sentry('Start migration v0'); const walletAddress = await loadAddress(); if (walletAddress) { - logger.sentry('v0 migration - Save loaded address'); + logger.debug('[runMigrations]: v0 migration - Save loaded address'); await saveAddress(walletAddress); } - logger.sentry('Complete migration v0'); }; migrations.push(v0); @@ -70,14 +68,13 @@ export default async function runMigrations() { * that were created / imported before we launched this feature */ const v1 = async () => { - logger.sentry('Start migration v1'); const { selected } = store.getState().wallets; if (!selected) { // Read from the old wallet data const address = await loadAddress(); if (address) { - logger.sentry('v1 migration - address found'); + logger.debug('[runMigrations]: v1 migration - address found'); const id = `wallet_${Date.now()}`; const currentWallet = { addresses: [ @@ -100,12 +97,11 @@ export default async function runMigrations() { const wallets = { [id]: currentWallet }; - logger.sentry('v1 migration - update wallets and selected wallet'); + logger.debug('[runMigrations]: v1 migration - update wallets and selected wallet'); await store.dispatch(walletsUpdate(wallets)); await store.dispatch(walletsSetSelected(currentWallet)); } } - logger.sentry('Complete migration v1'); }; migrations.push(v1); @@ -116,11 +112,10 @@ export default async function runMigrations() { * which are the only wallets allowed to create new accounts under it */ const v2 = async () => { - logger.sentry('Start migration v2'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.sentry('Complete migration v2 early'); + logger.debug('[runMigrations]: Complete migration v2 early'); return; } @@ -131,7 +126,7 @@ export default async function runMigrations() { // if there's a wallet with seed phrase that wasn't imported // and set it as primary if (!primaryWallet) { - logger.sentry('v2 migration - primary wallet not found'); + logger.debug('[runMigrations]: v2 migration - primary wallet not found'); let primaryWalletKey = null; Object.keys(wallets).some(key => { const wallet = wallets[key]; @@ -161,7 +156,7 @@ export default async function runMigrations() { ...updatedWallets[primaryWalletKey], primary: true, }; - logger.sentry('v2 migration - update wallets'); + logger.debug('[runMigrations]: v2 migration - update wallets'); await store.dispatch(walletsUpdate(updatedWallets)); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too @@ -171,7 +166,6 @@ export default async function runMigrations() { } } } - logger.sentry('Complete migration v2'); }; migrations.push(v2); @@ -182,7 +176,7 @@ export default async function runMigrations() { */ const v3 = async () => { - logger.sentry('Ignoring migration v3'); + logger.debug('[runMigrations]: Ignoring migration v3'); return true; }; @@ -194,7 +188,7 @@ export default async function runMigrations() { */ const v4 = async () => { - logger.sentry('Ignoring migration v4'); + logger.debug('[runMigrations]: Ignoring migration v4'); return true; }; @@ -206,43 +200,41 @@ export default async function runMigrations() { * incorrectly by the keychain integrity checks */ const v5 = async () => { - logger.sentry('Start migration v5'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.sentry('Complete migration v5 early'); + logger.debug('[runMigrations]: Complete migration v5 early'); return; } const hasMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (!hasMigratedFlag) { - logger.sentry('Migration flag not set'); + logger.debug('[runMigrations]: Migration flag not set'); const hasOldSeedphraseKey = await hasKey(seedPhraseKey); if (hasOldSeedphraseKey) { - logger.sentry('Old seedphrase is still there'); + logger.debug('[runMigrations]: Old seedphrase is still there'); let incorrectDamagedWalletId = null; const updatedWallets = { ...wallets }; keys(updatedWallets).forEach(walletId => { if (updatedWallets[walletId].damaged && !updatedWallets[walletId].imported) { - logger.sentry('found incorrect damaged wallet', walletId); + logger.debug(`[runMigrations]: found incorrect damaged wallet ${walletId}`); delete updatedWallets[walletId].damaged; incorrectDamagedWalletId = walletId; } }); - logger.sentry('updating all wallets'); + logger.debug('[runMigrations]: updating all wallets'); await store.dispatch(walletsUpdate(updatedWallets)); - logger.sentry('done updating all wallets'); + logger.debug('[runMigrations]: done updating all wallets'); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too if (selected!.id === incorrectDamagedWalletId) { - logger.sentry('need to update the selected wallet'); + logger.debug('[runMigrations]: need to update the selected wallet'); const updatedSelectedWallet = updatedWallets[incorrectDamagedWalletId]; await store.dispatch(walletsSetSelected(updatedSelectedWallet)); - logger.sentry('selected wallet updated'); + logger.debug('[runMigrations]: selected wallet updated'); } } } - logger.sentry('Complete migration v5'); }; migrations.push(v5); @@ -252,6 +244,7 @@ export default async function runMigrations() { */ /* Fix dollars => stablecoins */ const v6 = async () => { + logger.debug('[runMigrations]: Ignoring migration v6'); // try { // const userLists = await getUserLists(); // const newLists = userLists.map((list: { id: string }) => { @@ -282,7 +275,7 @@ export default async function runMigrations() { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let x = 0; x < wallet.addresses.length; x++) { const { address } = wallet.addresses[x]; - logger.log('setting web profiles for address', address); + logger.debug(`[runMigrations]: setting web profiles for address ${address}`); await store.dispatch(updateWebDataEnabled(true, address)); } } @@ -292,7 +285,7 @@ export default async function runMigrations() { migrations.push(v7); const v8 = async () => { - logger.log('wiping old metadata'); + logger.debug('[runMigrations]: wiping old metadata'); await deprecatedRemoveLocal(IMAGE_METADATA); }; @@ -305,7 +298,6 @@ export default async function runMigrations() { * same for contacts */ const v9 = async () => { - logger.log('Start migration v9'); // map from old color index to closest new color's index const newColorIndexes = [0, 4, 12, 21, 1, 20, 4, 9, 10]; try { @@ -329,12 +321,12 @@ export default async function runMigrations() { const newWallet = { ...wallet, addresses: newAddresses }; updatedWallets[walletKeys[i]] = newWallet; } - logger.log('update wallets in store to index new colors'); + logger.debug('[runMigrations]: update wallets in store to index new colors'); await store.dispatch(walletsUpdate(updatedWallets)); const selectedWalletId = selected?.id; if (selectedWalletId) { - logger.log('update selected wallet to index new color'); + logger.debug('[runMigrations]: update selected wallet to index new color'); await store.dispatch(walletsSetSelected(updatedWallets[selectedWalletId])); } @@ -355,12 +347,10 @@ export default async function runMigrations() { : getRandomColor(), }; } - logger.log('update contacts to index new colors'); + logger.debug('[runMigrations]: update contacts to index new colors'); await saveContacts(updatedContacts); } catch (error) { - logger.sentry('Migration v9 failed: ', error); - const migrationError = new Error('Migration 9 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v9 failed: ${error}`)); } }; @@ -371,7 +361,6 @@ export default async function runMigrations() { * This step makes sure all contacts have an emoji set based on the address */ const v10 = async () => { - logger.log('Start migration v10'); try { // migrate contacts to corresponding emoji const contacts = await getContacts(); @@ -403,12 +392,10 @@ export default async function runMigrations() { } } } - logger.log('update contacts to add emojis / colors'); + logger.debug('[runMigrations]: update contacts to add emojis / colors'); await saveContacts(updatedContacts); } catch (error) { - logger.sentry('Migration v10 failed: ', error); - const migrationError = new Error('Migration 10 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v10 failed: ${error}`)); } }; @@ -419,9 +406,9 @@ export default async function runMigrations() { * This step resets review timers if we havnt asked in the last 2 weeks prior to running this */ const v11 = async () => { - logger.log('Start migration v11'); const hasReviewed = review.get(['hasReviewed']); if (hasReviewed) { + logger.debug('[runMigrations]: Migration v11: exiting early - already reviewed'); return; } @@ -430,10 +417,12 @@ export default async function runMigrations() { const TWO_MONTHS = 2 * 30 * 24 * 60 * 60 * 1000; if (Number(reviewAsked) > Date.now() - TWO_WEEKS) { + logger.debug('[runMigrations]: Migration v11: exiting early - not reviewed in the last 2 weeks'); return; } review.set(['timeOfLastPrompt'], Date.now() - TWO_MONTHS); + logger.debug('[runMigrations]: Migration v11: updated review timeOfLastPrompt'); }; migrations.push(v11); @@ -458,8 +447,8 @@ export default async function runMigrations() { const hiddenCoins = await getHiddenCoins(address, network); const pinnedCoins = await getPinnedCoins(address, network); - logger.log(JSON.stringify({ pinnedCoins }, null, 2)); - logger.log(JSON.stringify({ hiddenCoins }, null, 2)); + logger.debug(`[runMigrations]: pinnedCoins: ${JSON.stringify({ pinnedCoins }, null, 2)}`); + logger.debug(`[runMigrations]: hiddenCoins: ${JSON.stringify({ hiddenCoins }, null, 2)}`); const pinnedCoinsMigrated = pinnedCoins.map((address: string) => { const asset = assets?.find((asset: any) => asset.address === address.toLowerCase()); @@ -471,8 +460,8 @@ export default async function runMigrations() { return getUniqueIdNetwork(asset?.address, network); }); - logger.log(JSON.stringify({ pinnedCoinsMigrated }, null, 2)); - logger.log(JSON.stringify({ hiddenCoinsMigrated }, null, 2)); + logger.debug(`[runMigrations]: pinnedCoinsMigrated: ${JSON.stringify({ pinnedCoinsMigrated }, null, 2)}`); + logger.debug(`[runMigrations]: hiddenCoinsMigrated: ${JSON.stringify({ hiddenCoinsMigrated }, null, 2)}`); await savePinnedCoins(uniq(pinnedCoinsMigrated), address, network); await saveHiddenCoins(uniq(hiddenCoinsMigrated), address, network); @@ -514,17 +503,14 @@ export default async function runMigrations() { const value = await loadString(key); if (typeof value === 'string') { await saveString(key, value, publicAccessControlOptions); - logger.debug('key migrated', key); + logger.debug(`[runMigrations]: key migrated: ${key}`); } } catch (error) { - logger.sentry('Error migration 13 :: key ', key); - logger.sentry('reason', error); + logger.error(new RainbowError(`[runMigrations]: Error migration 13 :: key ${key}: ${error}`)); } } } catch (error) { - logger.sentry('Migration v13 failed: ', error); - const migrationError = new Error('Migration 13 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v13 failed: ${error}`)); } }; @@ -557,6 +543,7 @@ export default async function runMigrations() { Ignored */ const v15 = async () => { + logger.debug('[runMigrations]: Ignoring migration v15'); return true; }; @@ -577,9 +564,7 @@ export default async function runMigrations() { // we don't care if it fails }); } catch (error: any) { - logger.sentry('Migration v16 failed: ', error); - const migrationError = new Error('Migration 16 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v16 failed: ${error}`)); } }; @@ -678,18 +663,18 @@ export default async function runMigrations() { migrations.push(v20); - logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`); + logger.debug(`[runMigrations]: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); if (migrations.length === currentVersion) { - logger.sentry(`Migrations: Nothing to run`); + logger.debug(`[runMigrations]: Nothing to run`); return; } for (let i = currentVersion; i < migrations.length; i++) { - logger.sentry(`Migrations: Running migration v${i}`); + logger.debug(`[runMigrations]: Running migration v${i}`); // @ts-expect-error await migrations[i].apply(null); - logger.sentry(`Migrations: Migration ${i} completed succesfully`); + logger.debug(`[runMigrations]: Migration ${i} completed succesfully`); await setMigrationVersion(i + 1); } } diff --git a/src/model/preferences.ts b/src/model/preferences.ts index 226424912e2..b74867ed820 100644 --- a/src/model/preferences.ts +++ b/src/model/preferences.ts @@ -88,13 +88,13 @@ export async function setPreference( }; const message = JSON.stringify(objToSign); const signature2 = await signWithSigningWallet(message); - logger.debug('☁️ SENDING ', { message }); + logger.debug(`[preferences]: ☁️ SENDING `, { message }); const { data } = await preferencesAPI.post>(`${PREFS_ENDPOINT}/${key}`, { message, signature, signature2, }); - logger.debug('☁️ RESPONSE', { + logger.debug(`[preferences]: ☁️ RESPONSE`, { reason: data?.reason, success: data?.success, }); @@ -105,7 +105,7 @@ export async function setPreference( return data?.success; } catch (e) { - logger.warn(`Preferences API failed to set preference`, { + logger.warn(`[preferences]: Preferences API failed to set preference`, { preferenceKey: key, }); return false; @@ -120,7 +120,7 @@ export async function getPreference( const { data } = await preferencesAPI.get>(`${PREFS_ENDPOINT}/${key}`, { params: { address }, }); - logger.debug('☁️ RESPONSE', { + logger.debug(`[preferences]: ☁️ RESPONSE`, { reason: data?.reason, success: data?.success, }); @@ -131,8 +131,9 @@ export async function getPreference( return data.data; } catch (e) { - logger.warn(`Preferences API failed to get preference`, { + logger.warn(`[preferences]: Preferences API failed to get preference`, { preferenceKey: key, + error: e, }); return null; } diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index 2f16922be6b..212f8de215e 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -179,7 +179,7 @@ export async function fetchRemoteConfig(): Promise { const config: RainbowConfig = { ...DEFAULT_CONFIG }; try { await remoteConfig().fetchAndActivate(); - logger.debug('Remote config fetched successfully'); + logger.debug(`[remoteConfig]: Remote config fetched successfully`); const parameters = remoteConfig().getAll(); Object.entries(parameters).forEach($ => { const [key, entry] = $; @@ -236,12 +236,12 @@ export async function fetchRemoteConfig(): Promise { }); return config; } catch (e) { - logger.error(new RainbowError('Failed to fetch remote config'), { + logger.error(new RainbowError(`[remoteConfig]: Failed to fetch remote config`), { error: e, }); throw e; } finally { - logger.debug(`Current remote config:\n${JSON.stringify(config, null, 2)}`); + logger.debug(`[remoteConfig]: Current remote config:\n${JSON.stringify(config, null, 2)}`); const currentNetwork = await getNetwork(); web3SetHttpProvider(currentNetwork); saveNetwork(currentNetwork); diff --git a/src/model/wallet.ts b/src/model/wallet.ts index a79e52b0d23..200fa0edbf9 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -317,7 +317,7 @@ export const sendTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.info('wallet: sending transaction', { transaction }); + logger.debug('[wallet]: sending transaction', { transaction }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -330,10 +330,10 @@ export const sendTransaction = async ({ if (!wallet) return null; try { const result = await wallet.sendTransaction(transaction); - logger.debug('send - tx result', { result }, DebugContext.wallet); + logger.debug(`[wallet]: send - tx result`, { result }, DebugContext.wallet); return { result }; } catch (error) { - logger.error(new RainbowError('Failed to send transaction'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to send transaction`), { error }); if (isHardwareWallet) { setHardwareTXError(true); } else { @@ -348,7 +348,7 @@ export const sendTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError('Failed to send transaction due to auth'), { + logger.error(new RainbowError(`[wallet]: Failed to send transaction due to auth`), { error, }); return null; @@ -365,7 +365,7 @@ export const signTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing transaction'); + logger.debug('[wallet]: signing transaction', {}, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -385,7 +385,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError('Failed to sign transaction'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign transaction`), { error }); return { error }; } } catch (error) { @@ -394,7 +394,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign transaction due to auth'), { + logger.error(new RainbowError(`[wallet]: Failed to sign transaction due to auth`), { error, }); return null; @@ -411,7 +411,7 @@ export const signPersonalMessage = async ( }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing personal message', { message }); + logger.debug('[wallet]: signing personal message', { message }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -433,7 +433,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError('Failed to sign personal message'), { + logger.error(new RainbowError(`[wallet]: Failed to sign personal message`), { error, }); return { error }; @@ -444,7 +444,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign personal message due to auth'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign personal message due to auth`), { error }); return null; } }; @@ -459,7 +459,7 @@ export const signTypedDataMessage = async ( }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing typed data message', { message }); + logger.debug('[wallet]: signing typed data message', { message }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -511,7 +511,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError('Failed to sign typed data message'), { + logger.error(new RainbowError(`[wallet]: Failed to sign typed data message`), { error, }); return { error }; @@ -522,7 +522,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign typed data message due to auth'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign typed data message due to auth`), { error }); return null; } }; @@ -558,7 +558,7 @@ export const loadPrivateKey = async (address: EthereumAddress, hardware: boolean return privateKey; } catch (error) { - logger.error(new RainbowError('Error loading private key'), { error }); + logger.error(new RainbowError(`[wallet]: Error loading private key`), { error }); return null; } }; @@ -617,15 +617,10 @@ export const createWallet = async ({ callbackAfterSeeds = null; } const isImported = !!seed; - logger.info('Importing new wallet'); - if (!seed) { - logger.info('Creating new wallet'); - } + logger.debug(`[wallet]: ${isImported ? 'Importing new wallet' : 'Creating new wallet'}`, {}, DebugContext.wallet); const walletSeed = seed || generateMnemonic(); const addresses: RainbowAccount[] = []; try { - const { dispatch } = store; - const { isHDWallet, type, @@ -645,17 +640,17 @@ export const createWallet = async ({ // hardware pkey format is ${bluetooth device id}/${index} pkey = `${seed}/0`; } - logger.debug('[createWallet] - getWallet from seed', {}, DebugContext.wallet); + logger.debug('[wallet]: getWallet from seed', {}, DebugContext.wallet); // Get all wallets const allWalletsResult = await getAllWallets(); - logger.debug('[createWallet] - getAllWallets', {}, DebugContext.wallet); + logger.debug('[wallet]: getAllWallets', {}, DebugContext.wallet); const allWallets: AllRainbowWallets = allWalletsResult?.wallets ?? {}; let existingWalletId = null; if (isImported) { // Checking if the generated account already exists and is visible - logger.debug('[createWallet] - checking if account already exists', {}, DebugContext.wallet); + logger.debug('[wallet]: checking if account already exists', {}, DebugContext.wallet); const alreadyExistingWallet = Object.values(allWallets).find((someWallet: RainbowWallet) => { return !!someWallet.addresses.find( account => toChecksumAddress(account.address) === toChecksumAddress(walletAddress) && account.visible @@ -673,13 +668,13 @@ export const createWallet = async ({ if (!isRestoring) { setTimeout(() => Alert.alert(lang.t('wallet.new.alert.oops'), lang.t('wallet.new.alert.looks_like_already_imported')), 1); } - logger.debug('[createWallet] - already imported this wallet', {}, DebugContext.wallet); + logger.debug('[wallet]: already imported this wallet', {}, DebugContext.wallet); return null; } } const id = existingWalletId || `wallet_${Date.now()}`; - logger.debug('[createWallet] - wallet ID', { id }, DebugContext.wallet); + logger.debug('[wallet]: wallet ID', { id }, DebugContext.wallet); // load this up front and pass to other keychain setters to avoid multiple // auth requests @@ -688,17 +683,17 @@ export const createWallet = async ({ await saveSeedPhrase(walletSeed, id, { androidEncryptionPin }); - logger.debug('[createWallet] - saved seed phrase', {}, DebugContext.wallet); + logger.debug('[wallet]: saved seed phrase', {}, DebugContext.wallet); // Save address await saveAddress(walletAddress); - logger.debug('[createWallet] - saved address', {}, DebugContext.wallet); + logger.debug('[wallet]: saved address', {}, DebugContext.wallet); // Save private key await saveKeyForWallet(walletAddress, pkey, isHardwareWallet, { androidEncryptionPin, }); - logger.debug('[createWallet] - saved private key', {}, DebugContext.wallet); + logger.debug('[wallet]: saved private key', {}, DebugContext.wallet); const colorIndexForWallet = color !== null ? color : addressHashedColorIndex(walletAddress) || 0; @@ -715,10 +710,10 @@ export const createWallet = async ({ }); if (type !== EthereumWalletType.readOnly && type !== EthereumWalletType.bluetooth) { // Creating signature for this wallet - logger.debug(`[createWallet] - generating signature`, {}, DebugContext.wallet); + logger.debug(`[wallet]: generating signature`, {}, DebugContext.wallet); await createSignature(walletAddress, pkey); // Enable web profile - logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); store.dispatch(updateWebDataEnabled(true, walletAddress)); // Save the color setPreference(PreferenceActionType.init, 'profile', address, { @@ -730,7 +725,7 @@ export const createWallet = async ({ // Initiate auto account discovery for imported wallets via seedphrase // or for hardware wallets if ((isHDWallet && root && isImported) || (isHardwareWallet && seed)) { - logger.debug('[createWallet] - initializing account auto discovery', {}, DebugContext.wallet); + logger.debug('[wallet]: initializing account auto discovery', {}, DebugContext.wallet); let index = 1; let lookup = 0; // Starting on index 1, we check the tx history @@ -757,7 +752,7 @@ export const createWallet = async ({ try { hasTxHistory = await ethereumUtils.hasPreviousTransactions(nextWallet.address); } catch (error) { - logger.error(new RainbowError('[createWallet] - Error getting txn history for address'), { error }); + logger.error(new RainbowError('[wallet]: Error getting txn history for address'), { error }); } let discoveredAccount: RainbowAccount | undefined; @@ -792,7 +787,7 @@ export const createWallet = async ({ if (hasTxHistory) { // Save private key await saveKeyForWallet(nextWallet.address, nextWallet.privateKey, isHardwareWallet, { androidEncryptionPin }); - logger.debug(`[createWallet] - saved private key for wallet index: ${index}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: saved private key for wallet index: ${index}`, {}, DebugContext.wallet); addresses.push({ address: nextWallet.address, @@ -806,7 +801,7 @@ export const createWallet = async ({ if (!isHardwareWallet) { // Creating signature for this wallet - logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); await createSignature(nextWallet.address, nextWallet.privateKey); // Enable web profile store.dispatch(updateWebDataEnabled(true, nextWallet.address)); @@ -870,11 +865,11 @@ export const createWallet = async ({ } if (!silent) { - logger.debug('[createWallet] - setting selected wallet', {}, DebugContext.wallet); + logger.debug('[wallet]: setting selected wallet', {}, DebugContext.wallet); await setSelectedWallet(allWallets[id]); } - logger.debug('[createWallet] - saving all wallets', {}, DebugContext.wallet); + logger.debug('[wallet]: saving all wallets', {}, DebugContext.wallet); await saveAllWallets(allWallets); if (walletResult && walletAddress) { @@ -885,7 +880,7 @@ export const createWallet = async ({ } return null; } catch (error) { - logger.error(new RainbowError('Error in createWallet'), { error }); + logger.error(new RainbowError('[wallet]: Error in createWallet'), { error }); return null; } }; @@ -996,7 +991,7 @@ export const getPrivateKey = async (address: EthereumAddress): Promise => } return null; } catch (error) { - logger.error(new RainbowError('Error in getAllWallets'), { error }); + logger.error(new RainbowError('[wallet]: Error in getAllWallets'), { error }); return null; } }; @@ -1159,26 +1154,26 @@ export const generateAccount = async (id: RainbowWallet['id'], index: number): P return newAccount; } catch (error) { - logger.error(new RainbowError('[generateAccount] - Error generating account for keychain'), { error }); + logger.error(new RainbowError('[wallet]: Error generating account for keychain'), { error }); return null; } }; const migrateSecrets = async (): Promise => { try { - logger.info('Migrating wallet secrets'); + logger.debug('[wallet]: Migrating wallet secrets', {}, DebugContext.wallet); const seedphrase = await oldLoadSeedPhrase(); if (!seedphrase) { - logger.debug('[migrateSecrets] - old seed doesnt exist!', {}, DebugContext.wallet); + logger.debug('[wallet]: old seed doesnt exist!', {}, DebugContext.wallet); // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[migrateSecrets] - marking secrets as migrated', {}, DebugContext.wallet); + logger.debug('[wallet]: marking secrets as migrated', {}, DebugContext.wallet); return null; } const type = identifyWalletType(seedphrase); - logger.debug(`[migrateSecrets] - wallet type: ${type}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: wallet type: ${type}`, {}, DebugContext.wallet); let hdnode: undefined | HDNode, node: undefined | HDNode, existingAccount: undefined | Wallet; switch (type) { case EthereumWalletType.privateKey: @@ -1200,10 +1195,10 @@ const migrateSecrets = async (): Promise => { } if (!existingAccount && hdnode) { - logger.debug('[migrateSecrets] - No existing account, so we have to derive it', {}, DebugContext.wallet); + logger.debug('[wallet]: No existing account, so we have to derive it', {}, DebugContext.wallet); node = hdnode.derivePath(getHdPath({ type: WalletLibraryType.ethers, index: 0 })); existingAccount = new Wallet(node.privateKey); - logger.debug('[migrateSecrets] - Got existing account', {}, DebugContext.wallet); + logger.debug('[wallet]: Got existing account', {}, DebugContext.wallet); } if (!existingAccount) { @@ -1213,10 +1208,10 @@ const migrateSecrets = async (): Promise => { // Check that wasn't migrated already! const pkeyExists = await keychain.hasKey(`${existingAccount.address}_${privateKeyKey}`); if (!pkeyExists) { - logger.debug('[migrateSecrets] - new pkey didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[wallet]: new pkey didnt exist so we should save it', {}, DebugContext.wallet); // Save the private key in the new format await saveKeyForWallet(existingAccount.address, existingAccount.privateKey, false); - logger.debug('[migrateSecrets] - new pkey saved', {}, DebugContext.wallet); + logger.debug('[wallet]: new pkey saved', {}, DebugContext.wallet); } const selectedWalletData = await getSelectedWallet(); @@ -1228,13 +1223,13 @@ const migrateSecrets = async (): Promise => { // Save the seedphrase in the new format const seedExists = await keychain.hasKey(`${wallet.id}_${seedPhraseKey}`); if (!seedExists) { - logger.debug('[migrateSecrets] - new seed didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[wallet]: new seed didnt exist so we should save it', {}, DebugContext.wallet); await saveSeedPhrase(seedphrase, wallet.id); - logger.debug('[migrateSecrets] - new seed saved', {}, DebugContext.wallet); + logger.debug('[wallet]: new seed saved', {}, DebugContext.wallet); } // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[migrateSecrets] - saved migrated key', {}, DebugContext.wallet); + logger.debug('[wallet]: saved migrated key', {}, DebugContext.wallet); return { hdnode, privateKey: existingAccount.privateKey, @@ -1242,7 +1237,7 @@ const migrateSecrets = async (): Promise => { type, }; } catch (error) { - logger.error(new RainbowError('[migrateSecrets] - Error while migrating secrets'), { error }); + logger.error(new RainbowError('[wallet]: Error while migrating secrets'), { error }); return null; } }; @@ -1257,7 +1252,7 @@ export const cleanUpWalletKeys = async (): Promise => { keychain.remove(key); } catch (error) { // key might not exists - logger.warn('[cleanUpWalletKeys] - failure to delete key', { + logger.warn('[wallet]: failure to delete key', { key, error, }); @@ -1277,10 +1272,10 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): // First we need to check if that key already exists const keyFound = await keychain.hasKey(`${id}_${seedPhraseKey}`); if (!keyFound) { - logger.debug('[loadAndMigrate] - key not found, should need migration', {}, DebugContext.wallet); + logger.debug('[wallet]: key not found, should need migration', {}, DebugContext.wallet); // if it doesn't we might have a migration pending const isSeedPhraseMigrated = await keychain.loadString(oldSeedPhraseMigratedKey); - logger.debug(`[loadAndMigrate] - Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); // We need to migrate the seedphrase & private key first // In that case we regenerate the existing private key to store it with the new format @@ -1288,24 +1283,24 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): const migratedSecrets = await migrateSecrets(); seedPhrase = migratedSecrets?.seedphrase ?? null; } else { - logger.error(new RainbowError('[loadAndMigrate] - Migrated flag was set but there is no key!'), { id }); + logger.error(new RainbowError('[wallet]: Migrated flag was set but there is no key!'), { id }); } } else { - logger.debug('[loadAndMigrate] - Getting seed directly', {}, DebugContext.wallet); + logger.debug('[wallet]: Getting seed directly', {}, DebugContext.wallet); const androidEncryptionPin = IS_ANDROID && !(await kc.getSupportedBiometryType()) ? await authenticateWithPIN() : undefined; const seedData = await getSeedPhrase(id, { androidEncryptionPin }); seedPhrase = seedData?.seedphrase ?? null; if (seedPhrase) { - logger.debug('[loadAndMigrate] - got seed succesfully', {}, DebugContext.wallet); + logger.debug('[wallet]: got seed succesfully', {}, DebugContext.wallet); } else { - logger.error(new RainbowError('[loadAndMigrate] - Missing seed for wallet - (Key exists but value isnt valid)!')); + logger.error(new RainbowError('[wallet]: Missing seed for wallet - (Key exists but value isnt valid)!')); } } return seedPhrase; } catch (error) { - logger.error(new RainbowError('[loadAndMigrate] - Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); + logger.error(new RainbowError('[wallet]: Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); throw error; } }; diff --git a/src/navigation/HardwareWalletTxNavigator.tsx b/src/navigation/HardwareWalletTxNavigator.tsx index f91b59e5a83..cff47c33435 100644 --- a/src/navigation/HardwareWalletTxNavigator.tsx +++ b/src/navigation/HardwareWalletTxNavigator.tsx @@ -23,7 +23,7 @@ export const ledgerStorage = new MMKV({ export const HARDWARE_TX_ERROR_KEY = 'hardwareTXError'; export const setHardwareTXError = (value: boolean) => { - logger.info(`setHardwareTXError`, { value }); + logger.warn(`[HardwareWalletTxNavigator]: setHardwareTXError`, { value }); ledgerStorage.set(HARDWARE_TX_ERROR_KEY, value); }; @@ -83,14 +83,14 @@ export const HardwareWalletTxNavigator = () => { ); const successCallback = useCallback(() => { - logger.debug('[LedgerTx] - submitting tx', {}, DebugContext.ledger); + logger.debug('[HardwareWalletTxNavigator]: submitting tx', {}, DebugContext.ledger); if (!isReady) { setReadyForPolling(false); setIsReady(true); setHardwareTXError(false); submit(); } else { - logger.debug('[LedgerTx] - already submitted', {}, DebugContext.ledger); + logger.debug('[HardwareWalletTxNavigator]: already submitted', {}, DebugContext.ledger); } }, [isReady, setIsReady, setReadyForPolling, submit]); diff --git a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx index 5de36d9bbfb..9c215ed804d 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx @@ -3,15 +3,15 @@ import React, { useCallback } from 'react'; import { StyleSheet, TouchableWithoutFeedback, View } from 'react-native'; const BottomSheetBackground = () => { - //#region hooks + // #region hooks const { close } = useBottomSheet(); - //#endregion + // #endregion - //#region callbacks + // #region callbacks const handleOnPress = useCallback(() => { close(); }, [close]); - //#endregion + // #endregion return ( diff --git a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx index b01d9db999c..8e17cd77670 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx @@ -12,11 +12,11 @@ type Props = BottomSheetNavigationConfig & { }; const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => { - //#region hooks + // #region hooks const forceUpdate = useForceUpdate(); - //#endregion + // #endregion - //#region variables + // #region variables const descriptorsCache = useRef({}); const [firstKey, ...restKeys] = useMemo( // @ts-ignore navigation type mismatch @@ -40,9 +40,9 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => .forEach(key => { descriptorsCache.current[key].removing = true; }); - //#endregion + // #endregion - //#region callbacks + // #region callbacks const handleOnDismiss = useCallback((key: string, removed: boolean) => { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete descriptorsCache.current[key]; @@ -64,7 +64,7 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - //#endregion + // #endregion return ( {descriptors[firstKey].render()} diff --git a/src/notifications/NotificationsHandler.tsx b/src/notifications/NotificationsHandler.tsx index d809e788d47..5c0d5a41af0 100644 --- a/src/notifications/NotificationsHandler.tsx +++ b/src/notifications/NotificationsHandler.tsx @@ -181,9 +181,9 @@ export const NotificationsHandler = ({ walletReady }: Props) => { transaction, }); } else if (type === NotificationTypes.walletConnect) { - logger.info(`NotificationsHandler: handling wallet connect notification`, { notification }); + logger.debug(`[NotificationsHandler]: handling wallet connect notification`, { notification }); } else if (type === NotificationTypes.marketing) { - logger.info(`NotificationsHandler: handling marketing notification`, { + logger.debug(`[NotificationsHandler]: handling marketing notification`, { notification, }); const data = notification.data as unknown as MarketingNotificationData; @@ -194,7 +194,7 @@ export const NotificationsHandler = ({ walletReady }: Props) => { }); } } else { - logger.warn(`NotificationsHandler: received unknown notification`, { + logger.warn(`[NotificationsHandler]: received unknown notification`, { notification, }); } diff --git a/src/notifications/foregroundHandler.ts b/src/notifications/foregroundHandler.ts index d8df820f7e1..75282a3f7a3 100644 --- a/src/notifications/foregroundHandler.ts +++ b/src/notifications/foregroundHandler.ts @@ -30,6 +30,8 @@ export function handleShowingForegroundNotification(remoteMessage: FixedRemoteMe } notifee.displayNotification(notification).catch(error => { - logger.error(new RainbowError('Error while displaying notification with notifee library'), { error }); + logger.error(new RainbowError('[notifications]: Error while displaying notification with notifee library'), { + error, + }); }); } diff --git a/src/notifications/permissions.ts b/src/notifications/permissions.ts index 11a794f542b..b2b1869fd32 100644 --- a/src/notifications/permissions.ts +++ b/src/notifications/permissions.ts @@ -25,7 +25,9 @@ export const checkPushNotificationPermissions = async () => { try { permissionStatus = await getPermissionStatus(); } catch (error) { - logger.error(new RainbowError('Error checking if a user has push notifications permission'), { error }); + logger.error(new RainbowError('[notifications]: Error checking if a user has push notifications permission'), { + error, + }); } if (permissionStatus !== messaging.AuthorizationStatus.AUTHORIZED && permissionStatus !== messaging.AuthorizationStatus.PROVISIONAL) { @@ -38,7 +40,7 @@ export const checkPushNotificationPermissions = async () => { trackPushNotificationPermissionStatus(status ? 'enabled' : 'disabled'); await saveFCMToken(); } catch (error) { - logger.error(new RainbowError('Error while getting permissions'), { error }); + logger.error(new RainbowError('[notifications]: Error while getting permissions'), { error }); } finally { resolve(true); } diff --git a/src/notifications/settings/firebase.ts b/src/notifications/settings/firebase.ts index 4448d4eb641..09fe6271985 100644 --- a/src/notifications/settings/firebase.ts +++ b/src/notifications/settings/firebase.ts @@ -35,7 +35,7 @@ export const subscribeWalletToNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ): Promise => { - logger.debug(`Notifications: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => trackChangedNotificationSettings(topic, 'subscribe', chainId, type)); @@ -47,7 +47,7 @@ export const unsubscribeWalletFromNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ) => { - logger.debug(`Notifications: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => { @@ -56,14 +56,14 @@ export const unsubscribeWalletFromNotificationTopic = async ( }; export const subscribeToGlobalNotificationTopic = async (topic: GlobalNotificationTopicType): Promise => { - logger.debug(`Notifications: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(topic) .then(() => trackChangedNotificationSettings(topic, 'subscribe')); }; export const unsubscribeFromGlobalNotificationTopic = async (topic: GlobalNotificationTopicType) => { - logger.debug(`Notifications: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(topic) .then(() => { diff --git a/src/notifications/settings/initialization.ts b/src/notifications/settings/initialization.ts index 4cd40737e78..58d67e30215 100644 --- a/src/notifications/settings/initialization.ts +++ b/src/notifications/settings/initialization.ts @@ -189,7 +189,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting await unsubscribeWalletFromAllNotificationTopics(newSettings.oldType, NOTIFICATIONS_DEFAULT_CHAIN_ID, newSettings.address); newSettings.oldType = undefined; } catch (e) { - logger.error(new RainbowError('Failed to unsubscribe old watcher mode notification topics')); + logger.error(new RainbowError('[notifications]: Failed to unsubscribe old watcher mode notification topics')); } } if (newSettings.type === WalletNotificationRelationship.OWNER && !newSettings.successfullyFinishedInitialSubscription) { @@ -198,7 +198,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting newSettings.successfullyFinishedInitialSubscription = true; newSettings.enabled = true; } catch (e) { - logger.error(new RainbowError('Failed to subscribe to default notification topics for newly added wallet')); + logger.error(new RainbowError('[notifications]: Failed to subscribe to default notification topics for newly added wallet')); } } diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index bbf194a46c7..8c88b9ee1bf 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -19,7 +19,7 @@ export const saveFCMToken = async () => { } } } catch (error) { - logger.warn('Error while getting and saving FCM token', { + logger.warn('[notifications]: Error while getting and saving FCM token', { error, }); } @@ -30,7 +30,7 @@ export async function getFCMToken(): Promise { const token = fcmTokenLocal?.data || undefined; if (!token) { - logger.debug('getFCMToken: No FCM token found'); + logger.debug('[notifications]: getFCMToken No FCM token found'); } return token; diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 80c13845313..ae7da2d87b6 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -43,7 +43,11 @@ export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { message = toUtf8String(message); } } catch (error) { - logger.debug('WC v2: getting display details, unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); + logger.warn( + '[parsers/requests]: WC v2: getting display details, unable to decode hex message to UTF8 string', + { error }, + logger.DebugContext.walletconnect + ); } return getMessageDisplayDetails(message, timestampInMs); } diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index be6a0df7c9a..92aea428ffc 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -130,7 +130,7 @@ export const crosschainSwap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('crosschainSwap: error estimateCrosschainSwapGasLimit'), { + logger.error(new RainbowError('[raps/crosschainSwap]: error estimateCrosschainSwapGasLimit'), { message: (e as Error)?.message, }); throw e; @@ -158,11 +158,13 @@ export const crosschainSwap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('crosschainSwap: error executeCrosschainSwap'), { message: (e as Error)?.message }); + logger.error(new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'), { + message: (e as Error)?.message, + }); throw e; } - if (!swap) throw new RainbowError('crosschainSwap: error executeCrosschainSwap'); + if (!swap) throw new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'); // TODO: MARK - Replace this once we migrate network => chainId const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts index 6c804fad086..b5a9a05a48b 100644 --- a/src/raps/actions/ens.ts +++ b/src/raps/actions/ens.ts @@ -11,7 +11,7 @@ import { ENSRegistrationTransactionType, getENSExecutionDetails, REGISTRATION_MO import * as i18n from '@/languages'; import { saveCommitRegistrationParameters, updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; import store from '@/redux/store'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { parseGasParamAmounts } from '@/parsers'; import { addNewTransaction } from '@/state/pendingTransactions'; import { Network } from '@/networks/types'; @@ -308,24 +308,23 @@ const ensAction = async ( type: ENSRegistrationTransactionType, baseNonce?: number ): Promise => { - logger.log(`[${actionName}] base nonce`, baseNonce, 'index:', index); + logger.debug(`[raps/ens]: [${actionName}] base nonce ${baseNonce} index: ${index}`); const { dispatch } = store; const { accountAddress: ownerAddress } = store.getState().settings; const { name, duration, rentPrice, records, salt, toAddress, mode } = parameters; - logger.log(`[${actionName}] rap for`, name); + logger.debug(`[raps/ens]: [${actionName}] rap for ${name}`); let gasLimit; const ensRegistrationRecords = formatRecordsForTransaction(records); try { - logger.sentry( - `[${actionName}] estimate gas`, - { + logger.debug(`[raps/ens]: [${actionName}] estimate gas`, { + data: { ...parameters, + type, }, - type - ); + }); // when registering the ENS if we try to estimate gas for setting records // (MULTICALL || SET_TEXT) it's going to fail if we put the account address @@ -346,8 +345,7 @@ const ensAction = async ( type, }); } catch (e) { - logger.sentry(`[${actionName}] Error estimating gas`); - captureException(e); + logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error estimating gas: ${e}`)); throw e; } let tx; @@ -358,8 +356,11 @@ const ensAction = async ( maxFeePerGas = gasParams.maxFeePerGas; maxPriorityFeePerGas = gasParams.maxPriorityFeePerGas; - logger.sentry(`[${actionName}] about to ${type}`, { - ...parameters, + logger.debug(`[raps/ens]: [${actionName}] about to ${type}`, { + data: { + ...parameters, + type, + }, }); const nonce = baseNonce ? baseNonce + index : null; @@ -457,12 +458,11 @@ const ensAction = async ( }); } } catch (e) { - logger.sentry(`[${actionName}] Error executing`); - captureException(e); + logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error executing: ${e}`)); throw e; } - logger.log(`[${actionName}] response`, tx); + logger.debug(`[raps/ens]: [${actionName}] response`, { data: tx }); const newTransaction: NewTransaction = { chainId: ChainId.mainnet, @@ -484,7 +484,8 @@ const ensAction = async ( network: NetworkTypes.mainnet, status: 'pending', }; - logger.log(`[${actionName}] adding new txn`, newTransaction); + + logger.debug(`[raps/ens]: [${actionName}] adding new txn`, { data: newTransaction }); addNewTransaction({ address: ownerAddress, @@ -670,11 +671,11 @@ const executeAction = async ( rapName: string, baseNonce?: number ): Promise => { - logger.log('[1 INNER] index', index); + logger.debug(`[raps/ens]: [${rapName}] 1 INNER index: ${index}`); const { type, parameters } = action; let nonce; try { - logger.log('[2 INNER] executing type', type); + logger.debug(`[raps/ens]: [${rapName}] 2 INNER executing type: ${type}`); const actionPromise = findENSActionByType(type); nonce = await performanceTracking.getState().executeFn({ fn: actionPromise, @@ -683,9 +684,7 @@ const executeAction = async ( })(wallet, rap, index, parameters as RapENSActionParameters, baseNonce); return { baseNonce: nonce, errorMessage: null }; } catch (error: any) { - logger.debug('Rap blew up', error); - logger.sentry('[3 INNER] error running action, code:', error?.code); - captureException(error); + logger.error(new RainbowError(`[raps/ens]: [${rapName}] Error executing action: ${action} ${error}`)); analytics.track('Rap failed', { category: 'raps', failed_action: type, @@ -694,7 +693,7 @@ const executeAction = async ( // If the first action failed, return an error message if (index === 0) { const errorMessage = parseError(error); - logger.log('[4 INNER] displaying error message', errorMessage); + logger.debug(`[raps/ens]: [${rapName}] 4 INNER displaying error message ${errorMessage}`); return { baseNonce: null, errorMessage }; } return { baseNonce: null, errorMessage: null }; @@ -722,7 +721,7 @@ export const executeENSRap = async ( let nonce = parameters?.nonce; - logger.log('[common - executing rap]: actions', actions); + logger.debug(`[raps/ens]: [${rapName}] actions`, { actions }); if (actions.length) { const firstAction = actions[0]; const { baseNonce, errorMessage } = await executeAction(firstAction, wallet, rap, 0, rapName, nonce); @@ -744,7 +743,8 @@ export const executeENSRap = async ( category: 'raps', label: rapName, }); - logger.log('[common - executing rap] finished execute rap function'); + + logger.debug(`[raps/ens]: [${rapName}] finished execute rap function`); return { nonce }; }; diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index 5dd61e809f2..7a060b2e173 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -267,7 +267,7 @@ export const swap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('swap: error estimateSwapGasLimit'), { + logger.error(new RainbowError('[raps/swap]: error estimateSwapGasLimit'), { message: (e as Error)?.message, }); @@ -295,7 +295,7 @@ export const swap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('swap: error executeSwap'), { + logger.error(new RainbowError('[raps/swap]: error executeSwap'), { message: (e as Error)?.message, }); throw e; diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index d2c95ea1c00..08592cc3597 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -41,7 +41,7 @@ export const getAssetRawAllowance = async ({ const allowance = await tokenContract.allowance(owner, spender); return allowance.toString(); } catch (error) { - logger.error(new RainbowError('getRawAllowance: error'), { + logger.error(new RainbowError('[raps/unlock]: error'), { message: (error as Error)?.message, }); return null; @@ -95,7 +95,7 @@ export const estimateApprove = async ({ }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('unlock: error estimateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -122,7 +122,7 @@ export const populateApprove = async ({ }); return approveTransaction; } catch (error) { - logger.error(new RainbowError(' error populateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error populateApprove'), { message: (error as Error)?.message, }); return null; @@ -149,7 +149,7 @@ export const estimateERC721Approval = async ({ }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('estimateERC721Approval: error estimateApproval'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApproval'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -234,7 +234,7 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('unlock: error estimateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { message: (e as Error)?.message, }); throw e; @@ -260,13 +260,13 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('unlock: error executeApprove'), { + logger.error(new RainbowError('[raps/unlock]: error executeApprove'), { message: (e as Error)?.message, }); throw e; } - if (!approval) throw new RainbowError('unlock: error executeApprove'); + if (!approval) throw new RainbowError('[raps/unlock]: error executeApprove'); const transaction = { asset: { diff --git a/src/raps/common.ts b/src/raps/common.ts index 82282d68678..477a0f68b46 100644 --- a/src/raps/common.ts +++ b/src/raps/common.ts @@ -98,7 +98,7 @@ export const createNewENSAction = (type: ENSRapActionType, parameters: ENSAction type, }; - logger.log('[common] Creating a new action', { + logger.debug('[raps/common]: Creating a new action', { extra: { ...newAction, }, diff --git a/src/raps/execute.ts b/src/raps/execute.ts index 5481e159b72..3ee2c9ec9b4 100644 --- a/src/raps/execute.ts +++ b/src/raps/execute.ts @@ -94,7 +94,7 @@ export async function executeAction({ const { nonce, hash } = (await typeAction(type, actionProps)()) as RapActionResult; return { baseNonce: nonce, errorMessage: null, hash }; } catch (error) { - logger.error(new RainbowError(`rap: ${rapName} - error execute action`), { + logger.error(new RainbowError(`[raps/execute]: ${rapName} - error execute action`), { message: (error as Error)?.message, }); if (index === 0) { diff --git a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js index a557b350e51..02a09ec6041 100644 --- a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js +++ b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js @@ -28,8 +28,8 @@ class BSpline { } seqAt(dim) { - let points = this.points; - let margin = this.degree + 1; + const points = this.points; + const margin = this.degree + 1; return function (n) { if (n < margin) { return points[0][dim]; @@ -84,9 +84,9 @@ class BSpline { } getInterpol(seq, t) { - let f = this.baseFunc; - let rangeInt = this.baseFuncRangeInt; - let tInt = Math.floor(t); + const f = this.baseFunc; + const rangeInt = this.baseFuncRangeInt; + const tInt = Math.floor(t); let result = 0; for (let i = tInt - rangeInt; i <= tInt + rangeInt; i++) { result += seq(i) * f(t - i); @@ -113,13 +113,13 @@ class BSpline { } calcAt(t) { - t = t * ((this.degree + 1) * 2 + this.points.length); //t must be in [0,1] + t = t * ((this.degree + 1) * 2 + this.points.length); // t must be in [0,1] if (this.dimension === 2) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t)]; } else if (this.dimension === 3) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t), this.getInterpol(this.seqAt(2), t)]; } else { - let res = []; + const res = []; for (let i = 0; i < this.dimension; i++) { res.push(this.getInterpol(this.seqAt(i), t)); } diff --git a/src/react-native-cool-modals/createNativeStackNavigator.js b/src/react-native-cool-modals/createNativeStackNavigator.js index 391384ec141..f4bba2bb834 100644 --- a/src/react-native-cool-modals/createNativeStackNavigator.js +++ b/src/react-native-cool-modals/createNativeStackNavigator.js @@ -2,7 +2,7 @@ import { createNavigatorFactory, StackRouter as OldStackRouter, StackActions, us import * as React from 'react'; import NativeStackView from './NativeStackView'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; function NativeStackNavigator(props) { const { children, initialRouteName, screenOptions, ...rest } = props; @@ -13,7 +13,7 @@ function NativeStackNavigator(props) { getStateForAction(state, action, options) { if (action.type === 'PUSH') { if (state.routes[state.routes.length - 1].name === action.payload.name) { - logger.log('pushing twice the same name is not allowed'); + logger.debug(`[NativeStackNavigator]: pushing twice the same name is not allowed`); return state; } } diff --git a/src/redux/gas.ts b/src/redux/gas.ts index 4c82beb67d1..60883b3c28b 100644 --- a/src/redux/gas.ts +++ b/src/redux/gas.ts @@ -276,7 +276,7 @@ export const getPolygonGasPrices = async () => { }; return polygonGasStationPrices; } catch (e) { - logger.error(new RainbowError(`failed to fetch polygon gas prices ${e}`)); + logger.error(new RainbowError(`[redux/gas]: failed to fetch polygon gas prices ${e}`)); return null; } }; @@ -307,7 +307,7 @@ export const getBscGasPrices = async () => { }; return bscGasStationPrices; } catch (e) { - logger.error(new RainbowError(`failed to fetch BSC gas prices ${e}`)); + logger.error(new RainbowError(`[redux/gas]: failed to fetch BSC gas prices ${e}`)); return null; } }; @@ -603,13 +603,12 @@ export const gasPricesStartPolling = type: GAS_FEES_SUCCESS, }); } catch (e) { - logger.error(new RainbowError(`Etherscan gas estimates error: ${e}`)); - logger.debug('falling back to eth gas station'); + logger.error(new RainbowError(`[redux/gas]: Etherscan gas estimates error: ${e}`)); } } fetchResolve(true); } catch (e) { - logger.error(new RainbowError(`Gas Estimates Failed for ${network}: ${e}`)); + logger.error(new RainbowError(`[redux/gas]: Gas Estimates Failed for ${network}: ${e}`)); fetchReject(e); } }) diff --git a/src/redux/requests.ts b/src/redux/requests.ts index d583e40cae1..aa5196b9cd4 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -5,9 +5,7 @@ import { maybeSignUri } from '@/handlers/imgix'; import { getLocalRequests, removeLocalRequest, saveLocalRequests } from '@/handlers/localstorage/walletconnectRequests'; import { omitFlatten } from '@/helpers/utilities'; import { getRequestDisplayDetails } from '@/parsers'; -import { ethereumUtils } from '@/utils'; -import logger from '@/utils/logger'; -import { Network } from '@/networks/types'; +import { logger } from '@/logger'; import { ChainId } from '@/__swaps__/types/chains'; // -- Constants --------------------------------------- // @@ -172,7 +170,7 @@ export const addRequestToApprove = // by an invalid access might be caught or expected elsewhere, so for now // `ts-expect-error` is used. if (displayDetails.timestampInMs < oneHourAgoTs) { - logger.log('request expired!'); + logger.debug(`[redux/requests]: [${requestId}] request expired!`); return; } const unsafeImageUrl = peerMeta?.icons?.[0]; diff --git a/src/redux/settings.ts b/src/redux/settings.ts index ab3e0a2d4aa..728caf45bcd 100644 --- a/src/redux/settings.ts +++ b/src/redux/settings.ts @@ -27,7 +27,7 @@ import { Network } from '@/helpers/networkTypes'; import { explorerClearState, explorerInit } from '@/redux/explorer'; import { AppState } from '@/redux/store'; import { ethereumUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; // -- Constants ------------------------------------------------------------- // const SETTINGS_UPDATE_SETTINGS_ADDRESS = 'settings/SETTINGS_UPDATE_SETTINGS_ADDRESS'; @@ -145,7 +145,7 @@ export const settingsLoadState = type: SETTINGS_UPDATE_ACCOUNT_SETTINGS_SUCCESS, }); } catch (error) { - logger.log('Error loading native currency and testnets pref', error); + logger.error(new RainbowError(`[redux/settings]: Error loading native currency and testnets pref: ${error}`)); } }; @@ -159,7 +159,7 @@ export const settingsLoadNetwork = () => async (dispatch: Dispatch async (dispatch: Dispatch (dispatch: Dispatch) => { const callback = async () => { - logger.log('changing app icon to', appIcon); + logger.debug(`[redux/settings]: changing app icon to ${appIcon}`); try { await changeIcon(appIcon); - logger.log('icon changed to ', appIcon); + logger.debug(`[redux/settings]: icon changed to ${appIcon}`); saveAppIcon(appIcon); dispatch({ payload: appIcon, type: SETTINGS_UPDATE_APP_ICON_SUCCESS, }); } catch (error) { - logger.log('Error changing app icon', error); + logger.error(new RainbowError(`[redux/settings]: Error changing app icon: ${error}`)); } }; @@ -247,7 +247,7 @@ export const settingsUpdateNetwork = (network: Network) => async (dispatch: Disp }); saveNetwork(network); } catch (error) { - logger.log('Error updating network settings', error); + logger.error(new RainbowError(`[redux/settings]: Error updating network settings: ${error}`)); } }; @@ -261,7 +261,7 @@ export const settingsChangeLanguage = (language: Language) => async (dispatch: D saveLanguage(language); analytics.identify({ language }); } catch (error) { - logger.log('Error changing language', error); + logger.error(new RainbowError(`[redux/settings]: Error changing language: ${error}`)); } }; @@ -278,7 +278,7 @@ export const settingsChangeNativeCurrency = saveNativeCurrency(nativeCurrency); analytics.identify({ currency: nativeCurrency }); } catch (error) { - logger.log('Error changing native currency', error); + logger.error(new RainbowError(`[redux/settings]: Error changing native currency: ${error}`)); } }; diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index 564d2893e5e..da0ea48400c 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -189,7 +189,7 @@ const getNativeOptions = async () => { const token = await getFCMToken(); if (!token && !IS_DEV) { - logger.error(new RainbowError(`WC: FCM token not found, push notifications will not be received`)); + logger.error(new RainbowError(`[redux/walletconnect]: FCM token not found, push notifications will not be received`)); } const nativeOptions = { @@ -353,7 +353,7 @@ export const walletConnectOnSessionRequest = error, payload, }); - logger.error(new RainbowError('WC: Error on wc session_request'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc session_request'), { error, payload, }); @@ -424,7 +424,7 @@ export const walletConnectOnSessionRequest = }, 2000); } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('WC: Exception during wc session_request'), { error }); + logger.error(new RainbowError('[redux/walletconnect]: Exception during wc session_request'), { error }); analytics.track('Exception on wc session_request', { error, }); @@ -432,7 +432,7 @@ export const walletConnectOnSessionRequest = } } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('WC: FCM exception during wc session_request'), { error }); + logger.error(new RainbowError('[redux/walletconnect]: FCM exception during wc session_request'), { error }); analytics.track('FCM exception on wc session_request', { error, }); @@ -449,7 +449,7 @@ export const walletConnectOnSessionRequest = const listenOnNewMessages = (walletConnector: WalletConnect) => (dispatch: ThunkDispatch, getState: AppGetState) => { walletConnector.on('call_request', async (error, payload) => { - logger.debug('WC: Request!', { error, payload }, logger.DebugContext.walletconnect); + logger.debug('[redux/walletconnect]: Request!', { error, payload }, logger.DebugContext.walletconnect); if (error) { analytics.track('Error on wc call_request', { @@ -457,7 +457,7 @@ const listenOnNewMessages = error, payload, }); - logger.error(new RainbowError('WC: Error on wc call_request'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc call_request'), { message: error, }); return; @@ -486,7 +486,7 @@ const listenOnNewMessages = result: null, }); const { accountAddress } = getState().settings; - logger.debug('WC: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); + logger.debug('[redux/walletconnect]: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); await walletConnector.updateSession({ accounts: [accountAddress], // @ts-expect-error "numericChainId" is a string, not a number. @@ -521,7 +521,7 @@ const listenOnNewMessages = type: WalletConnectApprovalSheetType.switch_chain, }); } else { - logger.info('WC: NOT SUPPORTED CHAIN'); + logger.warn(`[redux/walletconnect]: Unsupported chain ${numericChainId}`); walletConnector.rejectRequest({ error: { message: 'Chain currently not supported' }, id: requestId, @@ -573,7 +573,7 @@ const listenOnNewMessages = }); walletConnector.on('disconnect', error => { if (error) { - logger.error(new RainbowError('WC: Error on wc disconnect'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc disconnect'), { message: error, }); @@ -620,7 +620,7 @@ export const walletConnectLoadState = // @ts-ignore error, }); - logger.error(new RainbowError('WC: Error on wc walletConnectLoadState'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc walletConnectLoadState'), { error, }); newWalletConnectors = {}; diff --git a/src/redux/wallets.ts b/src/redux/wallets.ts index 519223b54c3..3e1fbd1675e 100644 --- a/src/redux/wallets.ts +++ b/src/redux/wallets.ts @@ -162,7 +162,7 @@ export const walletsLoadState = }); if (found) { selectedWallet = someWallet; - logger.info('Found selected wallet based on loadAddress result'); + logger.debug('[redux/wallets]: Found selected wallet based on loadAddress result'); } return found; }); @@ -172,7 +172,7 @@ export const walletsLoadState = // Recover from broken state (account address not in selected wallet) if (!addressFromKeychain) { addressFromKeychain = await loadAddress(); - logger.info("addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); + logger.debug("[redux/wallets]: addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); } const selectedAddress = selectedWallet?.addresses.find(a => { @@ -194,7 +194,7 @@ export const walletsLoadState = if (!account) return; await dispatch(settingsUpdateAccountAddress(account.address)); await saveAddress(account.address); - logger.info('Selected the first visible address because there was not selected one'); + logger.debug('[redux/wallets]: Selected the first visible address because there was not selected one'); } const walletNames = await getWalletNames(); @@ -209,7 +209,7 @@ export const walletsLoadState = return wallets; } catch (error) { - logger.error(new RainbowError('Exception during walletsLoadState'), { + logger.error(new RainbowError('[redux/wallets]: Exception during walletsLoadState'), { message: (error as Error)?.message, }); } @@ -281,7 +281,7 @@ export const setAllWalletsWithIdsAsBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('Saving multiple wallets UserData to cloud failed.'), { + logger.error(new RainbowError('[redux/wallets]: Saving multiple wallets UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -325,7 +325,7 @@ export const setWalletBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('Saving wallet UserData to cloud failed.'), { + logger.error(new RainbowError('[redux/wallets]: Saving wallet UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -346,7 +346,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = try { currentUserData = await fetchUserDataFromCloud(); } catch (error) { - logger.error(new RainbowError('There was an error when trying to update wallet backup statuses'), { + logger.error(new RainbowError('[redux/wallets]: There was an error when trying to update wallet backup statuses'), { error: (error as Error).message, }); return; @@ -385,7 +385,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = relatedCloudWalletId = walletDataForCurrentAddress.id; } else if (relatedCloudWalletId !== walletDataForCurrentAddress.id) { logger.warn( - 'Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' + '[redux/wallets]: Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' ); return; } @@ -612,37 +612,37 @@ export const fetchWalletNames = () => async (dispatch: Dispatch async (dispatch: ThunkDispatch, getState: AppGetState) => { try { let healthyKeychain = true; - logger.info('[KeychainIntegrityCheck]: starting checks'); + logger.debug('[redux/wallets]: Starting keychain integrity checks'); const hasAddress = await hasKey(addressKey); if (hasAddress) { - logger.info('[KeychainIntegrityCheck]: address is ok'); + logger.debug('[redux/wallets]: address is ok'); } else { healthyKeychain = false; - logger.info(`[KeychainIntegrityCheck]: address is missing: ${hasAddress}`); + logger.debug(`[redux/wallets]: address is missing: ${hasAddress}`); } const hasOldSeedPhraseMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (hasOldSeedPhraseMigratedFlag) { - logger.info('[KeychainIntegrityCheck]: migrated flag is OK'); + logger.debug('[redux/wallets]: migrated flag is OK'); } else { - logger.info(`[KeychainIntegrityCheck]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); + logger.debug(`[redux/wallets]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); } const hasOldSeedphrase = await hasKey(seedPhraseKey); if (hasOldSeedphrase) { - logger.info('[KeychainIntegrityCheck]: old seed is still present!'); + logger.debug('[redux/wallets]: old seed is still present!'); } else { - logger.info(`[KeychainIntegrityCheck]: old seed is present: ${hasOldSeedphrase}`); + logger.debug(`[redux/wallets]: old seed is present: ${hasOldSeedphrase}`); } const { wallets, selected } = getState().wallets; if (!wallets) { - logger.warn('[KeychainIntegrityCheck]: wallets are missing from redux'); + logger.warn('[redux/wallets]: wallets are missing from redux'); } if (!selected) { - logger.warn('[KeychainIntegrityCheck]: selectedWallet is missing from redux'); + logger.warn('[redux/wallets]: selectedWallet is missing from redux'); } const nonReadOnlyWalletKeys = keys(wallets).filter(key => wallets![key].type !== WalletTypes.readOnly); @@ -654,18 +654,18 @@ export const checkKeychainIntegrity = () => async (dispatch: ThunkDispatch async (dispatch: ThunkDispatch(key: string): T | null { return JSON.parse(data); } catch (error) { - logger.sentry('Error parsing token-list-cache data'); - logger.error(error); - captureException(error); - + logger.error(new RainbowError(`[rainbow-token-list]: Error parsing token-list-cache data: ${error}`)); return null; } } @@ -91,9 +87,7 @@ function writeJson(key: string, data: T) { try { rainbowListStorage.set(key, JSON.stringify(data)); } catch (error) { - logger.sentry(`Token List: Error saving ${key}`); - logger.error(error); - captureException(error); + logger.error(new RainbowError(`[rainbow-token-list]: Error saving ${key}: ${error}`)); } } @@ -115,7 +109,7 @@ class RainbowTokenList extends EventEmitter { } } - logger.debug('Token list initialized'); + logger.debug('[rainbow-token-list]: Token list initialized'); } // Wrapping #tokenListDataStorage so we can add events around updates. @@ -127,7 +121,7 @@ class RainbowTokenList extends EventEmitter { this.#tokenListDataStorage = val; this.#derivedData = generateDerivedData(RAINBOW_TOKEN_LIST_DATA); this.emit('update'); - logger.debug('Token list data replaced'); + logger.debug('[rainbow-token-list]: Token list data replaced'); } get CURATED_TOKENS() { diff --git a/src/references/shitcoins.ts b/src/references/shitcoins.ts index a3ba26b1621..dec188eb98c 100644 --- a/src/references/shitcoins.ts +++ b/src/references/shitcoins.ts @@ -1,7 +1,7 @@ export default [ '0xe233a118042ca180570bb450cceecc8f46c23710', // PolkaCover (fake) '0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e', // Kick token - '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', //启动公链 + '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', // 启动公链 '0xf222ba8af81d799c565241b0d3eedf9bdc4fc462', // betbeb.com空投1万个 '0xdbadabe39b91f2069e27291add14a1d95e3ff54f', // betbeb.com 挖矿每天 '0xc92e74b131d7b1d46e60e07f3fae5d8877dd03f0', // Minereum diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/hardhatAssets.ts index 697900446c8..4dde7452bca 100644 --- a/src/resources/assets/hardhatAssets.ts +++ b/src/resources/assets/hardhatAssets.ts @@ -1,5 +1,4 @@ import { Contract } from '@ethersproject/contracts'; -import { captureException } from '@sentry/react-native'; import { keyBy, mapValues } from 'lodash'; import { Network } from '@/helpers/networkTypes'; import { web3Provider } from '@/handlers/web3'; // TODO JIN @@ -7,7 +6,7 @@ import { getNetworkObj } from '@/networks'; import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS } from '@/references'; import { parseAddressAsset } from './assets'; import { RainbowAddressAssets } from './types'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; const ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT = '0x0000000000000000000000000000000000000000'; @@ -29,8 +28,7 @@ const fetchHardhatBalancesWithBalanceChecker = async ( }); return balances; } catch (e) { - logger.sentry('Error fetching balances from balanceCheckerContract', network, e); - captureException(new Error('fallbackExplorer::balanceChecker failure')); + logger.error(new RainbowError(`[hardhatAssets]: Error fetching balances from balanceCheckerContract: ${e}`)); return null; } }; diff --git a/src/resources/metadata/dapps.tsx b/src/resources/metadata/dapps.tsx index 0b7832d2fde..feeee95dc99 100644 --- a/src/resources/metadata/dapps.tsx +++ b/src/resources/metadata/dapps.tsx @@ -64,7 +64,7 @@ export function useDapps(config?: UseQueryOptions): { dapps: Dapp[] } { }, })); } catch (e: any) { - logger.error(new RainbowError('Failed to fetch dApps'), { message: e.message }); + logger.error(new RainbowError('[dapps]: Failed to fetch dApps'), { message: e.message }); return []; } }, diff --git a/src/resources/nfts/simplehash/index.ts b/src/resources/nfts/simplehash/index.ts index 4e39000052b..8e6e9f24271 100644 --- a/src/resources/nfts/simplehash/index.ts +++ b/src/resources/nfts/simplehash/index.ts @@ -23,7 +23,7 @@ export async function fetchSimpleHashNFT( const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; if (!chain) { - logger.error(new RainbowError(`fetchSimpleHashNFT: no SimpleHash chain for network: ${network}`)); + logger.warn(`[simplehash]: no SimpleHash chain for network: ${network}`); return; } @@ -48,7 +48,7 @@ export async function fetchSimpleHashNFTListing( const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; if (!chain) { - logger.error(new RainbowError(`fetchSimpleHashNFTListing: no SimpleHash chain for network: ${network}`)); + logger.warn(`[simplehash]: no SimpleHash chain for network: ${network}`); return; } @@ -87,7 +87,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; if (!chain) { - logger.error(new RainbowError(`refreshNFTContractMetadata: no SimpleHash chain for network: ${nft.network}`)); + logger.warn(`[simplehash]: no SimpleHash chain for network: ${nft.network}`); return; } @@ -105,7 +105,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { ); } catch { logger.warn( - `refreshNFTContractMetadata: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` + `[simplehash]: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` ); try { // If the collection has > 20k NFTs, the above request will fail. @@ -124,7 +124,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { } catch { logger.error( new RainbowError( - `refreshNFTContractMetadata: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` + `[simplehash]: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` ) ); } @@ -139,7 +139,7 @@ export async function reportNFT(nft: UniqueAsset) { const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; if (!chain) { - logger.error(new RainbowError(`reportNFT: no SimpleHash chain for network: ${nft.network}`)); + logger.warn(`[simplehash]: no SimpleHash chain for network: ${nft.network}`); return; } @@ -161,6 +161,6 @@ export async function reportNFT(nft: UniqueAsset) { } ); } catch { - logger.error(new RainbowError(`reportNFT: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); + logger.error(new RainbowError(`[simplehash]: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); } } diff --git a/src/resources/nfts/utils.ts b/src/resources/nfts/utils.ts index 8934f01eddc..23b0d94af41 100644 --- a/src/resources/nfts/utils.ts +++ b/src/resources/nfts/utils.ts @@ -42,7 +42,7 @@ export async function fetchReservoirNFTFloorPrice(nft: UniqueAsset): Promise { ); }; export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, network: Network) => { - logger.debug('Mints: Navigating to Mint Collection', { + logger.debug('[mints]: Navigating to Mint Collection', { contractAddress, network, }); @@ -35,11 +35,11 @@ export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint, }); } else { - logger.warn('Mints: No collection found', { contractAddress, network }); + logger.warn('[mints]: No collection found', { contractAddress, network }); showAlert(); } } catch (e) { - logger.warn('Mints: navigateToMintCollection error', { + logger.warn(`[mints]: navigateToMintCollection error`, { contractAddress, network, error: e, diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts index b1324ca1e4b..34208c52bc0 100644 --- a/src/resources/transactions/consolidatedTransactions.ts +++ b/src/resources/transactions/consolidatedTransactions.ts @@ -83,7 +83,7 @@ export async function consolidatedTransactionsQueryFunction({ transactions: consolidatedTransactions, }; } catch (e) { - logger.error(new RainbowError('consolidatedTransactionsQueryFunction: '), { + logger.error(new RainbowError('[consolidatedTransactions]: '), { message: e, }); return { transactions: [] }; diff --git a/src/resources/transactions/transaction.ts b/src/resources/transactions/transaction.ts index b4210f302bc..dd10966baea 100644 --- a/src/resources/transactions/transaction.ts +++ b/src/resources/transactions/transaction.ts @@ -57,7 +57,7 @@ export const fetchTransaction = async ({ if (!parsedTx) throw new Error('Failed to parse transaction'); return parsedTx; } catch (e) { - logger.error(new RainbowError('fetchTransaction: '), { + logger.error(new RainbowError('[transaction]: Failed to fetch transaction'), { message: (e as Error)?.message, }); return null; diff --git a/src/screens/AddCash/index.tsx b/src/screens/AddCash/index.tsx index bba373b1960..5d8da30e7b0 100644 --- a/src/screens/AddCash/index.tsx +++ b/src/screens/AddCash/index.tsx @@ -53,7 +53,7 @@ export function AddCashSheet() { const [{ data, error }] = await wait(1000, [await getProviders()]); if (!data || error) { - const e = new RainbowError('F2C: failed to fetch providers'); + const e = new RainbowError('[AddCash]: failed to fetch providers'); logger.error(e); diff --git a/src/screens/AddCash/providers/Coinbase/index.tsx b/src/screens/AddCash/providers/Coinbase/index.tsx index 0bf8b26c88d..ef82205ad2c 100644 --- a/src/screens/AddCash/providers/Coinbase/index.tsx +++ b/src/screens/AddCash/providers/Coinbase/index.tsx @@ -34,13 +34,13 @@ export function Coinbase({ accountAddress, config }: { accountAddress: string; c sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Coinbase, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Coinbase, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Moonpay/index.tsx b/src/screens/AddCash/providers/Moonpay/index.tsx index cc45c5306c9..64444471b4e 100644 --- a/src/screens/AddCash/providers/Moonpay/index.tsx +++ b/src/screens/AddCash/providers/Moonpay/index.tsx @@ -35,13 +35,13 @@ export function Moonpay({ accountAddress, config }: { accountAddress: string; co sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Moonpay, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Moonpay, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Ramp/index.tsx b/src/screens/AddCash/providers/Ramp/index.tsx index 679cfd3e02b..8b956a11dd4 100644 --- a/src/screens/AddCash/providers/Ramp/index.tsx +++ b/src/screens/AddCash/providers/Ramp/index.tsx @@ -35,13 +35,13 @@ export function Ramp({ accountAddress, config }: { accountAddress: string; confi sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Ramp, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Ramp, message: (e as Error).message, }); diff --git a/src/screens/AddWalletSheet.tsx b/src/screens/AddWalletSheet.tsx index dbad7feed37..67c13b27d42 100644 --- a/src/screens/AddWalletSheet.tsx +++ b/src/screens/AddWalletSheet.tsx @@ -123,10 +123,9 @@ export const AddWalletSheet = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Updating wallet userdata failed after new account creation', + logger.error(new RainbowError('[AddWalletSheet]: Updating wallet userdata failed after new account creation'), { + error: e, }); - captureException(e); throw e; } } @@ -143,10 +142,9 @@ export const AddWalletSheet = () => { await initializeWallet(); } } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { + error: e, }); - captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -165,8 +163,8 @@ export const AddWalletSheet = () => { }, 50); }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { + error: e, }); } }; @@ -213,7 +211,9 @@ export const AddWalletSheet = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to restore from cloud'), { + error: e, + }); } } else { const isAvailable = await isCloudBackupAvailable(); diff --git a/src/screens/ChangeWalletSheet.tsx b/src/screens/ChangeWalletSheet.tsx index f345079b3e3..0aea0778e11 100644 --- a/src/screens/ChangeWalletSheet.tsx +++ b/src/screens/ChangeWalletSheet.tsx @@ -20,7 +20,7 @@ import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBala import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { doesWalletsContainAddress, showActionSheetWithOptions } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { useTheme } from '@/theme'; import { EthereumAddress } from '@/entities'; import { getNotificationSettingsForWalletWithAddress } from '@/notifications/settings/storage'; @@ -149,7 +149,9 @@ export default function ChangeWalletSheet() { setTimeout(runChecks, 10_000); } } catch (e) { - logger.log('error while switching account', e); + logger.error(new RainbowError('[ChangeWalletSheet]: Error while switching account'), { + error: e, + }); } }, [currentAddress, dispatch, editMode, goBack, initializeWallet, onChangeWallet, runChecks, wallets, watchOnly] diff --git a/src/screens/CheckIdentifierScreen.tsx b/src/screens/CheckIdentifierScreen.tsx index f4b19d9aa62..060d4831031 100644 --- a/src/screens/CheckIdentifierScreen.tsx +++ b/src/screens/CheckIdentifierScreen.tsx @@ -60,14 +60,14 @@ export default function CheckIdentifierScreen() { const allKeys = await kc.getAllKeys(); if (!allKeys?.length) { - logger.error(new RainbowError('Unable to retrieve keychain values')); + logger.error(new RainbowError('[CheckIdentifierScreen]: Unable to retrieve keychain values')); ErrorAlert(); return; } const allAccountKeys = allKeys.filter(item => item.username.includes('_rainbowPrivateKey')); if (!allAccountKeys?.length) { - logger.error(new RainbowError('No private keys found in keychain')); + logger.error(new RainbowError('[CheckIdentifierScreen]: No private keys found in keychain')); return onFailure(); } @@ -81,7 +81,7 @@ export default function CheckIdentifierScreen() { }); if (hasAccountWithoutPrivateKey) { - logger.error(new RainbowError('Detected account without matching private key')); + logger.error(new RainbowError('[CheckIdentifierScreen]: Detected account without matching private key')); return onFailure(); } diff --git a/src/screens/Diagnostics/DiagnosticsItemRow.tsx b/src/screens/Diagnostics/DiagnosticsItemRow.tsx index e109d7a7163..4253c1d8eb4 100644 --- a/src/screens/Diagnostics/DiagnosticsItemRow.tsx +++ b/src/screens/Diagnostics/DiagnosticsItemRow.tsx @@ -28,7 +28,7 @@ export const DiagnosticsItemRow = ({ data }: any) => { // @ts-expect-error poorly typed function await handlePressImportButton(null, data.secret); } catch (error) { - logger.error(new RainbowError('Error restoring from wallet diagnostics'), { + logger.error(new RainbowError('[DiagnosticsItemRow]: Error restoring from wallet diagnostics'), { message: (error as Error).message, context: 'restore', }); diff --git a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts index 60abfb63ea0..fec38fd194b 100644 --- a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts +++ b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts @@ -72,6 +72,6 @@ export async function createAndShareStateDumpFile() { // clean up the file since we don't need it anymore await RNFS.unlink(documentsFilePath); } catch (error) { - logger.error(new RainbowError('Saving app state dump data failed')); + logger.error(new RainbowError('[createAndShareStateDumpFile]: Saving app state dump data failed')); } } diff --git a/src/screens/Diagnostics/index.tsx b/src/screens/Diagnostics/index.tsx index cdbbf4bd044..b3fe96aa2f3 100644 --- a/src/screens/Diagnostics/index.tsx +++ b/src/screens/Diagnostics/index.tsx @@ -102,7 +102,7 @@ export const WalletDiagnosticsSheet = () => { setKeys(processedKeys); } } catch (error) { - logger.error(new RainbowError('Error processing keys for wallet diagnostics'), { + logger.error(new RainbowError('[WalletDiagnosticsSheet]: Error processing keys for wallet diagnostics'), { message: (error as Error).message, context: 'init', }); diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 7398cd95bb7..304fcfe7764 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -54,7 +54,7 @@ import { ethUnits } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, gasUtils } from '@/utils'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; import store from '@/redux/store'; @@ -63,7 +63,6 @@ import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; import { Wallet } from '@ethersproject/wallet'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { useTheme } from '@/theme'; -import { logger as loggr } from '@/logger'; import { getNetworkObject } from '@/networks'; import Animated from 'react-native-reanimated'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -407,7 +406,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty }); if (!wallet) { setIsAuthorizing(false); - logger.sentry(`aborting ${type} due to missing wallet`); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing wallet`)); Alert.alert('Unable to determine wallet address'); return false; } @@ -415,25 +414,26 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Switch to the flashbots provider if enabled // TODO(skylarbarrera): need to check if ledger and handle differently here if (flashbots && getNetworkObject({ chainId: currentChainId }).features?.flashbots && wallet instanceof Wallet) { - logger.debug('flashbots provider being set on mainnet'); + logger.debug('[ExchangeModal]: flashbots provider being set on mainnet'); const flashbotsProvider = await getFlashbotsProvider(); wallet = new Wallet(wallet.privateKey, flashbotsProvider); } if (!inputAmount || !outputAmount) { - logger.log('[exchange - handle submit] inputAmount or outputAmount is missing'); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing inputAmount or outputAmount`)); Alert.alert('Input amount or output amount is missing'); return false; } if (!tradeDetails) { - logger.log('[exchange - handle submit] tradeDetails is missing'); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing tradeDetails`)); Alert.alert('Missing trade details for swap'); return false; } - logger.log('[exchange - handle submit] rap'); + logger.debug(`[ExchangeModal]: getting nonce for account ${accountAddress}`); const currentNonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + logger.debug(`[ExchangeModal]: nonce for account ${accountAddress} is ${currentNonce}`); const { independentField, independentValue, slippageInBips, source } = store.getState().swap; const transformedAssetToSell = { @@ -496,9 +496,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setIsAuthorizing(false); // if the transaction was not successful, we need to bubble that up to the caller if (errorMessage) { - loggr.debug('[ExchangeModal] transaction was not successful', { - errorMessage, - }); + logger.error(new RainbowError(`[ExchangeModal]: transaction was not successful: ${errorMessage}`)); if (wallet instanceof Wallet) { Alert.alert(errorMessage); } else { @@ -507,7 +505,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return false; } - logger.log('[exchange - handle submit] executed rap!'); + logger.debug('[ExchangeModal]: executed rap!'); const slippage = slippageInBips / 100; analytics.track(`Completed ${type}`, { aggregator: tradeDetails?.source || '', @@ -547,7 +545,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return true; } catch (error) { setIsAuthorizing(false); - logger.log('[exchange - handle submit] error submitting swap', error); + logger.error(new RainbowError(`[ExchangeModal]: error submitting swap: ${error}`)); setParams({ focused: false }); // close the hardware wallet modal before navigating if (isHardwareWallet) { @@ -588,7 +586,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Tell iOS we're running a rap (for tracking purposes) NotificationManager?.postNotification('rapInProgress'); } catch (e) { - logger.log('error getting the swap amount in USD price', e); + logger.error(new RainbowError(`[ExchangeModal]: error posting notification for rapInProgress: ${e}`)); } finally { const slippage = slippageInBips / 100; analytics.track(`Submitted ${type}`, { diff --git a/src/screens/NFTSingleOfferSheet/index.tsx b/src/screens/NFTSingleOfferSheet/index.tsx index 2902e041e9b..7d682f2f143 100644 --- a/src/screens/NFTSingleOfferSheet/index.tsx +++ b/src/screens/NFTSingleOfferSheet/index.tsx @@ -240,7 +240,7 @@ export function NFTSingleOfferSheet() { }, }); } catch { - logger.error(new RainbowError('NFT Offer: Failed to estimate gas')); + logger.error(new RainbowError('[NFTSingleOfferSheet]: Failed to estimate gas')); } }, [accountAddress, feeParam, offerChainId, offer, updateTxFee]); @@ -256,7 +256,7 @@ export function NFTSingleOfferSheet() { }, [estimateGas, isExpired, isReadOnlyWallet, offer?.network, startPollingGasFees, stopPollingGasFees, updateTxFee]); const acceptOffer = useCallback(async () => { - logger.info(`Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.debug(`[NFTSingleOfferSheet]: Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); const analyticsEventObject = { nft: { contractAddress: offer.nft.contractAddress, @@ -394,7 +394,7 @@ export function NFTSingleOfferSheet() { } ); - logger.info(`Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.debug(`[NFTSingleOfferSheet]: Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { status: 'completed', ...analyticsEventObject, @@ -404,7 +404,7 @@ export function NFTSingleOfferSheet() { } catch (e) { logger.error( new RainbowError( - `Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` + `[NFTSingleOfferSheet]: Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` ) ); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { diff --git a/src/screens/NotificationsPromoSheet/index.tsx b/src/screens/NotificationsPromoSheet/index.tsx index 3f9350a5827..cdafe241eb9 100644 --- a/src/screens/NotificationsPromoSheet/index.tsx +++ b/src/screens/NotificationsPromoSheet/index.tsx @@ -61,7 +61,7 @@ export function NotificationsPromoSheetInner({ const primaryButtonOnPress = React.useCallback(async () => { if (notificationsDenied) { - logger.debug(`NotificationsPromoSheet: notifications permissions denied (could be default state)`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions denied (could be default state)`); const result = await requestNotificationPermissions(); if (result.status === perms.RESULTS.BLOCKED) { analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsBlocked); @@ -71,11 +71,11 @@ export function NotificationsPromoSheetInner({ analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsGranted); } } else if (!hasSettingsEnabled || notificationsBlocked) { - logger.debug(`NotificationsPromoSheet: notifications permissions either blocked or all settings are disabled`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions either blocked or all settings are disabled`); analyticsV2.track(analyticsV2.event.notificationsPromoSystemSettingsOpened); await perms.openSettings(); } else if (notificationsEnabled) { - logger.debug(`NotificationsPromoSheet: notifications permissions enabled`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions enabled`); analyticsV2.track(analyticsV2.event.notificationsPromoNotificationSettingsOpened); navigateToNotifications(); } else { diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index dcb196b20f1..4fc67c9baaf 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -56,7 +56,7 @@ import styled from '@/styled-thing'; import { position } from '@/styles'; import { useTheme } from '@/theme'; import { ethereumUtils, getUniqueTokenType, promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { getNetworkObj } from '@/networks'; import { IS_ANDROID } from '@/env'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; @@ -318,7 +318,7 @@ export const SendConfirmationSheet = () => { updateTxFee(gasLimit, null); }) .catch(e => { - logger.sentry('Error calculating gas limit', e); + logger.error(new RainbowError(`[SendConfirmationSheet]: error calculating gas limit: ${e}`)); updateTxFee(null, null); }); } @@ -395,7 +395,7 @@ export const SendConfirmationSheet = () => { await callback(); } } catch (e) { - logger.sentry('TX submit failed', e); + logger.error(new RainbowError(`[SendConfirmationSheet]: error submitting transaction: ${e}`)); setIsAuthorizing(false); } }, diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 17a7cfe86dd..fc6acb6f425 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -54,7 +54,7 @@ import styled from '@/styled-thing'; import { borders } from '@/styles'; import { convertAmountAndPriceToNativeDisplay, convertAmountFromNativeValue, formatInputDecimals, lessThan } from '@/helpers/utilities'; import { deviceUtils, ethereumUtils, getUniqueTokenType, safeAreaInsetValues } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_IOS } from '@/env'; import { NoResults } from '@/components/list'; import { NoResultsType } from '@/components/list/NoResults'; @@ -400,12 +400,11 @@ export default function SendSheet(props) { const validTransaction = isValidAddress && amountDetails.isSufficientBalance && isSufficientGas && isValidGas; if (!selectedGasFee?.gasFee?.estimatedFee || !validTransaction) { - logger.sentry('preventing tx submit for one of the following reasons:'); - logger.sentry('selectedGasFee ? ', selectedGasFee); - logger.sentry('selectedGasFee.maxFee ? ', selectedGasFee?.maxFee); - logger.sentry('validTransaction ? ', validTransaction); - logger.sentry('isValidGas ? ', isValidGas); - captureEvent('Preventing tx submit'); + logger.error(new RainbowError(`[SendSheet]: preventing tx submit because selectedGasFee is missing or validTransaction is false`), { + selectedGasFee, + validTransaction, + isValidGas, + }); return false; } @@ -479,11 +478,10 @@ export default function SendSheet(props) { screen: isENS ? Screens.SEND_ENS : Screens.SEND, })(txDetails); if (!signableTransaction.to) { - logger.sentry('txDetails', txDetails); - logger.sentry('signableTransaction', signableTransaction); - logger.sentry('"to" field is missing!'); - const e = new Error('Transaction missing TO field'); - captureException(e); + logger.error(new RainbowError(`[SendSheet]: txDetails is missing the "to" field`), { + txDetails, + signableTransaction, + }); Alert.alert(lang.t('wallet.transaction.alert.invalid_transaction')); submitSuccess = false; } else { @@ -524,10 +522,10 @@ export default function SendSheet(props) { } } catch (error) { submitSuccess = false; - logger.sentry('TX Details', txDetails); - logger.sentry('SendSheet onSubmit error'); - logger.sentry(error); - captureException(error); + logger.error(new RainbowError(`[SendSheet]: onSubmit error`), { + txDetails, + error, + }); // if hardware wallet, we need to tell hardware flow there was error // have to check inverse or we trigger unwanted BT permissions requests @@ -564,8 +562,9 @@ export default function SendSheet(props) { const submitTransaction = useCallback( async (...args) => { if (Number(amountDetails.assetAmount) <= 0) { - logger.sentry('amountDetails.assetAmount ? ', amountDetails?.assetAmount); - captureEvent('Preventing tx submit due to amount <= 0'); + logger.error(new RainbowError(`[SendSheet]: preventing tx submit because amountDetails.assetAmount is <= 0`), { + amountDetails, + }); return false; } const submitSuccessful = await onSubmit(...args); @@ -788,7 +787,7 @@ export default function SendSheet(props) { } }) .catch(e => { - logger.sentry('Error calculating gas limit', e); + logger.error(new RainbowError(`[SendSheet]: error calculating gas limit: ${e}`)); updateTxFee(null, null); }); } diff --git a/src/screens/SettingsSheet/components/AppIconSection.tsx b/src/screens/SettingsSheet/components/AppIconSection.tsx index e1b7cbdcafa..318aaf62ffa 100644 --- a/src/screens/SettingsSheet/components/AppIconSection.tsx +++ b/src/screens/SettingsSheet/components/AppIconSection.tsx @@ -7,7 +7,7 @@ import { Box } from '@/design-system'; import { useAccountSettings } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { useTheme } from '@/theme'; -import Logger from '@/utils/logger'; +import { logger } from '@/logger'; import { analytics } from '@/analytics'; import { AppIcon, AppIconKey, UnlockableAppIcon, UnlockableAppIconKey, freeAppIcons, unlockableAppIcons } from '@/appIcons/appIcons'; import { unlockableAppIconStorage } from '@/featuresToUnlock/unlockableAppIconCheck'; @@ -18,7 +18,7 @@ const AppIconSection = () => { const onSelectIcon = useCallback( (icon: string) => { - Logger.log('onSelectIcon', icon); + logger.debug(`[AppIconSection]: onSelectIcon: ${icon}`); analytics.track('Set App Icon', { appIcon: icon }); settingsChangeAppIcon(icon); }, @@ -32,9 +32,9 @@ const AppIconSection = () => { (unlockedAppIcons, appIconKey) => { const appIcon = unlockableAppIcons[appIconKey]; const unlocked = unlockableAppIconStorage.getBoolean(appIconKey); - Logger.log('checking if unlocked', appIcon.displayName, unlocked, appIconKey); + logger.debug(`[AppIconSection]: checking if unlocked ${appIcon.displayName} ${unlocked} ${appIconKey}`); if (unlocked) { - Logger.log('unlocked', appIcon.displayName); + logger.debug(`[AppIconSection]: unlocked ${appIcon.displayName}`); unlockedAppIcons[appIconKey] = appIcon; } return unlockedAppIcons; diff --git a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx index b776bc6dd9a..6ea5ddbc87a 100644 --- a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx @@ -201,7 +201,7 @@ const ViewWalletBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError(`[ViewWalletBackup]: Logging into Google Drive failed`), { error: e }); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -281,10 +281,9 @@ const ViewWalletBackup = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Updating wallet userdata failed after new account creation', + logger.error(new RainbowError(`[ViewWalletBackup]: Updating wallet userdata failed after new account creation`), { + error: e, }); - captureException(e); throw e; } } @@ -301,10 +300,9 @@ const ViewWalletBackup = () => { await initializeWallet(); } } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { + error: e, }); - captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -323,8 +321,8 @@ const ViewWalletBackup = () => { }, 50); }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { + error: e, }); } }, [creatingWallet, dispatch, isDamaged, navigate, initializeWallet, profilesEnabled, wallet]); diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx index 17f0290ca6c..6ad3811626d 100644 --- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx @@ -179,7 +179,9 @@ export const WalletsAndBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError(`[WalletsAndBackup]: Logging into Google Drive failed`), { + error: e, + }); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -232,10 +234,8 @@ export const WalletsAndBackup = () => { // @ts-expect-error - no params await initializeWallet(); } catch (err) { - logger.error(new RainbowError('Failed to create new secret phrase'), { - extra: { - error: err, - }, + logger.error(new RainbowError(`[WalletsAndBackup]: Failed to create new secret phrase`), { + error: err, }); } }, diff --git a/src/screens/SettingsSheet/components/DevSection.tsx b/src/screens/SettingsSheet/components/DevSection.tsx index 26538473b3b..44daf6f517f 100644 --- a/src/screens/SettingsSheet/components/DevSection.tsx +++ b/src/screens/SettingsSheet/components/DevSection.tsx @@ -31,7 +31,7 @@ import { clearImageMetadataCache } from '@/redux/imageMetadata'; import store from '@/redux/store'; import { walletsUpdate } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import logger from 'logger'; +import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet, useAllNotificationSettingsFromStorage, @@ -76,10 +76,10 @@ const DevSection = () => { const connectToHardhat = useCallback(async () => { try { const ready = await web3SetHttpProvider((ios && HARDHAT_URL_IOS) || (android && HARDHAT_URL_ANDROID) || 'http://127.0.0.1:8545'); - logger.log('connected to hardhat', ready); + logger.debug(`[DevSection] connected to hardhat: ${ready}`); } catch (e) { await web3SetHttpProvider(networkTypes.mainnet); - logger.log('error connecting to hardhat', e); + logger.error(new RainbowError(`[DevSection] error connecting to hardhat: ${e}`)); } navigate(Routes.PROFILE_SCREEN); dispatch(explorerInit()); diff --git a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx index 9f8d64e0a9f..b415e1d4d30 100644 --- a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx +++ b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx @@ -20,7 +20,7 @@ export const GoogleAccountSection: React.FC = () => { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`Fetching google account data to display in Backups Section failed`), { + logger.error(new RainbowError(`[GoogleAccountSection]: Fetching google account data to display in Backups Section failed`), { error: (error as Error).message, }); }) @@ -66,7 +66,7 @@ export const GoogleAccountSection: React.FC = () => { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`Logging into Google Drive failed.`), { + logger.error(new RainbowError(`[GoogleAccountSection]: Logging into Google Drive failed`), { error: (error as Error).message, }); } finally { diff --git a/src/screens/SettingsSheet/components/PrivacySection.tsx b/src/screens/SettingsSheet/components/PrivacySection.tsx index 2be68989d53..ef0da3ea34c 100644 --- a/src/screens/SettingsSheet/components/PrivacySection.tsx +++ b/src/screens/SettingsSheet/components/PrivacySection.tsx @@ -25,7 +25,7 @@ const PrivacySection = () => { analyticsEnabled => { if (analyticsEnabled) { device.set(['doNotTrack'], true); - logger.debug(`Analytics tracking disabled`); + logger.debug(`[PrivacySection]: Analytics tracking disabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingDisabled); logger.disable(); analyticsV2.disable(); @@ -34,7 +34,7 @@ const PrivacySection = () => { device.set(['doNotTrack'], false); logger.enable(); analyticsV2.enable(); - logger.debug(`Analytics tracking enabled`); + logger.debug(`[PrivacySection]: Analytics tracking enabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingEnabled); return true; } diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index c3e68c1c306..49dfac6f483 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -241,18 +241,18 @@ export const SignTransactionSheet = () => { const provider = getProvider({ chainId: currentChainId }); try { // attempt to re-run estimation - logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); // safety precaution: we want to ensure these properties are not used for gas estimation const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); if (rawGasLimit) { gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('WC: error estimating gas'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); } finally { - logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); const networkObject = getNetworkObject({ chainId: currentChainId }); if (networkObject && networkObject.gas.OptimismTxFee) { const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); @@ -459,7 +459,7 @@ export const SignTransactionSheet = () => { } } } catch (error) { - logger.error(new RainbowError('Error while simulating'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); } finally { setIsLoading(false); } @@ -519,7 +519,7 @@ export const SignTransactionSheet = () => { closeScreen(true); }, 300); } catch (error) { - logger.error(new RainbowError('WC: error while handling cancel request'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error while handling cancel request'), { error }); closeScreen(true); } }, @@ -608,7 +608,7 @@ export const SignTransactionSheet = () => { if (!currentChainId) return; try { logger.debug( - 'WC: gas suggested by dapp', + '[SignTransactionSheet]: gas suggested by dapp', { gas: convertHexToString(gas), gasLimitFromPayload: convertHexToString(gasLimitFromPayload), @@ -629,11 +629,15 @@ export const SignTransactionSheet = () => { (!isNil(gas) && greaterThan(rawGasLimit, convertHexToString(gas))) || (!isNil(gasLimitFromPayload) && greaterThan(rawGasLimit, convertHexToString(gasLimitFromPayload))) ) { - logger.debug('WC: using padded estimation!', { gas: rawGasLimit.toString() }, logger.DebugContext.walletconnect); + logger.debug( + '[SignTransactionSheet]: using padded estimation!', + { gas: rawGasLimit.toString() }, + logger.DebugContext.walletconnect + ); gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('WC: error estimating gas'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); } // clean gas prices / fees sent from the dapp const cleanTxPayload = omitFlatten(txPayload, ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); @@ -649,7 +653,7 @@ export const SignTransactionSheet = () => { }; txPayloadUpdated = omitFlatten(txPayloadUpdated, ['from', 'gas', 'chainId']); - logger.debug(`WC: ${transactionDetails.payload.method} payload`, { + logger.debug(`[SignTransactionSheet]: ${transactionDetails.payload.method} payload`, { txPayload, txPayloadUpdated, }); @@ -703,7 +707,7 @@ export const SignTransactionSheet = () => { }); } } catch (e) { - logger.error(new RainbowError(`WC: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); + logger.error(new RainbowError(`[SignTransactionSheet]: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); } if (response?.result) { @@ -774,7 +778,7 @@ export const SignTransactionSheet = () => { }); } } else { - logger.error(new RainbowError(`WC: Tx failure - ${formattedDappUrl}`), { + logger.error(new RainbowError(`[SignTransactionSheet]: Tx failure - ${formattedDappUrl}`), { dappName: transactionDetails?.dappName, dappUrl: transactionDetails?.dappUrl, formattedDappUrl, @@ -1471,7 +1475,7 @@ const MessageCard = ({ displayMessage = sanitizedMessage; // eslint-disable-next-line no-empty } catch (e) { - logger.warn(''); + logger.warn('[SignTransactionSheet]: Error while parsing message'); } displayMessage = JSON.stringify(displayMessage, null, 4); diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index dc3f25f771b..521fee3648f 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -28,7 +28,7 @@ import { ethUnits } from '@/references'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { ethereumUtils, gasUtils, safeAreaInsetValues } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { getNetworkObj } from '@/networks'; import * as i18n from '@/languages'; import { updateTransaction } from '@/state/pendingTransactions'; @@ -174,7 +174,7 @@ export default function SpeedUpAndCancelSheet() { updatedTx.type = 'cancel'; updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); } catch (e) { - logger.log('Error submitting cancel tx', e); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting cancel tx: ${e}`)); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -244,7 +244,7 @@ export default function SpeedUpAndCancelSheet() { updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); } catch (e) { - logger.log('Error submitting speed up tx', e); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting speed up tx: ${e}`)); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -288,10 +288,10 @@ export default function SpeedUpAndCancelSheet() { const updateProvider = async () => { let provider; if (getNetworkObj(tx?.network).features.flashbots && tx.flashbots) { - logger.debug('using flashbots provider'); + logger.debug(`[SpeedUpAndCancelSheet]: using flashbots provider for network ${currentNetwork}`); provider = await getFlashbotsProvider(); } else { - logger.debug('using normal provider'); + logger.debug(`[SpeedUpAndCancelSheet]: using provider for network ${currentNetwork}`); provider = getProviderForNetwork(currentNetwork); } setCurrentProvider(provider); @@ -342,11 +342,13 @@ export default function SpeedUpAndCancelSheet() { setMinGasPrice(calcGasParamRetryValue(hexGasPrice)); } } catch (e) { - logger.log('something went wrong while fetching tx info ', e); - logger.sentry('Error speeding up or canceling transaction: [error]', e); - logger.sentry('Error speeding up or canceling transaction: [transaction]', tx); - const speedUpOrCancelError = new Error('Error speeding up or canceling transaction'); - captureException(speedUpOrCancelError); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error fetching tx info: ${e}`), { + data: { + tx, + }, + }); + + // NOTE: We don't care about this for cancellations if (type === SPEED_UP) { Alert.alert(lang.t('wallet.speed_up.unable_to_speed_up'), lang.t('wallet.speed_up.problem_while_fetching_transaction_data'), [ { @@ -354,7 +356,6 @@ export default function SpeedUpAndCancelSheet() { }, ]); } - // We don't care about this for cancellations } } }; diff --git a/src/screens/WelcomeScreen/index.tsx b/src/screens/WelcomeScreen/index.tsx index 462c892cfed..c175c259d0a 100644 --- a/src/screens/WelcomeScreen/index.tsx +++ b/src/screens/WelcomeScreen/index.tsx @@ -27,7 +27,7 @@ import Routes from '@rainbow-me/routes'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { ThemeContextProps, useTheme } from '@/theme'; -import logger from 'logger'; +import { logger } from '@/logger'; import { IS_ANDROID, IS_TEST } from '@/env'; import { WelcomeScreenRainbowButton } from '@/screens/WelcomeScreen/WelcomeScreenRainbowButton'; @@ -85,7 +85,7 @@ export default function WelcomeScreen() { useEffect(() => { const initialize = async () => { if (IS_TEST) { - logger.log('Skipping animations because IS_TEST is true'); + logger.debug('[WelcomeScreen] Skipping animations because IS_TEST is true'); contentAnimation.value = 1; createWalletButtonAnimation.value = 1; colorAnimation.value = 0; diff --git a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx index 4842cdbc8cb..fca391ca357 100644 --- a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx +++ b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx @@ -109,10 +109,10 @@ export function PairHardwareWalletSigningSheet() { const importHardwareWallet = useCallback( async (deviceId: string) => { if (busy) { - logger.debug('[importHardwareWallet] - busy, already trying to import', { deviceId }, DebugContext.ledger); + logger.debug('[PairHardwareWalletSigningSheet]: busy, already trying to import', { deviceId }, DebugContext.ledger); return; } - logger.debug('[importHardwareWallet] - importing Hardware Wallet', { deviceId }, DebugContext.ledger); + logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId }, DebugContext.ledger); handleSetSeedPhrase(deviceId); handlePressImportButton(null, deviceId, null, null); }, @@ -137,8 +137,7 @@ export function PairHardwareWalletSigningSheet() { }, }); } else { - logger.error(new RainbowError('[importHardwareWallet] - Disconnected or Unkown Error'), { errorType }); - logger.info('[importHardwareWallet] - issue connecting, trying again '); + logger.error(new RainbowError('[PairHardwareWalletSigningSheet]: Disconnected or Unkown Error'), { errorType }); const transport = await TransportBLE.open(deviceId); await checkLedgerConnection({ transport, diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index 2a58d15fcf7..596e7c6f150 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -264,7 +264,7 @@ const MintSheet = () => { const txs: Transaction[] = []; steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${step.error}`)); + logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${step.error}`)); return; } step.items?.forEach(item => { @@ -296,7 +296,7 @@ const MintSheet = () => { }); } catch (e) { setGasError(true); - logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${(e as Error).message}`)); + logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${(e as Error).message}`)); } }; estimateMintGas(); @@ -344,7 +344,7 @@ const MintSheet = () => { return; } - logger.info('Minting NFT', { name: mintCollection.name }); + logger.debug('[MintSheet]: Minting NFT', { name: mintCollection.name }); analyticsV2.track(event.mintsMintingNFT, { collectionName: mintCollection.name || '', contract: mintCollection.id || '', @@ -381,7 +381,7 @@ const MintSheet = () => { onProgress: (steps: Execute['steps']) => { steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`Error minting NFT: ${step.error}`)); + logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${step.error}`)); setMintStatus('error'); return; } @@ -462,7 +462,7 @@ const MintSheet = () => { quantity, priceInEth: mintPriceAmount, }); - logger.error(new RainbowError(`Error minting NFT: ${(e as Error).message}`)); + logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${(e as Error).message}`)); } }, [ accountAddress, diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index 5bee2b44794..a1e710bfe53 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -290,7 +290,7 @@ const ClaimingRewards = ({ setClaimStatus('bridge-error'); } - logger.error(new RainbowError('ETH REWARDS CLAIM ERROR'), { message: errorMessage }); + logger.error(new RainbowError('[ClaimRewardsPanel]: Failed to claim ETH rewards'), { message: errorMessage }); return { nonce: null }; } diff --git a/src/screens/points/content/ReferralContent.tsx b/src/screens/points/content/ReferralContent.tsx index e17d4aa6c58..f9719f9fd16 100644 --- a/src/screens/points/content/ReferralContent.tsx +++ b/src/screens/points/content/ReferralContent.tsx @@ -87,7 +87,7 @@ export function ReferralContent() { setStatus('invalid'); haptics.notificationError(); } else { - logger.error(new RainbowError('Error validating referral code'), { + logger.error(new RainbowError('[ReferralContent]: Error validating referral code'), { referralCode: code, }); Alert.alert(i18n.t(i18n.l.points.referral.error)); diff --git a/src/screens/points/contexts/PointsProfileContext.tsx b/src/screens/points/contexts/PointsProfileContext.tsx index 193d206760e..93d8bd841d0 100644 --- a/src/screens/points/contexts/PointsProfileContext.tsx +++ b/src/screens/points/contexts/PointsProfileContext.tsx @@ -176,7 +176,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); break; } - logger.info('Points: Failed to onboard user', { errorType }); + logger.error(new RainbowError('[PointsProfileContext]: Failed to onboard user'), { errorType }); analyticsV2.track(analyticsV2.event.pointsOnboardingScreenFailedToSignIn, { deeplinked, referralCode: !!referralCode, @@ -201,7 +201,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode hardwareWallet: isHardwareWallet, errorType: undefined, }); - logger.error(new RainbowError('Points: signIn error'), { error }); + logger.error(new RainbowError('[PointsProfileContext]: signIn error'), { error }); } }, [accountAddress, deeplinked, goBack, isHardwareWallet, referralCode]); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 90834dde2e0..91b3efd0f49 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -66,7 +66,7 @@ function serializeUserAssetsState(state: Partial, version?: num version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to serialize state for user assets storage`), { error }); throw error; } } @@ -76,7 +76,7 @@ function deserializeUserAssetsState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to parse serialized state from user assets storage`), { error }); throw error; } @@ -88,7 +88,7 @@ function deserializeUserAssetsState(serializedState: string) { chainBalances = new Map(state.chainBalances); } } catch (error) { - logger.error(new RainbowError('Failed to convert chainBalances from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert chainBalances from user assets storage`), { error }); } let idsByChain = new Map(); @@ -97,7 +97,7 @@ function deserializeUserAssetsState(serializedState: string) { idsByChain = new Map(state.idsByChain); } } catch (error) { - logger.error(new RainbowError('Failed to convert idsByChain from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert idsByChain from user assets storage`), { error }); } let userAssetsData: Map = new Map(); @@ -106,7 +106,7 @@ function deserializeUserAssetsState(serializedState: string) { userAssetsData = new Map(state.userAssets); } } catch (error) { - logger.error(new RainbowError('Failed to convert userAssets from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert userAssets from user assets storage`), { error }); } return { diff --git a/src/state/browser/browserStore.ts b/src/state/browser/browserStore.ts index f6c7340c3da..041913d506e 100644 --- a/src/state/browser/browserStore.ts +++ b/src/state/browser/browserStore.ts @@ -19,7 +19,7 @@ const lazyPersist = debounce( const serializedValue = serializeBrowserState(value.state, value.version ?? BROWSER_STORAGE_VERSION); browserStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError('Failed to serialize persisted browser data'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to serialize persisted browser data`), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -36,7 +36,7 @@ function serializeBrowserState(state: BrowserState, version: number): string { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to serialize state for browser storage`), { error }); throw error; } } @@ -46,7 +46,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to parse serialized state from browser storage`), { error }); throw error; } @@ -56,7 +56,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { tabsData = new Map(state.tabsData); } catch (error) { - logger.error(new RainbowError('Failed to convert tabsData from browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to convert tabsData from browser storage`), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState if (tabData) { tabData.url = persistedUrl; } else { - logger.warn(`No tabData found for tabId ${tabId} during URL restoration`); + logger.warn(`[browserStore]: No tabData found for tabId ${tabId} during URL restoration`); } } } diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts index 3e504ced84e..01dbcd64012 100644 --- a/src/state/internal/createRainbowStore.ts +++ b/src/state/internal/createRainbowStore.ts @@ -92,7 +92,7 @@ const lazyPersist = ({ name, serializer, storageKey, value }: LazyPersistPara const serializedValue = serializer(value.state, value.version ?? 0); rainbowStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError('Failed to serialize persisted store data'), { error }); + logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize persisted store data`), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -109,7 +109,7 @@ function defaultSerializeState(state: StorageValue>['state'], vers try { return JSON.stringify({ state, version }); } catch (error) { - logger.error(new RainbowError('Failed to serialize Rainbow store data'), { error }); + logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize Rainbow store data`), { error }); throw error; } } @@ -123,7 +123,7 @@ function defaultDeserializeState(serializedState: string): StorageValue, version?: number) { const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys?.id); if (state.cards && validCards.length < state.cards.size) { - logger.error(new RainbowError('remoteCardsStore: filtered cards without sys.id during serialization'), { + logger.error(new RainbowError(`[remoteCardsStore]: filtered cards without sys.id during serialization`), { filteredCount: state.cards.size - validCards.length, }); } @@ -44,7 +44,7 @@ function serializeState(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to serialize state for remote cards storage`), { error }); throw error; } } @@ -54,7 +54,7 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to parse serialized state from remote cards storage`), { error }); throw error; } @@ -66,7 +66,7 @@ function deserializeState(serializedState: string) { cardsByIdData = new Set(state.cardsById.filter(id => typeof id === 'string' && id.length > 0)); } } catch (error) { - logger.error(new RainbowError('Failed to convert cardsById from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cardsById from remote cards storage`), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeState(serializedState: string) { const validCards = state.cards.filter(([, card]) => card && card.sys?.id); if (validCards.length < state.cards.length) { - logger.error(new RainbowError('Filtered out cards without sys.id during deserialization'), { + logger.error(new RainbowError(`[remoteCardsStore]: Filtered out cards without sys.id during deserialization`), { filteredCount: state.cards.length - validCards.length, }); } @@ -84,7 +84,7 @@ function deserializeState(serializedState: string) { cardsData = new Map(validCards); } } catch (error) { - logger.error(new RainbowError('Failed to convert cards from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cards from remote cards storage`), { error }); throw error; } diff --git a/src/state/remotePromoSheets/remotePromoSheets.ts b/src/state/remotePromoSheets/remotePromoSheets.ts index 9e364bbf91e..1edd98a3a27 100644 --- a/src/state/remotePromoSheets/remotePromoSheets.ts +++ b/src/state/remotePromoSheets/remotePromoSheets.ts @@ -55,7 +55,7 @@ function serializeState(state: Partial, version?: number version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to serialize state for remote promo sheets storage`), { error }); throw error; } } @@ -65,7 +65,9 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to parse serialized state from remote promo sheets storage`), { + error, + }); throw error; } @@ -77,7 +79,7 @@ function deserializeState(serializedState: string) { sheetsByIdData = new Set(state.sheetsById); } } catch (error) { - logger.error(new RainbowError('Failed to convert sheetsById from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheetsById from remote promo sheets storage`), { error }); throw error; } @@ -87,7 +89,7 @@ function deserializeState(serializedState: string) { sheetsData = new Map(state.sheets); } } catch (error) { - logger.error(new RainbowError('Failed to convert sheets from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheets from remote promo sheets storage`), { error }); throw error; } diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index ac5fb83d92f..8f3c4917fea 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -66,7 +66,7 @@ function serialize(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to serialize state for swaps storage`), { error }); throw error; } } @@ -76,7 +76,7 @@ function deserialize(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to parse serialized state from swaps storage`), { error }); throw error; } @@ -88,7 +88,7 @@ function deserialize(serializedState: string) { recentSwaps = new Map(state.recentSwaps); } } catch (error) { - logger.error(new RainbowError('Failed to convert recentSwaps from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to convert recentSwaps from swaps storage`), { error }); } let latestSwapAt: Map = new Map(); @@ -97,7 +97,7 @@ function deserialize(serializedState: string) { latestSwapAt = new Map(state.latestSwapAt); } } catch (error) { - logger.error(new RainbowError('Failed to convert latestSwapAt from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to convert latestSwapAt from swaps storage`), { error }); } return { diff --git a/src/storage/legacy.ts b/src/storage/legacy.ts index 2246c2bc2c4..64adfbec6e6 100644 --- a/src/storage/legacy.ts +++ b/src/storage/legacy.ts @@ -32,7 +32,7 @@ class LegacyStorage extends Storage => { ]; const res = await checkForMultiplePermissions(ANDROID_BT_PERMISSION); - logger.debug('[Bluetooth] Android Permission status: ', { res }); + logger.debug('[bluetoothPermissions]: Android Permission status: ', { res }); const deniedPermissions: AndroidPermission[] = []; @@ -72,13 +72,13 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { } if (deniedPermissions.length === 0) { - logger.debug('[Bluetooth] Android Permissions all granted'); + logger.debug('[bluetoothPermissions]: Android Permissions all granted'); return true; } // if we're only missing one, only request one else if (deniedPermissions.length === 1) { const askResult = await requestPermission(deniedPermissions[0]); - logger.debug('[Bluetooth] Android Permission single askResult: ', { + logger.debug('[bluetoothPermissions]: Android Permission single askResult: ', { askResult, }); if (askResult === RESULTS.GRANTED) { @@ -97,7 +97,7 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { // else request in a group } else if (deniedPermissions.length > 1) { const askResults = await requestMultiplePermissions(deniedPermissions); - logger.debug('[Bluetooth] Android Bluetooth Permission multiple askResult: ', { askResults }); + logger.debug('[bluetoothPermissions]: Android Bluetooth Permission multiple askResult: ', { askResults }); const deniedOrBlockedPermissions: AndroidPermission[] = []; // check if we are missing any permissions diff --git a/src/utils/branch.ts b/src/utils/branch.ts index 474eaca32a6..f7c50bb79fe 100644 --- a/src/utils/branch.ts +++ b/src/utils/branch.ts @@ -8,7 +8,7 @@ import * as ls from '@/storage'; import { logger, RainbowError } from '@/logger'; export const branchListener = async (handleOpenLinkingURL: (url: any) => void) => { - logger.debug(`Branch: setting up listener`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: setting up listener`, {}, logger.DebugContext.deeplinks); /* * This is run every time the app is opened, whether from a cold start of from the background. @@ -19,30 +19,30 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = case 'Trouble reaching the Branch servers, please try again shortly.': break; default: - logger.error(new RainbowError('Branch: error when handling event'), { + logger.error(new RainbowError(`[branchListener]: error when handling event`), { error, }); } } - logger.debug(`Branch: handling event`, { params, uri }, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event`, { params, uri }, logger.DebugContext.deeplinks); if (!params && uri) { - logger.debug(`Branch: no params but we have a URI`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: no params but we have a URI`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } else if (!params) { // We got absolutely nothing to work with. - logger.warn(`Branch: received no params or URI when handling event`, { + logger.warn(`[branchListener]: received no params or URI when handling event`, { params, uri, }); } else if (params['+non_branch_link']) { const nonBranchUrl = params['+non_branch_link']; - logger.debug(`Branch: handling non-Branch link`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling non-Branch link`, {}, logger.DebugContext.deeplinks); if (typeof nonBranchUrl === 'string' && nonBranchUrl?.startsWith('rainbow://open')) { - logger.debug(`Branch: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); /** * This happens when the user hits the Branch-hosted fallback page in @@ -64,7 +64,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = handleOpenLinkingURL(url); } } else { - logger.debug(`Branch: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); /** * This can happen when the user clicks on a deeplink and we pass its handling on to Branch. @@ -80,7 +80,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * * No link was opened, so we don't typically need to do anything. */ - logger.debug(`Branch: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); if (IS_TESTING === 'true' && !!uri) { handleOpenLinkingURL(uri); @@ -91,14 +91,14 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * should use `params.uri`. This happens about 8k times per week, so it's * very expected. */ - logger.debug(`Branch: using preferred URI value from params`, { + logger.debug(`[branchListener]: using preferred URI value from params`, { params, uri, }); handleOpenLinkingURL(params.uri); } else if (uri) { - logger.debug(`Branch: handling event default case`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event default case`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } @@ -112,7 +112,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = .getFirstReferringParams() .then(branchParams => branchParams) .catch(e => { - logger.error(new RainbowError('error calling branch.getFirstReferringParams()'), e); + logger.error(new RainbowError(`[branchListener]: error calling branch.getFirstReferringParams()`), e); return null; }); diff --git a/src/utils/contenthash.ts b/src/utils/contenthash.ts index 6ae3225ba73..948ad38ab13 100644 --- a/src/utils/contenthash.ts +++ b/src/utils/contenthash.ts @@ -12,7 +12,7 @@ export function encodeContenthash(text: string) { let encoded = ''; let error; if (text) { - let matched = matchProtocol(text); + const matched = matchProtocol(text); if (matched) { contentType = matched[1]; content = matched[2]; diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index f0f685191f4..46f3a0433db 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -522,7 +522,7 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const l1FeeInWei = await OVM_GasPriceOracle.getL1Fee(serializedTx); return l1FeeInWei; } catch (e: any) { - logger.error(new RainbowError('error calculating l1 fee'), { + logger.error(new RainbowError(`[ethereumUtils]: error calculating l1 fee`), { message: e.message, }); } diff --git a/src/utils/index.ts b/src/utils/index.ts index 0ebd4ada6b7..7e70e27cf5d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -19,7 +19,6 @@ export { default as haptics } from './haptics'; export { default as isETH } from './isETH'; export { default as isLowerCaseMatch } from './isLowerCaseMatch'; export { default as labelhash } from './labelhash'; -export { default as logger } from './logger'; export { default as magicMemo } from './magicMemo'; export { default as measureText } from './measureText'; export { default as neverRerender } from './neverRerender'; diff --git a/src/utils/ledger.ts b/src/utils/ledger.ts index af9bb6cd725..c7da0a7657f 100644 --- a/src/utils/ledger.ts +++ b/src/utils/ledger.ts @@ -25,14 +25,14 @@ export const ledgerErrorHandler = (error: Error) => { return LEDGER_ERROR_CODES.OFF_OR_LOCKED; } if (error.name.includes('Disconnected')) { - logger.error(new RainbowError('[Ledger] - Disconnected Error'), { + logger.error(new RainbowError(`[ledger]: Disconnected Error`), { name: error.name, message: error.message, }); return LEDGER_ERROR_CODES.DISCONNECTED; } - logger.error(new RainbowError('[LedgerConnect] - Unknown Error'), { + logger.error(new RainbowError(`[ledger]: Unknown Error`), { name: error.name, message: error.message, }); @@ -62,12 +62,12 @@ export const checkLedgerConnection = async ({ ethApp .getAddress(path) .then(res => { - logger.info('[checkLedgerConnection] - ledger is ready', {}); + logger.debug(`[ledger]: ledger is ready`, {}); successCallback?.(deviceId); }) .catch(e => { const errorType = ledgerErrorHandler(e); - logger.warn('[checkLedgerConnection] - ledger is not ready', { + logger.warn('[ledger] - ledger is not ready', { errorType: errorType, error: e, }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index c69ff91a70f..00000000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { captureException } from '@sentry/react-native'; -import { QUIET_OLD_LOGGER } from 'react-native-dotenv'; -import sentryUtils from './sentry'; - -/** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ -const Logger = { - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - debug(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}] ⚡⚡⚡ `); - console.log(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - error(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - console.error(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - log(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}]`); - console.log(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - prettyLog() { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const allArgs = Array.prototype.slice.call(arguments).map(arg => { - try { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } else { - return arg; - } - } catch (e) { - return arg; - } - }); - console.log(allArgs.length > 0 ? allArgs : allArgs[0]); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - sentry(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}]`); - console.log(...args); // eslint-disable-line no-console - } - if (args.length === 1 && typeof args[0] === 'string') { - sentryUtils.addInfoBreadcrumb.apply(null, [args[0]]); - } else { - const safeData = safelyStringifyWithFormat(args[1]); - sentryUtils.addDataBreadcrumb(args[0], safeData); - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - warn(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - console.warn(...args); // eslint-disable-line no-console - } - }, -}; - -const safelyStringifyWithFormat = (data: any) => { - try { - const seen: any = []; - const newData = JSON.stringify( - data, - // Required to ignore cyclic structures - (key, val) => { - if (val != null && typeof val == 'object') { - if (seen.indexOf(val) >= 0) { - return; - } - seen.push(val); - } - return val; - }, - 2 - ); - return { data: newData }; - } catch (e) { - captureException(e); - return {}; - } -}; - -export default Logger; diff --git a/src/utils/memoFn.ts b/src/utils/memoFn.ts index d279ae5feb6..da14394b00e 100644 --- a/src/utils/memoFn.ts +++ b/src/utils/memoFn.ts @@ -27,7 +27,7 @@ export function memoFn( // if no arguments used we just want the developer and run the function as is if (args.length === 0) { if (IS_DEV) { - logger.warn(`memoized function ${fn.name} was called with no arguments`); + logger.warn(`[memoFn]: memoized function ${fn.name} was called with no arguments`); } // Call it anyway to not break stuff @@ -41,7 +41,7 @@ export function memoFn( if (typeof arg !== 'number' && typeof arg !== 'boolean' && typeof arg !== 'string') { if (IS_DEV) { logger.warn( - `memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( + `[memoFn]: memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( args )}. Typeof of ${i + 1} argument is ${typeof arg}` ); @@ -57,8 +57,8 @@ export function memoFn( if (cache.has(key)) { // For debugging - // logger.debug(`Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); - // logger.debug('Total cached', cachedCalls++); + // logger.debug(`[memoFn]: Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); + // logger.debug('[memoFn]: Total cached', cachedCalls++); // return cached result return cache.get(key)!; // we did a check for that key already diff --git a/src/utils/poaps.ts b/src/utils/poaps.ts index 555a5a13da3..d0020a031ad 100644 --- a/src/utils/poaps.ts +++ b/src/utils/poaps.ts @@ -20,7 +20,7 @@ export const getPoapAndOpenSheetWithSecretWord = async (secretWord: string, goBa }); } } catch (e) { - logger.warn('Error getting POAP with secret word'); + logger.warn('[poaps]: Error getting POAP with secret word'); } }; @@ -39,6 +39,6 @@ export const getPoapAndOpenSheetWithQRHash = async (qrHash: string, goBack: bool }); } } catch { - logger.warn('Error getting POAP with qrHash'); + logger.warn('[poaps]: Error getting POAP with qrHash'); } }; diff --git a/src/utils/reviewAlert.ts b/src/utils/reviewAlert.ts index 44b3113b6f8..4deef6eb9c9 100644 --- a/src/utils/reviewAlert.ts +++ b/src/utils/reviewAlert.ts @@ -30,7 +30,7 @@ export const numberOfTimesBeforePrompt: { }; export const handleReviewPromptAction = async (action: ReviewPromptAction) => { - logger.debug(`handleReviewPromptAction: ${action}`); + logger.debug(`[reviewAlert]: handleReviewPromptAction: ${action}`); if (IS_TESTING === 'true') { return; @@ -53,10 +53,10 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } const timeOfLastPrompt = ls.review.get(['timeOfLastPrompt']) || 0; - logger.debug(`timeOfLastPrompt: ${timeOfLastPrompt}`); + logger.debug(`[reviewAlert]: timeOfLastPrompt: ${timeOfLastPrompt}`); actionToDispatch.numOfTimesDispatched += 1; - logger.debug(`numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); + logger.debug(`[reviewAlert]: numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); const hasReachedAmount = actionToDispatch.numOfTimesDispatched >= numberOfTimesBeforePrompt[action]; @@ -66,7 +66,7 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } if (hasReachedAmount && timeOfLastPrompt + TWO_MONTHS <= Date.now()) { - logger.debug(`Prompting for review`); + logger.debug(`[reviewAlert]: Prompting for review`); actionToDispatch.numOfTimesDispatched = 0; ls.review.set(['timeOfLastPrompt'], Date.now()); promptForReview(); diff --git a/src/utils/simplifyChartData.ts b/src/utils/simplifyChartData.ts index a6397322946..d5d5e151116 100644 --- a/src/utils/simplifyChartData.ts +++ b/src/utils/simplifyChartData.ts @@ -3,13 +3,13 @@ import { maxBy, minBy } from 'lodash'; export default function simplifyChartData(data: any, destinatedNumberOfPoints: number) { if (!data) return null; - let allSegmentDividers: any = []; + const allSegmentDividers: any = []; let allSegmentsPoints: any = []; - let colors = []; - let lines = []; - let dividers = []; - let lastPoints = []; - let createdLastPoints: any = []; + const colors = []; + const lines = []; + const dividers = []; + const lastPoints = []; + const createdLastPoints: any = []; if (data.segments.length > 0) { for (let i = 0; i < 1; i++) { @@ -25,13 +25,13 @@ export default function simplifyChartData(data: any, destinatedNumberOfPoints: n } } if (allSegmentsPoints.length > destinatedNumberOfPoints) { - let destMul = allSegmentsPoints.length / destinatedNumberOfPoints; + const destMul = allSegmentsPoints.length / destinatedNumberOfPoints; const maxValue = maxBy(allSegmentsPoints, 'y'); const minValue = minBy(allSegmentsPoints, 'y'); const dataDiff = allSegmentsPoints[allSegmentsPoints.length - 1].x - allSegmentsPoints[0].x; const xMul = Math.floor(dataDiff / allSegmentsPoints.length); - let newData = []; + const newData = []; newData.push({ isImportant: true, x: allSegmentsPoints[0].x - xMul * 2, diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index b653aa9406e..66bd9039751 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -69,7 +69,7 @@ let hasDeeplinkPendingRedirect = false; * listeners. BE CAREFUL WITH THIS. */ export function setHasPendingDeeplinkPendingRedirect(value: boolean) { - logger.info(`setHasPendingDeeplinkPendingRedirect`, { value }); + logger.debug(`[walletConnect]: setHasPendingDeeplinkPendingRedirect`, { value }); hasDeeplinkPendingRedirect = value; } @@ -149,7 +149,11 @@ export function parseRPCParams({ method, params }: RPCPayload): { decodedMessage = toUtf8String(message); } } catch (err) { - logger.debug('WC v2: parsing RPC params unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); + logger.debug( + `[walletConnect]: parsing RPC params unable to decode hex message to UTF8 string`, + {}, + logger.DebugContext.walletconnect + ); } return { @@ -216,7 +220,7 @@ export function getApprovedNamespaces(props: Parameters[0]; }) { - logger.warn(`WC v2: session approval denied`, { + logger.warn(`[walletConnect]: session approval denied`, { reason, proposal, }); @@ -317,7 +321,7 @@ async function rejectProposal({ } export async function pair({ uri, connector }: { uri: string; connector?: string }) { - logger.debug(`WC v2: pair`, { uri }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: pair`, { uri }, logger.DebugContext.walletconnect); /** * Make sure this is cleared if we get multiple pairings in rapid succession @@ -326,11 +330,11 @@ export async function pair({ uri, connector }: { uri: string; connector?: string const { topic, ...rest } = parseUri(uri); const client = await getWeb3WalletClient(); - logger.debug(`WC v2: pair: parsed uri`, { topic, rest }); + logger.debug(`[walletConnect]: pair: parsed uri`, { topic, rest }); // listen for THIS topic pairing, and clear timeout if received function handler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { - logger.debug(`WC v2: pair: handler`, { proposal }); + logger.debug(`[walletConnect]: pair: handler`, { proposal }); const { metadata } = (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; @@ -357,13 +361,13 @@ export async function initListeners() { syncWeb3WalletClient = client; - logger.debug(`WC v2: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); client.on('session_proposal', onSessionProposal); client.on('session_request', onSessionRequest); client.on('auth_request', onAuthRequest); client.on('session_delete', () => { - logger.debug(`WC v2: session_delete`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_delete`, {}, logger.DebugContext.walletconnect); setTimeout(() => { events.emit('walletConnectV2SessionDeleted'); @@ -393,11 +397,11 @@ export async function initListeners() { * which could be due to network flakiness, SSL server error (has * happened), etc. Things out of our control. */ - logger.warn(`WC v2: FCM token not found, push notifications will not be received`); + logger.warn(`[walletConnect]: FCM token not found, push notifications will not be received`); } } } catch (e) { - logger.error(new RainbowError(`WC v2: initListeners failed`), { error: e }); + logger.error(new RainbowError(`[walletConnect]: initListeners failed`), { error: e }); } } @@ -417,7 +421,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; * should report these to Datadog, and we can leave this as a warn to * continue to monitor. */ - logger.warn(`WC v2: echo server subscription failed`, { + logger.warn(`[walletConnect]: echo server subscription failed`, { error: res.error, }); } @@ -425,7 +429,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposal) { try { - logger.debug(`WC v2: session_proposal`, { proposal }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_proposal`, { proposal }, logger.DebugContext.walletconnect); const verifiedData = proposal.verifyContext.verified; const receivedTimestamp = Date.now(); @@ -465,7 +469,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa if (approved) { logger.debug( - `WC v2: session approved`, + `[walletConnect]: session approved`, { approved, approvedChainId, @@ -491,7 +495,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa }, }); - logger.debug(`WC v2: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); try { if (namespaces.success) { @@ -511,7 +515,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa // let the ConnectedDappsSheet know we've got a new one events.emit('walletConnectV2SessionCreated'); - logger.debug(`WC v2: session created`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session created`, {}, logger.DebugContext.walletconnect); analytics.track(analytics.event.wcNewSessionApproved, { dappName: proposer.metadata.name, @@ -553,7 +557,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa title: lang.t(lang.l.walletconnect.connection_failed), }); - logger.error(new RainbowError(`WC v2: session approval failed`), { + logger.error(new RainbowError(`[walletConnect]: session approval failed`), { error: (e as Error).message, }); } @@ -574,7 +578,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa ); } catch (error) { logger.error( - new RainbowError(`WC v2: session request catch all`, { + new RainbowError(`[walletConnect]: session request catch all`, { ...(error as Error), }) ); @@ -586,12 +590,12 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se setHasPendingDeeplinkPendingRedirect(true); const client = await getWeb3WalletClient(); - logger.debug(`WC v2: session_request`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_request`, {}, logger.DebugContext.walletconnect); const { id, topic } = event; const { method, params } = event.params.request; - logger.debug(`WC v2: session_request method`, { method, params }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_request method`, { method, params }, logger.DebugContext.walletconnect); // we allow eth sign for connections but we dont want to support actual singing if (method === RPCMethod.Sign) { @@ -614,19 +618,23 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se params, }); if (!address) { - logger.error(new RainbowError('No Address in the RPC Params')); + logger.error(new RainbowError('[walletConnect]: No Address in the RPC Params')); return; } const allWallets = store.getState().wallets.wallets; - logger.debug(`WC v2: session_request method is supported`, { method, params, address, message }, logger.DebugContext.walletconnect); + logger.debug( + `[walletConnect]: session_request method is supported`, + { method, params, address, message }, + logger.DebugContext.walletconnect + ); if (isSigningMethod) { - logger.debug(`WC v2: validating session_request signing method`); + logger.debug(`[walletConnect]: validating session_request signing method`); if (!address || !message) { - logger.error(new RainbowError(`WC v2: session_request exited, signing request had no address and/or messsage`), { + logger.error(new RainbowError(`[walletConnect]: session_request exited, signing request had no address and/or messsage`), { address, message, }); @@ -647,7 +655,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // for TS only, should never happen if (!allWallets) { - logger.error(new RainbowError(`WC v2: allWallets is null, this should never happen`)); + logger.error(new RainbowError(`[walletConnect]: allWallets is null, this should never happen`)); return; } @@ -655,7 +663,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const isReadOnly = selectedWallet?.type === WalletTypes.readOnly; if (!selectedWallet || isReadOnly) { - logger.error(new RainbowError(`WC v2: session_request exited, selectedWallet was falsy or read only`), { + logger.error(new RainbowError(`[walletConnect]: session_request exited, selectedWallet was falsy or read only`), { selectedWalletType: selectedWallet?.type, }); @@ -682,7 +690,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // mostly a TS guard, pry won't happen if (!session) { - logger.error(new RainbowError(`WC v2: session_request topic was not found`)); + logger.error(new RainbowError(`[walletConnect]: session_request topic was not found`)); await client.respondSessionRequest({ topic, @@ -695,9 +703,9 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const { nativeCurrency, network } = store.getState().settings; const chainId = Number(event.params.chainId.split(':')[1]); - logger.debug(`WC v2: getting session for topic`, { session }); + logger.debug(`[walletConnect]: getting session for topic`, { session }); - logger.debug(`WC v2: handling request`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handling request`, {}, logger.DebugContext.walletconnect); const dappNetwork = ethereumUtils.getNetworkFromChainId(chainId); const displayDetails = getRequestDisplayDetails(event.params.request, nativeCurrency, dappNetwork); @@ -749,7 +757,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); saveLocalRequests(updatedRequests, address, network); - logger.debug(`WC v2: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); handleWalletConnectRequest(request); @@ -759,7 +767,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); } } else { - logger.error(new RainbowError(`WC v2: received unsupported session_request RPC method`), { + logger.error(new RainbowError(`[walletConnect]: received unsupported session_request RPC method`), { method, }); @@ -769,7 +777,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se response: formatJsonRpcError(id, `Method ${method} not supported`), }); } catch (e) { - logger.error(new RainbowError(`WC v2: error rejecting session_request`), { + logger.error(new RainbowError(`[walletConnect]: error rejecting session_request`), { error: (e as Error).message, }); } @@ -794,7 +802,7 @@ export async function handleSessionRequestResponse( }, { result, error }: { result: string | null; error: any } ) { - logger.info(`WC v2: handleSessionRequestResponse`, { + logger.debug(`[walletConnect]: handleSessionRequestResponse`, { success: Boolean(result), }); @@ -805,14 +813,14 @@ export async function handleSessionRequestResponse( topic, response: formatJsonRpcResult(id, result), }; - logger.debug(`WC v2: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } else { const payload = { topic, response: formatJsonRpcError(id, error), }; - logger.debug(`WC v2: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } @@ -822,7 +830,7 @@ export async function handleSessionRequestResponse( export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { const client = await getWeb3WalletClient(); - logger.debug(`WC v2: auth_request`, { event }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: auth_request`, { event }, logger.DebugContext.walletconnect); const authenticate: AuthRequestAuthenticateSignature = async ({ address }) => { try { @@ -859,7 +867,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, provider }); if (!wallet) { - logger.error(new RainbowError(`WC v2: could not loadWallet to sign auth_request`)); + logger.error(new RainbowError(`[walletConnect]: could not loadWallet to sign auth_request`)); return undefined; } @@ -908,7 +916,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { return { success: true }; } catch (e: any) { - logger.error(new RainbowError(`WC v2: an unknown error occurred when signing auth_request`), { + logger.error(new RainbowError(`[walletConnect]: an unknown error occurred when signing auth_request`), { message: e.message, }); return { success: false, reason: AuthRequestResponseErrorReason.Unknown }; @@ -993,13 +1001,13 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres } } } else { - logger.error(new RainbowError(`WC v2: namespace is missing chains prop when updating`), { + logger.error(new RainbowError(`[walletConnect]: namespace is missing chains prop when updating`), { requiredNamespaces: session.requiredNamespaces, }); } } - logger.debug(`WC v2: updating session`, { + logger.debug(`[walletConnect]: updating session`, { namespaces, }); @@ -1008,7 +1016,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres namespaces, }); } catch (e: any) { - logger.error(new RainbowError(`WC v2: error adding account to session`), { + logger.error(new RainbowError(`[walletConnect]: error adding account to session`), { message: e.message, }); } @@ -1026,12 +1034,12 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { for (const value of Object.values(session.requiredNamespaces)) { if (!value.chains) { - logger.debug(`WC v2: changeAccount, no chains found for namespace`); + logger.debug(`[walletConnect]: changeAccount, no chains found for namespace`); continue; } for (const chainId of value.chains) { - logger.debug(`WC v2: changeAccount, updating accounts for chainId`, { + logger.debug(`[walletConnect]: changeAccount, updating accounts for chainId`, { chainId, }); @@ -1045,15 +1053,15 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { chainId, }); - logger.debug(`WC v2: changeAccount, updated accounts for chainId`, { + logger.debug(`[walletConnect]: changeAccount, updated accounts for chainId`, { chainId, }); } } - logger.debug(`WC v2: changeAccount complete`); + logger.debug(`[walletConnect]: changeAccount complete`); } catch (e: any) { - logger.error(new RainbowError(`WC v2: error changing account`), { + logger.error(new RainbowError(`[walletConnect]: error changing account`), { message: e.message, }); } From dd030ca15cc86207c1f5f78d611d20ed07891f40 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 23 Aug 2024 09:15:19 -0400 Subject: [PATCH 53/78] Add swaps settings trigger (#6036) * finish swap settings trigger * Update src/navigation/types.ts * fix runtime error --- src/__swaps__/screens/Swap/Swap.tsx | 2 ++ .../NavigateToSwapSettingsTrigger.tsx | 29 +++++++++++++++++++ src/navigation/types.ts | 3 ++ 3 files changed, 34 insertions(+) create mode 100644 src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 6634ee6b99b..7f005ef3095 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -28,6 +28,7 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { SwapWarning } from './components/SwapWarning'; import { clearCustomGasSettings } from './hooks/useCustomGas'; import { SwapProvider, useSwapContext } from './providers/swap-provider'; +import { NavigateToSwapSettingsTrigger } from './components/NavigateToSwapSettingsTrigger'; /** README * This prototype is largely driven by Reanimated and Gesture Handler, which @@ -85,6 +86,7 @@ export function SwapScreen() { + ); } diff --git a/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx b/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx new file mode 100644 index 00000000000..912f20c8a3e --- /dev/null +++ b/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx @@ -0,0 +1,29 @@ +import { useNavigation } from '@/navigation'; +import { RootStackParamList } from '@/navigation/types'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; +import { useSwapContext } from '../providers/swap-provider'; + +export const NavigateToSwapSettingsTrigger = () => { + const route = useRoute>(); + const { setParams } = useNavigation(); + const { SwapNavigation } = useSwapContext(); + + useAnimatedReaction( + () => route.params, + (current, previous) => { + if (!current || current === previous) return; + + if (current.action === 'open_swap_settings') { + SwapNavigation.handleShowSettings(); + runOnJS(setParams)({ + ...route.params, + action: undefined, + }); + } + }, + [route.params?.action, setParams] + ); + + return null; +}; diff --git a/src/navigation/types.ts b/src/navigation/types.ts index 8a35cda2633..d84c2139148 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -72,4 +72,7 @@ export type RootStackParamList = { onSuccess: () => Promise; onFailure: () => Promise; }; + [Routes.SWAP]: { + action?: 'open_swap_settings'; + }; }; From 4cdc78321fe2059adef26334915dff0543e2101d Mon Sep 17 00:00:00 2001 From: brdy <41711440+BrodyHughes@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:46:56 -0500 Subject: [PATCH 54/78] Revert "Revert "Brody/swap v2 e2e (#5915)" (#5987)" (#5988) * Revert "Revert "Brody/swap v2 e2e (#5915)" (#5987)" This reverts commit fdcdd09dcf416da82e50be95035fa91a960d7a41. * fix test * Revert "fix test" This reverts commit a7f5b1ccdfe7f5b334bf9c014873607d5b26e2f4. * fix * fix test * typecast to fix lint * matthew :D * test this * Revert "test this" This reverts commit ab6b11dcb3844d1b2439902596b2da9c6143a19c. * fix lint * fix user assets not working on swaps e2e * . * oop * lint * lel our @/references folder is fked * fix some things * fix user assets in testnet mode * fix up e2e * readd memo * . * revert change to swipeUntilVisible * re add very long delay * remove react memo --------- Co-authored-by: Matthew Wall --- e2e/3_homeScreen.spec.ts | 14 +- e2e/9_swaps.spec.ts | 144 ++++++++++++++++++ e2e/helpers.ts | 34 ++++- package.json | 7 +- .../screens/Swap/components/CoinRow.tsx | 6 +- .../Swap/components/SwapActionButton.tsx | 7 +- .../Swap/components/SwapBackground.tsx | 11 +- .../Swap/components/SwapBottomPanel.tsx | 1 + .../Swap/components/SwapInputAsset.tsx | 12 +- .../Swap/components/SwapOutputAsset.tsx | 1 + .../components/TokenList/TokenToBuyList.tsx | 7 +- src/__swaps__/screens/Swap/constants.ts | 32 ++-- .../screens/Swap/hooks/useAssetsToSell.ts | 6 +- .../Swap/hooks/useSwapInputsController.ts | 1 - .../Swap/resources/assets/userAssets.ts | 31 +++- src/__swaps__/types/chains.ts | 4 + src/components/activity-list/ActivityList.js | 1 - src/components/animations/animationConfigs.ts | 44 +++--- .../FastComponents/FastBalanceCoinRow.tsx | 2 +- src/config/experimental.ts | 2 +- src/design-system/components/Box/Box.tsx | 15 +- src/handlers/swap.ts | 4 +- src/navigation/SwipeNavigator.tsx | 8 +- src/references/chain-assets.json | 19 ++- src/references/index.ts | 29 ++-- src/references/testnet-assets-by-chain.ts | 70 +++++++++ src/resources/assets/hardhatAssets.ts | 79 +++++++++- src/state/assets/userAssets.ts | 10 +- src/state/sync/UserAssetsSync.tsx | 6 +- 29 files changed, 501 insertions(+), 106 deletions(-) create mode 100644 e2e/9_swaps.spec.ts create mode 100644 src/references/testnet-assets-by-chain.ts diff --git a/e2e/3_homeScreen.spec.ts b/e2e/3_homeScreen.spec.ts index c090519ded0..94fea2546f0 100644 --- a/e2e/3_homeScreen.spec.ts +++ b/e2e/3_homeScreen.spec.ts @@ -5,8 +5,9 @@ import { checkIfExists, checkIfExistsByText, swipe, - waitAndTap, afterAllcleanApp, + tap, + delayTime, } from './helpers'; const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth'; @@ -41,19 +42,20 @@ describe('Home Screen', () => { }); it('tapping "Swap" opens the swap screen', async () => { - await waitAndTap('swap-button'); + await tap('swap-button'); + await delayTime('long'); await checkIfExists('swap-screen'); - await swipe('swap-screen', 'down', 'slow'); + await swipe('swap-screen', 'down', 'fast'); }); it('tapping "Send" opens the send screen', async () => { - await waitAndTap('send-button'); + await tap('send-button'); await checkIfVisible('send-asset-form-field'); - await swipe('send-asset-form-field', 'down'); + await swipe('send-asset-form-field', 'down', 'fast'); }); it('tapping "Copy" shows copy address toast', async () => { - await waitAndTap('receive-button'); + await tap('receive-button'); await checkIfVisible('address-copied-toast'); }); }); diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts new file mode 100644 index 00000000000..f899fbdc68f --- /dev/null +++ b/e2e/9_swaps.spec.ts @@ -0,0 +1,144 @@ +/* + * // Other tests to consider: + * - Flip assets + * - exchange button onPress + * - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged + * - swap execution + * - token search (both from userAssets and output token list) + * - custom gas panel + * - flashbots + * - slippage + * - explainer sheets + * - switching wallets inside of swap screen + */ + +import { + importWalletFlow, + sendETHtoTestWallet, + checkIfVisible, + beforeAllcleanApp, + afterAllcleanApp, + fetchElementAttributes, + tap, + tapByText, + delayTime, + swipeUntilVisible, + tapAndLongPressByText, + tapAndLongPress, + swipe, +} from './helpers'; + +import { expect } from '@jest/globals'; +import { WALLET_VARS } from './testVariables'; + +describe('Swap Sheet Interaction Flow', () => { + beforeAll(async () => { + await beforeAllcleanApp({ hardhat: true }); + }); + afterAll(async () => { + await afterAllcleanApp({ hardhat: true }); + }); + + it('Import a wallet and go to welcome', async () => { + await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK); + }); + + it('Should send ETH to test wallet', async () => { + // send 20 eth + await sendETHtoTestWallet(); + }); + + it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => { + await tap('dev-button-hardhat'); + await checkIfVisible('testnet-toast-Hardhat'); + + // doesn't work atm + // validate it has the expected funds of 20 eth + // const attributes = await fetchElementAttributes('fast-coin-info'); + // expect(attributes.label).toContain('Ethereum'); + // expect(attributes.label).toContain('20'); + }); + + it('Should open swap screen with 50% inputAmount for inputAsset', async () => { + await device.disableSynchronization(); + await tap('swap-button'); + await delayTime('long'); + + await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100); + await swipe('token-to-buy-list', 'up', 'slow', 0.1); + + await tap('token-to-buy-dai-1'); + await delayTime('medium'); + const swapInput = await fetchElementAttributes('swap-asset-input'); + + expect(swapInput.label).toContain('ETH'); + expect(swapInput.label).toContain('10'); + }); + + it('Should be able to go to review and execute a swap', async () => { + await tap('swap-bottom-action-button'); + const inputAssetActionButton = await fetchElementAttributes('swap-input-asset-action-button'); + const outputAssetActionButton = await fetchElementAttributes('swap-output-asset-action-button'); + const holdToSwapButton = await fetchElementAttributes('swap-bottom-action-button'); + + expect(inputAssetActionButton.label).toBe('ETH 􀆏'); + expect(outputAssetActionButton.label).toBe('DAI 􀆏'); + expect(holdToSwapButton.label).toBe('􀎽 Hold to Swap'); + + await tapAndLongPress('swap-bottom-action-button', 1500); + + // TODO: This doesn't work so need to figure this out eventually... + // await checkIfVisible('profile-screen'); + }); + + it.skip('Should be able to verify swap is happening', async () => { + // await delayTime('very-long'); + // const activityListElements = await fetchElementAttributes('wallet-activity-list'); + // expect(activityListElements.label).toContain('ETH'); + // expect(activityListElements.label).toContain('DAI'); + // await tapByText('Swapping'); + // await delayTime('long'); + // const transactionSheet = await checkIfVisible('transaction-details-sheet'); + // expect(transactionSheet).toBeTruthy(); + }); + + it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should open swap screen from asset chart with that asset selected', async () => { + /** + * tap any user asset (store const uniqueId here) + * wait for Swap header to be visible + * expect inputAsset.uniqueId === const uniqueId ^^ + */ + }); + + it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should not be able to type in output amount if cross-chain quote', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * select different chain in output list chain selector + * select any asset in output token list + * focus output amount + * attempt to type any number in the SwapNumberPad + * attempt to remove a character as well + * + * ^^ expect both of those to not change the outputAmount + */ + }); +}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 55344f6052c..dd82d8f2754 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -4,8 +4,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { expect, device, element, by, waitFor } from 'detox'; import { parseEther } from '@ethersproject/units'; +import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox'; -const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608'; +const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F'; const DEFAULT_TIMEOUT = 20_000; const android = device.getPlatform() === 'android'; @@ -70,6 +71,16 @@ export async function tap(elementId: string | RegExp) { } } +interface CustomElementAttributes { + elements: Array; +} + +type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes; + +export const fetchElementAttributes = async (testId: string): Promise => { + return (await element(by.id(testId)).getAttributes()) as ElementAttributes; +}; + export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) { await delayTime('medium'); try { @@ -188,17 +199,17 @@ export async function clearField(elementId: string | RegExp) { } } -export async function tapAndLongPress(elementId: string | RegExp) { +export async function tapAndLongPress(elementId: string | RegExp, duration?: number) { try { - return await element(by.id(elementId)).longPress(); + return await element(by.id(elementId)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by id "${elementId}": ${error}`); } } -export async function tapAndLongPressByText(text: string | RegExp) { +export async function tapAndLongPressByText(text: string | RegExp, duration?: number) { try { - return await element(by.text(text)).longPress(); + return await element(by.text(text)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by text "${text}": ${error}`); } @@ -263,15 +274,22 @@ export async function scrollTo(scrollviewId: string | RegExp, edge: Direction) { } } -export async function swipeUntilVisible(elementId: string | RegExp, scrollViewId: string, direction: Direction, pctVisible = 75) { +export async function swipeUntilVisible( + elementId: string | RegExp, + scrollViewId: string, + direction: Direction, + percentageVisible?: number +) { let stop = false; + while (!stop) { try { await waitFor(element(by.id(elementId))) - .toBeVisible(pctVisible) + .toBeVisible(percentageVisible) .withTimeout(500); + stop = true; - } catch { + } catch (e) { await swipe(scrollViewId, direction, 'slow', 0.2); } } diff --git a/package.json b/package.json index 3dddbc815ca..698efeb2060 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,10 @@ "nuke": "./scripts/nuke.sh", "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", - "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1", - "detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests", - "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1", + "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", + "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", + "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", + "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1", "ds:install": "cd src/design-system/docs && yarn install", "ds": "cd src/design-system/docs && yarn dev", "fast": "yarn install && yarn setup && yarn install-pods-fast", diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 02e439c90db..0728370c7aa 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -52,6 +52,7 @@ interface InputCoinRowProps { onPress: (asset: ParsedSearchAsset | null) => void; output?: false | undefined; uniqueId: string; + testID?: string; } type PartialAsset = Pick; @@ -62,11 +63,12 @@ interface OutputCoinRowProps extends PartialAsset { output: true; nativePriceChange?: string; isTrending?: boolean; + testID?: string; } type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { +export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -116,7 +118,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps } if (!address || !chainId) return null; return ( - + diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index e407de019ab..a6a8d913966 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -34,6 +34,7 @@ function SwapButton({ disabled, opacity, children, + testID, }: { asset: DerivedValue; borderRadius?: number; @@ -47,6 +48,7 @@ function SwapButton({ disabled?: DerivedValue; opacity?: DerivedValue; children?: React.ReactNode; + testID?: string; }) { const { isDarkMode } = useColorMode(); const fallbackColor = useForegroundColor('label'); @@ -110,6 +112,7 @@ function SwapButton({ return ( ; @@ -248,6 +252,7 @@ export const SwapActionButton = ({ style?: ViewStyle; disabled?: DerivedValue; opacity?: DerivedValue; + testID?: string; }) => { const disabledWrapper = useAnimatedStyle(() => { return { @@ -268,7 +273,7 @@ export const SwapActionButton = ({ style={[hugContent && feedActionButtonStyles.buttonWrapper, style]} > {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {holdProgress && } diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 8fc56cf4f0e..594b33f61bc 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -1,11 +1,11 @@ import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia'; import React from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { useDerivedValue, withTiming } from 'react-native-reanimated'; import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useColorMode } from '@/design-system'; -import { IS_ANDROID } from '@/env'; +import { IS_ANDROID, IS_TEST } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swaps__/utils/swaps'; import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -18,12 +18,15 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig ); }); + const animatedBottomColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig @@ -34,6 +37,10 @@ export const SwapBackground = () => { return [animatedTopColor.value, animatedBottomColor.value]; }); + if (IS_TEST) { + return ; + } + return ( diff --git a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx index d41f5d28733..87298a06eb8 100644 --- a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx @@ -98,6 +98,7 @@ export function SwapBottomPanel() { } style={styles.inputTextMask}> - + {SwapInputController.formattedInputAmount} @@ -123,7 +131,7 @@ export function SwapInputAsset() { return ( - + diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index ac2dc4a4aba..74ffb1cc923 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -39,6 +39,7 @@ function SwapOutputActionButton() { return ( { if (isLoading) return null; + const getFormattedTestId = (name: string, chainId: ChainId) => { + return `token-to-buy-${name}-${chainId}`.toLowerCase().replace(/\s+/g, '-'); + }; + return ( - + } @@ -160,6 +164,7 @@ export const TokenToBuyList = () => { } return ( (config: T): T => { + if (!IS_TEST) return config; + return { + ...config, + duration: 0, + } as T; +}; + +export const buttonPressConfig = disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }); +export const caretConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }); +export const fadeConfig = disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const pulsingConfig = disableForTestingEnvironment({ duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }); +export const sliderConfig = disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }); +export const slowFadeConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const snappySpringConfig = disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }); +export const snappierSpringConfig = disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }); +export const springConfig = disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }); // // /---- END animation configs ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts index 13f6d54e02d..a2d181c9a08 100644 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts @@ -10,6 +10,7 @@ import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; import { useAccountSettings, useDebounce } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { getIsHardhatConnected } from '@/handlers/web3'; const sortBy = (by: UserAssetFilter) => { switch (by) { @@ -21,7 +22,9 @@ const sortBy = (by: UserAssetFilter) => { }; export const useAssetsToSell = () => { - const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); + const { accountAddress: currentAddress, nativeCurrency: currentCurrency, network: currentNetwork } = useAccountSettings(); + + const connectedToHardhat = getIsHardhatConnected(); const filter = userAssetsStore(state => state.filter); const searchQuery = userAssetsStore(state => state.inputSearchQuery); @@ -32,6 +35,7 @@ export const useAssetsToSell = () => { { address: currentAddress as Address, currency: currentCurrency, + testnetMode: connectedToHardhat, }, { select: data => diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 2d1944a544b..6aa89017e0a 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -872,7 +872,6 @@ export function useSwapInputsController({ } } ); - return { debouncedFetchQuote, formattedInputAmount, diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index 1408eec1fb1..d6d689682e2 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -15,6 +15,7 @@ import { parseUserAsset } from '@/__swaps__/utils/assets'; import { greaterThan } from '@/__swaps__/utils/numbers'; import { fetchUserAssetsByChain } from './userAssetsByChain'; +import { fetchHardhatBalances, fetchHardhatBalancesByChainId } from '@/resources/assets/hardhatAssets'; const addysHttp = new RainbowFetchClient({ baseURL: 'https://addys.p.rainbow.me/v3', @@ -81,10 +82,32 @@ export const userAssetsSetQueryData = ({ address, currency, userAssets, testnetM queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), userAssets); }; -async function userAssetsQueryFunction({ queryKey: [{ address, currency, testnetMode }] }: QueryFunctionArgs) { +async function userAssetsQueryFunction({ + queryKey: [{ address, currency, testnetMode }], +}: QueryFunctionArgs): Promise { if (!address) { return {}; } + if (testnetMode) { + const { assets, chainIdsInResponse } = await fetchHardhatBalancesByChainId(address); + const parsedAssets: Array<{ + asset: ZerionAsset; + quantity: string; + small_balances: boolean; + }> = Object.values(assets).map(asset => ({ + asset: asset.asset, + quantity: asset.quantity, + small_balances: false, + })); + + const parsedAssetsDict = await parseUserAssets({ + assets: parsedAssets, + chainIds: chainIdsInResponse, + currency, + }); + + return parsedAssetsDict; + } const cache = queryClient.getQueryCache(); const cachedUserAssets = (cache.find(userAssetsQueryKey({ address, currency, testnetMode }))?.state?.data || {}) as ParsedAssetsDictByChain; @@ -208,12 +231,10 @@ export async function parseUserAssets({ // Query Hook export function useUserAssets( - { address, currency }: UserAssetsArgs, + { address, currency, testnetMode }: UserAssetsArgs, config: QueryConfigWithSelect = {} ) { - const isHardhatConnected = getIsHardhatConnected(); - - return useQuery(userAssetsQueryKey({ address, currency, testnetMode: isHardhatConnected }), userAssetsQueryFunction, { + return useQuery(userAssetsQueryKey({ address, currency, testnetMode }), userAssetsQueryFunction, { ...config, refetchInterval: USER_ASSETS_REFETCH_INTERVAL, staleTime: process.env.IS_TESTING === 'true' ? 0 : 1000, diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts index 98105ad5628..ff59ca43b05 100644 --- a/src/__swaps__/types/chains.ts +++ b/src/__swaps__/types/chains.ts @@ -47,6 +47,7 @@ export enum ChainName { celo = 'celo', degen = 'degen', gnosis = 'gnosis', + goerli = 'goerli', linea = 'linea', manta = 'manta', optimism = 'optimism', @@ -117,6 +118,7 @@ export const chainNameToIdMapping: { [ChainName.celo]: ChainId.celo, [ChainName.degen]: ChainId.degen, [ChainName.gnosis]: ChainId.gnosis, + [ChainName.goerli]: chain.goerli.id, [ChainName.linea]: ChainId.linea, [ChainName.manta]: ChainId.manta, [ChainName.optimism]: ChainId.optimism, @@ -156,6 +158,7 @@ export const chainIdToNameMapping: { [ChainId.celo]: ChainName.celo, [ChainId.degen]: ChainName.degen, [ChainId.gnosis]: ChainName.gnosis, + [chain.goerli.id]: ChainName.goerli, [ChainId.linea]: ChainName.linea, [ChainId.manta]: ChainName.manta, [ChainId.optimism]: ChainName.optimism, @@ -197,6 +200,7 @@ export const ChainNameDisplay = { [ChainId.scroll]: chain.scroll.name, [ChainId.zora]: 'Zora', [ChainId.mainnet]: 'Ethereum', + [chain.goerli.id]: 'Goerli', [ChainId.hardhat]: 'Hardhat', [ChainId.hardhatOptimism]: chainHardhatOptimism.name, [ChainId.sepolia]: chain.sepolia.name, diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index d829d4fa8dc..b57e625fe12 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -106,7 +106,6 @@ const ActivityList = ({ if (!ref) return; setScrollToTopRef(ref); }; - if (network === networkTypes.mainnet) { return ( >(configs: T): T { return configs; } + function createTimingConfigs>(configs: T): T { return configs; } +type AnyConfig = WithSpringConfig | WithTimingConfig; + +export const disableForTestingEnvironment = (config: T): T => { + if (!IS_TEST) return config; + return { + ...config, + duration: 0, + } as T; +}; + // /---- 🍎 Spring Animations 🍎 ----/ // -// const springAnimations = createSpringConfigs({ - browserTabTransition: { dampingRatio: 0.82, duration: 800 }, - keyboardConfig: { damping: 500, mass: 3, stiffness: 1000 }, - sliderConfig: { damping: 40, mass: 1.25, stiffness: 450 }, - slowSpring: { damping: 500, mass: 3, stiffness: 800 }, - snappierSpringConfig: { damping: 42, mass: 0.8, stiffness: 800 }, - snappySpringConfig: { damping: 100, mass: 0.8, stiffness: 275 }, - springConfig: { damping: 100, mass: 1.2, stiffness: 750 }, + browserTabTransition: disableForTestingEnvironment({ dampingRatio: 0.82, duration: 800 }), + keyboardConfig: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 1000 }), + sliderConfig: disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }), + slowSpring: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 800 }), + snappierSpringConfig: disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }), + snappySpringConfig: disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }), + springConfig: disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }), }); export const SPRING_CONFIGS: Record = springAnimations; -// // /---- END ----/ // // /---- ⏱️ Timing Animations ⏱️ ----/ // -// const timingAnimations = createTimingConfigs({ - buttonPressConfig: { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }, - fadeConfig: { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - fastFadeConfig: { duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowFadeConfig: { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowerFadeConfig: { duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowestFadeConfig: { duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - tabPressConfig: { duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + buttonPressConfig: disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), + fadeConfig: disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + fastFadeConfig: disableForTestingEnvironment({ duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowFadeConfig: disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowerFadeConfig: disableForTestingEnvironment({ duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowestFadeConfig: disableForTestingEnvironment({ duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + tabPressConfig: disableForTestingEnvironment({ duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }), }); export const TIMING_CONFIGS: Record = timingAnimations; -// // /---- END ----/ // diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 30b8915ed54..adb20230da8 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -94,7 +94,7 @@ const MemoizedBalanceCoinRow = React.memo( const chainId = ethereumUtils.getChainIdFromNetwork(item?.network); return ( - + diff --git a/src/config/experimental.ts b/src/config/experimental.ts index 77c87bd58a4..c9d422f38c1 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -61,7 +61,7 @@ export const defaultConfig: Record = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: false }, + [SWAPS_V2]: { settings: true, value: !!IS_TEST }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, }; diff --git a/src/design-system/components/Box/Box.tsx b/src/design-system/components/Box/Box.tsx index 26287386771..3d051af2a34 100644 --- a/src/design-system/components/Box/Box.tsx +++ b/src/design-system/components/Box/Box.tsx @@ -10,6 +10,10 @@ import { BackgroundProvider, BackgroundProviderProps } from '../BackgroundProvid import { Border, BorderProps } from '../Border/Border'; import { ApplyShadow } from '../private/ApplyShadow/ApplyShadow'; import type * as Polymorphic from './polymorphic'; +import { IS_TEST } from '@/env'; +import LinearGradient from 'react-native-linear-gradient'; + +const COMPONENTS_TO_OVERRIDE_IN_TEST_MODE = [LinearGradient]; const positions = ['absolute'] as const; type Position = (typeof positions)[number]; @@ -174,7 +178,8 @@ export const Box = forwardRef(function Box( const width = typeof widthProp === 'number' ? widthProp : resolveToken(widths, widthProp); const height = typeof heightProp === 'number' ? heightProp : resolveToken(heights, heightProp); - const isView = Component === View || Component === Animated.View; + const ComponentToUse = IS_TEST && COMPONENTS_TO_OVERRIDE_IN_TEST_MODE.some(_C => Component instanceof _C) ? View : Component; + const isView = ComponentToUse === View || ComponentToUse === Animated.View; const shadowStylesExist = !!styleProp && @@ -273,7 +278,7 @@ export const Box = forwardRef(function Box( {({ backgroundColor, backgroundStyle }) => ( - + {children} {borderColor || borderWidth ? ( ) : null} - + )} ) : ( - + {children} {borderColor || borderWidth ? ( ) : null} - + ); }) as PolymorphicBox; diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index c1dc6f021bd..51c4b9de402 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -205,7 +205,7 @@ export const isUnwrapNative = ({ buyTokenAddress: string; }) => { return ( - sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() && + sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() && buyTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() ); }; @@ -221,7 +221,7 @@ export const isWrapNative = ({ }) => { return ( sellTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() && - buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() + buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() ); }; diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index b3f89f1ec1e..45fd589fcc6 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -92,7 +92,13 @@ const ActivityTabIcon = React.memo( }, [pendingCount]); return pendingCount > 0 ? ( - + diff --git a/src/references/chain-assets.json b/src/references/chain-assets.json index 44548600043..766268a1054 100644 --- a/src/references/chain-assets.json +++ b/src/references/chain-assets.json @@ -3,14 +3,22 @@ { "asset": { "asset_code": "eth", + "mainnet_address": "eth", "colors": { "fallback": "#E8EAF5", "primary": "#808088" }, + "chainId": 5, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Ether", + "name": "Goerli", "network": "goerli", + "networks": { + "1": { + "address": "eth", + "decimals": 18 + } + }, "price": { "changed_at": 1582568575, "relative_change_24h": -4.586615622469276, @@ -29,10 +37,17 @@ "fallback": "#E8EAF5", "primary": "#808088" }, + "chainId": 1, "decimals": 18, "icon_url": "https://s3.amazonaws.com/icons.assets/ETH.png", - "name": "Ether", + "name": "Ethereum", "network": "mainnet", + "networks": { + "1": { + "address": "eth", + "decimals": 18 + } + }, "price": { "changed_at": 1582568575, "relative_change_24h": -4.586615622469276, diff --git a/src/references/index.ts b/src/references/index.ts index b4b7cc6686a..a692c06120e 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -229,21 +229,10 @@ export const SUPPORTED_MAINNET_CHAINS: Chain[] = [mainnet, polygon, optimism, ar name: ChainNameDisplay[chain.id], })); -export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => - [ - // In default order of appearance - mainnet, - base, - optimism, - arbitrum, - polygon, - zora, - blast, - degen, - avalanche, - bsc, +export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => { + const mainnetChains: Chain[] = [mainnet, base, optimism, arbitrum, polygon, zora, blast, degen, avalanche, bsc]; - // Testnets + const testnetChains: Chain[] = [ goerli, holesky, sepolia, @@ -255,12 +244,12 @@ export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolea zoraSepolia, avalancheFuji, bscTestnet, - ].reduce((chainList, chain) => { - if (testnetMode || !chain.testnet) { - chainList.push({ ...chain, name: ChainNameDisplay[chain.id] }); - } - return chainList; - }, [] as Chain[]); + ]; + + const allChains = mainnetChains.concat(testnetMode ? testnetChains : []); + + return allChains.map(chain => ({ ...chain, name: ChainNameDisplay[chain.id] ?? chain.name })); +}; export const SUPPORTED_CHAIN_IDS = ({ testnetMode = false }: { testnetMode?: boolean }): ChainId[] => SUPPORTED_CHAINS({ testnetMode }).map(chain => chain.id); diff --git a/src/references/testnet-assets-by-chain.ts b/src/references/testnet-assets-by-chain.ts new file mode 100644 index 00000000000..eb56ab9f147 --- /dev/null +++ b/src/references/testnet-assets-by-chain.ts @@ -0,0 +1,70 @@ +import { UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; +import { ChainName } from '@/__swaps__/types/chains'; +import { Network } from '@/helpers'; + +type ChainAssets = { + [uniqueId: UniqueId]: { + asset: ZerionAsset; + quantity: string; + }; +}; + +// NOTE: Don't import `ETH_ADDRESS` as it's resolving to undefined... +export const chainAssets: Partial> = { + [Network.goerli]: { + eth_5: { + asset: { + asset_code: 'eth', + mainnet_address: 'eth', + colors: { + fallback: '#E8EAF5', + primary: '#808088', + }, + implementations: {}, + bridging: { + bridgeable: true, + networks: {}, // TODO: Add bridgeable networks + }, + decimals: 18, + icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', + name: 'Goerli', + network: ChainName.goerli, + price: { + relative_change_24h: -4.586615622469276, + value: 2590.2, + }, + symbol: 'ETH', + }, + quantity: '0', + }, + }, + [Network.mainnet]: { + eth_1: { + asset: { + asset_code: 'eth', + mainnet_address: 'eth', + colors: { + fallback: '#E8EAF5', + primary: '#808088', + }, + decimals: 18, + icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', + name: 'Ethereum', + network: ChainName.mainnet, + implementations: {}, + bridging: { + bridgeable: true, + networks: {}, // TODO: Add bridgeable networks + }, + price: { + relative_change_24h: -4.586615622469276, + value: 2590.2, + }, + symbol: 'ETH', + }, + quantity: '0', + }, + }, +}; + +export default chainAssets; diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/hardhatAssets.ts index 4dde7452bca..63ba4a2b21f 100644 --- a/src/resources/assets/hardhatAssets.ts +++ b/src/resources/assets/hardhatAssets.ts @@ -3,12 +3,14 @@ import { keyBy, mapValues } from 'lodash'; import { Network } from '@/helpers/networkTypes'; import { web3Provider } from '@/handlers/web3'; // TODO JIN import { getNetworkObj } from '@/networks'; -import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS } from '@/references'; +import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS, SUPPORTED_CHAIN_IDS } from '@/references'; import { parseAddressAsset } from './assets'; import { RainbowAddressAssets } from './types'; import { logger, RainbowError } from '@/logger'; - -const ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT = '0x0000000000000000000000000000000000000000'; +import { AddressOrEth, UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { AddressZero } from '@ethersproject/constants'; +import chainAssetsByChainId from '@/references/testnet-assets-by-chain'; const fetchHardhatBalancesWithBalanceChecker = async ( tokens: string[], @@ -23,7 +25,7 @@ const fetchHardhatBalancesWithBalanceChecker = async ( } = {}; tokens.forEach((tokenAddr, tokenIdx) => { const balance = values[tokenIdx]; - const assetCode = tokenAddr === ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT ? ETH_ADDRESS : tokenAddr; + const assetCode = tokenAddr === AddressZero ? ETH_ADDRESS : tokenAddr; balances[assetCode] = balance.toString(); }); return balances; @@ -33,11 +35,18 @@ const fetchHardhatBalancesWithBalanceChecker = async ( } }; +/** + * @deprecated - to be removed once rest of the app is converted to new userAssetsStore + * Fetches the balances of the hardhat assets for the given account address and network. + * @param accountAddress - The address of the account to fetch the balances for. + * @param network - The network to fetch the balances for. + * @returns The balances of the hardhat assets for the given account address and network. + */ export const fetchHardhatBalances = async (accountAddress: string, network: Network = Network.mainnet): Promise => { - const chainAssetsMap = keyBy(chainAssets[network as keyof typeof chainAssets], ({ asset }) => `${asset.asset_code}_${asset.network}`); + const chainAssetsMap = keyBy(chainAssets[network as keyof typeof chainAssets], ({ asset }) => `${asset.asset_code}_${asset.chainId}`); const tokenAddresses = Object.values(chainAssetsMap).map(({ asset: { asset_code } }) => - asset_code === ETH_ADDRESS ? ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT : asset_code.toLowerCase() + asset_code === ETH_ADDRESS ? AddressZero : asset_code.toLowerCase() ); const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); if (!balances) return {}; @@ -55,3 +64,61 @@ export const fetchHardhatBalances = async (accountAddress: string, network: Netw }); return updatedAssets; }; + +export const fetchHardhatBalancesByChainId = async ( + accountAddress: string, + network: Network = Network.mainnet +): Promise<{ + assets: { + [uniqueId: UniqueId]: { + asset: ZerionAsset; + quantity: string; + }; + }; + chainIdsInResponse: ChainId[]; +}> => { + const chainAssetsMap = chainAssetsByChainId[network as keyof typeof chainAssets] || {}; + const tokenAddresses = Object.values(chainAssetsMap).map(({ asset }) => + asset.asset_code === ETH_ADDRESS ? AddressZero : asset.asset_code.toLowerCase() + ); + + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); + if (!balances) + return { + assets: {}, + chainIdsInResponse: [], + }; + + const updatedAssets = Object.entries(chainAssetsMap).reduce( + (acc, [uniqueId, chainAsset]) => { + const assetCode = chainAsset.asset.asset_code || ETH_ADDRESS; + const quantity = balances[assetCode.toLowerCase()] || '0'; + + const asset: ZerionAsset = { + ...chainAsset.asset, + asset_code: assetCode as AddressOrEth, + mainnet_address: (chainAsset.asset.mainnet_address as AddressOrEth) || (assetCode as AddressOrEth), + network: (chainAsset.asset.network as ChainName) || ChainName.mainnet, + bridging: chainAsset.asset.bridging || { + bridgeable: false, + networks: {}, + }, + implementations: chainAsset.asset.implementations || {}, + name: chainAsset.asset.name || 'Unknown Token', + symbol: chainAsset.asset.symbol || 'UNKNOWN', + decimals: chainAsset.asset.decimals || 18, + icon_url: chainAsset.asset.icon_url || '', + price: chainAsset.asset.price || { value: 0, relative_change_24h: 0 }, + }; + + acc[uniqueId] = { asset, quantity }; + return acc; + }, + {} as { [uniqueId: UniqueId]: { asset: ZerionAsset; quantity: string } } + ); + + return { + assets: updatedAssets, + chainIdsInResponse: SUPPORTED_CHAIN_IDS({ testnetMode: true }), + }; +}; diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 91b3efd0f49..87dbe97b7c0 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,11 +1,12 @@ +import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; import { ETH_ADDRESS, SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; -import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; -import { swapsStore } from '../swaps/swapsStore'; +import { swapsStore } from '@/state/swaps/swapsStore'; const SEARCH_CACHE_MAX_ENTRIES = 50; @@ -179,7 +180,6 @@ export const userAssetsStore = createRainbowStore( return filteredIds; } }, - getHighestValueEth: () => { const preferredNetwork = swapsStore.getState().preferredNetwork; const assets = get().userAssets; @@ -278,7 +278,7 @@ export const userAssetsStore = createRainbowStore( }); // Ensure all supported chains are in the map with a fallback value of 0 - SUPPORTED_CHAIN_IDS({ testnetMode: false }).forEach(chainId => { + SUPPORTED_CHAIN_IDS({ testnetMode: getIsHardhatConnected() }).forEach(chainId => { if (!unsortedChainBalances.has(chainId)) { unsortedChainBalances.set(chainId, 0); idsByChain.set(chainId, []); diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index f4573bb7834..8d547f2f8eb 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -6,9 +6,10 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/__swaps__/types/chains'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; -export const UserAssetsSync = memo(function UserAssetsSync() { +export const UserAssetsSync = function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const userAssetsWalletAddress = userAssetsStore(state => state.associatedWalletAddress); @@ -18,6 +19,7 @@ export const UserAssetsSync = memo(function UserAssetsSync() { { address: currentAddress as Address, currency: currentCurrency, + testnetMode: getIsHardhatConnected(), }, { enabled: !isSwapsOpen || userAssetsWalletAddress !== currentAddress, @@ -41,4 +43,4 @@ export const UserAssetsSync = memo(function UserAssetsSync() { ); return null; -}); +}; From dc159c887637a46b46235ef3dce1cf6d702f547d Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:12:15 -0600 Subject: [PATCH 55/78] Package upgrades (#6040) --- ios/Podfile.lock | 78 +++++++-- package.json | 12 +- .../@candlefinance+faster-image+1.5.0.patch | 29 --- .../@candlefinance+faster-image+1.6.2.patch | 24 +++ ...react-native-gesture-handler+2.18.1.patch} | 0 ...h => react-native-reanimated+3.15.0.patch} | 0 ...atch => react-native-tooltips+1.0.4.patch} | 0 yarn.lock | 165 ++++++++++++++---- 8 files changed, 220 insertions(+), 88 deletions(-) delete mode 100644 patches/@candlefinance+faster-image+1.5.0.patch create mode 100644 patches/@candlefinance+faster-image+1.6.2.patch rename patches/{react-native-gesture-handler+2.17.1.patch => react-native-gesture-handler+2.18.1.patch} (100%) rename patches/{react-native-reanimated+3.14.0.patch => react-native-reanimated+3.15.0.patch} (100%) rename patches/{react-native-tooltips+1.0.3.patch => react-native-tooltips+1.0.4.patch} (100%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ea5bcba53fc..f98236605ce 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,13 +5,13 @@ PODS: - React-Core - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FasterImage (1.5.0): - - FasterImage/Nuke (= 1.5.0) - - FasterImage/NukeUI (= 1.5.0) + - FasterImage (1.6.2): + - FasterImage/Nuke (= 1.6.2) + - FasterImage/NukeUI (= 1.6.2) - React-Core - - FasterImage/Nuke (1.5.0): + - FasterImage/Nuke (1.6.2): - React-Core - - FasterImage/NukeUI (1.5.0): + - FasterImage/NukeUI (1.6.2): - React-Core - FBLazyVector (0.74.3) - Firebase (10.27.0): @@ -1241,7 +1241,7 @@ PODS: - React-Core - react-native-screen-corner-radius (0.2.2): - React - - react-native-skia (1.3.8): + - react-native-skia (1.3.11): - DoubleConversion - glog - hermes-engine @@ -1624,7 +1624,7 @@ PODS: - Yoga - RNFS (2.16.6): - React - - RNGestureHandler (2.17.1): + - RNGestureHandler (2.18.1): - DoubleConversion - glog - hermes-engine @@ -1670,7 +1670,51 @@ PODS: - React-Core - RNReactNativeHapticFeedback (2.2.0): - React-Core - - RNReanimated (3.14.0): + - RNReanimated (3.15.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated (= 3.15.0) + - RNReanimated/worklets (= 3.15.0) + - Yoga + - RNReanimated/reanimated (3.15.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNReanimated/worklets (3.15.0): - DoubleConversion - glog - hermes-engine @@ -1694,7 +1738,7 @@ PODS: - RNRudderSdk (1.12.1): - React - Rudder (< 2.0.0, >= 1.24.1) - - RNScreens (3.32.0): + - RNScreens (3.34.0): - DoubleConversion - glog - hermes-engine @@ -1728,7 +1772,7 @@ PODS: - RNSound/Core (= 0.11.2) - RNSound/Core (0.11.2): - React-Core - - RNSVG (15.3.0): + - RNSVG (15.6.0): - React-Core - RNTextSize (4.0.0-rc.1): - React @@ -2214,7 +2258,7 @@ SPEC CHECKSUMS: BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 - FasterImage: 1f65cdb2966c47b2a7b83162e5fc712534e50149 + FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe @@ -2293,7 +2337,7 @@ SPEC CHECKSUMS: react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d react-native-screen-corner-radius: 67064efbb78f2d48f01626713ae8142f6a20f925 - react-native-skia: 929b6d19b41e3483a33a410f5c1efdf002929230 + react-native-skia: 8da84ea9410504bf27f0db229539a43f6caabb6a react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 react-native-text-input-mask: 07227297075f9653315f43b0424d596423a01736 react-native-udp: 96a517e5a121cfe69f4b05eeeafefe00c623debf @@ -2338,7 +2382,7 @@ SPEC CHECKSUMS: RNFBRemoteConfig: a8325e2d3772127358dd2e7d5e39c9ad3e3379ac RNFlashList: e9b57a5553639f9b528cc50ab53f25831722ed62 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: 8dbcccada4a7e702e7dec9338c251b1cf393c960 + RNGestureHandler: efed690b8493a00b99654043daeb1335276ac4a2 RNImageCropPicker: 13eab07a785c7a8f8047a1146f7e59d1911c7bb8 RNInputMask: 815461ebdf396beb62cf58916c35cf6930adb991 RNKeyboard: 14793d75953d99c6d950090b8e9698b234c5d08c @@ -2347,13 +2391,13 @@ SPEC CHECKSUMS: RNOS: 31db6fa4a197d179afbba9e6b4d28d450a7f250b RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 - RNReanimated: f4ff116e33e0afc3d127f70efe928847c7c66355 + RNReanimated: 45553a3ae29a75a76269595f8554d07d4090e392 RNRudderSdk: 805d4b7064714f3295bf5f152d3812cc67f67a93 - RNScreens: 5aeecbb09aa7285379b6e9f3c8a3c859bb16401c + RNScreens: aa943ad421c3ced3ef5a47ede02b0cbfc43a012e RNSentry: 7ae2a06a5563de39ec09dcb1e751ac0c67f1883c RNShare: eaee3dd5a06dad397c7d3b14762007035c5de405 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 - RNSVG: a48668fd382115bc89761ce291a81c4ca5f2fd2e + RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b RNTextSize: 21c94a67819fbf2c358a219bf6d251e3be9e5f29 RSCrashReporter: 6b8376ac729b0289ebe0908553e5f56d8171f313 Rudder: 3cfcd9e6c5359cd6d49f411101ed9b894e3b64bd @@ -2369,7 +2413,7 @@ SPEC CHECKSUMS: TOCropViewController: b9b2905938c0424f289ea2d9964993044913fac6 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c + Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d PODFILE CHECKSUM: 0839e4141c8f26133bf9a961f5ded1ea3127af54 diff --git a/package.json b/package.json index 698efeb2060..43d123e8c5a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "dependencies": { "@bankify/react-native-animate-number": "0.2.1", "@bradgarropy/use-countdown": "1.4.1", - "@candlefinance/faster-image": "1.5.0", + "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", @@ -122,7 +122,7 @@ "@rudderstack/rudder-sdk-react-native": "1.12.1", "@sentry/react-native": "5.22.0", "@shopify/flash-list": "1.7.0", - "@shopify/react-native-skia": "1.3.8", + "@shopify/react-native-skia": "1.3.11", "@tanstack/query-async-storage-persister": "4.2.1", "@tanstack/react-query": "4.2.1", "@tanstack/react-query-persist-client": "4.2.1", @@ -227,7 +227,7 @@ "react-native-extra-dimensions-android": "1.2.2", "react-native-fast-image": "8.5.11", "react-native-fs": "2.16.6", - "react-native-gesture-handler": "2.17.1", + "react-native-gesture-handler": "2.18.1", "react-native-get-random-values": "1.5.0", "react-native-haptic-feedback": "2.2.0", "react-native-image-crop-picker": "0.41.0", @@ -248,19 +248,19 @@ "react-native-quick-md5": "3.0.6", "react-native-radial-gradient": "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c", "react-native-randombytes": "3.5.3", - "react-native-reanimated": "3.14.0", + "react-native-reanimated": "3.15.0", "react-native-redash": "16.3.0", "react-native-restart": "0.0.22", "react-native-safe-area-context": "4.10.1", "react-native-safe-area-view": "rainbow-me/react-native-safe-area-view", "react-native-screen-corner-radius": "0.2.2", - "react-native-screens": "3.32.0", + "react-native-screens": "3.34.0", "react-native-section-list-get-item-layout": "2.2.3", "react-native-share": "8.2.1", "react-native-sound": "0.11.2", "react-native-splash-screen": "3.3.0", "react-native-storage": "1.0.1", - "react-native-svg": "15.3.0", + "react-native-svg": "15.6.0", "react-native-tab-view": "3.5.1", "react-native-tcp": "3.3.2", "react-native-text-input-mask": "2.0.0", diff --git a/patches/@candlefinance+faster-image+1.5.0.patch b/patches/@candlefinance+faster-image+1.5.0.patch deleted file mode 100644 index f29f47af8db..00000000000 --- a/patches/@candlefinance+faster-image+1.5.0.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -index a0b0ac6..e093754 100644 ---- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -+++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -@@ -41,7 +41,10 @@ final class FasterImageView: UIView { - lazyImageView.trailingAnchor.constraint(equalTo: trailingAnchor), - ]) - lazyImageView.pipeline = .shared -- lazyImageView.priority = .high -+ // 🌈🌈 -+ lazyImageView.priority = .veryLow -+// lazyImageView.priority = .high -+ // 🌈🌈 - lazyImageView.onCompletion = { [weak self] result in - self?.completionHandler(with: result) - } -@@ -121,11 +124,7 @@ final class FasterImageView: UIView { - } - } - -- var showActivityIndicator = false { -- didSet { -- lazyImageView.placeholderView = UIActivityIndicatorView() -- } -- } -+ var showActivityIndicator = false - - var resizeMode = "contain" { - didSet { diff --git a/patches/@candlefinance+faster-image+1.6.2.patch b/patches/@candlefinance+faster-image+1.6.2.patch new file mode 100644 index 00000000000..238dfac6712 --- /dev/null +++ b/patches/@candlefinance+faster-image+1.6.2.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +index a8dee30..e8e38d3 100644 +--- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift ++++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +@@ -233,17 +233,9 @@ final class FasterImageView: UIView { + } + } + +- var showActivityIndicator = false { +- didSet { +- let activity = UIActivityIndicatorView() +- if self.activityColor != nil { +- activity.color = self.activityColor +- } +- lazyImageView.placeholderView = activity +- } +- } ++ var showActivityIndicator = false + +- var activityColor: UIColor? ++ var activityColor: UIColor? = Color.clear + + var resizeMode = "contain" { + didSet { diff --git a/patches/react-native-gesture-handler+2.17.1.patch b/patches/react-native-gesture-handler+2.18.1.patch similarity index 100% rename from patches/react-native-gesture-handler+2.17.1.patch rename to patches/react-native-gesture-handler+2.18.1.patch diff --git a/patches/react-native-reanimated+3.14.0.patch b/patches/react-native-reanimated+3.15.0.patch similarity index 100% rename from patches/react-native-reanimated+3.14.0.patch rename to patches/react-native-reanimated+3.15.0.patch diff --git a/patches/react-native-tooltips+1.0.3.patch b/patches/react-native-tooltips+1.0.4.patch similarity index 100% rename from patches/react-native-tooltips+1.0.3.patch rename to patches/react-native-tooltips+1.0.4.patch diff --git a/yarn.lock b/yarn.lock index 37b92dd5fd3..3027c01a3ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -115,6 +115,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/generator@npm:7.25.0" + dependencies: + "@babel/types": "npm:^7.25.0" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/d0e2dfcdc8bdbb5dded34b705ceebf2e0bc1b06795a1530e64fb6a3ccf313c189db7f60c1616effae48114e1a25adc75855bc4496f3779a396b3377bae718ce7 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.0.0, @babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" @@ -355,6 +367,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-replace-supers@npm:7.25.0" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-simple-access@npm:7.24.7" @@ -466,6 +491,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3": + version: 7.25.3 + resolution: "@babel/parser@npm:7.25.3" + dependencies: + "@babel/types": "npm:^7.25.2" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/874b01349aedb805d6694f867a752fdc7469778fad76aca4548d2cc6ce96087c3ba5fb917a6f8d05d2d1a74aae309b5f50f1a4dba035f5a2c9fcfe6e106d2c4e + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" @@ -963,7 +999,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": +"@babel/plugin-transform-class-properties@npm:^7.0.0-0, @babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" dependencies: @@ -1006,6 +1042,22 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-classes@npm:^7.0.0-0": + version: 7.25.0 + resolution: "@babel/plugin-transform-classes@npm:7.25.0" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-replace-supers": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" + globals: "npm:^11.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4451dccf8a7979427ae042afe381233f30764a8072faf0de1337a4fc297c6d7cb40df9e28931ac096e5b56392d0cd97d3ce10aee68288150a8701624d362a791 + languageName: node + linkType: hard + "@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.21.5, @babel/plugin-transform-computed-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" @@ -1576,7 +1628,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": +"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.0.0-0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" dependencies: @@ -1887,6 +1939,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b + languageName: node + linkType: hard + "@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.2.3, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.0, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.4.5": version: 7.24.8 resolution: "@babel/traverse@npm:7.24.8" @@ -1905,6 +1968,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.0": + version: 7.25.3 + resolution: "@babel/traverse@npm:7.25.3" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.3" + "@babel/template": "npm:^7.25.0" + "@babel/types": "npm:^7.25.2" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/4c8a1966fa90b53a783a4afd2fcdaa6ab1a912e6621dca9fcc6633e80ccb9491620e88caf73b537da4e16cefd537b548c87d7087868d5b0066414dea375c0e9b + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.1.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.24.9 resolution: "@babel/types@npm:7.24.9" @@ -1916,6 +1994,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/types@npm:7.25.2" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/e489435856be239f8cc1120c90a197e4c2865385121908e5edb7223cfdff3768cba18f489adfe0c26955d9e7bbb1fb10625bc2517505908ceb0af848989bd864 + languageName: node + linkType: hard + "@bankify/react-native-animate-number@npm:0.2.1": version: 0.2.1 resolution: "@bankify/react-native-animate-number@npm:0.2.1" @@ -1943,13 +2032,13 @@ __metadata: languageName: node linkType: hard -"@candlefinance/faster-image@npm:1.5.0": - version: 1.5.0 - resolution: "@candlefinance/faster-image@npm:1.5.0" +"@candlefinance/faster-image@npm:1.6.2": + version: 1.6.2 + resolution: "@candlefinance/faster-image@npm:1.6.2" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/34666d7abb079d13209b9a16adf0758d6fb7d9041e8cb9a112dcca4d8c128f14017a5c34a33eeb93dd08c2f0e5802da134a185d40620225c10dc0049d08a5d79 + checksum: 10c0/d9d5a2ead7351fe5305fe9b298cddd7f7debe8c8d2524faf4e0dc66a5f1dd275f3ef73464b69cda792ce636578a3f5c8ef9a1b047f4e8ae9941001fb5b8f6f26 languageName: node linkType: hard @@ -5495,9 +5584,9 @@ __metadata: languageName: node linkType: hard -"@shopify/react-native-skia@npm:1.3.8": - version: 1.3.8 - resolution: "@shopify/react-native-skia@npm:1.3.8" +"@shopify/react-native-skia@npm:1.3.11": + version: 1.3.11 + resolution: "@shopify/react-native-skia@npm:1.3.11" dependencies: canvaskit-wasm: "npm:0.39.1" react-reconciler: "npm:0.27.0" @@ -5512,7 +5601,7 @@ __metadata: optional: true bin: setup-skia-web: scripts/setup-canvaskit.js - checksum: 10c0/df665797f0948265432a3c3786a9861b117ebf8945702fc2d552c4b6a4e48edb685584050165d382db162e2dcf84f5ee166f46737131342e4e9e93c5f95f1e6b + checksum: 10c0/b069d05ff1bf3599cc16eadc492614ab55b9e9130ba1aac22d83f19e6b7f37e57260471cf8bda86b9bbdebb71834bf4b83f33da2f7c0b18b31df104015b72fef languageName: node linkType: hard @@ -6338,11 +6427,11 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.40 - resolution: "@types/node@npm:18.19.40" + version: 18.19.45 + resolution: "@types/node@npm:18.19.45" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/e91c139cbfa7593d9634fc75fa73de787c35c0ca949f629bf65a66bb0909c6de508b974c5953bf3ab910474847287b92366bed632ac6bc14d78fa81b1ddf049f + checksum: 10c0/79c324176411dcfa92f76b0ffc0673aa4bd8da82d003b44633e927c9493cdc46c35f04c0873b096b23b12bab090a6bbdea21242b3bbb2ea5dc1d9bf72adaa04f languageName: node linkType: hard @@ -7773,7 +7862,7 @@ __metadata: "@babel/runtime": "npm:7.22.0" "@bankify/react-native-animate-number": "npm:0.2.1" "@bradgarropy/use-countdown": "npm:1.4.1" - "@candlefinance/faster-image": "npm:1.5.0" + "@candlefinance/faster-image": "npm:1.6.2" "@capsizecss/core": "npm:3.0.0" "@ensdomains/address-encoder": "npm:0.2.16" "@ensdomains/content-hash": "npm:2.5.7" @@ -7834,7 +7923,7 @@ __metadata: "@rudderstack/rudder-sdk-react-native": "npm:1.12.1" "@sentry/react-native": "npm:5.22.0" "@shopify/flash-list": "npm:1.7.0" - "@shopify/react-native-skia": "npm:1.3.8" + "@shopify/react-native-skia": "npm:1.3.11" "@tanstack/query-async-storage-persister": "npm:4.2.1" "@tanstack/react-query": "npm:4.2.1" "@tanstack/react-query-persist-client": "npm:4.2.1" @@ -7977,7 +8066,7 @@ __metadata: react-native-extra-dimensions-android: "npm:1.2.2" react-native-fast-image: "npm:8.5.11" react-native-fs: "npm:2.16.6" - react-native-gesture-handler: "npm:2.17.1" + react-native-gesture-handler: "npm:2.18.1" react-native-get-random-values: "npm:1.5.0" react-native-haptic-feedback: "npm:2.2.0" react-native-image-crop-picker: "npm:0.41.0" @@ -7998,19 +8087,19 @@ __metadata: react-native-quick-md5: "npm:3.0.6" react-native-radial-gradient: "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c" react-native-randombytes: "npm:3.5.3" - react-native-reanimated: "npm:3.14.0" + react-native-reanimated: "npm:3.15.0" react-native-redash: "npm:16.3.0" react-native-restart: "npm:0.0.22" react-native-safe-area-context: "npm:4.10.1" react-native-safe-area-view: rainbow-me/react-native-safe-area-view react-native-screen-corner-radius: "npm:0.2.2" - react-native-screens: "npm:3.32.0" + react-native-screens: "npm:3.34.0" react-native-section-list-get-item-layout: "npm:2.2.3" react-native-share: "npm:8.2.1" react-native-sound: "npm:0.11.2" react-native-splash-screen: "npm:3.3.0" react-native-storage: "npm:1.0.1" - react-native-svg: "npm:15.3.0" + react-native-svg: "npm:15.6.0" react-native-tab-view: "npm:3.5.1" react-native-tcp: "npm:3.3.2" react-native-text-input-mask: "npm:2.0.0" @@ -20926,9 +21015,9 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.17.1": - version: 2.17.1 - resolution: "react-native-gesture-handler@npm:2.17.1" +"react-native-gesture-handler@npm:2.18.1": + version: 2.18.1 + resolution: "react-native-gesture-handler@npm:2.18.1" dependencies: "@egjs/hammerjs": "npm:^2.0.17" hoist-non-react-statics: "npm:^3.3.0" @@ -20937,7 +21026,7 @@ __metadata: peerDependencies: react: "*" react-native: "*" - checksum: 10c0/01ea97f347df2505c58be8551c62e9fd0bae7814346a41f3367ec0b4836877fd8b9b710619f8bc10c6cdbb0e2670dd6c131124d27aabc4745383aab690dae6b3 + checksum: 10c0/6c0f69de1f31eb92cf858903223cbe37b5a71b9e943b70a284e564507067539c5381956b0f832a874da5e1185d01a0a56f06f11ea79985973235eaf3d469274b languageName: node linkType: hard @@ -21149,15 +21238,18 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:3.14.0": - version: 3.14.0 - resolution: "react-native-reanimated@npm:3.14.0" +"react-native-reanimated@npm:3.15.0": + version: 3.15.0 + resolution: "react-native-reanimated@npm:3.15.0" dependencies: "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" + "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" + "@babel/plugin-transform-classes": "npm:^7.0.0-0" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" + "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" "@babel/preset-typescript": "npm:^7.16.7" convert-source-map: "npm:^2.0.0" invariant: "npm:^2.2.4" @@ -21165,7 +21257,7 @@ __metadata: "@babel/core": ^7.0.0-0 react: "*" react-native: "*" - checksum: 10c0/a62ba3e4475c474bc7359d5175f192e86bbe86378a61041247b81beec6e93b4806cd8a5a8a61d1458d56eeef991803e5a128db69d260fa53d78050216b17d014 + checksum: 10c0/86420a616ed7d80404847b584c9fa8f31b516f05401274f32eee908727bcf3fdfa0f09e0b195cd1d11cd0efaa4264ee42d47e2d2000bdab6626f53a795edb6a8 languageName: node linkType: hard @@ -21227,16 +21319,16 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-screens@npm:3.32.0": - version: 3.32.0 - resolution: "react-native-screens@npm:3.32.0" +"react-native-screens@npm:3.34.0": + version: 3.34.0 + resolution: "react-native-screens@npm:3.34.0" dependencies: react-freeze: "npm:^1.0.0" warn-once: "npm:^0.1.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/38dc2e2bf3072c544d3bacceedbcc8ffaee5a588f4984c8d7186a236bd436f761c9b987a37fb3af8e0df2e5980db6155c5f6b6d4a3983001d0f45aa704a20b93 + checksum: 10c0/568af49a1d79797ebecf5b8c815d44b651b0affdd82d7a8024b9e5e71250a23e108d73562200ef571b806c6a9ebba0484510e710246165dbdc50ad18e4d77619 languageName: node linkType: hard @@ -21282,16 +21374,17 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-svg@npm:15.3.0": - version: 15.3.0 - resolution: "react-native-svg@npm:15.3.0" +"react-native-svg@npm:15.6.0": + version: 15.6.0 + resolution: "react-native-svg@npm:15.6.0" dependencies: css-select: "npm:^5.1.0" css-tree: "npm:^1.1.3" + warn-once: "npm:0.1.1" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/72ac639de834d943c3a52f1ac1703e0858eefe59f32d8db4c7e4736e117afea8b88efe8eef9fd1051367d601076090e733fcb93e70df0ec8c6ff6df4859874af + checksum: 10c0/213b6ee33651bb7d2dd17a51c62981264fc519283dc14493361e7816005e2a7b5f2784af11076129bd7402ae2dc0104478f5bd8f894d55f8d4c82c65060bbac6 languageName: node linkType: hard @@ -25374,7 +25467,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"warn-once@npm:^0.1.0": +"warn-once@npm:0.1.1, warn-once@npm:^0.1.0": version: 0.1.1 resolution: "warn-once@npm:0.1.1" checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7 From 731c81ee695966c34ba4003cc89a4436e21325af Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 27 Aug 2024 10:08:51 -0400 Subject: [PATCH 56/78] Fix sentry error boundary crash (#6044) * fix * address more instances of potentially accessing addresses in an empty wallet * optional chaining instead of defaulting to empty array --- .../DappBrowser/control-panel/ControlPanel.tsx | 2 +- src/components/backup/BackupChooseProviderStep.tsx | 4 +++- src/components/backup/BackupManuallyStep.tsx | 4 +++- src/components/backup/RestoreCloudStep.tsx | 2 +- src/components/change-wallet/WalletList.tsx | 2 +- .../ens-profile/ActionButtons/ActionButtons.tsx | 2 +- .../ens-profile/ActionButtons/MoreButton.tsx | 4 ++-- src/helpers/accountInfo.ts | 2 +- src/helpers/findWalletWithAccount.ts | 2 +- src/hooks/useDeleteWallet.ts | 2 +- src/hooks/useRefreshAccountData.ts | 2 +- src/hooks/useUserAccounts.ts | 4 ++-- src/hooks/useWalletBalances.ts | 2 +- src/hooks/useWalletsWithBalancesAndNames.ts | 2 +- src/hooks/useWatchWallet.ts | 6 ++++-- src/model/migrations.ts | 10 +++++----- src/model/wallet.ts | 4 ++-- src/redux/wallets.ts | 12 ++++++------ src/resources/nfts/index.ts | 2 +- src/screens/ChangeWalletSheet.tsx | 6 +++--- .../components/Backups/WalletsAndBackup.tsx | 2 +- src/screens/SettingsSheet/useVisibleWallets.ts | 2 +- src/screens/points/components/LeaderboardRow.tsx | 5 ++--- src/utils/doesWalletsContainAddress.ts | 2 +- 24 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index c02f018636d..34a8421d1e8 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -142,7 +142,7 @@ export const ControlPanel = () => { const accountBalances: Record = {}; Object.values(walletsWithBalancesAndNames).forEach(wallet => { - wallet.addresses + (wallet.addresses || []) .filter(account => account.visible) .forEach(account => { const balanceText = account.balances ? account.balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupChooseProviderStep.tsx index 9e75ac5af13..38325639704 100644 --- a/src/components/backup/BackupChooseProviderStep.tsx +++ b/src/components/backup/BackupChooseProviderStep.tsx @@ -88,7 +88,9 @@ export default function BackupSheetSectionNoProvider() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey + ? (selectedWallet.addresses || [])[0].label + : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/BackupManuallyStep.tsx b/src/components/backup/BackupManuallyStep.tsx index 211eda2d196..da18d73806a 100644 --- a/src/components/backup/BackupManuallyStep.tsx +++ b/src/components/backup/BackupManuallyStep.tsx @@ -20,7 +20,9 @@ export default function BackupManuallyStep() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey + ? (selectedWallet.addresses || [])[0].label + : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/RestoreCloudStep.tsx b/src/components/backup/RestoreCloudStep.tsx index 898533eb7c6..e8bd83aa7a3 100644 --- a/src/components/backup/RestoreCloudStep.tsx +++ b/src/components/backup/RestoreCloudStep.tsx @@ -183,7 +183,7 @@ export default function RestoreCloudStep() { const walletKeys = Object.keys(newWalletsState || {}); // @ts-expect-error TypeScript doesn't play nicely with Redux types here const firstWallet = walletKeys.length > 0 ? (newWalletsState || {})[walletKeys[0]] : undefined; - const firstAddress = firstWallet ? firstWallet.addresses[0].address : undefined; + const firstAddress = firstWallet ? (firstWallet.addresses || [])[0].address : undefined; const p1 = dispatch(walletsSetSelected(firstWallet)); const p2 = dispatch(addressSetSelected(firstAddress)); await Promise.all([p1, p2]); diff --git a/src/components/change-wallet/WalletList.tsx b/src/components/change-wallet/WalletList.tsx index 17c0965d8bc..37a7cbde2ae 100644 --- a/src/components/change-wallet/WalletList.tsx +++ b/src/components/change-wallet/WalletList.tsx @@ -127,7 +127,7 @@ export default function WalletList({ const sortedKeys = Object.keys(allWallets).sort(); sortedKeys.forEach(key => { const wallet = allWallets[key]; - const filteredAccounts = wallet.addresses.filter((account: any) => account.visible); + const filteredAccounts = (wallet.addresses || []).filter((account: any) => account.visible); filteredAccounts.forEach((account: any) => { const row = { ...account, diff --git a/src/components/ens-profile/ActionButtons/ActionButtons.tsx b/src/components/ens-profile/ActionButtons/ActionButtons.tsx index bf10fe77235..87ae91aabac 100644 --- a/src/components/ens-profile/ActionButtons/ActionButtons.tsx +++ b/src/components/ens-profile/ActionButtons/ActionButtons.tsx @@ -19,7 +19,7 @@ export default function ActionButtons({ const isOwner = useMemo(() => { return Object.values(wallets || {}).some( - (wallet: any) => wallet.type !== 'readOnly' && wallet.addresses.some(({ address }: any) => address === primaryAddress) + (wallet: any) => wallet.type !== 'readOnly' && (wallet.addresses || []).some(({ address }: any) => address === primaryAddress) ); }, [primaryAddress, wallets]); diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index d8bd9130d39..d22be293a06 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -34,9 +34,9 @@ export default function MoreButton({ address, ensName }: { address?: string; ens params: { setIsSearchModeEnabled }, } = useRoute(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); + const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); - return visibleWallet.address.toLowerCase() === address?.toLowerCase(); + return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; diff --git a/src/helpers/accountInfo.ts b/src/helpers/accountInfo.ts index a3005dde802..d694523cb71 100644 --- a/src/helpers/accountInfo.ts +++ b/src/helpers/accountInfo.ts @@ -17,7 +17,7 @@ export function getAccountProfileInfo(selectedWallet: any, walletNames: any, acc const accountENS = walletNames?.[accountAddress]; - const selectedAccount = selectedWallet.addresses.find((account: any) => account.address === accountAddress); + const selectedAccount = selectedWallet.addresses?.find((account: any) => account.address === accountAddress); if (!selectedAccount) { return {}; diff --git a/src/helpers/findWalletWithAccount.ts b/src/helpers/findWalletWithAccount.ts index 85b06a2bd8a..109371d2b82 100644 --- a/src/helpers/findWalletWithAccount.ts +++ b/src/helpers/findWalletWithAccount.ts @@ -5,7 +5,7 @@ export function findWalletWithAccount(wallets: { [key: string]: RainbowWallet }, let walletWithAccount: RainbowWallet | undefined; sortedKeys.forEach(key => { const wallet = wallets[key]; - const found = wallet.addresses.find((account: any) => account.address === accountAddress); + const found = wallet.addresses?.find((account: any) => account.address === accountAddress); if (found) { walletWithAccount = wallet; } diff --git a/src/hooks/useDeleteWallet.ts b/src/hooks/useDeleteWallet.ts index 4613034e530..bbe54e5b587 100644 --- a/src/hooks/useDeleteWallet.ts +++ b/src/hooks/useDeleteWallet.ts @@ -13,7 +13,7 @@ export default function useDeleteWallet({ address: primaryAddress }: { address?: const [watchingWalletId] = useMemo(() => { return ( Object.entries(wallets || {}).find(([_, wallet]: [string, RainbowWallet]) => - wallet.addresses.some(({ address }: RainbowAccount) => address === primaryAddress) + (wallet.addresses || []).some(({ address }: RainbowAccount) => address === primaryAddress) ) || ['', ''] ); }, [primaryAddress, wallets]); diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index aa03ef77468..056b3c1af94 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -27,7 +27,7 @@ export default function useRefreshAccountData() { const { wallets } = useWallets(); const allAddresses = useMemo( - () => Object.values(wallets || {}).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), + () => Object.values(wallets || {}).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), [wallets] ); diff --git a/src/hooks/useUserAccounts.ts b/src/hooks/useUserAccounts.ts index 3c999e3e1d5..4ad20883a51 100644 --- a/src/hooks/useUserAccounts.ts +++ b/src/hooks/useUserAccounts.ts @@ -15,7 +15,7 @@ export default function useUserAccounts() { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type !== walletTypes.readOnly); const addresses: (RainbowAccount & { network: Network })[] = []; filteredWallets.forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addresses.push({ ...account, network, @@ -29,7 +29,7 @@ export default function useUserAccounts() { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type === walletTypes.readOnly); const addresses: (RainbowAccount & { network: Network })[] = []; filteredWallets.forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addresses.push({ ...account, network, diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 5fbffcede7a..5e52dbf1aa2 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -32,7 +32,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); const allAddresses = useMemo( - () => Object.values(wallets).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), + () => Object.values(wallets).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), [wallets] ); diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index dcf48245d98..51ec2a03b12 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -11,7 +11,7 @@ export default function useWalletsWithBalancesAndNames() { const walletsWithBalancesAndNames = useMemo( () => mapValues(wallets, wallet => { - const updatedAccounts = (wallet.addresses ?? []).map(account => ({ + const updatedAccounts = (wallet.addresses || []).map(account => ({ ...account, balances: balances[account.address.toLowerCase() as Address], ens: walletNames[account.address], diff --git a/src/hooks/useWatchWallet.ts b/src/hooks/useWatchWallet.ts index fa5754ac1a8..19703a3b344 100644 --- a/src/hooks/useWatchWallet.ts +++ b/src/hooks/useWatchWallet.ts @@ -25,7 +25,9 @@ export default function useWatchWallet({ const { wallets } = useWallets(); const watchingWallet = useMemo(() => { - return Object.values(wallets || {}).find(wallet => wallet.addresses.some(({ address }) => address === primaryAddress)); + return Object.values(wallets || {}).find(wallet => + (wallet.addresses || []).some(({ address }) => address === primaryAddress) + ); }, [primaryAddress, wallets]); const isWatching = useMemo(() => Boolean(watchingWallet), [watchingWallet]); @@ -64,7 +66,7 @@ export default function useWatchWallet({ // it's deletable const isLastAvailableWallet = Object.keys(wallets!).find(key => { const someWallet = wallets![key]; - const otherAccount = someWallet.addresses.find((account: any) => account.visible && account.address !== accountAddress); + const otherAccount = someWallet.addresses?.find((account: any) => account.visible && account.address !== accountAddress); if (otherAccount) { return true; } diff --git a/src/model/migrations.ts b/src/model/migrations.ts index d38340607ff..561500aa58a 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -273,8 +273,8 @@ export default async function runMigrations() { const wallet = wallets[walletKeys[i]]; if (wallet.type !== WalletTypes.readOnly) { // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < wallet.addresses.length; x++) { - const { address } = wallet.addresses[x]; + for (let x = 0; x < (wallet.addresses || []).length; x++) { + const { address } = (wallet.addresses || [])[x]; logger.debug(`[runMigrations]: setting web profiles for address ${address}`); await store.dispatch(updateWebDataEnabled(true, address)); } @@ -308,7 +308,7 @@ export default async function runMigrations() { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; - const newAddresses = wallet.addresses.map((account: RainbowAccount) => { + const newAddresses = (wallet.addresses || []).map((account: RainbowAccount) => { const accountEmoji = returnStringFirstEmoji(account?.label); return { ...account, @@ -440,8 +440,8 @@ export default async function runMigrations() { for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < wallet.addresses.length; x++) { - const { address } = wallet.addresses[x]; + for (let x = 0; x < (wallet.addresses || []).length; x++) { + const { address } = (wallet.addresses || [])[x]; const assets = await getAssets(address, network); const hiddenCoins = await getHiddenCoins(address, network); diff --git a/src/model/wallet.ts b/src/model/wallet.ts index 200fa0edbf9..3271e2f55b6 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -652,7 +652,7 @@ export const createWallet = async ({ // Checking if the generated account already exists and is visible logger.debug('[wallet]: checking if account already exists', {}, DebugContext.wallet); const alreadyExistingWallet = Object.values(allWallets).find((someWallet: RainbowWallet) => { - return !!someWallet.addresses.find( + return !!someWallet.addresses?.find( account => toChecksumAddress(account.address) === toChecksumAddress(walletAddress) && account.visible ); }); @@ -759,7 +759,7 @@ export const createWallet = async ({ let discoveredWalletId: RainbowWallet['id'] | undefined; Object.values(allWallets).forEach(someWallet => { - const existingAccount = someWallet.addresses.find( + const existingAccount = someWallet.addresses?.find( account => toChecksumAddress(account.address) === toChecksumAddress(nextWallet.address) ); if (existingAccount) { diff --git a/src/redux/wallets.ts b/src/redux/wallets.ts index 3e1fbd1675e..deb49a5ea9b 100644 --- a/src/redux/wallets.ts +++ b/src/redux/wallets.ts @@ -157,7 +157,7 @@ export const walletsLoadState = } else { keys(wallets).some(key => { const someWallet = wallets[key]; - const found = someWallet.addresses.some(account => { + const found = (someWallet.addresses || []).some(account => { return toChecksumAddress(account.address) === toChecksumAddress(address!); }); if (found) { @@ -184,7 +184,7 @@ export const walletsLoadState = const allWallets = Object.values(allWalletsResult?.wallets || {}); let account = null; for (const wallet of allWallets) { - for (const rainbowAccount of wallet.addresses) { + for (const rainbowAccount of wallet.addresses || []) { if (rainbowAccount.visible) { account = rainbowAccount; break; @@ -358,7 +358,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = // build hashmap of address to wallet based on backup metadata const addressToWalletLookup = new Map(); Object.values(currentUserData.wallets).forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addressToWalletLookup.set(account.address, wallet); }); }); @@ -376,7 +376,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = const localWalletId = wallet.id; let relatedCloudWalletId: string | null = null; - for (const account of wallet.addresses) { + for (const account of wallet.addresses || []) { const walletDataForCurrentAddress = addressToWalletLookup.get(account.address); if (!walletDataForCurrentAddress) { return; @@ -584,7 +584,7 @@ export const fetchWalletNames = () => async (dispatch: Dispatch { - const visibleAccounts = wallet.addresses?.filter(address => address.visible); + const visibleAccounts = (wallet.addresses || []).filter(address => address.visible); return visibleAccounts.map(async account => { try { const ens = await fetchReverseRecordWithRetry(account.address); @@ -659,7 +659,7 @@ export const checkKeychainIntegrity = () => async (dispatch: ThunkDispatch account.address === address)) { + if ((wallet.addresses || []).some(account => account.address === address)) { return true; } } diff --git a/src/screens/ChangeWalletSheet.tsx b/src/screens/ChangeWalletSheet.tsx index 0aea0778e11..82918ffdcd7 100644 --- a/src/screens/ChangeWalletSheet.tsx +++ b/src/screens/ChangeWalletSheet.tsx @@ -167,14 +167,14 @@ export default function ChangeWalletSheet() { ...wallets, [walletId]: { ...currentWallet, - addresses: (currentWallet.addresses ?? []).map(account => + addresses: (currentWallet.addresses || []).map(account => account.address.toLowerCase() === address.toLowerCase() ? { ...account, visible: false } : account ), }, }; // If there are no visible wallets // then delete the wallet - const visibleAddresses = (newWallets as any)[walletId].addresses.filter((account: any) => account.visible); + const visibleAddresses = ((newWallets as any)[walletId]?.addresses || []).filter((account: any) => account.visible); if (visibleAddresses.length === 0) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete newWallets[walletId]; @@ -191,7 +191,7 @@ export default function ChangeWalletSheet() { (walletId: string, address: string) => { const wallet = wallets?.[walletId]; if (!wallet) return; - const account = wallet.addresses.find(account => account.address === address); + const account = wallet.addresses?.find(account => account.address === address); InteractionManager.runAfterInteractions(() => { goBack(); diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx index 6ad3811626d..9823fd2555f 100644 --- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx @@ -253,7 +253,7 @@ export const WalletsAndBackup = () => { (walletId: string, name: string) => { const wallet = wallets?.[walletId]; - const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? wallet.addresses[0].label : name; + const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? (wallet.addresses || [])[0].label : name; navigate(SETTINGS_BACKUP_ROUTES.VIEW_WALLET_BACKUP, { imported: wallet?.imported, title, diff --git a/src/screens/SettingsSheet/useVisibleWallets.ts b/src/screens/SettingsSheet/useVisibleWallets.ts index c817ceeb883..64e73aa0929 100644 --- a/src/screens/SettingsSheet/useVisibleWallets.ts +++ b/src/screens/SettingsSheet/useVisibleWallets.ts @@ -65,7 +65,7 @@ export const useVisibleWallets = ({ wallets, walletTypeCount }: UseVisibleWallet .filter(key => wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth) .map(key => { const wallet = wallets[key]; - const visibleAccounts = wallet.addresses.filter(a => a.visible); + const visibleAccounts = (wallet.addresses || []).filter(a => a.visible); const totalAccounts = visibleAccounts.length; if ( diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 60951803f17..357630f1aa3 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -52,9 +52,8 @@ export const LeaderboardRow = memo(function LeaderboardRow({ const { setClipboard } = useClipboard(); const { contacts, onRemoveContact } = useContacts(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); - ``; - return visibleWallet.address.toLowerCase() === address?.toLowerCase(); + const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); + return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; diff --git a/src/utils/doesWalletsContainAddress.ts b/src/utils/doesWalletsContainAddress.ts index 4929399d672..088a7bb2953 100644 --- a/src/utils/doesWalletsContainAddress.ts +++ b/src/utils/doesWalletsContainAddress.ts @@ -5,7 +5,7 @@ export default function doesWalletsContainAddress({ address, wallets }: { addres for (let i = 0; i < Object.keys(wallets).length; i++) { const key = Object.keys(wallets)[i]; const someWallet = wallets[key]; - const found = someWallet.addresses.find((account: any) => account.visible && account.address !== address); + const found = someWallet.addresses?.find((account: any) => account.visible && account.address !== address); if (found) { return { key, wallet: found }; From cce25d611be88135f2c89675ab52aa34ed07ee42 Mon Sep 17 00:00:00 2001 From: Daniel Sinclair <4412473+DanielSinclair@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:33:05 -0400 Subject: [PATCH 57/78] chore: degen mode translations (#6020) Co-authored-by: Ibrahim Taveras --- src/languages/ar_AR.json | 15 +++++++++++---- src/languages/es_419.json | 13 ++++++++++--- src/languages/fr_FR.json | 13 ++++++++++--- src/languages/hi_IN.json | 15 +++++++++++---- src/languages/id_ID.json | 13 ++++++++++--- src/languages/ja_JP.json | 15 +++++++++++---- src/languages/ko_KR.json | 15 +++++++++++---- src/languages/pt_BR.json | 13 ++++++++++--- src/languages/ru_RU.json | 13 ++++++++++--- src/languages/th_TH.json | 15 +++++++++++---- src/languages/tr_TR.json | 13 ++++++++++--- src/languages/zh_CN.json | 13 ++++++++++--- 12 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/languages/ar_AR.json b/src/languages/ar_AR.json index 529ecaacb6a..2ca00d05971 100644 --- a/src/languages/ar_AR.json +++ b/src/languages/ar_AR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "أوتو", - "degen_mode": "وضع ديجين", - "degen_mode_description": "تجاوز ورقة المراجعة للتبديل بشكل أسرع", + "degen_mode": "وضع Degen", + "degen_mode_description": "تخطي خطوة المراجعة للتبديل بشكل أسرع", "maximum_sold": "الحد الأقصى المباع", "minimum_received": "الحد الأدنى المستلم", "preferred_network": "الشبكة المفضلة", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "بيع", "sort": { + "ABC": "أبج", "abc": "أبج", + "MOST_RECENT": "الأخير", "most_recent": "الأحدث", + "FLOOR_PRICE": "سعر الأرضية", "floor_price": "سعر الأرضية" - } + }, + "empty": "القطع التذكارية", + "collect_now": "اجمع الآن", + "will_appear_here": "سوف تظهر مقتنياتك هنا" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "الأخير", + "popular": "شائع في Rainbow", "favorites": "المفضلة", "bridge": "جسر", "verified": "تم التحقق منه", @@ -2496,7 +2503,7 @@ "balance_title": "رصيد", "buy": "شراء", "change_wallet": { - "no_balance": "لا يوجد رصيد", + "loading_balance": "جاري تحميل الرصيد...", "balance_eth": "%{balanceEth} ETH", "watching": "مشاهدة", "ledger": "ليدجر" diff --git a/src/languages/es_419.json b/src/languages/es_419.json index 43216bd6eb9..5a5759b5ab8 100644 --- a/src/languages/es_419.json +++ b/src/languages/es_419.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Omitir la hoja de revisión para intercambiar más rápido", + "degen_mode_description": "Saltar el paso de revisión para intercambiar más rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Mínimo Recibido", "preferred_network": "Red preferida", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vendiendo", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Reciente", "most_recent": "Más Reciente", + "FLOOR_PRICE": "Precio Base", "floor_price": "Precio Base" - } + }, + "empty": "Coleccionables", + "collect_now": "Colecciona Ahora", + "will_appear_here": "Tus coleccionables aparecerán aquí" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Reciente", + "popular": "Popular en Rainbow", "favorites": "Favoritos", "bridge": "Puente", "verified": "Verificado", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "no_balance": "Sin Balance", + "loading_balance": "Cargando saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/fr_FR.json b/src/languages/fr_FR.json index 8866fc35723..18e626c7058 100644 --- a/src/languages/fr_FR.json +++ b/src/languages/fr_FR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Auto", "degen_mode": "Mode Degen", - "degen_mode_description": "Passez la feuille de révision pour un échange plus rapide", + "degen_mode_description": "Passer l'étape de l'examen pour échanger plus rapidement", "maximum_sold": "Vendu au maximum", "minimum_received": "Montant minimum reçu", "preferred_network": "Réseau Préféré", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vente", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Récent", "most_recent": "Le plus récent", + "FLOOR_PRICE": "Prix Plancher", "floor_price": "Prix Plancher" - } + }, + "empty": "Objets de collection", + "collect_now": "Collect Now", + "will_appear_here": "Vos objets de collection apparaîtront ici" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Récent", + "popular": "Populaire sur Rainbow", "favorites": "Favoris", "bridge": "Pont", "verified": "Vérifié", @@ -2496,7 +2503,7 @@ "balance_title": "Solde", "buy": "Acheter", "change_wallet": { - "no_balance": "Pas de solde", + "loading_balance": "Chargement du solde...", "balance_eth": "%{balanceEth} ETH", "watching": "Regarder", "ledger": "Ledger" diff --git a/src/languages/hi_IN.json b/src/languages/hi_IN.json index 69f9d44673b..1a92d75d7f0 100644 --- a/src/languages/hi_IN.json +++ b/src/languages/hi_IN.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "ऑटो", - "degen_mode": "डिजन मोड", - "degen_mode_description": "तेज स्वैप करने के लिए समीक्षा शीट को छोड़ें", + "degen_mode": "Degen मोड", + "degen_mode_description": "फ़ास्ट स्वैप के लिए समीक्षा चरण को छोड़ें", "maximum_sold": "अधिकतम बेचा गया", "minimum_received": "न्यूनतम प्राप्त", "preferred_network": "पसंदीदा नेटवर्क", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "बिक्री", "sort": { + "ABC": "एबीसी", "abc": "एबीसी", + "MOST_RECENT": "हाल का", "most_recent": "सबसे हालिया", + "FLOOR_PRICE": "मंजिल मूल्य", "floor_price": "मंजिल मूल्य" - } + }, + "empty": "संग्रह", + "collect_now": "अभी इकट्ठा करें", + "will_appear_here": "आपके संग्रह योग्य सामग्री यहाँ दिखाई देंगे" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "हाल का", + "popular": "रेनबो में लोकप्रिय", "favorites": "पसंदीदा", "bridge": "ब्रिज", "verified": "सत्यापित", @@ -2496,7 +2503,7 @@ "balance_title": "बैलेंस", "buy": "खरीदें", "change_wallet": { - "no_balance": "कोई बैलेंस नहीं", + "loading_balance": "शेष लोड हो रहा है...", "balance_eth": "%{balanceEth} ETH", "watching": "देख रहा है", "ledger": "लेजर" diff --git a/src/languages/id_ID.json b/src/languages/id_ID.json index 312d0d854ac..55e9437efae 100644 --- a/src/languages/id_ID.json +++ b/src/languages/id_ID.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Otomatis", "degen_mode": "Mode Degen", - "degen_mode_description": "Lewati lembar tinjauan untuk swap lebih cepat", + "degen_mode_description": "Lewati langkah ulasan untuk bertukar lebih cepat", "maximum_sold": "Maksimum Dijual", "minimum_received": "Minimum yang diterima", "preferred_network": "Jaringan yang Disukai", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Menjual", "sort": { + "ABC": "Abjad", "abc": "Abjad", + "MOST_RECENT": "Baru-baru ini", "most_recent": "Terbaru", + "FLOOR_PRICE": "Harga Dasar", "floor_price": "Harga Dasar" - } + }, + "empty": "Koleksi", + "collect_now": "Koleksi Sekarang", + "will_appear_here": "Koleksi Anda Akan Muncul di Sini" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Baru-baru ini", + "popular": "Populer di Rainbow", "favorites": "Favorit", "bridge": "Jembatan", "verified": "Terverifikasi", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Beli", "change_wallet": { - "no_balance": "Tidak Ada Saldo", + "loading_balance": "Memuat Saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Menonton", "ledger": "Ledger" diff --git a/src/languages/ja_JP.json b/src/languages/ja_JP.json index a3a1f552f82..ee48137fd7a 100644 --- a/src/languages/ja_JP.json +++ b/src/languages/ja_JP.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "自動", - "degen_mode": "デゲンモード", - "degen_mode_description": "レビューページをスキップして速くスワップする", + "degen_mode": "Degenモード", + "degen_mode_description": "レビューステップをスキップしてより早くスワップ", "maximum_sold": "最大売却数量", "minimum_received": "受け取る最小額", "preferred_network": "優先ネットワーク", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "販売", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "最近の", "most_recent": "最新順", + "FLOOR_PRICE": "フロア価格", "floor_price": "フロア価格" - } + }, + "empty": "コレクタブル", + "collect_now": "今すぐコレクト", + "will_appear_here": "あなたのコレクティブルはこちらに表示されます" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "最近の", + "popular": "Rainbowの人気", "favorites": "お気に入り", "bridge": "ブリッジ", "verified": "確認済み", @@ -2496,7 +2503,7 @@ "balance_title": "残高", "buy": "購入", "change_wallet": { - "no_balance": "残高なし", + "loading_balance": "残高を読み込み中...", "balance_eth": "%{balanceEth} ETH", "watching": "ウォッチ中", "ledger": "Ledger" diff --git a/src/languages/ko_KR.json b/src/languages/ko_KR.json index ee720cf8789..87f8d541567 100644 --- a/src/languages/ko_KR.json +++ b/src/languages/ko_KR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "자동", - "degen_mode": "디겐 모드", - "degen_mode_description": "검토 시트를 건너뛰어 더 빠르게 스왑", + "degen_mode": "Degen모드", + "degen_mode_description": "리뷰 단계를 건너뛰어 더 빠르게 교환", "maximum_sold": "최대 판매", "minimum_received": "최소 수령량", "preferred_network": "선호 네트워크", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "판매", "sort": { + "ABC": "가나다", "abc": "가나다", + "MOST_RECENT": "최근", "most_recent": "최신순", + "FLOOR_PRICE": "최저 가격", "floor_price": "최저 가격" - } + }, + "empty": "수집품", + "collect_now": "지금 수집", + "will_appear_here": "여기에 수집품이 나타납니다" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "최근", + "popular": "레인보우에서 인기", "favorites": "즐겨찾기", "bridge": "브릿지", "verified": "인증됨", @@ -2496,7 +2503,7 @@ "balance_title": "균형", "buy": "구매", "change_wallet": { - "no_balance": "잔액 없음", + "loading_balance": "잔액 로딩 중...", "balance_eth": "%{balanceEth} 이더리움", "watching": "시청 중", "ledger": "렛저" diff --git a/src/languages/pt_BR.json b/src/languages/pt_BR.json index 37c17e65acc..503cb630c1b 100644 --- a/src/languages/pt_BR.json +++ b/src/languages/pt_BR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Pule a revisão para trocar mais rápido", + "degen_mode_description": "Pule a etapa de revisão para trocar mais rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Valor Mínimo Recebido", "preferred_network": "Rede Preferida", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vendendo", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Recente", "most_recent": "Mais Recente", + "FLOOR_PRICE": "Preço do Piso", "floor_price": "Preço do Piso" - } + }, + "empty": "Colecionáveis", + "collect_now": "Colecione Agora", + "will_appear_here": "Suas Colecionáveis Aparecerão Aqui" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Recente", + "popular": "Popular no Rainbow", "favorites": "Favoritos", "bridge": "Ponte", "verified": "Verificado", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "no_balance": "Sem Saldo", + "loading_balance": "Carregando saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/ru_RU.json b/src/languages/ru_RU.json index 7bbc404f7f8..d6868c93fc0 100644 --- a/src/languages/ru_RU.json +++ b/src/languages/ru_RU.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Авто", "degen_mode": "Режим Degen", - "degen_mode_description": "Пропустите лист проверки для ускорения обмена", + "degen_mode_description": "Пропустить этап обзора, чтобы ускорить обмен", "maximum_sold": "Максимум продано", "minimum_received": "Минимальная получаемая", "preferred_network": "Предпочитаемая сеть", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Продажа", "sort": { + "ABC": "АБВ", "abc": "АБВ", + "MOST_RECENT": "Последние", "most_recent": "Самые последние", + "FLOOR_PRICE": "Начальная цена", "floor_price": "Начальная цена" - } + }, + "empty": "Коллекционные предметы", + "collect_now": "Соберите сейчас", + "will_appear_here": "Ваши коллекции появятся здесь" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Последние", + "popular": "Популярное в Rainbow", "favorites": "Избранное", "bridge": "Мост", "verified": "Проверенные", @@ -2496,7 +2503,7 @@ "balance_title": "Баланс", "buy": "Купить", "change_wallet": { - "no_balance": "Нет Баланса", + "loading_balance": "Загрузка баланса...", "balance_eth": "%{balanceEth} ETH", "watching": "Наблюдение", "ledger": "Ledger" diff --git a/src/languages/th_TH.json b/src/languages/th_TH.json index 9c82f0bfb88..d2fb43308fd 100644 --- a/src/languages/th_TH.json +++ b/src/languages/th_TH.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "อัตโนมัติ", - "degen_mode": "โหมดดีเจ็น", - "degen_mode_description": "ข้ามหน้าตรวจสอบเพื่อแลกเปลี่ยนเร็วขึ้น", + "degen_mode": "Degenโหมด", + "degen_mode_description": "ข้ามขั้นตอนการตรวจสอบเพื่อการสลับที่เร็วขึ้น", "maximum_sold": "ขายสูงสุด", "minimum_received": "รับต่ำสุด", "preferred_network": "เครือข่ายที่คุณต้องการ", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "ขาย", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "ล่าสุด", "most_recent": "ล่าสุด", + "FLOOR_PRICE": "ราคาพื้น", "floor_price": "ราคาพื้น" - } + }, + "empty": "สะสม", + "collect_now": "เก็บสะสมทันที", + "will_appear_here": "ของสะสมจะปรากฏที่นี่" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "ล่าสุด", + "popular": "เป็นที่นิยมใน Rainbow", "favorites": "รายการโปรด", "bridge": "สะพาน", "verified": "ได้รับการตรวจสอบ", @@ -2496,7 +2503,7 @@ "balance_title": "ยอดคงเหลือ", "buy": "ซื้อ", "change_wallet": { - "no_balance": "ไม่มีคงเหลือ", + "loading_balance": "กำลังโหลดยอดคงเหลือ...", "balance_eth": "%{balanceEth} ETH", "watching": "กำลังรับชม", "ledger": "เลเจอร์" diff --git a/src/languages/tr_TR.json b/src/languages/tr_TR.json index 0ccec26d073..05d5df71b1e 100644 --- a/src/languages/tr_TR.json +++ b/src/languages/tr_TR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Oto", "degen_mode": "Degen Modu", - "degen_mode_description": "Hızlı Takas İçin İnceleme Sayfasını Atlayın", + "degen_mode_description": "İnceleme adımını atlayarak daha hızlı takas yapın", "maximum_sold": "Azami Satılan", "minimum_received": "Asgari Alınan", "preferred_network": "Tercih Edilen Ağ", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Satış", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Son", "most_recent": "En Yeni", + "FLOOR_PRICE": "Taban Fiyatı", "floor_price": "Taban Fiyatı" - } + }, + "empty": "Koleksiyonlar", + "collect_now": "Şimdi Koleksiyon Yap", + "will_appear_here": "Koleksiyonlarınız Burada Görünecek" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Son", + "popular": "Rainbow'da Popüler", "favorites": "Favoriler", "bridge": "Köprü", "verified": "Doğrulanmış", @@ -2496,7 +2503,7 @@ "balance_title": "Bakiye", "buy": "Satın Al", "change_wallet": { - "no_balance": "Bakiye Yok", + "loading_balance": "Bakiye Yükleniyor...", "balance_eth": "%{balanceEth} ETH", "watching": "İzliyor", "ledger": "Ledger" diff --git a/src/languages/zh_CN.json b/src/languages/zh_CN.json index a347d97c12e..809425396e8 100644 --- a/src/languages/zh_CN.json +++ b/src/languages/zh_CN.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "自动", "degen_mode": "Degen 模式", - "degen_mode_description": "跳过审核表以更快地交换", + "degen_mode_description": "跳过审查步骤以更快交换", "maximum_sold": "最大出售量", "minimum_received": "最低接收金额", "preferred_network": "首选网络", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "出售", "sort": { + "ABC": "字母顺序", "abc": "字母顺序", + "MOST_RECENT": "最近", "most_recent": "最新的", + "FLOOR_PRICE": "底价", "floor_price": "底价" - } + }, + "empty": "收藏品", + "collect_now": "立即收藏", + "will_appear_here": "您的收藏品将会出现在这里" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "最近", + "popular": "在 Rainbow 中流行", "favorites": "收藏夹", "bridge": "桥接", "verified": "已验证", @@ -2496,7 +2503,7 @@ "balance_title": "余额", "buy": "购买", "change_wallet": { - "no_balance": "无余额", + "loading_balance": "正在加载余额...", "balance_eth": "%{balanceEth} ETH", "watching": "正在关注", "ledger": "分类账" From c9f8d606282e9de21defb5da3c94ea79b085c207 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 27 Aug 2024 12:44:39 -0400 Subject: [PATCH 58/78] Featured results integration (#6046) * spindl data layer * rename folder * remove store * add selectors * add cacheTime * add uil to take locale to country code * yerp * finish spindl --- src/components/DappBrowser/Homepage.tsx | 79 ++++++++++++++++++- .../FeaturedResult/FeaturedResultCard.tsx | 60 ++++++++++++++ .../FeaturedResult/FeaturedResultStack.tsx | 48 +++++++++++ src/components/exchange/ExchangeAssetList.tsx | 1 - src/config/experimental.ts | 2 + src/graphql/index.ts | 20 ++++- src/model/remoteConfig.ts | 5 +- .../featuredResults/trackFeaturedResult.ts | 12 +-- .../DiscoverFeaturedResultsCard.tsx | 38 +++++++++ .../{DiscoverHome.js => DiscoverHome.tsx} | 37 +++++++-- 10 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 src/components/FeaturedResult/FeaturedResultCard.tsx create mode 100644 src/components/FeaturedResult/FeaturedResultStack.tsx create mode 100644 src/screens/discover/components/DiscoverFeaturedResultsCard.tsx rename src/screens/discover/components/{DiscoverHome.js => DiscoverHome.tsx} (72%) diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index d4fe0a7c014..4053671b59c 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -20,7 +20,7 @@ import { } from '@/design-system'; import { ImgixImage } from '@/components/images'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { IS_ANDROID, IS_IOS } from '@/env'; +import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { opacity } from '@/__swaps__/utils/swaps'; import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; @@ -41,6 +41,10 @@ import { getNameFromFormattedUrl } from './utils'; import { useTrendingDApps } from '@/resources/metadata/trendingDapps'; import { DApp } from '@/graphql/__generated__/metadata'; import { DEFAULT_TAB_URL } from './constants'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { FEATURED_RESULTS, useExperimentalFlag } from '@/config'; +import { FeaturedResultStack } from '../FeaturedResult/FeaturedResultStack'; const HORIZONTAL_PAGE_INSET = 24; const MAX_RECENTS_TO_DISPLAY = 6; @@ -57,7 +61,7 @@ const NUM_CARDS = 2; const CARD_PADDING = 12; const CARD_HEIGHT = 137; const RAW_CARD_WIDTH = (DEVICE_WIDTH - HORIZONTAL_PAGE_INSET * 2 - (NUM_CARDS - 1) * CARD_PADDING) / NUM_CARDS; -const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); +export const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); export const Homepage = ({ tabId }: { tabId: string }) => { const { goToUrl } = useBrowserContext(); @@ -82,6 +86,25 @@ export const Homepage = ({ tabId }: { tabId: string }) => { ); }; +const DappBrowserFeaturedResults = () => { + const { goToUrl } = useBrowserContext(); + const { featured_results } = useRemoteConfig(); + const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; + + const onNavigate = useCallback( + (href: string) => { + goToUrl(href); + }, + [goToUrl] + ); + + if (!featuredResultsEnabled) { + return null; + } + + return ; +}; + const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { const { data } = useTrendingDApps(); @@ -109,6 +132,7 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { > + {data.dApps .filter((dApp): dApp is DApp => dApp !== null) .map((dApp, index) => ( @@ -475,6 +499,57 @@ const Card = memo(function Card({ ); }); +export const DappBrowserFeaturedResultsCard = memo(function Card({ + handlePress, + featuredResult, +}: { + handlePress: () => void; + featuredResult: FeaturedResult; +}) { + const { isDarkMode } = useColorMode(); + + return ( + + + + + + {IS_IOS && ( + + )} + + + ); +}); + const CardBackground = memo(function CardBackgroundOverlay({ imageUrl, isDarkMode, diff --git a/src/components/FeaturedResult/FeaturedResultCard.tsx b/src/components/FeaturedResult/FeaturedResultCard.tsx new file mode 100644 index 00000000000..594449258ea --- /dev/null +++ b/src/components/FeaturedResult/FeaturedResultCard.tsx @@ -0,0 +1,60 @@ +import { FeaturedResultsVariables, useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; +import { getFeaturedResultById } from '@/resources/featuredResults/_selectors/getFeaturedResultById'; +import { useTrackFeaturedResult } from '@/resources/featuredResults/trackFeaturedResult'; +import { TrackFeaturedResultType } from '@/graphql/__generated__/arc'; +import React, { useCallback, useEffect } from 'react'; +import { FeaturedResultStackProps } from './FeaturedResultStack'; +import { logger } from '@/logger'; + +type FeaturedResultCardProps = FeaturedResultStackProps & + FeaturedResultsVariables & { + featuredResultId: string; + }; + +export const FeaturedResultCard = ({ featuredResultId, onNavigate, Card, ...props }: FeaturedResultCardProps) => { + const { data: featuredResult } = useFeaturedResults(props, { + select: data => getFeaturedResultById(data, featuredResultId), + }); + + const { mutateAsync: trackFeaturedResult } = useTrackFeaturedResult(); + + useEffect(() => { + (async () => { + if (!featuredResult) { + return; + } + + await trackFeaturedResult({ + featuredResultCreativeId: featuredResult.advertiserId, + placementId: featuredResult.placementSlug, + impressionId: featuredResult.impressionId, + type: TrackFeaturedResultType.Impression, + }); + })(); + }, [featuredResult, trackFeaturedResult]); + + const handlePress = useCallback(async () => { + try { + if (!featuredResult) return; + + const [cta] = featuredResult.ctas || []; + + await trackFeaturedResult({ + featuredResultCreativeId: featuredResult.advertiserId, + placementId: featuredResult.placementSlug, + impressionId: featuredResult.impressionId, + type: TrackFeaturedResultType.Click, + }); + + onNavigate(cta.href); + } catch (error) { + logger.warn(`[FeaturedResultCard] Error tracking featured result click`, { error }); + } + }, [featuredResult, trackFeaturedResult, onNavigate]); + + if (!featuredResult) { + return null; + } + + return ; +}; diff --git a/src/components/FeaturedResult/FeaturedResultStack.tsx b/src/components/FeaturedResult/FeaturedResultStack.tsx new file mode 100644 index 00000000000..17649921268 --- /dev/null +++ b/src/components/FeaturedResult/FeaturedResultStack.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useAccountSettings } from '@/hooks'; +import { useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; +import { languageLocaleToCountry } from '@/utils/languageLocaleToCountry'; +import { getFeaturedResultsById } from '@/resources/featuredResults/_selectors/getFeaturedResultIds'; +import { useSharedValue } from 'react-native-reanimated'; +import { FeaturedResultCard } from '@/components/FeaturedResult/FeaturedResultCard'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; + +export type FeaturedResultStackProps = { + onNavigate: (url: string) => void; + placementId: string; + Card: React.FC<{ handlePress: () => void; featuredResult: FeaturedResult }>; +}; + +export const FeaturedResultStack = ({ onNavigate, placementId, Card }: FeaturedResultStackProps) => { + const { accountAddress, language } = useAccountSettings(); + const currentIndex = useSharedValue(0); + + // @ts-expect-error - language is type string instead of typeof keyof Language + const country = languageLocaleToCountry(language); + const { data: featuredResultIds } = useFeaturedResults( + { + placementId, + walletAddress: accountAddress, + country, + }, + { + select: getFeaturedResultsById, + } + ); + + const featuredResultId = featuredResultIds?.[currentIndex.value]; + if (!featuredResultId) { + return null; + } + + return ( + + ); +}; diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/exchange/ExchangeAssetList.tsx index 4fd91c50b27..ff7da7c343e 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/exchange/ExchangeAssetList.tsx @@ -201,7 +201,6 @@ const ExchangeAssetList: ForwardRefRenderFunction items.map(({ data, ...item }) => ({ diff --git a/src/config/experimental.ts b/src/config/experimental.ts index c9d422f38c1..b380ec0b0ac 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -28,6 +28,7 @@ export const SWAPS_V2 = 'SwapsV2'; export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; +export const FEATURED_RESULTS = 'Featured Results'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -64,6 +65,7 @@ export const defaultConfig: Record = { [SWAPS_V2]: { settings: true, value: !!IS_TEST }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, + [FEATURED_RESULTS]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/graphql/index.ts b/src/graphql/index.ts index b3ababa3bbd..2675fa9a603 100644 --- a/src/graphql/index.ts +++ b/src/graphql/index.ts @@ -15,7 +15,25 @@ export const ensClient = getEnsSdk(getFetchRequester(config.ens)); export const metadataClient = getMetadataSdk(metadataRequester); export const metadataPOSTClient = getMetadataSdk(getFetchRequester(config.metadataPOST)); export const arcClient = IS_PROD ? getArcSdk(getFetchRequester(config.arc)) : getArcDevSdk(getFetchRequester(config.arcDev)); - +export const arcPOSTClient = IS_PROD + ? getArcSdk( + getFetchRequester({ + ...config.arc, + schema: { + ...config.arc.schema, + method: 'POST', + }, + }) + ) + : getArcDevSdk( + getFetchRequester({ + ...config.arcDev, + schema: { + ...config.arcDev.schema, + method: 'POST', + }, + }) + ); export const requestMetadata = (q: string, options?: Pick) => metadataRequester( gql` diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index 212f8de215e..9974e62ed2e 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -91,6 +91,7 @@ export interface RainbowConfig extends Record rewards_enabled: boolean; degen_mode: boolean; + featured_results: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -173,6 +174,7 @@ export const DEFAULT_CONFIG: RainbowConfig = { rewards_enabled: true, degen_mode: false, + featured_results: false, }; export async function fetchRemoteConfig(): Promise { @@ -227,7 +229,8 @@ export async function fetchRemoteConfig(): Promise { key === 'swaps_v2' || key === 'idfa_check_enabled' || key === 'rewards_enabled' || - key === 'degen_mode' + key === 'degen_mode' || + key === 'featured_results' ) { config[key] = entry.asBoolean(); } else { diff --git a/src/resources/featuredResults/trackFeaturedResult.ts b/src/resources/featuredResults/trackFeaturedResult.ts index 7209cc38dae..094882b369b 100644 --- a/src/resources/featuredResults/trackFeaturedResult.ts +++ b/src/resources/featuredResults/trackFeaturedResult.ts @@ -1,13 +1,13 @@ import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; import { useMutation } from '@tanstack/react-query'; -import { arcClient } from '@/graphql'; +import { arcPOSTClient } from '@/graphql'; -export type TrackFeaturedResultVariables = Parameters['0']; -export type TrackFeaturedResultResult = Awaited>; +export type TrackFeaturedResultVariables = Parameters['0']; +export type TrackFeaturedResultResult = Awaited>; // /////////////////////////////////////////////// // Mutation Key -export const trackFeaturedResultMutationKey = (props: TrackFeaturedResultVariables) => +export const trackFeaturedResultMutationKey = (props: Partial) => createQueryKey('track-featured-result', props, { persisterVersion: 1 }); export type TrackFeaturedResultMutationKey = ReturnType; @@ -16,10 +16,10 @@ export type TrackFeaturedResultMutationKey = ReturnType = {}, config: QueryConfigWithSelect = {} ) { - return useMutation(trackFeaturedResultMutationKey(props), () => arcClient.trackFeaturedResult(props), { + return useMutation(trackFeaturedResultMutationKey(props), arcPOSTClient.trackFeaturedResult, { ...config, }); } diff --git a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx new file mode 100644 index 00000000000..1f3502d637e --- /dev/null +++ b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { GenericCard } from '@/components/cards/GenericCard'; +import { ImgixImage } from '@/components/images'; +import { HORIZONTAL_PADDING } from './DiscoverHome'; +import { deviceUtils } from '@/utils'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { StyleSheet } from 'react-native'; + +const { width: SCREEN_WIDTH } = deviceUtils.dimensions; +const CARD_WIDTH = SCREEN_WIDTH - HORIZONTAL_PADDING * 2; +const IMAGE_ASPECT_RATIO = 16 / 9; + +type DiscoverFeaturedResultsCardProps = { + handlePress: () => void; + featuredResult: FeaturedResult; +}; + +export const DiscoverFeaturedResultsCard = ({ handlePress, featuredResult }: DiscoverFeaturedResultsCardProps) => { + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + image: { + width: CARD_WIDTH, + height: CARD_WIDTH / IMAGE_ASPECT_RATIO, + borderRadius: 12, + }, +}); diff --git a/src/screens/discover/components/DiscoverHome.js b/src/screens/discover/components/DiscoverHome.tsx similarity index 72% rename from src/screens/discover/components/DiscoverHome.js rename to src/screens/discover/components/DiscoverHome.tsx index 1cf59a2c636..67a6e8ce011 100644 --- a/src/screens/discover/components/DiscoverHome.js +++ b/src/screens/discover/components/DiscoverHome.tsx @@ -1,5 +1,12 @@ -import React, { useMemo } from 'react'; -import useExperimentalFlag, { OP_REWARDS, PROFILES, HARDWARE_WALLETS, MINTS, NFT_OFFERS } from '@rainbow-me/config/experimentalHooks'; +import React, { useCallback } from 'react'; +import useExperimentalFlag, { + OP_REWARDS, + PROFILES, + HARDWARE_WALLETS, + MINTS, + NFT_OFFERS, + FEATURED_RESULTS, +} from '@rainbow-me/config/experimentalHooks'; import { isTestnetNetwork } from '@/handlers/web3'; import { Inline, Inset, Stack, Box } from '@/design-system'; import { useAccountSettings, useWallets } from '@/hooks'; @@ -17,26 +24,43 @@ import { MintsCard } from '@/components/cards/MintsCard/MintsCard'; import { FeaturedMintCard } from '@/components/cards/FeaturedMintCard'; import { IS_TEST } from '@/env'; import { RemoteCardCarousel } from '@/components/cards/remote-cards'; +import { FeaturedResultStack } from '@/components/FeaturedResult/FeaturedResultStack'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { DiscoverFeaturedResultsCard } from './DiscoverFeaturedResultsCard'; + +export const HORIZONTAL_PADDING = 20; export default function DiscoverHome() { - const { profiles_enabled, mints_enabled, op_rewards_enabled } = useRemoteConfig(); + const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results } = useRemoteConfig(); const { network } = useAccountSettings(); const profilesEnabledLocalFlag = useExperimentalFlag(PROFILES); const profilesEnabledRemoteFlag = profiles_enabled; const hardwareWalletsEnabled = useExperimentalFlag(HARDWARE_WALLETS); const nftOffersEnabled = useExperimentalFlag(NFT_OFFERS); + const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; const opRewardsLocalFlag = useExperimentalFlag(OP_REWARDS); const opRewardsRemoteFlag = op_rewards_enabled; const testNetwork = isTestnetNetwork(network); + const { navigate } = useNavigation(); const isProfilesEnabled = profilesEnabledLocalFlag && profilesEnabledRemoteFlag; const { wallets } = useWallets(); - const hasHardwareWallets = Object.keys(wallets || {}).filter(key => wallets[key].type === walletTypes.bluetooth).length > 0; + const hasHardwareWallets = Object.keys(wallets || {}).filter(key => (wallets || {})[key].type === walletTypes.bluetooth).length > 0; + + const onNavigate = useCallback( + (url: string) => { + navigate(Routes.DAPP_BROWSER_SCREEN, { + url, + }); + }, + [navigate] + ); return ( - + {!testNetwork ? ( @@ -55,6 +79,9 @@ export default function DiscoverHome() { {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */} {!IS_TEST && nftOffersEnabled && } {/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/} + {featuredResultsEnabled && ( + + )} {(opRewardsRemoteFlag || opRewardsLocalFlag) && } {hardwareWalletsEnabled && !hasHardwareWallets && } {isProfilesEnabled && } From b13d4530493c88fa0c69f881b57d35585f6d7960 Mon Sep 17 00:00:00 2001 From: brdy <41711440+BrodyHughes@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:13:38 -0500 Subject: [PATCH 59/78] android e2e clean up (#5970) * init zsh:1: command not found: :q * change * changes * clean up * new cloud package commit * package bump * oops * skip * skip test * rm redundancies * oop * bruno change --- .github/workflows/macstadium-android-e2e.yml | 94 +++++++++++++------- android/app/build.gradle | 41 ++++----- android/build.gradle | 4 +- android/settings.gradle | 4 +- ios/Podfile.lock | 6 +- package.json | 5 +- yarn.lock | 10 +-- 7 files changed, 95 insertions(+), 69 deletions(-) diff --git a/.github/workflows/macstadium-android-e2e.yml b/.github/workflows/macstadium-android-e2e.yml index 44e6bdb75c4..facc0b7861a 100644 --- a/.github/workflows/macstadium-android-e2e.yml +++ b/.github/workflows/macstadium-android-e2e.yml @@ -1,56 +1,82 @@ -# This is a basic workflow to help you get started with Actions - name: Android e2e tests -# Controls when the workflow will run -on: [pull_request, workflow_dispatch] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel +on: [] jobs: - # This workflow contains a single job called "android-e2e" android-e2e: - if: startsWith(github.head_ref, 'android-ci') - # The type of runner that the job will run on - runs-on: ["self-hosted", "ci-5"] - # Cancel current builds if there's a newer commit on the same branch - concurrency: + runs-on: ["self-hosted"] + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - # Steps represent a sequence of tasks that will be executed as part of the job + permissions: + contents: read + steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - name: Checkout repo + uses: actions/checkout@v4 - name: Set up github keys run: git config core.sshCommand "ssh -i ~/.ssh/id_ed25519 -F /dev/null" + - name: Clean Android app + run: yarn clean:android > /dev/null 2>&1 || true + - name: Set up ENV vars & scripts + env: + CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} run: | - # read local env vars - source ~/.bashrc - # fetch env vars + source ~/.zshrc git clone git@github.com:rainbow-me/rainbow-env.git - # unpack dotenv - mv rainbow-env/android/app/google-services.json android/app mv rainbow-env/dotenv .env && rm -rf rainbow-env - # run CI scripts - eval $CI_SCRIPTS - # tweak dotenv for e2e - sed -i''-e "s/\IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - # set up password - cp android/keystores/debug.keystore android/keystores/rainbow-key.keystore - sed -i -e "s:rainbow-alias:androiddebugkey:g" android/app/build.gradle - export RAINBOW_KEY_ANDROID_PASSWORD=android - - name: Install deps via Yarn - run: yarn setup-ci + eval $CI_SCRIPTS_RN_UPGRADE + sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e + + - name: Get Yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache Yarn dependencies + uses: actions/cache@v4 + with: + path: | + ${{ steps.yarn-cache-dir-path.outputs.dir }} + .yarn/cache + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + run: | + yarn cache clean --all && yarn install && yarn setup + + - name: Check for frozen lockfile + run: ./scripts/check-lockfile.sh + + - name: Audit CI + run: yarn audit-ci --config audit-ci.jsonc + + - name: Lint + run: yarn lint:ci + + - name: Unit tests + run: yarn test - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache + + - name: Version debug + run: | + npx react-native info - - name: Build the app in Release mode - run: ./node_modules/.bin/detox build --configuration android.emu.release + - name: Fix permissions + run: | + chmod -R +x node_modules/react-native/scripts + chmod -R +x node_modules/@sentry/react-native/scripts - - name: Run Android e2e tests - run: ./node_modules/.bin/detox test -R 5 --configuration android.emu.release --forceExit + - name: Build the app in Release mode + run: yarn detox build --configuration android.emu.release + # change the '3' here to how many times you want the tests to rerun on failure + - name: Run iOS e2e tests with retry + run: ./scripts/run-retry-tests.sh 3 diff --git a/android/app/build.gradle b/android/app/build.gradle index 09503d90d47..7297b466b66 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: "org.jetbrains.kotlin.android" apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' - def getPassword(String currentUser, String keyChain) { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() @@ -30,15 +29,9 @@ def getPassword(String currentUser, String keyChain) { stdout.toString().trim() } - import com.android.build.OutputFile - - apply plugin: "com.facebook.react" - - - apply from: "../../node_modules/@sentry/react-native/sentry.gradle" react { @@ -114,11 +107,9 @@ def enableProguardInReleaseBuilds = false */ def jscFlavor = 'org.webkit:android-jsc:+' - - android { def envFile = project.file('../../.env') - def env =[:] + def env = [:] envFile.eachLine { if (it.contains('=') && (!it.startsWith("#"))) { def (key, value) = it.split('=') @@ -145,19 +136,19 @@ android { renderscriptTargetApi 23 renderscriptSupportModeEnabled true multiDexEnabled true - testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type + testBuildType System.getProperty('testBuildType', 'debug') + missingDimensionStrategy 'detox', 'full' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' manifestPlaceholders = [ - BRANCH_KEY: env.get('BRANCH_KEY') - ] + BRANCH_KEY: env.get('BRANCH_KEY') + ] ndk { - abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } - signingConfigs { debug { storeFile file('../keystores/debug.keystore') @@ -187,7 +178,7 @@ android { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" // Detox-specific additions to pro-guard - //proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" + proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } @@ -199,8 +190,17 @@ android { pickFirst '**/x86_64/libc++_shared.so' pickFirst '**/x86/libjsc.so' pickFirst '**/armeabi-v7a/libjsc.so' + // exclude + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + exclude("META-INF/*.kotlin_module") } - } dependencies { @@ -229,8 +229,8 @@ dependencies { } // DETOX - //androidTestImplementation('com.wix:detox:+') - + androidTestImplementation('com.wix:detox:+') { transitive = true } + androidTestImplementation(project(path: ":detox")) } // Run this once to be able to run the application with BUCK @@ -241,6 +241,3 @@ task copyDownloadableDepsToLibs(type: Copy) { } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) - - - diff --git a/android/build.gradle b/android/build.gradle index 8199414d720..1de07c21f7c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,11 +22,13 @@ buildscript { url 'https://maven.fabric.io/public' } mavenCentral() + maven { + url("${rootProject.projectDir}/../node_modules/detox/Detox-android") + } } dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") -// classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" classpath("de.undercouch:gradle-download-task:5.0.1") classpath 'com.google.gms:google-services:4.3.15' diff --git a/android/settings.gradle b/android/settings.gradle index b03e948f98f..9bc55e25785 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -6,7 +6,7 @@ project(':react-native-palette-full').projectDir = new File(rootProject.projectD apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' -/*include ':detox' +include ':detox' project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') -*/ + includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f98236605ce..078a02d832c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1169,7 +1169,7 @@ PODS: - Yoga - react-native-change-icon (4.0.0): - React-Core - - react-native-cloud-fs (2.6.1): + - react-native-cloud-fs (2.6.2): - React - react-native-compat (2.11.2): - DoubleConversion @@ -2321,7 +2321,7 @@ SPEC CHECKSUMS: react-native-branch: 960c897d57b9f4912b08b9d06a25284b6e9879da react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22 react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 - react-native-cloud-fs: e7103d1f693c57b481f820fa5e6b6b0522a5a31e + react-native-cloud-fs: c90379f513b8d7ad5cfed610ccf50f27d837016e react-native-compat: 0468620f537a51ce0d3f4ba65bda6872240b9a0d react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 @@ -2413,7 +2413,7 @@ SPEC CHECKSUMS: TOCropViewController: b9b2905938c0424f289ea2d9964993044913fac6 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d + Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c PODFILE CHECKSUM: 0839e4141c8f26133bf9a961f5ded1ea3127af54 diff --git a/package.json b/package.json index 43d123e8c5a..3ddf643ef6c 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,9 @@ "clean:packager": "watchman watch-del-all", "clean:node": "rm -rf node_modules", "nuke": "./scripts/nuke.sh", - "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", + "detox:android:tests": "detox test -c android.emu.debug --maxWorkers 2 -- --bail 1", + "detox:android": "detox build -c android.emu.debug && yarn detox:android:tests", "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", @@ -219,7 +220,7 @@ "react-native-branch": "5.3.1", "react-native-change-icon": "4.0.0", "react-native-circular-progress": "1.3.8", - "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a", + "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a", "react-native-crypto": "2.2.0", "react-native-dark-mode": "0.2.2", "react-native-device-info": "5.3.1", diff --git a/yarn.lock b/yarn.lock index 3027c01a3ea..3577aa5a418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8058,7 +8058,7 @@ __metadata: react-native-branch: "npm:5.3.1" react-native-change-icon: "npm:4.0.0" react-native-circular-progress: "npm:1.3.8" - react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" + react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a" react-native-crypto: "npm:2.2.0" react-native-dark-mode: "npm:0.2.2" react-native-device-info: "npm:5.3.1" @@ -20917,10 +20917,10 @@ __metadata: languageName: node linkType: hard -"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a": - version: 2.6.1 - resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" - checksum: 10c0/74fe20b46f13a502176bb4bbc78ea24f5ec35d09c37a9404bc15a16c085d8459c184c948841bd8b0b1e5d649609fec2b8279ea31c18c70b24835609e34bbc253 +"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a": + version: 2.6.2 + resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=9b204615b76cf3d29bd86a9094dbd95d717b6a7a" + checksum: 10c0/db1c719b90475201aa1e1177209723598ac38689a827d387dd281ea5190ad09f3e6c8fee77caff70b46a228b6552459d9a9e73e4159c18a29d19d235e17d7907 languageName: node linkType: hard From b992e7dc8ca46eae126220ca4b66ed25be05cda4 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:14:19 -0400 Subject: [PATCH 60/78] WC Bump & refactor (#6047) * wc updates * fix initialization * update ios files * Update package.json Co-authored-by: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> * update lockfile * use the right type --------- Co-authored-by: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> --- ios/Podfile.lock | 4 +- ios/Rainbow.xcodeproj/project.pbxproj | 326 +++++++++++++------------- package.json | 12 +- src/walletConnect/index.tsx | 95 ++++---- yarn.lock | 298 ++++++++++++----------- 5 files changed, 372 insertions(+), 363 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 078a02d832c..1c400b0f1c1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1171,7 +1171,7 @@ PODS: - React-Core - react-native-cloud-fs (2.6.2): - React - - react-native-compat (2.11.2): + - react-native-compat (2.15.1): - DoubleConversion - glog - hermes-engine @@ -2322,7 +2322,7 @@ SPEC CHECKSUMS: react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22 react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 react-native-cloud-fs: c90379f513b8d7ad5cfed610ccf50f27d837016e - react-native-compat: 0468620f537a51ce0d3f4ba65bda6872240b9a0d + react-native-compat: 408a4b320fa4426f2c25ab74ed5764be6b3eb5aa react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index fbb316edc0e..e586a215e55 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -34,8 +34,8 @@ 66A1FEB624AB641100C3F539 /* RNCMScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEB324AB641100C3F539 /* RNCMScreen.m */; }; 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */; }; 66A28EB024CAF1B500410A88 /* TestFlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A28EAF24CAF1B500410A88 /* TestFlight.m */; }; - 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */; }; - 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */; }; + 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */; }; + 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -68,7 +68,7 @@ B5CE8FFF29A5758100EB1EFA /* pooly@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */; }; B5D7F2F029E8D41E003D6A54 /* finiliar@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */; }; B5D7F2F129E8D41E003D6A54 /* finiliar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */; }; - B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */; }; + B8CD55BCF7A0AB6EFF7E2107 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */; }; C04D10F025AFC8C1003BEF7A /* Extras.json in Resources */ = {isa = PBXBuildFile; fileRef = C04D10EF25AFC8C1003BEF7A /* Extras.json */; }; C1038325273C2D0C00B18210 /* PriceWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF75272BA7AA00FF5C78 /* PriceWidgetView.swift */; }; C1038337273C5C4200B18210 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF62272BA6EF00FF5C78 /* PriceWidget.swift */; }; @@ -139,8 +139,8 @@ C9B378BB2C515A860085E5D0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B378BA2C515A860085E5D0 /* ShareViewController.swift */; }; C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; - F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,13 +203,14 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; + 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE772886202800B5C7E7 /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE792886202800B5C7E7 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; - 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; + 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; + 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -235,9 +236,6 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; - 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; - 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; - 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -250,21 +248,19 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; - 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; - 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; + 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; - 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; + 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; - 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; 664612ED274848B000B43F5A /* SelectTokenIntent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SelectTokenIntent.entitlements; sourceTree = ""; }; 6655FFB325BB2B0700642961 /* ThemeModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThemeModule.m; sourceTree = ""; }; + 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; 668ADB2C25A4E3A40050859D /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = ""; }; 668ADB2E25A4E3A40050859D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66A1FEAF24AB641100C3F539 /* RNCMScreenStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCMScreenStack.h; path = "../src/react-native-cool-modals/ios/RNCMScreenStack.h"; sourceTree = ""; }; @@ -275,8 +271,11 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; + 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; + 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; @@ -292,6 +291,7 @@ AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; @@ -313,6 +313,8 @@ B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pooly@3x.png"; sourceTree = ""; }; B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@3x.png"; sourceTree = ""; }; B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@2x.png"; sourceTree = ""; }; + B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C04D10EF25AFC8C1003BEF7A /* Extras.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Extras.json; sourceTree = ""; }; C11640E7274DC10B00C9120A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; C1272389274EBBB6006AC743 /* CurrencyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyDetails.swift; sourceTree = ""; }; @@ -340,7 +342,6 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; - C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -353,15 +354,14 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; + D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; - E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; - FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; + FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */, + B8CD55BCF7A0AB6EFF7E2107 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */, + 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */, + CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */, + 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */, - 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */, - 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */, - 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */, + 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */, + D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */, + 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */, + BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */, - 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */, - C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */, - 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */, - E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */, - FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */, - 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */, - 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */, - E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */, - 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */, - 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */, - 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */, - 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */, - 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */, - D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */, + 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */, + 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */, + 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */, + E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */, + E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */, + 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */, + B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */, + 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */, + 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */, + 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */, + 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */, + 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */, + E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */, + 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */, + FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */, + FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */, + BB2F78BEBE245D7D48C41820 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */, + 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */, - B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */, - 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */, + 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */, + F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */, + 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */, + 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */, + BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */, + 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */, + EE77FD4F5751D4E858F705B1 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1098,7 +1098,29 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */ = { + 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1111,7 +1133,7 @@ shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */ = { + 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1126,14 +1148,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */ = { + 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1151,40 +1173,26 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */ = { + 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { @@ -1202,35 +1210,13 @@ shellPath = /bin/sh; shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; }; - B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */ = { + BB2F78BEBE245D7D48C41820 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1242,15 +1228,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1265,28 +1242,19 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */ = { + BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1297,7 +1265,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1311,14 +1278,13 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */ = { + EE77FD4F5751D4E858F705B1 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1354,29 +1320,63 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */ = { + F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */ = { + FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1391,7 +1391,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1633,7 +1633,7 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */; + baseConfigurationReference = 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1681,7 +1681,7 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */; + baseConfigurationReference = 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1729,7 +1729,7 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */; + baseConfigurationReference = E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1777,7 +1777,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */; + baseConfigurationReference = 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1854,7 +1854,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2087,7 +2087,7 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */; + baseConfigurationReference = 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2260,7 +2260,7 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */; + baseConfigurationReference = E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2309,7 +2309,7 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */; + baseConfigurationReference = 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2356,7 +2356,7 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */; + baseConfigurationReference = B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2403,7 +2403,7 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */; + baseConfigurationReference = B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2450,7 +2450,7 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */; + baseConfigurationReference = 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2497,7 +2497,7 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */; + baseConfigurationReference = E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2542,7 +2542,7 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */; + baseConfigurationReference = 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2587,7 +2587,7 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */; + baseConfigurationReference = FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; diff --git a/package.json b/package.json index 3ddf643ef6c..e430d27e344 100644 --- a/package.json +++ b/package.json @@ -136,12 +136,12 @@ "@unstoppabledomains/resolution": "7.1.4", "@wagmi/chains": "1.8.0", "@walletconnect/client": "1.8.0", - "@walletconnect/core": "2.11.2", + "@walletconnect/core": "2.15.1", "@walletconnect/legacy-utils": "2.0.0", - "@walletconnect/react-native-compat": "2.11.2", - "@walletconnect/types": "2.11.2", - "@walletconnect/utils": "2.11.2", - "@walletconnect/web3wallet": "1.10.2", + "@walletconnect/react-native-compat": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", + "@walletconnect/web3wallet": "1.14.1", "assert": "1.5.0", "async-mutex": "0.3.2", "asyncstorage-down": "4.2.0", @@ -345,7 +345,7 @@ "babel-plugin-date-fns": "2.0.0", "babel-plugin-graphql-tag": "2.5.0", "babel-plugin-lodash": "3.3.4", - "babel-plugin-module-resolver": "4.0.0", + "babel-plugin-module-resolver": "5.0.2", "babel-plugin-rewire": "1.2.0", "babel-plugin-styled-components": "1.11.1", "babel-plugin-transform-remove-console": "6.9.4", diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 66bd9039751..8507c2e048e 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -8,7 +8,7 @@ import { isAddress, getAddress } from '@ethersproject/address'; import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; -import { Core } from '@walletconnect/core'; +import WalletConnectCore, { Core } from '@walletconnect/core'; import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -95,32 +95,46 @@ export function maybeGoBackAndClearHasPendingRedirect({ delay = 0 }: { delay?: n */ let syncWeb3WalletClient: Awaited> | undefined; -const walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); - -const web3WalletClient = Web3Wallet.init({ - core: walletConnectCore, - metadata: { - name: '🌈 Rainbow', - description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', - url: 'https://rainbow.me', - icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], - redirect: { - native: 'rainbow://wc', - universal: 'https://rnbwapp.com/wc', - }, - }, -}); +let lastConnector: string | undefined = undefined; + +let walletConnectCore: WalletConnectCore | undefined; + +let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; let initPromise: ReturnType<(typeof Web3Wallet)['init']> | null = null; +export const initializeWCv2 = async () => { + walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); + + web3WalletClient = Web3Wallet.init({ + core: walletConnectCore, + metadata: { + name: '🌈 Rainbow', + description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', + url: 'https://rainbow.me', + icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], + redirect: { + native: 'rainbow://wc', + universal: 'https://rnbwapp.com/wc', + }, + }, + }); + return web3WalletClient; +}; + // this function ensures we only initialize the client once export async function getWeb3WalletClient() { if (!syncWeb3WalletClient) { if (!initPromise) { - initPromise = web3WalletClient.then(client => { - syncWeb3WalletClient = client; - return client; - }); + if (web3WalletClient) { + initPromise = web3WalletClient.then(client => { + syncWeb3WalletClient = client; + return client; + }); + } else { + await initializeWCv2(); + return getWeb3WalletClient(); + } } // Wait for the initialization promise to resolve return initPromise; @@ -320,9 +334,23 @@ async function rejectProposal({ }); } +// listen for THIS topic pairing, and clear timeout if received +function trackTopicHandler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { + logger.debug(`[walletConnect]: pair: handler`, { proposal }); + + const { metadata } = + (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; + + analytics.track(analytics.event.wcNewPairing, { + dappName: metadata.name, + dappUrl: metadata.url, + connector: lastConnector || 'unknown', + }); +} + export async function pair({ uri, connector }: { uri: string; connector?: string }) { logger.debug(`[walletConnect]: pair`, { uri }, logger.DebugContext.walletconnect); - + lastConnector = connector; /** * Make sure this is cleared if we get multiple pairings in rapid succession */ @@ -332,25 +360,8 @@ export async function pair({ uri, connector }: { uri: string; connector?: string logger.debug(`[walletConnect]: pair: parsed uri`, { topic, rest }); - // listen for THIS topic pairing, and clear timeout if received - function handler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { - logger.debug(`[walletConnect]: pair: handler`, { proposal }); - - const { metadata } = - (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; - analytics.track(analytics.event.wcNewPairing, { - dappName: metadata.name, - dappUrl: metadata.url, - connector, - }); - } - - // CAN get fired on subsequent pairs, so need to make sure we clean up - client.on('session_proposal', handler); - client.on('auth_request', handler); - // init pairing - await client.core.pairing.pair({ uri }); + await client.pair({ uri }); } export async function initListeners() { @@ -386,7 +397,7 @@ export async function initListeners() { /** * Ensure that if the FCM token changes we update the echo server */ - messaging().onTokenRefresh(async token => { + messaging().onTokenRefresh(async (token: string) => { await subscribeToEchoServer({ token, client_id }); }); } else { @@ -429,6 +440,8 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposal) { try { + trackTopicHandler(proposal); + logger.debug(`[walletConnect]: session_proposal`, { proposal }, logger.DebugContext.walletconnect); const verifiedData = proposal.verifyContext.verified; @@ -828,6 +841,8 @@ export async function handleSessionRequestResponse( } export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { + trackTopicHandler(event); + const client = await getWeb3WalletClient(); logger.debug(`[walletConnect]: auth_request`, { event }, logger.DebugContext.walletconnect); diff --git a/yarn.lock b/yarn.lock index 3577aa5a418..79f2367ed43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5819,7 +5819,7 @@ __metadata: languageName: node linkType: hard -"@stablelib/x25519@npm:1.0.3, @stablelib/x25519@npm:^1.0.3": +"@stablelib/x25519@npm:1.0.3": version: 1.0.3 resolution: "@stablelib/x25519@npm:1.0.3" dependencies: @@ -7078,28 +7078,27 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/core@npm:2.11.2" +"@walletconnect/core@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/core@npm:2.15.1" dependencies: - "@walletconnect/heartbeat": "npm:1.2.1" - "@walletconnect/jsonrpc-provider": "npm:1.0.13" - "@walletconnect/jsonrpc-types": "npm:1.0.3" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" - "@walletconnect/keyvaluestorage": "npm:^1.1.1" - "@walletconnect/logger": "npm:^2.0.1" - "@walletconnect/relay-api": "npm:^1.0.9" - "@walletconnect/relay-auth": "npm:^1.0.4" - "@walletconnect/safe-json": "npm:^1.0.2" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - events: "npm:^3.3.0" - isomorphic-unfetch: "npm:3.1.0" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:^3.1.0" - checksum: 10c0/a1bd4f028c08b668b5b5ddd9adbf136010b55dea90f1b7203b45c7149acdb7a8f03e1310f4c7bbd9580b6ba163962914a86a5a3360359a32f7932de0963e8cb3 + uint8arrays: "npm:3.1.0" + checksum: 10c0/3c831303bffcc360bb7d2f6e71b9928e73039e34e582e8da3f03dbc67e7876cf3ff89491ec9bad7ae31c3c3706ea6e44d2cd1be501349e882739a5184e917797 languageName: node linkType: hard @@ -7183,17 +7182,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/heartbeat@npm:1.2.1": - version: 1.2.1 - resolution: "@walletconnect/heartbeat@npm:1.2.1" - dependencies: - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/time": "npm:^1.0.2" - tslib: "npm:1.14.1" - checksum: 10c0/5ad46f26dcb7b9b3227f004cd74b18741d4cd32c21825a036eb03985c67a0cf859c285bc5635401966a99129e854d72de3458ff592370575ef7e52f5dd12ebbc - languageName: node - linkType: hard - "@walletconnect/heartbeat@npm:1.2.2, @walletconnect/heartbeat@npm:^1.2.1": version: 1.2.2 resolution: "@walletconnect/heartbeat@npm:1.2.2" @@ -7216,17 +7204,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/jsonrpc-provider@npm:1.0.13": - version: 1.0.13 - resolution: "@walletconnect/jsonrpc-provider@npm:1.0.13" - dependencies: - "@walletconnect/jsonrpc-utils": "npm:^1.0.8" - "@walletconnect/safe-json": "npm:^1.0.2" - tslib: "npm:1.14.1" - checksum: 10c0/9b5b2f0ce516d2ddebe2cd1a2c8ea18a6b765b0d068162caf39745c18534e264a0cc6198adb869ba8684d0efa563be30956a3b9a7cc82b80b9e263f6211e30ab - languageName: node - linkType: hard - "@walletconnect/jsonrpc-provider@npm:1.0.14": version: 1.0.14 resolution: "@walletconnect/jsonrpc-provider@npm:1.0.14" @@ -7238,16 +7215,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/jsonrpc-types@npm:1.0.3": - version: 1.0.3 - resolution: "@walletconnect/jsonrpc-types@npm:1.0.3" - dependencies: - keyvaluestorage-interface: "npm:^1.0.0" - tslib: "npm:1.14.1" - checksum: 10c0/a0fc8a88c62795bf4bf83d4e98a4e2cdd659ef70c73642582089fdf0994c54fd8050aa6cca85cfdcca6b77994e71334895e7a19649c325a8c822b059c2003884 - languageName: node - linkType: hard - "@walletconnect/jsonrpc-types@npm:1.0.4, @walletconnect/jsonrpc-types@npm:^1.0.2, @walletconnect/jsonrpc-types@npm:^1.0.3": version: 1.0.4 resolution: "@walletconnect/jsonrpc-types@npm:1.0.4" @@ -7281,7 +7248,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/keyvaluestorage@npm:1.1.1, @walletconnect/keyvaluestorage@npm:^1.1.1": +"@walletconnect/keyvaluestorage@npm:1.1.1": version: 1.1.1 resolution: "@walletconnect/keyvaluestorage@npm:1.1.1" dependencies: @@ -7322,16 +7289,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/logger@npm:2.0.1": - version: 2.0.1 - resolution: "@walletconnect/logger@npm:2.0.1" - dependencies: - pino: "npm:7.11.0" - tslib: "npm:1.14.1" - checksum: 10c0/1778686f608f03bc8a67fb560a2694e8aef74b392811508e98cc158d1839a1bb0a0256eb2ed719c4ee17e65a11543ddc4f9059d3bdd5dddcca6359ba1bab18bd - languageName: node - linkType: hard - "@walletconnect/logger@npm:2.1.2, @walletconnect/logger@npm:^2.0.1": version: 2.1.2 resolution: "@walletconnect/logger@npm:2.1.2" @@ -7354,13 +7311,13 @@ __metadata: languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/react-native-compat@npm:2.11.2" +"@walletconnect/react-native-compat@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/react-native-compat@npm:2.15.1" dependencies: events: "npm:3.3.0" - fast-text-encoding: "npm:^1.0.6" - react-native-url-polyfill: "npm:^2.0.0" + fast-text-encoding: "npm:1.0.6" + react-native-url-polyfill: "npm:2.0.0" peerDependencies: "@react-native-async-storage/async-storage": "*" "@react-native-community/netinfo": "*" @@ -7369,11 +7326,11 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: 10c0/b8a9d63de7e57078203c08a70f3b406d21582495cbea089160f5fd670d9c4db9afb9dee5507acb534507d4651cd6e1e38231930996cad6d8d5115d9b0d92124a + checksum: 10c0/996452a58797b811b4417c649943602decadb5cbe4dd2717f1aaaed42df0bebcbaeda81e43754d62c319d335702d9e1cb8161e9102ce1bd25354e41588b26b97 languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.10, @walletconnect/relay-api@npm:^1.0.9": +"@walletconnect/relay-api@npm:1.0.10": version: 1.0.10 resolution: "@walletconnect/relay-api@npm:1.0.10" dependencies: @@ -7382,7 +7339,16 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-auth@npm:1.0.4, @walletconnect/relay-auth@npm:^1.0.4": +"@walletconnect/relay-api@npm:1.0.11": + version: 1.0.11 + resolution: "@walletconnect/relay-api@npm:1.0.11" + dependencies: + "@walletconnect/jsonrpc-types": "npm:^1.0.2" + checksum: 10c0/2595d7e68d3a93e7735e0b6204811762898b0ce1466e811d78be5bcec7ac1cde5381637615a99104099165bf63695da5ef9381d6ded29924a57a71b10712a91d + languageName: node + linkType: hard + +"@walletconnect/relay-auth@npm:1.0.4": version: 1.0.4 resolution: "@walletconnect/relay-auth@npm:1.0.4" dependencies: @@ -7412,20 +7378,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/sign-client@npm:2.11.2" +"@walletconnect/sign-client@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/sign-client@npm:2.15.1" dependencies: - "@walletconnect/core": "npm:2.11.2" - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/heartbeat": "npm:1.2.1" + "@walletconnect/core": "npm:2.15.1" + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:^2.0.1" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - events: "npm:^3.3.0" - checksum: 10c0/c6ecf82bb247a5ae72e78a1f122c2458c5f7a36811125223bf394aaac92994db94782775cbbe3957a121c49c404cfd72ed09f648882407ccc1c180618ca6124c + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + events: "npm:3.3.0" + checksum: 10c0/4ff66239c2994cb501cd1830b2cbc2ecf854799cfc4d100cc6af3523f8524daead7c08daa7dba944def79640515ccabe2250f700e0be1a07dfee5c3668aa2dee languageName: node linkType: hard @@ -7449,20 +7415,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/types@npm:2.11.2" - dependencies: - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/heartbeat": "npm:1.2.1" - "@walletconnect/jsonrpc-types": "npm:1.0.3" - "@walletconnect/keyvaluestorage": "npm:^1.1.1" - "@walletconnect/logger": "npm:^2.0.1" - events: "npm:^3.3.0" - checksum: 10c0/3d0027e1b4bb6f281a237e4f887aa1ab36f17d713f8dce5987e5b5ca6890f50025d163fdf874d7057a554bdaaf19305c7afeefa7e7a6512037525263f64c2784 - languageName: node - linkType: hard - "@walletconnect/types@npm:2.13.3": version: 2.13.3 resolution: "@walletconnect/types@npm:2.13.3" @@ -7477,6 +7429,20 @@ __metadata: languageName: node linkType: hard +"@walletconnect/types@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/types@npm:2.15.1" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + events: "npm:3.3.0" + checksum: 10c0/0666874a4acd9326f2554542936a88f6df50ca958440254ab605dca81d57cdaf839b05cac983ad194f4ed44734d07d564f4277156556cadac07302e8dd86aa4d + languageName: node + linkType: hard + "@walletconnect/types@npm:^1.8.0": version: 1.8.0 resolution: "@walletconnect/types@npm:1.8.0" @@ -7484,47 +7450,47 @@ __metadata: languageName: node linkType: hard -"@walletconnect/utils@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/utils@npm:2.11.2" +"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": + version: 2.13.3 + resolution: "@walletconnect/utils@npm:2.13.3" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:^1.0.2" + "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:^1.0.3" - "@walletconnect/relay-api": "npm:^1.0.9" - "@walletconnect/safe-json": "npm:^1.0.2" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/window-getters": "npm:^1.0.1" - "@walletconnect/window-metadata": "npm:^1.0.1" + "@stablelib/x25519": "npm:1.0.3" + "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.13.3" + "@walletconnect/window-getters": "npm:1.0.1" + "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" - uint8arrays: "npm:^3.1.0" - checksum: 10c0/0adec7e6e88805d74b7bd44a12cf6765bae86678dd152dd23f120f5c183e10fad35d8257e1c5ae5a55f9b28fe0e2a9b07486e70280cd07b1267abb899b0a9ec1 + uint8arrays: "npm:3.1.0" + checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": - version: 2.13.3 - resolution: "@walletconnect/utils@npm:2.13.3" +"@walletconnect/utils@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/utils@npm:2.15.1" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/relay-api": "npm:1.0.11" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.3" + "@walletconnect/types": "npm:2.15.1" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad + checksum: 10c0/bde087f530f91502ba26c9553abd464234adb5738c7e10cfbdd275699d630a81601b5928abeeb77abe29e88238a4067a1bd1c5159f04b49178452066c82d99ae languageName: node linkType: hard @@ -7543,19 +7509,19 @@ __metadata: languageName: node linkType: hard -"@walletconnect/web3wallet@npm:1.10.2": - version: 1.10.2 - resolution: "@walletconnect/web3wallet@npm:1.10.2" +"@walletconnect/web3wallet@npm:1.14.1": + version: 1.14.1 + resolution: "@walletconnect/web3wallet@npm:1.14.1" dependencies: "@walletconnect/auth-client": "npm:2.1.2" - "@walletconnect/core": "npm:2.11.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.13" + "@walletconnect/core": "npm:2.15.1" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.0.1" - "@walletconnect/sign-client": "npm:2.11.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - checksum: 10c0/9f6950a1d49f8fbc14ba560bd98654f0d1cdaac5e2fd64560fe9cbfdc6fea98258c117fdf47c53754c44cae92a62dc29af24d2853afdca58d9d7e9e0a922ab0a + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.15.1" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + checksum: 10c0/1918cb3319036fef3ed5565a607b230e083ea37b12a067df22f1bc81ab34fcf9a48d7ff2f4eb9dbd16066432f0e53460db727466b976e009bab378db5b4da1f6 languageName: node linkType: hard @@ -7950,12 +7916,12 @@ __metadata: "@unstoppabledomains/resolution": "npm:7.1.4" "@wagmi/chains": "npm:1.8.0" "@walletconnect/client": "npm:1.8.0" - "@walletconnect/core": "npm:2.11.2" + "@walletconnect/core": "npm:2.15.1" "@walletconnect/legacy-utils": "npm:2.0.0" - "@walletconnect/react-native-compat": "npm:2.11.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - "@walletconnect/web3wallet": "npm:1.10.2" + "@walletconnect/react-native-compat": "npm:2.15.1" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + "@walletconnect/web3wallet": "npm:1.14.1" assert: "npm:1.5.0" ast-parser: "npm:0.0.5" async-mutex: "npm:0.3.2" @@ -7965,7 +7931,7 @@ __metadata: babel-plugin-date-fns: "npm:2.0.0" babel-plugin-graphql-tag: "npm:2.5.0" babel-plugin-lodash: "npm:3.3.4" - babel-plugin-module-resolver: "npm:4.0.0" + babel-plugin-module-resolver: "npm:5.0.2" babel-plugin-rewire: "npm:1.2.0" babel-plugin-styled-components: "npm:1.11.1" babel-plugin-transform-remove-console: "npm:6.9.4" @@ -9047,16 +9013,16 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:4.0.0": - version: 4.0.0 - resolution: "babel-plugin-module-resolver@npm:4.0.0" +"babel-plugin-module-resolver@npm:5.0.2": + version: 5.0.2 + resolution: "babel-plugin-module-resolver@npm:5.0.2" dependencies: - find-babel-config: "npm:^1.2.0" - glob: "npm:^7.1.6" + find-babel-config: "npm:^2.1.1" + glob: "npm:^9.3.3" pkg-up: "npm:^3.1.0" - reselect: "npm:^4.0.0" - resolve: "npm:^1.13.1" - checksum: 10c0/3b3eda8487aa56d768032763d653eab87eb7a3199e2cbf606e5c6883233f1b668c838bc751164bd28af5e12c566f9291bc928f845795b038805c227b2691e193 + reselect: "npm:^4.1.7" + resolve: "npm:^1.22.8" + checksum: 10c0/ccbb9e673c4219f68937349267521becb72be292cf30bf70b861c3e709d24fbfa589da0bf6c100a0def799d38199299171cb6eac3fb00b1ea740373e2c1fe54c languageName: node linkType: hard @@ -13484,7 +13450,7 @@ __metadata: languageName: node linkType: hard -"fast-text-encoding@npm:^1.0.6": +"fast-text-encoding@npm:1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" checksum: 10c0/e1d0381bda229c92c7906f63308f3b9caca8c78b732768b1ee16f560089ed21bc159bbe1434138ccd3815931ec8d4785bdade1ad1c45accfdf27ac6606ac67d2 @@ -13660,13 +13626,13 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^1.2.0": - version: 1.2.2 - resolution: "find-babel-config@npm:1.2.2" +"find-babel-config@npm:^2.1.1": + version: 2.1.1 + resolution: "find-babel-config@npm:2.1.1" dependencies: - json5: "npm:^1.0.2" - path-exists: "npm:^3.0.0" - checksum: 10c0/c82631323b055a3ea8d2dbc42593d243dddf39ec20e83bb6aad847d77676829f4a2bdf507c5177bc9d2d4509a5e239a6023631f1e8b8011ab16d44d227c65639 + json5: "npm:^2.2.3" + path-exists: "npm:^4.0.0" + checksum: 10c0/fb1f348027b83e9fe3b4163ec9c45719adab1205b2807708d660fe2a7beb5f812e0fabb6b21746cf0ef9c9fbf67bcc8e37f6cddb9d43229f0524767522d6110b languageName: node linkType: hard @@ -14408,6 +14374,18 @@ __metadata: languageName: node linkType: hard +"glob@npm:^9.3.3": + version: 9.3.5 + resolution: "glob@npm:9.3.5" + dependencies: + fs.realpath: "npm:^1.0.0" + minimatch: "npm:^8.0.2" + minipass: "npm:^4.2.4" + path-scurry: "npm:^1.6.1" + checksum: 10c0/2f6c2b9ee019ee21dc258ae97a88719614591e4c979cb4580b1b9df6f0f778a3cb38b4bdaf18dfa584637ea10f89a3c5f2533a5e449cf8741514ad18b0951f2e + languageName: node + linkType: hard + "global-modules@npm:^1.0.0": version: 1.0.0 resolution: "global-modules@npm:1.0.0" @@ -18262,6 +18240,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^8.0.2": + version: 8.0.4 + resolution: "minimatch@npm:8.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/a0a394c356dd5b4cb7f821720841a82fa6f07c9c562c5b716909d1b6ec5e56a7e4c4b5029da26dd256b7d2b3a3f38cbf9ddd8680e887b9b5282b09c05501c1ca + languageName: node + linkType: hard + "minimist@npm:1.2.0": version: 1.2.0 resolution: "minimist@npm:1.2.0" @@ -18360,6 +18347,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^4.2.4": + version: 4.2.8 + resolution: "minipass@npm:4.2.8" + checksum: 10c0/4ea76b030d97079f4429d6e8a8affd90baf1b6a1898977c8ccce4701c5a2ba2792e033abc6709373f25c2c4d4d95440d9d5e9464b46b7b76ca44d2ce26d939ce + languageName: node + linkType: hard + "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -19866,7 +19860,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": +"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -21460,7 +21454,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-url-polyfill@npm:2.0.0, react-native-url-polyfill@npm:^2.0.0": +"react-native-url-polyfill@npm:2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0" dependencies: @@ -22163,7 +22157,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"reselect@npm:^4.0.0": +"reselect@npm:^4.1.7": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: 10c0/06a305a504affcbb67dd0561ddc8306b35796199c7e15b38934c80606938a021eadcf68cfd58e7bb5e17786601c37602a3362a4665c7bf0a96c1041ceee9d0b7 @@ -22247,7 +22241,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4": +"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -22282,7 +22276,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": +"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -24805,7 +24799,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"uint8arrays@npm:^3.0.0, uint8arrays@npm:^3.1.0": +"uint8arrays@npm:^3.0.0": version: 3.1.1 resolution: "uint8arrays@npm:3.1.1" dependencies: From ea42f5fe27a86ac95cb2cfcfd65ffc2bf50134ac Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 27 Aug 2024 17:22:19 -0400 Subject: [PATCH 61/78] change aspect ratio (#6049) --- .../discover/components/DiscoverFeaturedResultsCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx index 1f3502d637e..78a7249adf2 100644 --- a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx +++ b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx @@ -8,7 +8,7 @@ import { StyleSheet } from 'react-native'; const { width: SCREEN_WIDTH } = deviceUtils.dimensions; const CARD_WIDTH = SCREEN_WIDTH - HORIZONTAL_PADDING * 2; -const IMAGE_ASPECT_RATIO = 16 / 9; +const CARD_HEIGHT = 238; type DiscoverFeaturedResultsCardProps = { handlePress: () => void; @@ -32,7 +32,7 @@ export const DiscoverFeaturedResultsCard = ({ handlePress, featuredResult }: Dis const styles = StyleSheet.create({ image: { width: CARD_WIDTH, - height: CARD_WIDTH / IMAGE_ASPECT_RATIO, + height: CARD_HEIGHT, borderRadius: 12, }, }); From e7d9b7c6b4f0420174412d4f5cc102afda11759d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:03:10 -0400 Subject: [PATCH 62/78] Bump webpack from 5.90.3 to 5.94.0 (#6048) Bumps [webpack](https://github.com/webpack/webpack) from 5.90.3 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.90.3...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 85 +++++++++++++++++++++++----------------------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index e430d27e344..533313474f1 100644 --- a/package.json +++ b/package.json @@ -372,7 +372,7 @@ "ts-migrate": "0.1.26", "typescript": "5.1.6", "typescript-coverage-report": "0.6.1", - "webpack": "5.90.3", + "webpack": "5.94.0", "webpack-cli": "5.1.4" }, "engines": { diff --git a/yarn.lock b/yarn.lock index 79f2367ed43..346f7b3376b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6229,27 +6229,7 @@ __metadata: languageName: node linkType: hard -"@types/eslint-scope@npm:^3.7.3": - version: 3.7.7 - resolution: "@types/eslint-scope@npm:3.7.7" - dependencies: - "@types/eslint": "npm:*" - "@types/estree": "npm:*" - checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc - languageName: node - linkType: hard - -"@types/eslint@npm:*": - version: 8.56.10 - resolution: "@types/eslint@npm:8.56.10" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 10c0/674349d6c342c3864d70f4d5a9965f96fb253801532752c8c500ad6a1c2e8b219e01ccff5dc8791dcb58b5483012c495708bb9f3ff929f5c9322b3da126c15d3 - languageName: node - linkType: hard - -"@types/estree@npm:*, @types/estree@npm:^1.0.5": +"@types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d @@ -6331,7 +6311,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -7560,7 +7540,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5": +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: @@ -7646,7 +7626,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5": +"@webassemblyjs/wasm-edit@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: @@ -7687,7 +7667,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5": +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: @@ -8118,7 +8098,7 @@ __metadata: viem: "npm:2.9.16" vm-browserify: "npm:0.0.4" w2t: "npm:3.0.2" - webpack: "npm:5.90.3" + webpack: "npm:5.94.0" webpack-cli: "npm:5.1.4" zod: "npm:3.23.8" zustand: "npm:4.5.4" @@ -8228,12 +8208,12 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.9.0": - version: 1.9.0 - resolution: "acorn-import-assertions@npm:1.9.0" +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" peerDependencies: acorn: ^8 - checksum: 10c0/3b4a194e128efdc9b86c2b1544f623aba4c1aa70d638f8ab7dc3971a5b4aa4c57bd62f99af6e5325bb5973c55863b4112e708a6f408bad7a138647ca72283afe + checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d languageName: node linkType: hard @@ -12281,7 +12261,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.15.0": +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0": version: 5.17.0 resolution: "enhanced-resolve@npm:5.17.0" dependencies: @@ -12291,6 +12271,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.17.1": + version: 5.17.1 + resolution: "enhanced-resolve@npm:5.17.1" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/81a0515675eca17efdba2cf5bad87abc91a528fc1191aad50e275e74f045b41506167d420099022da7181c8d787170ea41e4a11a0b10b7a16f6237daecb15370 + languageName: node + linkType: hard + "enquirer@npm:^2.3.0, enquirer@npm:^2.3.5, enquirer@npm:^2.3.6": version: 2.4.1 resolution: "enquirer@npm:2.4.1" @@ -14493,7 +14483,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -25477,13 +25467,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"watchpack@npm:^2.4.0": - version: 2.4.1 - resolution: "watchpack@npm:2.4.1" +"watchpack@npm:^2.4.1": + version: 2.4.2 + resolution: "watchpack@npm:2.4.2" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/c694de0a61004e587a8a0fdc9cfec20ee692c52032d9ab2c2e99969a37fdab9e6e1bd3164ed506f9a13f7c83e65563d563e0d6b87358470cdb7309b83db78683 + checksum: 10c0/ec60a5f0e9efaeca0102fd9126346b3b2d523e01c34030d3fddf5813a7125765121ebdc2552981136dcd2c852deb1af0b39340f2fcc235f292db5399d0283577 languageName: node linkType: hard @@ -25573,25 +25563,24 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"webpack@npm:5.90.3": - version: 5.90.3 - resolution: "webpack@npm:5.90.3" +"webpack@npm:5.94.0": + version: 5.94.0 + resolution: "webpack@npm:5.94.0" dependencies: - "@types/eslint-scope": "npm:^3.7.3" "@types/estree": "npm:^1.0.5" - "@webassemblyjs/ast": "npm:^1.11.5" - "@webassemblyjs/wasm-edit": "npm:^1.11.5" - "@webassemblyjs/wasm-parser": "npm:^1.11.5" + "@webassemblyjs/ast": "npm:^1.12.1" + "@webassemblyjs/wasm-edit": "npm:^1.12.1" + "@webassemblyjs/wasm-parser": "npm:^1.12.1" acorn: "npm:^8.7.1" - acorn-import-assertions: "npm:^1.9.0" + acorn-import-attributes: "npm:^1.9.5" browserslist: "npm:^4.21.10" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.15.0" + enhanced-resolve: "npm:^5.17.1" es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.9" + graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" loader-runner: "npm:^4.2.0" mime-types: "npm:^2.1.27" @@ -25599,14 +25588,14 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: schema-utils: "npm:^3.2.0" tapable: "npm:^2.1.1" terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.0" + watchpack: "npm:^2.4.1" webpack-sources: "npm:^3.2.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10c0/f737aa871cadbbae89833eb85387f1bf9ee0768f039100a3c8134f2fdcc78c3230ca775c373b1aa467b272f74c6831e119f7a8a1c14dcac97327212be9c93eeb + checksum: 10c0/b4d1b751f634079bd177a89eef84d80fa5bb8d6fc15d72ab40fc2b9ca5167a79b56585e1a849e9e27e259803ee5c4365cb719e54af70a43c06358ec268ff4ebf languageName: node linkType: hard From deaf14bfa1c0158734e5e87f9444b0356e24a5f3 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:36:37 -0600 Subject: [PATCH 63/78] Fix iOS dapp browser http loads (#6054) --- ios/Rainbow/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/Rainbow/Info.plist b/ios/Rainbow/Info.plist index 4d31e1cc358..91736a5af8d 100644 --- a/ios/Rainbow/Info.plist +++ b/ios/Rainbow/Info.plist @@ -187,6 +187,8 @@ NSAllowsArbitraryLoads + NSAllowsArbitraryLoadsInWebContent + NSAllowsLocalNetworking From 254e06db205c247d7472ba38278142de81c3d52e Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 28 Aug 2024 15:39:15 -0400 Subject: [PATCH 64/78] Transaction Simulation Cleanup (#5977) * lots of sign transaction sheet cleanup including original ticket fix * fix toHex ref * mwp * create mwp secure store and wrap app in provider * add android intents and clena up app.tsx * idk * changes * handshake request * basic impl * update some comments * some progress today at least * move business logic off App.tsx * move backup check to wallet screen * latest changes * use suspense and lazy to fix oop issue * expose config * recursively handle incoming actions * only process one incoming message at once * UNDO THIS PROBABLY * progress on personal sign * personal sign working * send transactions working * cleanup * fix lint and other calls to getRequestDisplayDetails * Update src/handlers/deeplinks.ts * fml * add switch eth chain * lint * fix lint * code review changes --- globals.d.ts | 1 + ios/Podfile.lock | 12 + package.json | 1 + src/App.tsx | 167 +- src/components/AppStateChangeHandler.tsx | 38 + src/components/DeeplinkHandler.tsx | 65 + src/components/FadeGradient.tsx | 47 + src/components/FadedScrollCard.tsx | 281 +++ .../MobileWalletProtocolListener.tsx | 51 + .../Transactions/TransactionDetailsCard.tsx | 127 ++ .../Transactions/TransactionDetailsRow.tsx | 90 + .../Transactions/TransactionIcons.tsx | 196 ++ .../Transactions/TransactionMessageCard.tsx | 113 + .../TransactionSimulatedEventRow.tsx | 109 + .../TransactionSimulationCard.tsx | 315 +++ src/components/Transactions/constants.ts | 121 ++ src/components/Transactions/types.ts | 18 + src/handlers/deeplinks.ts | 19 +- src/hooks/useApplicationSetup.ts | 55 + src/hooks/useCalculateGasLimit.ts | 74 + src/hooks/useConfirmTransaction.ts | 27 + src/hooks/useHasEnoughBalance.ts | 47 + src/hooks/useNonceForDisplay.ts | 32 + src/hooks/useProviderSetup.ts | 50 + src/hooks/useSubmitTransaction.ts | 55 + src/hooks/useTransactionSetup.ts | 76 + src/languages/en_US.json | 1 + src/navigation/Routes.android.tsx | 29 +- src/navigation/Routes.ios.tsx | 29 +- src/navigation/config.tsx | 3 +- src/notifications/tokens.ts | 2 +- src/parsers/requests.js | 6 +- src/redux/requests.ts | 6 +- src/redux/walletconnect.ts | 4 +- .../transactions/transactionSimulation.ts | 148 ++ src/screens/SignTransactionSheet.tsx | 1920 ++--------------- src/screens/WalletScreen/index.tsx | 6 + src/state/performance/operations.ts | 1 + src/storage/index.ts | 51 +- src/utils/formatDate.ts | 28 + src/utils/requestNavigationHandlers.ts | 274 ++- src/walletConnect/index.tsx | 2 +- tsconfig.json | 1 + yarn.lock | 34 + 44 files changed, 2831 insertions(+), 1901 deletions(-) create mode 100644 src/components/AppStateChangeHandler.tsx create mode 100644 src/components/DeeplinkHandler.tsx create mode 100644 src/components/FadeGradient.tsx create mode 100644 src/components/FadedScrollCard.tsx create mode 100644 src/components/MobileWalletProtocolListener.tsx create mode 100644 src/components/Transactions/TransactionDetailsCard.tsx create mode 100644 src/components/Transactions/TransactionDetailsRow.tsx create mode 100644 src/components/Transactions/TransactionIcons.tsx create mode 100644 src/components/Transactions/TransactionMessageCard.tsx create mode 100644 src/components/Transactions/TransactionSimulatedEventRow.tsx create mode 100644 src/components/Transactions/TransactionSimulationCard.tsx create mode 100644 src/components/Transactions/constants.ts create mode 100644 src/components/Transactions/types.ts create mode 100644 src/hooks/useApplicationSetup.ts create mode 100644 src/hooks/useCalculateGasLimit.ts create mode 100644 src/hooks/useConfirmTransaction.ts create mode 100644 src/hooks/useHasEnoughBalance.ts create mode 100644 src/hooks/useNonceForDisplay.ts create mode 100644 src/hooks/useProviderSetup.ts create mode 100644 src/hooks/useSubmitTransaction.ts create mode 100644 src/hooks/useTransactionSetup.ts create mode 100644 src/resources/transactions/transactionSimulation.ts create mode 100644 src/utils/formatDate.ts diff --git a/globals.d.ts b/globals.d.ts index 29d1107dba2..068c5a86e74 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -109,4 +109,5 @@ declare module 'react-native-dotenv' { export const REACT_NATIVE_RUDDERSTACK_WRITE_KEY: string; export const RUDDERSTACK_DATA_PLANE_URL: string; export const SILENCE_EMOJI_WARNINGS: boolean; + export const MWP_ENCRYPTION_KEY: string; } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1c400b0f1c1..f28ca0bae70 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,6 +4,9 @@ PODS: - BVLinearGradient (2.8.3): - React-Core - CocoaAsyncSocket (7.6.5) + - CoinbaseWalletSDK/Client (1.1.0) + - CoinbaseWalletSDK/Host (1.1.0): + - CoinbaseWalletSDK/Client - DoubleConversion (1.1.6) - FasterImage (1.6.2): - FasterImage/Nuke (= 1.6.2) @@ -171,6 +174,9 @@ PODS: - MMKV (1.3.9): - MMKVCore (~> 1.3.9) - MMKVCore (1.3.9) + - mobile-wallet-protocol-host (0.1.7): + - CoinbaseWalletSDK/Host + - React-Core - MultiplatformBleAdapter (0.1.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -1826,6 +1832,7 @@ DEPENDENCIES: - GoogleUtilities - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libwebp + - "mobile-wallet-protocol-host (from `../node_modules/@coinbase/mobile-wallet-protocol-host`)" - nanopb - PanModal (from `https://github.com/rainbow-me/PanModal`, commit `ab97d74279ba28c2891b47a5dc767ed4dd7cf994`) - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`) @@ -1956,6 +1963,7 @@ SPEC REPOS: https://github.com/CocoaPods/Specs.git: - Branch - CocoaAsyncSocket + - CoinbaseWalletSDK - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -2007,6 +2015,8 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 + mobile-wallet-protocol-host: + :path: "../node_modules/@coinbase/mobile-wallet-protocol-host" PanModal: :commit: ab97d74279ba28c2891b47a5dc767ed4dd7cf994 :git: https://github.com/rainbow-me/PanModal @@ -2257,6 +2267,7 @@ SPEC CHECKSUMS: Branch: d99436c6f3d5b2529ba948d273e47e732830f207 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 + CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 @@ -2282,6 +2293,7 @@ SPEC CHECKSUMS: MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843 MMKV: 817ba1eea17421547e01e087285606eb270a8dcb MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 + mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d nanopb: 438bc412db1928dac798aa6fd75726007be04262 PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3 diff --git a/package.json b/package.json index 533313474f1..7ff3845f425 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@bradgarropy/use-countdown": "1.4.1", "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", + "@coinbase/mobile-wallet-protocol-host": "0.1.7", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", "@ensdomains/eth-ens-namehash": "2.0.15", diff --git a/src/App.tsx b/src/App.tsx index 02c836a846e..128324099b8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,11 @@ import './languages'; import * as Sentry from '@sentry/react-native'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; -import branch from 'react-native-branch'; - +import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react'; +import { AppRegistry, Dimensions, LogBox, StyleSheet, View } from 'react-native'; +import { MobileWalletProtocolProvider } from '@coinbase/mobile-wallet-protocol-host'; +import { DeeplinkHandler } from '@/components/DeeplinkHandler'; +import { AppStateChangeHandler } from '@/components/AppStateChangeHandler'; +import { useApplicationSetup } from '@/hooks/useApplicationSetup'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; @@ -15,26 +17,16 @@ import { OfflineToast } from './components/toasts'; import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug'; import monitorNetwork from './debugging/network'; import { Playground } from './design-system/playground/Playground'; -import handleDeeplink from './handlers/deeplinks'; -import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; import RainbowContextWrapper from './helpers/RainbowContext'; -import isTestFlight from './helpers/isTestFlight'; import * as keychain from '@/model/keychain'; -import { loadAddress } from './model/wallet'; import { Navigation } from './navigation'; import RoutesComponent from './navigation/Routes'; -import { PerformanceContextMap } from './performance/PerformanceContextMap'; -import { PerformanceTracking } from './performance/tracking'; -import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetrics'; import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query'; import store from './redux/store'; -import { walletConnectLoadState } from './redux/walletconnect'; import { MainThemeProvider } from './theme/ThemeContext'; -import { branchListener } from './utils/branch'; import { addressKey } from './utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute'; -import Routes from '@/navigation/routesNames'; import { Portal } from '@/react-native-cool-modals/Portal'; import { NotificationsHandler } from '@/notifications/NotificationsHandler'; import { analyticsV2 } from '@/analytics'; @@ -42,19 +34,17 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; -import { initListeners as initWalletConnectListeners } from '@/walletConnect'; -import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; -import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { initializeRemoteConfig } from '@/model/remoteConfig'; import { NavigationContainerRef } from '@react-navigation/native'; import { RootStackParamList } from './navigation/types'; import { Address } from 'viem'; import { IS_DEV } from './env'; -import { checkIdentifierOnLaunch } from './model/backup'; import { prefetchDefaultFavorites } from './resources/favorites'; +const LazyRoutesComponent = lazy(() => import('./navigation/Routes')); + if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); (showNetworkRequests || showNetworkResponses) && monitorNetwork(showNetworkRequests, showNetworkResponses); @@ -62,123 +52,40 @@ if (IS_DEV) { enableScreens(); -const containerStyle = { flex: 1 }; +const sx = StyleSheet.create({ + container: { + flex: 1, + }, +}); interface AppProps { walletReady: boolean; } function App({ walletReady }: AppProps) { - const [appState, setAppState] = useState(AppState.currentState); - const [initialRoute, setInitialRoute] = useState(null); - const eventSubscription = useRef | null>(null); - const branchListenerRef = useRef | null>(null); - const navigatorRef = useRef | null>(null); - - const setupDeeplinking = useCallback(async () => { - const initialUrl = await Linking.getInitialURL(); - - branchListenerRef.current = await branchListener(url => { - logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); - try { - handleDeeplink(url, initialRoute); - } catch (error) { - if (error instanceof Error) { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: error.message, - url, - }); - } else { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: 'Unknown error', - url, - }); - } - } - }); - - if (initialUrl) { - logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); - branch.openURL(initialUrl); - } - }, [initialRoute]); - - const identifyFlow = useCallback(async () => { - const address = await loadAddress(); - if (address) { - setTimeout(() => { - InteractionManager.runAfterInteractions(() => { - handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); - }); - }, 10_000); - - InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); - } - - setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - }, []); - - const handleAppStateChange = useCallback( - (nextAppState: AppStateStatus) => { - if (appState === 'background' && nextAppState === 'active') { - store.dispatch(walletConnectLoadState()); - } - setAppState(nextAppState); - analyticsV2.track(analyticsV2.event.appStateChange, { - category: 'app state', - label: nextAppState, - }); - }, - [appState] - ); + const { initialRoute } = useApplicationSetup(); const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { - navigatorRef.current = ref; Navigation.setTopLevelNavigator(ref); }, []); - useEffect(() => { - if (!__DEV__ && isTestFlight) { - logger.debug(`[App]: Test flight usage - ${isTestFlight}`); - } - identifyFlow(); - eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); - - const p1 = analyticsV2.initializeRudderstack(); - const p2 = setupDeeplinking(); - const p3 = saveFCMToken(); - Promise.all([p1, p2, p3]).then(() => { - initWalletConnectListeners(); - PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); - analyticsV2.track(analyticsV2.event.applicationDidMount); - }); - - return () => { - eventSubscription.current?.remove(); - branchListenerRef.current?.(); - }; - }, []); - - useEffect(() => { - if (walletReady) { - logger.debug(`[App]: ✅ Wallet ready!`); - runWalletBackupStatusChecks(); - } - }, [walletReady]); - return ( - + {initialRoute && ( - + }> + {/* @ts-expect-error - Property 'ref' does not exist on the 'IntrinsicAttributes' object */} + + )} + + ); } @@ -192,9 +99,9 @@ const AppWithRedux = connect(state => }))(App); function Root() { - const [initializing, setInitializing] = React.useState(true); + const [initializing, setInitializing] = useState(true); - React.useEffect(() => { + useEffect(() => { async function initializeApplication() { await initializeRemoteConfig(); await migrate(); @@ -300,19 +207,21 @@ function Root() { prefetchDefaultFavorites(); }} > - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/components/AppStateChangeHandler.tsx b/src/components/AppStateChangeHandler.tsx new file mode 100644 index 00000000000..ee8b29d2ebb --- /dev/null +++ b/src/components/AppStateChangeHandler.tsx @@ -0,0 +1,38 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { AppState, AppStateStatus, Linking } from 'react-native'; +import { analyticsV2 } from '@/analytics'; +import store from '@/redux/store'; +import { walletConnectLoadState } from '@/redux/walletconnect'; + +type AppStateChangeHandlerProps = { + walletReady: boolean; +}; + +export function AppStateChangeHandler({ walletReady }: AppStateChangeHandlerProps) { + const [appState, setAppState] = useState(AppState.currentState); + const eventSubscription = useRef | null>(null); + + const handleAppStateChange = useCallback( + (nextAppState: AppStateStatus) => { + if (appState === 'background' && nextAppState === 'active') { + store.dispatch(walletConnectLoadState()); + } + setAppState(nextAppState); + analyticsV2.track(analyticsV2.event.appStateChange, { + category: 'app state', + label: nextAppState, + }); + }, + [appState] + ); + + useEffect(() => { + if (!walletReady) return; + + eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + + return () => eventSubscription.current?.remove(); + }, [handleAppStateChange]); + + return null; +} diff --git a/src/components/DeeplinkHandler.tsx b/src/components/DeeplinkHandler.tsx new file mode 100644 index 00000000000..e34f7ef4e8b --- /dev/null +++ b/src/components/DeeplinkHandler.tsx @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { Linking } from 'react-native'; +import branch from 'react-native-branch'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import handleDeeplink from '@/handlers/deeplinks'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { logger, RainbowError } from '@/logger'; +import { branchListener } from '@/utils/branch'; + +type DeeplinkHandlerProps = { + initialRoute: InitialRoute; + walletReady: boolean; +}; + +export function DeeplinkHandler({ initialRoute, walletReady }: DeeplinkHandlerProps) { + const branchListenerRef = useRef | null>(null); + const { handleRequestUrl, sendFailureToClient } = useMobileWalletProtocolHost(); + + const setupDeeplinking = useCallback(async () => { + const initialUrl = await Linking.getInitialURL(); + + branchListenerRef.current = await branchListener(async url => { + logger.debug(`[App]: Branch listener called`, {}, logger.DebugContext.deeplinks); + + try { + handleDeeplink({ + url, + initialRoute, + handleRequestUrl, + sendFailureToClient, + }); + } catch (error) { + if (error instanceof Error) { + logger.error(new RainbowError('Error opening deeplink'), { + message: error.message, + url, + }); + } else { + logger.error(new RainbowError('Error opening deeplink'), { + message: 'Unknown error', + url, + }); + } + } + }); + + if (initialUrl) { + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); + branch.openURL(initialUrl); + } + }, [handleRequestUrl, initialRoute, sendFailureToClient]); + + useEffect(() => { + if (!walletReady) return; + + setupDeeplinking(); + return () => { + if (branchListenerRef.current) { + branchListenerRef.current(); + } + }; + }, [setupDeeplinking, walletReady]); + + return null; +} diff --git a/src/components/FadeGradient.tsx b/src/components/FadeGradient.tsx new file mode 100644 index 00000000000..88fdade67a7 --- /dev/null +++ b/src/components/FadeGradient.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated from 'react-native-reanimated'; + +import { Box, globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +type FadeGradientProps = { side: 'top' | 'bottom'; style?: StyleProp>> }; + +export const FadeGradient = ({ side, style }: FadeGradientProps) => { + const { colors, isDarkMode } = useTheme(); + + const isTop = side === 'top'; + const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; + const transparentColor = colors.alpha(solidColor, 0); + + return ( + + + + ); +}; diff --git a/src/components/FadedScrollCard.tsx b/src/components/FadedScrollCard.tsx new file mode 100644 index 00000000000..bf8a1ed9c39 --- /dev/null +++ b/src/components/FadedScrollCard.tsx @@ -0,0 +1,281 @@ +import React, { useCallback, useState } from 'react'; +import { TouchableWithoutFeedback } from 'react-native'; +import Animated, { + Easing, + SharedValue, + interpolate, + interpolateColor, + measure, + runOnJS, + runOnUI, + useAnimatedReaction, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +import { globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +import { useDimensions } from '@/hooks'; +import { FadeGradient } from '@/components/FadeGradient'; + +const COLLAPSED_CARD_HEIGHT = 56; +const MAX_CARD_HEIGHT = 176; + +const CARD_BORDER_WIDTH = 1.5; + +const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +type FadedScrollCardProps = { + cardHeight: SharedValue; + children: React.ReactNode; + contentHeight: SharedValue; + expandedCardBottomInset?: number; + expandedCardTopInset?: number; + initialScrollEnabled?: boolean; + isExpanded: boolean; + onPressCollapsedCard?: () => void; + skipCollapsedState?: boolean; +}; + +export const FadedScrollCard = ({ + cardHeight, + children, + contentHeight, + expandedCardBottomInset = 120, + expandedCardTopInset = 120, + initialScrollEnabled, + isExpanded, + onPressCollapsedCard, + skipCollapsedState, +}: FadedScrollCardProps) => { + const { height: deviceHeight, width: deviceWidth } = useDimensions(); + const { isDarkMode } = useTheme(); + + const cardRef = useAnimatedRef(); + + const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); + const [isFullyExpanded, setIsFullyExpanded] = useState(false); + + const yPosition = useSharedValue(0); + + const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); + + const containerStyle = useAnimatedStyle(() => { + return { + height: + cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState + ? interpolate( + cardHeight.value, + [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], + [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], + 'clamp' + ) + : undefined, + zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), + }; + }); + + const backdropStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const cardStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); + + const outputRange = [0, 0]; + + const yPos = -yPosition.value; + const offset = + deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight - (yPosition.value + expandedCardHeight); + + if (yPos + expandedCardTopInset + offset >= deviceHeight - expandedCardBottomInset) { + outputRange.push(0); + } else { + outputRange.push(deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight); + } + + return { + borderColor: interpolateColor( + cardHeight.value, + [0, MAX_CARD_HEIGHT, expandedCardHeight], + isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] + ), + height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, + position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', + transform: [ + { + translateY: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, expandedCardHeight], outputRange), + }, + ], + }; + }); + + const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { + return { + transform: skipCollapsedState + ? undefined + : [ + { + translateY: interpolate( + cardHeight.value, + [ + 0, + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT + ? MAX_CARD_HEIGHT + : contentHeight.value + CARD_BORDER_WIDTH * 2, + maxExpandedHeight, + ], + [-2, -2, 0, 0] + ), + }, + ], + }; + }); + + const shadowStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const handleContentSizeChange = useCallback( + (width: number, height: number) => { + contentHeight.value = Math.round(height); + }, + [contentHeight] + ); + + const handleOnLayout = useCallback(() => { + runOnUI(() => { + if (cardHeight.value === MAX_CARD_HEIGHT) { + const measurement = measure(cardRef); + if (measurement === null) { + return; + } + if (yPosition.value !== measurement.pageY) { + yPosition.value = measurement.pageY; + } + } + })(); + }, [cardHeight, cardRef, yPosition]); + + useAnimatedReaction( + () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), + ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { + if ( + isFullyExpanded !== previous?.isFullyExpanded || + isExpanded !== previous?.isExpanded || + contentHeight !== previous?.contentHeight + ) { + if (isFullyExpanded) { + const expandedCardHeight = + contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; + if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { + cardHeight.value = withTiming(expandedCardHeight, timingConfig); + } else { + runOnJS(setIsFullyExpanded)(false); + } + } else if (isExpanded) { + cardHeight.value = withTiming( + contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, + timingConfig + ); + } else { + cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); + } + + const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); + runOnJS(setScrollEnabled)(enableScroll); + } + } + ); + + return ( + + { + if (isFullyExpanded) { + setIsFullyExpanded(false); + } + }} + pointerEvents={isFullyExpanded ? 'auto' : 'none'} + style={[ + { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + height: deviceHeight * 3, + left: -deviceWidth * 0.5, + position: 'absolute', + top: -deviceHeight, + width: deviceWidth * 2, + zIndex: -1, + }, + backdropStyle, + ]} + /> + + + + { + if (!isFullyExpanded) { + setIsFullyExpanded(true); + } else setIsFullyExpanded(false); + } + } + > + {children} + + + + + + + + ); +}; diff --git a/src/components/MobileWalletProtocolListener.tsx b/src/components/MobileWalletProtocolListener.tsx new file mode 100644 index 00000000000..395c58fd6b4 --- /dev/null +++ b/src/components/MobileWalletProtocolListener.tsx @@ -0,0 +1,51 @@ +import { useEffect, useRef } from 'react'; +import { addDiagnosticLogListener, getAndroidIntentUrl, useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { handleMobileWalletProtocolRequest } from '@/utils/requestNavigationHandlers'; +import { logger, RainbowError } from '@/logger'; +import { IS_ANDROID, IS_DEV } from '@/env'; + +export const MobileWalletProtocolListener = () => { + const { message, handleRequestUrl, sendFailureToClient, ...mwpProps } = useMobileWalletProtocolHost(); + const lastMessageUuidRef = useRef(null); + + useEffect(() => { + if (message && lastMessageUuidRef.current !== message.uuid) { + lastMessageUuidRef.current = message.uuid; + try { + handleMobileWalletProtocolRequest({ request: message, ...mwpProps }); + } catch (error) { + logger.error(new RainbowError('Error handling Mobile Wallet Protocol request'), { + error, + }); + } + } + }, [message, mwpProps]); + + useEffect(() => { + if (IS_DEV) { + const removeListener = addDiagnosticLogListener(event => { + console.log('Event:', JSON.stringify(event, null, 2)); + }); + + return () => removeListener(); + } + }, []); + + useEffect(() => { + if (IS_ANDROID) { + (async function handleAndroidIntent() { + const intentUrl = await getAndroidIntentUrl(); + if (intentUrl) { + const response = await handleRequestUrl(intentUrl); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + } + })(); + } + }, [handleRequestUrl, sendFailureToClient]); + + return null; +}; diff --git a/src/components/Transactions/TransactionDetailsCard.tsx b/src/components/Transactions/TransactionDetailsCard.tsx new file mode 100644 index 00000000000..8e00ee8e78a --- /dev/null +++ b/src/components/Transactions/TransactionDetailsCard.tsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import * as i18n from '@/languages'; +import Animated, { interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { abbreviations, ethereumUtils } from '@/utils'; +import { TransactionSimulationMeta } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/__swaps__/types/chains'; + +import { getNetworkObj, getNetworkObject } from '@/networks'; +import { TransactionDetailsRow } from '@/components/Transactions/TransactionDetailsRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { IconContainer } from '@/components/Transactions/TransactionIcons'; +import { formatDate } from '@/utils/formatDate'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; + +interface TransactionDetailsCardProps { + currentChainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + meta: TransactionSimulationMeta | undefined; + methodName: string; + noChanges: boolean; + nonce: string | undefined; + toAddress: string; +} + +export const TransactionDetailsCard = ({ + currentChainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + meta, + methodName, + noChanges, + nonce, + toAddress, +}: TransactionDetailsCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const [isExpanded, setIsExpanded] = useState(false); + + const currentNetwork = getNetworkObject({ chainId: currentChainId }); + + const listStyle = useAnimatedStyle(() => ({ + opacity: interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; + + const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); + const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; + const showTransferToRow = !!meta?.transferTo?.address; + // Hide DetailsCard if balance is insufficient once loaded + if (!isLoading && isBalanceEnough === false) { + return <>; + } + return ( + setIsExpanded(true)} + > + + + + + + 􁙠 + + + + {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} + + + + + + {} + {!!(meta?.to?.address || toAddress || showTransferToRow) && ( + + ethereumUtils.openAddressInBlockExplorer( + meta?.to?.address || toAddress || meta?.transferTo?.address || '', + currentChainId + ) + } + value={ + meta?.to?.name || + abbreviations.address(meta?.to?.address || toAddress, 4, 6) || + meta?.to?.address || + toAddress || + meta?.transferTo?.address || + '' + } + /> + )} + {showFunctionRow && } + {!!meta?.to?.sourceCodeStatus && } + {!!meta?.to?.created && } + {nonce && } + + + + + ); +}; diff --git a/src/components/Transactions/TransactionDetailsRow.tsx b/src/components/Transactions/TransactionDetailsRow.tsx new file mode 100644 index 00000000000..bbe2a8394de --- /dev/null +++ b/src/components/Transactions/TransactionDetailsRow.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { ChainImage } from '@/components/coin-icon/ChainImage'; +import { Box, Inline, Text } from '@/design-system'; + +import { DetailIcon, DetailBadge, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { SMALL_CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { DetailType, DetailInfo } from '@/components/Transactions/types'; +import { ChainId } from '@/__swaps__/types/chains'; + +interface TransactionDetailsRowProps { + currentChainId?: ChainId; + detailType: DetailType; + onPress?: () => void; + value: string; +} + +export const TransactionDetailsRow = ({ currentChainId, detailType, onPress, value }: TransactionDetailsRowProps) => { + const detailInfo: DetailInfo = infoForDetailType[detailType]; + + return ( + + + + + + {detailInfo.label} + + + + {detailType === 'function' && } + {detailType === 'sourceCodeVerification' && ( + + )} + {detailType === 'chain' && currentChainId && } + {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( + + {value} + + )} + {(detailType === 'contract' || detailType === 'to') && ( + + + + + 􀂄 + + + + + )} + + + + ); +}; + +const infoForDetailType: { [key: string]: DetailInfo } = { + chain: { + icon: '􀤆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), + }, + contract: { + icon: '􀉆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), + }, + to: { + icon: '􀉩', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), + }, + function: { + icon: '􀡅', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), + }, + sourceCodeVerification: { + icon: '􀕹', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), + }, + dateCreated: { + icon: '􀉉', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), + }, + nonce: { + icon: '􀆃', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), + }, +}; diff --git a/src/components/Transactions/TransactionIcons.tsx b/src/components/Transactions/TransactionIcons.tsx new file mode 100644 index 00000000000..53ef21fcd6e --- /dev/null +++ b/src/components/Transactions/TransactionIcons.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { AnimatePresence, MotiView } from 'moti'; + +import { Bleed, Box, Text, globalColors, useForegroundColor } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; +import { infoForEventType, motiTimingConfig } from '@/components/Transactions/constants'; + +import { useTheme } from '@/theme'; +import { DetailInfo, EventInfo, EventType } from '@/components/Transactions/types'; + +export const EventIcon = ({ eventType }: { eventType: EventType }) => { + const eventInfo: EventInfo = infoForEventType[eventType]; + + const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; + const isWarningIcon = + eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; + + return ( + + {!hideInnerFill && ( + + )} + + {eventInfo.icon} + + + ); +}; + +export const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { + return ( + + + {detailInfo.icon} + + + ); +}; + +export const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { + const { colors, isDarkMode } = useTheme(); + const separatorTertiary = useForegroundColor('separatorTertiary'); + + const infoForBadgeType: { + [key: string]: { + backgroundColor: string; + borderColor: string; + label?: string; + text: TextColor; + textOpacity?: number; + }; + } = { + function: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + text: 'labelQuaternary', + }, + unknown: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + label: 'Unknown', + text: 'labelQuaternary', + }, + unverified: { + backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, + borderColor: colors.alpha(colors.red, 0.02), + label: 'Unverified', + text: 'red', + textOpacity: 0.76, + }, + verified: { + backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, + borderColor: colors.alpha(colors.green, 0.02), + label: 'Verified', + text: 'green', + textOpacity: 0.76, + }, + }; + + return ( + + + + {infoForBadgeType[type].label || value} + + + + ); +}; + +export const VerifiedBadge = () => { + return ( + + + + + 􀇻 + + + + ); +}; + +export const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { + return ( + + {visible && ( + + + + + + 􀁣 + + + + + )} + + ); +}; + +export const IconContainer = ({ + children, + hitSlop, + opacity, + size = 20, +}: { + children: React.ReactNode; + hitSlop?: number; + opacity?: number; + size?: number; +}) => { + // Prevent wide icons from being clipped + const extraHorizontalSpace = 4; + + return ( + + + {children} + + + ); +}; diff --git a/src/components/Transactions/TransactionMessageCard.tsx b/src/components/Transactions/TransactionMessageCard.tsx new file mode 100644 index 00000000000..3e946bf6d6d --- /dev/null +++ b/src/components/Transactions/TransactionMessageCard.tsx @@ -0,0 +1,113 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; +import { useSharedValue } from 'react-native-reanimated'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; + +import { useClipboard } from '@/hooks'; +import { logger } from '@/logger'; +import { isSignTypedData } from '@/utils/signingMethods'; + +import { RPCMethod } from '@/walletConnect/types'; +import { sanitizeTypedData } from '@/utils/signingUtils'; +import { + estimateMessageHeight, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { AnimatedCheckmark, IconContainer } from '@/components/Transactions/TransactionIcons'; + +type TransactionMessageCardProps = { + expandedCardBottomInset: number; + message: string; + method: RPCMethod; +}; + +export const TransactionMessageCard = ({ expandedCardBottomInset, message, method }: TransactionMessageCardProps) => { + const { setClipboard } = useClipboard(); + const [didCopy, setDidCopy] = useState(false); + + let displayMessage = message; + if (isSignTypedData(method)) { + try { + const parsedMessage = JSON.parse(message); + const sanitizedMessage = sanitizeTypedData(parsedMessage); + displayMessage = sanitizedMessage; + } catch (error) { + logger.warn(`[TransactionMessageCard]: Error parsing signed typed data for ${method}`, { + error, + }); + } + + displayMessage = JSON.stringify(displayMessage, null, 4); + } + + const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); + + const cardHeight = useSharedValue( + estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 + ); + const contentHeight = useSharedValue(estimatedMessageHeight); + + const handleCopyPress = useCallback( + (message: string) => { + if (didCopy) return; + setClipboard(message); + setDidCopy(true); + const copyTimer = setTimeout(() => { + setDidCopy(false); + }, 2000); + return () => clearTimeout(copyTimer); + }, + [didCopy, setClipboard] + ); + + return ( + MAX_CARD_HEIGHT} + isExpanded + skipCollapsedState + > + + + + + + 􀙤 + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} + + + + handleCopyPress(message)}> + + + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} + + + + + + + + + {displayMessage} + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulatedEventRow.tsx b/src/components/Transactions/TransactionSimulatedEventRow.tsx new file mode 100644 index 00000000000..ce4d26052eb --- /dev/null +++ b/src/components/Transactions/TransactionSimulatedEventRow.tsx @@ -0,0 +1,109 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import { Image, PixelRatio } from 'react-native'; + +import { Bleed, Box, Inline, Text } from '@/design-system'; + +import { useTheme } from '@/theme'; +import { TransactionAssetType, TransactionSimulationAsset } from '@/graphql/__generated__/metadataPOST'; +import { Network } from '@/networks/types'; +import { convertAmountToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; + +import { useAccountSettings } from '@/hooks'; + +import { maybeSignUri } from '@/handlers/imgix'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; +import { EventInfo, EventType } from '@/components/Transactions/types'; +import { infoForEventType, CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { EventIcon } from '@/components/Transactions/TransactionIcons'; +import { ethereumUtils } from '@/utils'; + +type TransactionSimulatedEventRowProps = { + amount: string | 'unlimited'; + asset: TransactionSimulationAsset | undefined; + eventType: EventType; + price?: number | undefined; +}; + +export const TransactionSimulatedEventRow = ({ amount, asset, eventType, price }: TransactionSimulatedEventRowProps) => { + const theme = useTheme(); + const { nativeCurrency } = useAccountSettings(); + + const chainId = ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet); + + const { data: externalAsset } = useExternalToken({ + address: asset?.assetCode || '', + chainId, + currency: nativeCurrency, + }); + + const eventInfo: EventInfo = infoForEventType[eventType]; + + const formattedAmount = useMemo(() => { + if (!asset) return; + + const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; + const assetDisplayName = + asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; + const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; + + const displayAmount = + asset?.decimals === 0 + ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` + : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; + + const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; + + return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; + }, [amount, asset, eventInfo?.amountPrefix]); + + const url = maybeSignUri(asset?.iconURL, { + fm: 'png', + w: 16 * PixelRatio.get(), + }); + + const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; + + const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); + + return ( + + + + + + + {eventInfo.label} + + {showUSD && ( + + {formattedPrice} + + )} + + + + + {asset?.type !== TransactionAssetType.Nft ? ( + + ) : ( + + )} + + + {formattedAmount} + + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulationCard.tsx b/src/components/Transactions/TransactionSimulationCard.tsx new file mode 100644 index 00000000000..51244f5b030 --- /dev/null +++ b/src/components/Transactions/TransactionSimulationCard.tsx @@ -0,0 +1,315 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import Animated, { + interpolate, + useAnimatedReaction, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { TransactionErrorType, TransactionSimulationResult, TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; +import { Network } from '@/networks/types'; + +import { getNetworkObj, getNetworkObject } from '@/networks'; +import { isEmpty } from 'lodash'; +import { TransactionSimulatedEventRow } from '@/components/Transactions/TransactionSimulatedEventRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { EventIcon, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, + rotationConfig, + timingConfig, +} from '@/components/Transactions/constants'; +import { ChainId } from '@/__swaps__/types/chains'; + +interface TransactionSimulationCardProps { + currentChainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + txSimulationApiError: unknown; + isPersonalSignRequest: boolean; + noChanges: boolean; + simulation: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; + walletBalance: { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; + }; +} + +export const TransactionSimulationCard = ({ + currentChainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + txSimulationApiError, + isPersonalSignRequest, + noChanges, + simulation, + simulationError, + simulationScanResult, + walletBalance, +}: TransactionSimulationCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const spinnerRotation = useSharedValue(0); + + const listStyle = useAnimatedStyle(() => ({ + opacity: noChanges + ? withTiming(1, timingConfig) + : interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const spinnerStyle = useAnimatedStyle(() => { + return { + transform: [{ rotate: `${spinnerRotation.value}deg` }], + }; + }); + + useAnimatedReaction( + () => ({ isLoading, isPersonalSignRequest }), + ({ isLoading, isPersonalSignRequest }, previous = { isLoading: false, isPersonalSignRequest: false }) => { + if (isLoading && !previous?.isLoading) { + spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); + } else if ( + (!isLoading && previous?.isLoading) || + (isPersonalSignRequest && !previous?.isPersonalSignRequest && previous?.isLoading) + ) { + spinnerRotation.value = withTiming(360, timingConfig); + } + }, + [isLoading, isPersonalSignRequest] + ); + const renderSimulationEventRows = useMemo(() => { + if (isBalanceEnough === false) return null; + + return ( + <> + {simulation?.approvals?.map(change => { + return ( + + ); + })} + {simulation?.out?.map(change => { + return ( + + ); + })} + {simulation?.in?.map(change => { + return ( + + ); + })} + + ); + }, [isBalanceEnough, simulation]); + + const titleColor: TextColor = useMemo(() => { + if (isLoading) { + return 'label'; + } + if (isBalanceEnough === false) { + return 'blue'; + } + if (noChanges || isPersonalSignRequest || txSimulationApiError) { + return 'labelQuaternary'; + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return 'orange'; + } + if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { + return 'red'; + } + return 'label'; + }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, isPersonalSignRequest, txSimulationApiError]); + + const titleText = useMemo(() => { + if (isLoading) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); + } + if (isBalanceEnough === false) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); + } + if (txSimulationApiError || isPersonalSignRequest) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); + } + if (simulationScanResult === TransactionScanResultType.Malicious) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); + } + if (noChanges) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); + } + if (simulationError) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); + } + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); + }, [ + isBalanceEnough, + isLoading, + noChanges, + simulationError, + simulationScanResult, + isPersonalSignRequest, + txSimulationApiError, + walletBalance?.symbol, + ]); + + const isExpanded = useMemo(() => { + if (isLoading || isPersonalSignRequest) { + return false; + } + const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; + return shouldExpandOnLoad; + }, [isBalanceEnough, isLoading, isPersonalSignRequest, noChanges, simulation, simulationError]); + + return ( + + + + + {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( + + ) : ( + + {!isLoading && noChanges && !isPersonalSignRequest ? ( + + {/* The extra space avoids icon clipping */} + {'􀻾 '} + + ) : ( + + + 􀬨 + + + )} + + )} + + {titleText} + + + {/* TODO: Unhide once we add explainer sheets */} + {/* + + + + + 􀁜 + + + + + */} + + + + {isBalanceEnough === false ? ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { + symbol: walletBalance?.symbol, + network: getNetworkObject({ chainId: currentChainId }).name, + })} + + ) : ( + <> + {isPersonalSignRequest && ( + + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} + + + )} + {txSimulationApiError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.tx_api_error)} + + )} + {simulationError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} + + )} + {simulationScanResult === TransactionScanResultType.Warning && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} + + )} + {simulationScanResult === TransactionScanResultType.Malicious && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} + + )} + + )} + {renderSimulationEventRows} + + + + + ); +}; diff --git a/src/components/Transactions/constants.ts b/src/components/Transactions/constants.ts new file mode 100644 index 00000000000..79e6e0d8df5 --- /dev/null +++ b/src/components/Transactions/constants.ts @@ -0,0 +1,121 @@ +import * as i18n from '@/languages'; +import { Screens } from '@/state/performance/operations'; +import { safeAreaInsetValues } from '@/utils'; +import { TransitionConfig } from 'moti'; +import { Easing } from 'react-native-reanimated'; +import { EventInfo } from '@/components/Transactions/types'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; + +export const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; +export const GAS_BUTTON_SPACE = + 30 + // GasSpeedButton height + 24; // Between GasSpeedButton and bottom of sheet + +export const EXPANDED_CARD_BOTTOM_INSET = + SCREEN_BOTTOM_INSET + + 24 + // Between bottom of sheet and bottom of Cancel/Confirm + 52 + // Cancel/Confirm height + 24 + // Between Cancel/Confirm and wallet avatar row + 44 + // Wallet avatar row height + 24; // Between wallet avatar row and bottom of expandable area + +export const COLLAPSED_CARD_HEIGHT = 56; +export const MAX_CARD_HEIGHT = 176; + +export const CARD_ROW_HEIGHT = 12; +export const SMALL_CARD_ROW_HEIGHT = 10; +export const CARD_BORDER_WIDTH = 1.5; + +export const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; + +export const rotationConfig = { + duration: 2100, + easing: Easing.linear, +}; + +export const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +export const motiTimingConfig: TransitionConfig = { + duration: 225, + easing: Easing.bezier(0.2, 0, 0, 1), + type: 'timing', +}; + +export const SCREEN_FOR_REQUEST_SOURCE = { + [RequestSource.BROWSER]: Screens.DAPP_BROWSER, + [RequestSource.WALLETCONNECT]: Screens.WALLETCONNECT, + [RequestSource.MOBILE_WALLET_PROTOCOL]: Screens.MOBILE_WALLET_PROTOCOL, +}; + +export const CHARACTERS_PER_LINE = 40; +export const LINE_HEIGHT = 11; +export const LINE_GAP = 9; + +export const estimateMessageHeight = (message: string) => { + const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); + const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; + + return messageHeight; +}; + +export const infoForEventType: { [key: string]: EventInfo } = { + send: { + amountPrefix: '- ', + icon: '􀁷', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), + textColor: 'red', + }, + receive: { + amountPrefix: '+ ', + icon: '􀁹', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), + textColor: 'green', + }, + approve: { + amountPrefix: '', + icon: '􀎤', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), + textColor: 'label', + }, + revoke: { + amountPrefix: '', + icon: '􀎠', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), + textColor: 'label', + }, + failed: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), + textColor: 'red', + }, + insufficientBalance: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'blue', + label: '', + textColor: 'blue', + }, + MALICIOUS: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: '', + textColor: 'red', + }, + WARNING: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'orange', + label: '', + textColor: 'orange', + }, +}; diff --git a/src/components/Transactions/types.ts b/src/components/Transactions/types.ts new file mode 100644 index 00000000000..ce9eaa8b3c3 --- /dev/null +++ b/src/components/Transactions/types.ts @@ -0,0 +1,18 @@ +import { TextColor } from '@/design-system/color/palettes'; + +export type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; + +export type EventInfo = { + amountPrefix: string; + icon: string; + iconColor: TextColor; + label: string; + textColor: TextColor; +}; + +export type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; + +export type DetailInfo = { + icon: string; + label: string; +}; diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index c8914e691bd..33316a8c0a0 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -19,6 +19,13 @@ import { FiatProviderName } from '@/entities/f2c'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { queryClient } from '@/react-query'; import { pointsReferralCodeQueryKey } from '@/resources/points'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { InitialRoute } from '@/navigation/initialRoute'; + +interface DeeplinkHandlerProps extends Pick, 'handleRequestUrl' | 'sendFailureToClient'> { + url: string; + initialRoute: InitialRoute; +} /* * You can test these deeplinks with the following command: @@ -26,7 +33,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; * `xcrun simctl openurl booted "https://link.rainbow.me/0x123"` */ -export default async function handleDeeplink(url: string, initialRoute: any = null) { +export default async function handleDeeplink({ url, initialRoute, handleRequestUrl, sendFailureToClient }: DeeplinkHandlerProps) { if (!url) { logger.warn(`[handleDeeplink]: No url provided`); return; @@ -200,6 +207,16 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu break; } + case 'wsegue': { + const response = await handleRequestUrl(url); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + break; + } + default: { logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; diff --git a/src/hooks/useApplicationSetup.ts b/src/hooks/useApplicationSetup.ts new file mode 100644 index 00000000000..dab223def15 --- /dev/null +++ b/src/hooks/useApplicationSetup.ts @@ -0,0 +1,55 @@ +import { useCallback, useEffect, useState } from 'react'; +import { logger, RainbowError } from '@/logger'; +import { InteractionManager } from 'react-native'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { ReviewPromptAction } from '@/storage/schema'; +import { loadAddress } from '@/model/wallet'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { PerformanceContextMap } from '@/performance/PerformanceContextMap'; +import Routes from '@/navigation/routesNames'; +import { checkIdentifierOnLaunch } from '@/model/backup'; +import { analyticsV2 } from '@/analytics'; +import { saveFCMToken } from '@/notifications/tokens'; +import { initListeners as initWalletConnectListeners } from '@/walletConnect'; +import isTestFlight from '@/helpers/isTestFlight'; +import { PerformanceTracking } from '@/performance/tracking'; +import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; + +export function useApplicationSetup() { + const [initialRoute, setInitialRoute] = useState(null); + + const identifyFlow = useCallback(async () => { + const address = await loadAddress(); + if (address) { + setTimeout(() => { + InteractionManager.runAfterInteractions(() => { + handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); + }); + }, 10_000); + + InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); + } + + setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + }, []); + + useEffect(() => { + if (!IS_DEV && isTestFlight) { + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); + } + identifyFlow(); + + Promise.all([analyticsV2.initializeRudderstack(), saveFCMToken()]) + .catch(error => { + logger.error(new RainbowError('Failed to initialize rudderstack or save FCM token', error)); + }) + .finally(() => { + initWalletConnectListeners(); + PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); + analyticsV2.track(analyticsV2.event.applicationDidMount); + }); + }, [identifyFlow]); + + return { initialRoute }; +} diff --git a/src/hooks/useCalculateGasLimit.ts b/src/hooks/useCalculateGasLimit.ts new file mode 100644 index 00000000000..b0c6fb098bb --- /dev/null +++ b/src/hooks/useCalculateGasLimit.ts @@ -0,0 +1,74 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { estimateGas, web3Provider, toHex } from '@/handlers/web3'; +import { convertHexToString, omitFlatten } from '@/helpers/utilities'; +import { logger, RainbowError } from '@/logger'; +import { getNetworkObject } from '@/networks'; +import { ethereumUtils } from '@/utils'; +import { hexToNumber, isHex } from 'viem'; +import { isEmpty } from 'lodash'; +import { InteractionManager } from 'react-native'; +import { GasFeeParamsBySpeed } from '@/entities'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { useGas } from '@/hooks'; +import { ChainId } from '@/__swaps__/types/chains'; + +type CalculateGasLimitProps = { + isMessageRequest: boolean; + gasFeeParamsBySpeed: GasFeeParamsBySpeed; + provider: StaticJsonRpcProvider | null; + req: any; + updateTxFee: ReturnType['updateTxFee']; + currentChainId: ChainId; +}; + +export const useCalculateGasLimit = ({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + currentChainId, +}: CalculateGasLimitProps) => { + const calculatingGasLimit = useRef(false); + + const calculateGasLimit = useCallback(async () => { + calculatingGasLimit.current = true; + const txPayload = req; + if (isHex(txPayload?.type)) { + txPayload.type = hexToNumber(txPayload?.type); + } + let gas = txPayload.gasLimit || txPayload.gas; + + try { + logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); + const rawGasLimit = await estimateGas(cleanTxPayload, provider); + logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + if (rawGasLimit) { + gas = toHex(rawGasLimit); + } + } catch (error) { + logger.error(new RainbowError('WC: error estimating gas'), { error }); + } finally { + logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + + const networkObject = getNetworkObject({ chainId: currentChainId }); + if (currentChainId && networkObject.gas.OptimismTxFee) { + const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider || web3Provider); + updateTxFee(gas, null, l1GasFeeOptimism); + } else { + updateTxFee(gas, null); + } + } + }, [currentChainId, req, updateTxFee, provider]); + + useEffect(() => { + if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { + InteractionManager.runAfterInteractions(() => { + calculateGasLimit(); + }); + } + }, [calculateGasLimit, gasFeeParamsBySpeed, isMessageRequest, provider]); + + return { calculateGasLimit }; +}; diff --git a/src/hooks/useConfirmTransaction.ts b/src/hooks/useConfirmTransaction.ts new file mode 100644 index 00000000000..6600cead84b --- /dev/null +++ b/src/hooks/useConfirmTransaction.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; + +type UseConfirmTransactionProps = { + isMessageRequest: boolean; + isBalanceEnough: boolean | undefined; + isValidGas: boolean; + handleSignMessage: () => void; + handleConfirmTransaction: () => void; +}; + +export const useConfirmTransaction = ({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, +}: UseConfirmTransactionProps) => { + const onConfirm = useCallback(async () => { + if (isMessageRequest) { + return handleSignMessage(); + } + if (!isBalanceEnough || !isValidGas) return; + return handleConfirmTransaction(); + }, [isMessageRequest, isBalanceEnough, isValidGas, handleConfirmTransaction, handleSignMessage]); + + return { onConfirm }; +}; diff --git a/src/hooks/useHasEnoughBalance.ts b/src/hooks/useHasEnoughBalance.ts new file mode 100644 index 00000000000..56c5c766ae1 --- /dev/null +++ b/src/hooks/useHasEnoughBalance.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { fromWei, greaterThanOrEqualTo } from '@/helpers/utilities'; +import BigNumber from 'bignumber.js'; +import { SelectedGasFee } from '@/entities'; +import { ChainId } from '@/__swaps__/types/chains'; + +type WalletBalance = { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; +}; + +type BalanceCheckParams = { + isMessageRequest: boolean; + walletBalance: WalletBalance; + currentChainId: ChainId; + selectedGasFee: SelectedGasFee; + req: any; +}; + +export const useHasEnoughBalance = ({ isMessageRequest, walletBalance, currentChainId, selectedGasFee, req }: BalanceCheckParams) => { + const [isBalanceEnough, setIsBalanceEnough] = useState(); + + useEffect(() => { + if (isMessageRequest) { + setIsBalanceEnough(true); + return; + } + + const { gasFee } = selectedGasFee; + if (!walletBalance.isLoaded || !currentChainId || !gasFee?.estimatedFee) { + return; + } + + const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const balanceAmount = walletBalance.amount; + const value = req?.value ?? 0; + + const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); + const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + + setIsBalanceEnough(isEnough); + }, [isMessageRequest, currentChainId, selectedGasFee, walletBalance, req]); + + return { isBalanceEnough }; +}; diff --git a/src/hooks/useNonceForDisplay.ts b/src/hooks/useNonceForDisplay.ts new file mode 100644 index 00000000000..34ba719c5e9 --- /dev/null +++ b/src/hooks/useNonceForDisplay.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { getNextNonce } from '@/state/nonces'; +import { ChainId } from '@/__swaps__/types/chains'; +import { ethereumUtils } from '@/utils'; + +type UseNonceParams = { + isMessageRequest: boolean; + currentAddress: string; + currentChainId: ChainId; +}; + +export const useNonceForDisplay = ({ isMessageRequest, currentAddress, currentChainId }: UseNonceParams) => { + const [nonceForDisplay, setNonceForDisplay] = useState(); + + useEffect(() => { + if (!isMessageRequest && !nonceForDisplay) { + (async () => { + try { + const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + if (nonce || nonce === 0) { + const nonceAsString = nonce.toString(); + setNonceForDisplay(nonceAsString); + } + } catch (error) { + console.error('Failed to get nonce for display:', error); + } + })(); + } + }, [currentAddress, currentChainId, isMessageRequest, nonceForDisplay]); + + return { nonceForDisplay }; +}; diff --git a/src/hooks/useProviderSetup.ts b/src/hooks/useProviderSetup.ts new file mode 100644 index 00000000000..83b372bf1a0 --- /dev/null +++ b/src/hooks/useProviderSetup.ts @@ -0,0 +1,50 @@ +import { useEffect, useState } from 'react'; +import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { ethereumUtils } from '@/utils'; +import { getOnchainAssetBalance } from '@/handlers/assets'; +import { ParsedAddressAsset } from '@/entities'; +import { ChainId } from '@/__swaps__/types/chains'; + +export const useProviderSetup = (currentChainId: ChainId, accountAddress: string) => { + const [provider, setProvider] = useState(null); + const [nativeAsset, setNativeAsset] = useState(null); + + useEffect(() => { + const initProvider = async () => { + let p; + if (currentChainId === ChainId.mainnet) { + p = await getFlashbotsProvider(); + } else { + p = getProvider({ chainId: currentChainId }); + } + setProvider(p); + }; + initProvider(); + }, [currentChainId]); + + useEffect(() => { + const fetchNativeAsset = async () => { + if (provider) { + const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountAddress); + if (asset) { + const balance = await getOnchainAssetBalance( + asset, + accountAddress, + ethereumUtils.getNetworkFromChainId(currentChainId), + provider + ); + if (balance) { + const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; + setNativeAsset(assetWithOnchainBalance); + } else { + setNativeAsset(asset); + } + } + } + }; + fetchNativeAsset(); + }, [accountAddress, currentChainId, provider]); + + return { provider, nativeAsset }; +}; diff --git a/src/hooks/useSubmitTransaction.ts b/src/hooks/useSubmitTransaction.ts new file mode 100644 index 00000000000..166f2f4004f --- /dev/null +++ b/src/hooks/useSubmitTransaction.ts @@ -0,0 +1,55 @@ +import { useCallback, useState } from 'react'; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { SCREEN_FOR_REQUEST_SOURCE } from '@/components/Transactions/constants'; + +export const useTransactionSubmission = ({ + isBalanceEnough, + accountInfo, + onConfirm, + source, +}: { + isBalanceEnough: boolean | undefined; + accountInfo: { isHardwareWallet: boolean }; + onConfirm: () => Promise; + source: RequestSource; +}) => { + const [isAuthorizing, setIsAuthorizing] = useState(false); + const { navigate } = useNavigation(); + + const onPressSend = useCallback(async () => { + if (isAuthorizing) return; + try { + setIsAuthorizing(true); + await onConfirm(); + } catch (error) { + console.error('Error while sending transaction:', error); + } finally { + setIsAuthorizing(false); + } + }, [isAuthorizing, onConfirm]); + + const submitFn = useCallback( + () => + performanceTracking.getState().executeFn({ + fn: async () => { + if (!isBalanceEnough) { + navigate(Routes.ADD_CASH_SHEET); + return; + } + if (accountInfo.isHardwareWallet) { + navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); + } else { + await onPressSend(); + } + }, + operation: TimeToSignOperation.CallToAction, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + })(), + [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] + ); + + return { submitFn, isAuthorizing }; +}; diff --git a/src/hooks/useTransactionSetup.ts b/src/hooks/useTransactionSetup.ts new file mode 100644 index 00000000000..6bc4f9528ab --- /dev/null +++ b/src/hooks/useTransactionSetup.ts @@ -0,0 +1,76 @@ +import * as i18n from '@/languages'; +import { useCallback, useEffect, useState } from 'react'; +import { InteractionManager } from 'react-native'; +import useGas from '@/hooks/useGas'; +import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; +import { analytics } from '@/analytics'; +import { event } from '@/analytics/event'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { ChainId } from '@/__swaps__/types/chains'; +import { ethereumUtils } from '@/utils'; + +type TransactionSetupParams = { + currentChainId: ChainId; + startPollingGasFees: ReturnType['startPollingGasFees']; + stopPollingGasFees: ReturnType['stopPollingGasFees']; + isMessageRequest: boolean; + transactionDetails: any; + source: RequestSource; +}; + +export const useTransactionSetup = ({ + currentChainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, +}: TransactionSetupParams) => { + const [methodName, setMethodName] = useState(null); + + const fetchMethodName = useCallback( + async (data: string) => { + const methodSignaturePrefix = data.substr(0, 10); + try { + const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); + if (name) { + setMethodName(name); + } + } catch (e) { + setMethodName(data); + } + }, + [currentChainId] + ); + + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (currentChainId) { + if (!isMessageRequest) { + const network = ethereumUtils.getNetworkFromChainId(currentChainId); + startPollingGasFees(network); + fetchMethodName(transactionDetails?.payload?.params?.[0].data); + } else { + setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); + } + analytics.track(event.txRequestShownSheet, { source }); + } + }); + + return () => { + if (!isMessageRequest) { + stopPollingGasFees(); + } + }; + }, [ + isMessageRequest, + currentChainId, + transactionDetails?.payload?.params, + source, + fetchMethodName, + startPollingGasFees, + stopPollingGasFees, + ]); + + return { methodName }; +}; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 5130d449fc6..4f157ecc5c2 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2835,6 +2835,7 @@ "unavailable_personal_sign": "Simulation for personal signs is not yet supported", "unavailable_zora_network": "Simulation on Zora is not yet supported", "failed_to_simulate": "The simulation failed, which suggests your transaction is likely to fail. This may be an issue with the app you’re using.", + "tx_api_error": "We are unable to determine whether or not your transaction will succeed or fail. Proceed with caution.", "warning": "No known malicious behavior was detected, but this transaction has characteristics that may pose a risk to your wallet.", "malicious": "Signing this transaction could result in losing access to everything in your wallet." }, diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 39d424b8c06..007652f2302 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -90,6 +90,7 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const OuterStack = createStackNavigator(); @@ -269,25 +270,13 @@ function AuthNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index c468af96ecb..4f1a8e96407 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -104,6 +104,7 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const NativeStack = createNativeStackNavigator(); @@ -292,25 +293,13 @@ function NativeStackNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 3e9de2383ff..8324c450a34 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -29,6 +29,7 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -277,7 +278,7 @@ export const signTransactionSheetConfig = { options: ({ route }: { route: SignTransactionSheetRouteProp }) => ({ ...buildCoolModalConfig({ ...route.params, - backgroundOpacity: route?.params?.source === 'walletconnect' ? 1 : 0.7, + backgroundOpacity: route?.params?.source === RequestSource.WALLETCONNECT ? 1 : 0.7, cornerRadius: 0, springDamping: 1, topOffset: 0, diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index 8c88b9ee1bf..4bc8b78a88d 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -2,7 +2,7 @@ import messaging from '@react-native-firebase/messaging'; import { getLocal, saveLocal } from '@/handlers/localstorage/common'; import { getPermissionStatus } from '@/notifications/permissions'; -import { logger, RainbowError } from '@/logger'; +import { logger } from '@/logger'; export const registerTokenRefreshListener = () => messaging().onTokenRefresh(fcmToken => { diff --git a/src/parsers/requests.js b/src/parsers/requests.js index ae7da2d87b6..a3fa2ac7dc6 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -9,7 +9,7 @@ import { isSignTypedData, SIGN, PERSONAL_SIGN, SEND_TRANSACTION, SIGN_TRANSACTIO import { isAddress } from '@ethersproject/address'; import { toUtf8String } from '@ethersproject/strings'; -export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { +export const getRequestDisplayDetails = async (payload, nativeCurrency, chainId) => { const timestampInMs = Date.now(); if (payload.method === SEND_TRANSACTION || payload.method === SIGN_TRANSACTION) { const transaction = Object.assign(payload?.params?.[0] ?? null); @@ -75,9 +75,9 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ timestampInMs, }); -const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { +const getTransactionDisplayDetails = async (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index aa5196b9cd4..05350697d48 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -71,7 +71,7 @@ export interface WalletconnectRequestData extends RequestData { /** * Display details loaded for a request. */ -interface RequestDisplayDetails { +export interface RequestDisplayDetails { /** * Data loaded for the request, depending on the type of request. */ @@ -154,7 +154,7 @@ export const addRequestToApprove = icons?: string[]; } ) => - (dispatch: Dispatch, getState: AppGetState) => { + async (dispatch: Dispatch, getState: AppGetState) => { const { requests } = getState().requests; const { walletConnectors } = getState().walletconnect; const { accountAddress, network, nativeCurrency } = getState().settings; @@ -163,7 +163,7 @@ export const addRequestToApprove = const chainId = walletConnector._chainId; // @ts-expect-error "_accounts" is private. const address = walletConnector._accounts[0]; - const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, chainId); + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, chainId); const oneHourAgoTs = Date.now() - EXPIRATION_THRESHOLD_IN_MS; // @ts-expect-error This fails to compile as `displayDetails` does not // always return an object with `timestampInMs`. Still, the error thrown diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index da0ea48400c..fa1b19b631a 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -559,7 +559,9 @@ const listenOnNewMessages = return; } const { requests: pendingRequests } = getState().requests; - const request = !pendingRequests[requestId] ? dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) : null; + const request = !pendingRequests[requestId] + ? await dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) + : null; if (request) { handleWalletConnectRequest(request); InteractionManager.runAfterInteractions(() => { diff --git a/src/resources/transactions/transactionSimulation.ts b/src/resources/transactions/transactionSimulation.ts new file mode 100644 index 00000000000..01d76df5de2 --- /dev/null +++ b/src/resources/transactions/transactionSimulation.ts @@ -0,0 +1,148 @@ +import { createQueryKey, QueryConfig, QueryFunctionArgs } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { RainbowError, logger } from '@/logger'; +import { metadataPOSTClient } from '@/graphql'; +import { TransactionErrorType, TransactionScanResultType, TransactionSimulationResult } from '@/graphql/__generated__/metadataPOST'; +import { isNil } from 'lodash'; +import { RequestData } from '@/redux/requests'; +import { ChainId } from '@/__swaps__/types/chains'; + +type SimulationArgs = { + accountAddress: string; + currentChainId: ChainId; + isMessageRequest: boolean; + nativeCurrency: string; + req: any; // Replace 'any' with the correct type for 'req' + requestMessage: string; + simulationUnavailable: boolean; + transactionDetails: RequestData; +}; + +type SimulationResult = { + simulationData: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; +}; + +const simulationQueryKey = ({ + accountAddress, + currentChainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, +}: SimulationArgs) => + createQueryKey( + 'txSimulation', + { + accountAddress, + currentChainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, + }, + { persisterVersion: 1 } + ); + +const fetchSimulation = async ({ + queryKey: [ + { accountAddress, currentChainId, isMessageRequest, nativeCurrency, req, requestMessage, simulationUnavailable, transactionDetails }, + ], +}: QueryFunctionArgs): Promise => { + try { + let simulationData; + + if (isMessageRequest) { + simulationData = await metadataPOSTClient.simulateMessage({ + address: accountAddress, + chainId: currentChainId, + message: { + method: transactionDetails?.payload?.method, + params: [requestMessage], + }, + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateMessage?.error?.type, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { + return { + simulationData: simulationData.simulateMessage?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } + } else { + simulationData = await metadataPOSTClient.simulateTransactions({ + chainId: currentChainId, + currency: nativeCurrency?.toLowerCase(), + transactions: [ + { + from: req?.from, + to: req?.to, + data: req?.data || '0x', + value: req?.value || '0x0', + }, + ], + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions?.[0]?.scanning?.result, + }; + } else if (simulationData?.simulateTransactions?.[0]?.error) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateTransactions?.[0]?.error?.type, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } else if (simulationData.simulateTransactions?.[0]?.simulation) { + return { + simulationData: simulationData.simulateTransactions[0]?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } + } + + return { + simulationData: undefined, + simulationError: undefined, + simulationScanResult: undefined, + }; + } catch (error) { + logger.error(new RainbowError('Error while simulating'), { error }); + throw error; + } +}; + +export const useSimulation = ( + args: SimulationArgs, + config: QueryConfig> = {} +) => { + return useQuery(simulationQueryKey(args), fetchSimulation, { + enabled: !!args.accountAddress && !!args.currentChainId, + retry: 3, + refetchOnWindowFocus: false, + staleTime: Infinity, + ...config, + }); +}; diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 49dfac6f483..c3961c962ef 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -1,68 +1,31 @@ -/* eslint-disable no-nested-ternary */ -import BigNumber from 'bignumber.js'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { AnimatePresence, MotiView, TransitionConfig } from 'moti'; +import React, { useCallback, useMemo } from 'react'; +import { AnimatePresence, MotiView } from 'moti'; import * as i18n from '@/languages'; -import { Image, InteractionManager, PixelRatio, ScrollView, StyleProp, TouchableWithoutFeedback, ViewStyle } from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import Animated, { - Easing, - SharedValue, - interpolate, - interpolateColor, - measure, - runOnJS, - runOnUI, - useAnimatedReaction, - useAnimatedRef, - useAnimatedStyle, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; +import { Image, InteractionManager, PixelRatio, ScrollView } from 'react-native'; +import Animated from 'react-native-reanimated'; import { Transaction } from '@ethersproject/transactions'; -import { ButtonPressAnimation } from '@/components/animations'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { SheetActionButton } from '@/components/sheet'; import { Bleed, Box, Columns, Inline, Inset, Stack, Text, globalColors, useBackgroundColor, useForegroundColor } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; -import { NewTransaction, ParsedAddressAsset } from '@/entities'; +import { NewTransaction } from '@/entities'; import { useNavigation } from '@/navigation'; import { useTheme } from '@/theme'; -import { abbreviations, deviceUtils, ethereumUtils, safeAreaInsetValues } from '@/utils'; +import { deviceUtils, ethereumUtils } from '@/utils'; import { PanGestureHandler } from 'react-native-gesture-handler'; import { RouteProp, useRoute } from '@react-navigation/native'; -import { metadataPOSTClient } from '@/graphql'; -import { - TransactionAssetType, - TransactionErrorType, - TransactionSimulationAsset, - TransactionSimulationMeta, - TransactionSimulationResult, - TransactionScanResultType, -} from '@/graphql/__generated__/metadataPOST'; +import { TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; import { Network } from '@/networks/types'; -import { - convertAmountToNativeDisplay, - convertHexToString, - convertRawAmountToBalance, - delay, - fromWei, - greaterThan, - greaterThanOrEqualTo, - omitFlatten, -} from '@/helpers/utilities'; +import { convertHexToString, delay, greaterThan, omitFlatten } from '@/helpers/utilities'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { getAccountProfileInfo } from '@/helpers/accountInfo'; -import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWallet, useWallets } from '@/hooks'; +import { useAccountSettings, useGas, useSwitchWallet, useWallets } from '@/hooks'; import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; -import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { estimateGasWithPadding, getProvider, getProviderForNetwork, toHex } from '@/handlers/web3'; import { GasSpeedButton } from '@/components/gas'; import { getNetworkObj, getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; @@ -72,71 +35,45 @@ import { SIGN_TYPED_DATA, SIGN_TYPED_DATA_V4, isMessageDisplayType, - isPersonalSign as checkIsPersonalSign, - isSignTypedData, + isPersonalSign, } from '@/utils/signingMethods'; -import { isEmpty, isNil } from 'lodash'; -import Routes from '@/navigation/routesNames'; +import { isNil } from 'lodash'; import { parseGasParamsForTransaction } from '@/parsers/gas'; import { loadWallet, sendTransaction, signPersonalMessage, signTransaction, signTypedDataMessage } from '@/model/wallet'; import { analyticsV2 as analytics } from '@/analytics'; import { maybeSignUri } from '@/handlers/imgix'; -import { RPCMethod } from '@/walletConnect/types'; import { isAddress } from '@ethersproject/address'; -import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; -import { sanitizeTypedData } from '@/utils/signingUtils'; import { hexToNumber, isHex } from 'viem'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNextNonce } from '@/state/nonces'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { RequestData } from '@/redux/requests'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; -import { getOnchainAssetBalance } from '@/handlers/assets'; -import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import { useSimulation } from '@/resources/transactions/transactionSimulation'; +import { TransactionSimulationCard } from '@/components/Transactions/TransactionSimulationCard'; +import { TransactionDetailsCard } from '@/components/Transactions/TransactionDetailsCard'; +import { TransactionMessageCard } from '@/components/Transactions/TransactionMessageCard'; +import { VerifiedBadge } from '@/components/Transactions/TransactionIcons'; +import { + SCREEN_FOR_REQUEST_SOURCE, + EXPANDED_CARD_BOTTOM_INSET, + GAS_BUTTON_SPACE, + motiTimingConfig, + SCREEN_BOTTOM_INSET, + infoForEventType, +} from '@/components/Transactions/constants'; +import { useCalculateGasLimit } from '@/hooks/useCalculateGasLimit'; +import { useTransactionSetup } from '@/hooks/useTransactionSetup'; +import { useHasEnoughBalance } from '@/hooks/useHasEnoughBalance'; +import { useNonceForDisplay } from '@/hooks/useNonceForDisplay'; +import { useProviderSetup } from '@/hooks/useProviderSetup'; +import { useTransactionSubmission } from '@/hooks/useSubmitTransaction'; +import { useConfirmTransaction } from '@/hooks/useConfirmTransaction'; import { ChainId } from '@/__swaps__/types/chains'; -const COLLAPSED_CARD_HEIGHT = 56; -const MAX_CARD_HEIGHT = 176; - -const CARD_ROW_HEIGHT = 12; -const SMALL_CARD_ROW_HEIGHT = 10; -const CARD_BORDER_WIDTH = 1.5; - -const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; -const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; - -const GAS_BUTTON_SPACE = - 30 + // GasSpeedButton height - 24; // Between GasSpeedButton and bottom of sheet - -const EXPANDED_CARD_BOTTOM_INSET = - SCREEN_BOTTOM_INSET + - 24 + // Between bottom of sheet and bottom of Cancel/Confirm - 52 + // Cancel/Confirm height - 24 + // Between Cancel/Confirm and wallet avatar row - 44 + // Wallet avatar row height - 24; // Between wallet avatar row and bottom of expandable area - -const rotationConfig = { - duration: 2100, - easing: Easing.linear, -}; - -const timingConfig = { - duration: 300, - easing: Easing.bezier(0.2, 0, 0, 1), -}; - -const motiTimingConfig: TransitionConfig = { - duration: 225, - easing: Easing.bezier(0.2, 0, 0, 1), - type: 'timing', -}; - type SignTransactionSheetParams = { transactionDetails: RequestData; onSuccess: (hash: string) => void; @@ -148,20 +85,12 @@ type SignTransactionSheetParams = { source: RequestSource; }; -const SCREEN_FOR_REQUEST_SOURCE = { - browser: Screens.DAPP_BROWSER, - walletconnect: Screens.WALLETCONNECT, -}; - export type SignTransactionSheetRouteProp = RouteProp<{ SignTransactionSheet: SignTransactionSheetParams }, 'SignTransactionSheet'>; export const SignTransactionSheet = () => { - const { goBack, navigate } = useNavigation(); + const { goBack } = useNavigation(); const { colors, isDarkMode } = useTheme(); const { accountAddress, nativeCurrency } = useAccountSettings(); - const [simulationData, setSimulationData] = useState(); - const [simulationError, setSimulationError] = useState(undefined); - const [simulationScanResult, setSimulationScanResult] = useState(undefined); const { params: routeParams } = useRoute(); const { wallets, walletNames } = useWallets(); @@ -177,22 +106,14 @@ export const SignTransactionSheet = () => { source, } = routeParams; - const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + const { provider, nativeAsset } = useProviderSetup(currentChainId, accountAddress); - const isPersonalSign = checkIsPersonalSign(transactionDetails.payload.method); + const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + const isPersonalSignRequest = isPersonalSign(transactionDetails.payload.method); const label = useForegroundColor('label'); const surfacePrimary = useBackgroundColor('surfacePrimary'); - const [provider, setProvider] = useState(null); - const [isAuthorizing, setIsAuthorizing] = useState(false); - const [isLoading, setIsLoading] = useState(!isPersonalSign); - const [methodName, setMethodName] = useState(null); - const calculatingGasLimit = useRef(false); - const [isBalanceEnough, setIsBalanceEnough] = useState(); - const [nonceForDisplay, setNonceForDisplay] = useState(); - - const [nativeAsset, setNativeAsset] = useState(null); const formattedDappUrl = useMemo(() => { try { const { hostname } = new URL(transactionDetails?.dappUrl); @@ -202,107 +123,16 @@ export const SignTransactionSheet = () => { } }, [transactionDetails]); - const { - gasLimit, - isValidGas, - startPollingGasFees, - stopPollingGasFees, - isSufficientGas, - updateTxFee, - selectedGasFee, - gasFeeParamsBySpeed, - } = useGas(); - - const simulationUnavailable = isPersonalSign; - - const itemCount = (simulationData?.in?.length || 0) + (simulationData?.out?.length || 0) + (simulationData?.approvals?.length || 0); - - const noChanges = !!(simulationData && itemCount === 0) && simulationScanResult === TransactionScanResultType.Ok; - const req = transactionDetails?.payload?.params?.[0]; const request = useMemo(() => { return isMessageRequest - ? { message: transactionDetails?.displayDetails?.request } + ? { message: transactionDetails?.displayDetails?.request || '' } : { ...transactionDetails?.displayDetails?.request, - nativeAsset: nativeAsset, + nativeAsset, }; }, [isMessageRequest, transactionDetails?.displayDetails?.request, nativeAsset]); - const calculateGasLimit = useCallback(async () => { - calculatingGasLimit.current = true; - const txPayload = req; - if (isHex(txPayload?.type)) { - txPayload.type = hexToNumber(txPayload?.type); - } - // use the default - let gas = txPayload.gasLimit || txPayload.gas; - - const provider = getProvider({ chainId: currentChainId }); - try { - // attempt to re-run estimation - logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); - // safety precaution: we want to ensure these properties are not used for gas estimation - const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); - const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); - if (rawGasLimit) { - gas = toHex(rawGasLimit); - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); - } finally { - logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); - const networkObject = getNetworkObject({ chainId: currentChainId }); - if (networkObject && networkObject.gas.OptimismTxFee) { - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); - updateTxFee(gas, null, l1GasFeeOptimism); - } else { - updateTxFee(gas, null); - } - } - }, [currentChainId, req, updateTxFee]); - - const fetchMethodName = useCallback( - async (data: string) => { - const methodSignaturePrefix = data.substr(0, 10); - try { - const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); - if (name) { - setMethodName(name); - } - } catch (e) { - setMethodName(data); - } - }, - [currentChainId] - ); - - // start polling for gas and get fn name - useEffect(() => { - InteractionManager.runAfterInteractions(() => { - if (currentChainId) { - if (!isMessageRequest) { - const network = ethereumUtils.getNetworkFromChainId(currentChainId); - startPollingGasFees(network); - fetchMethodName(transactionDetails?.payload?.params[0].data); - } else { - setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); - } - analytics.track(event.txRequestShownSheet), { source }; - } - }); - }, [isMessageRequest, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source, currentChainId]); - - // get gas limit - useEffect(() => { - if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { - InteractionManager.runAfterInteractions(() => { - calculateGasLimit(); - }); - } - }, [calculateGasLimit, gasLimit, gasFeeParamsBySpeed, isMessageRequest, provider, updateTxFee]); - const walletBalance = useMemo(() => { return { amount: nativeAsset?.balance?.amount || 0, @@ -312,34 +142,67 @@ export const SignTransactionSheet = () => { }; }, [nativeAsset?.balance?.amount, nativeAsset?.balance?.display, nativeAsset?.symbol]); - // check native balance is sufficient - useEffect(() => { - if (isMessageRequest) { - setIsBalanceEnough(true); - return; - } + const { gasLimit, isValidGas, startPollingGasFees, stopPollingGasFees, updateTxFee, selectedGasFee, gasFeeParamsBySpeed } = useGas(); - const { gasFee } = selectedGasFee; - if (!walletBalance?.isLoaded || !currentChainId || !gasFee?.estimatedFee) { - return; - } + const { methodName } = useTransactionSetup({ + currentChainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, + }); - // Get the TX fee Amount - const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const { isBalanceEnough } = useHasEnoughBalance({ + isMessageRequest, + walletBalance, + currentChainId, + selectedGasFee, + req, + }); - // Get the ETH balance - const balanceAmount = walletBalance?.amount ?? 0; + useCalculateGasLimit({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + currentChainId, + }); - // Get the TX value - const txPayload = req; - const value = txPayload?.value ?? 0; + const { nonceForDisplay } = useNonceForDisplay({ + isMessageRequest, + currentAddress, + currentChainId, + }); - // Check that there's enough ETH to pay for everything! - const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); - const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + const { + data: simulationResult, + isLoading: txSimulationLoading, + error: txSimulationApiError, + } = useSimulation( + { + accountAddress, + currentChainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage: request.message, + simulationUnavailable: isPersonalSignRequest, + transactionDetails, + }, + { + enabled: !isPersonalSignRequest, + } + ); - setIsBalanceEnough(isEnough); - }, [isMessageRequest, isSufficientGas, selectedGasFee, walletBalance, req, currentChainId]); + const itemCount = + (simulationResult?.simulationData?.in?.length || 0) + + (simulationResult?.simulationData?.out?.length || 0) + + (simulationResult?.simulationData?.approvals?.length || 0); + + const noChanges = + !!(simulationResult?.simulationData && itemCount === 0) && simulationResult?.simulationScanResult === TransactionScanResultType.Ok; const accountInfo = useMemo(() => { const selectedWallet = wallets ? findWalletWithAccount(wallets, currentAddress) : undefined; @@ -351,135 +214,6 @@ export const SignTransactionSheet = () => { }; }, [wallets, currentAddress, walletNames]); - useEffect(() => { - const initProvider = async () => { - let p; - // check on this o.O - if (currentChainId === ChainId.mainnet) { - p = await getFlashbotsProvider(); - } else { - p = getProvider({ chainId: currentChainId }); - } - - setProvider(p); - }; - initProvider(); - }, [currentChainId, setProvider]); - - useEffect(() => { - (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); - if (asset && provider) { - const balance = await getOnchainAssetBalance( - asset, - accountInfo.address, - ethereumUtils.getNetworkFromChainId(currentChainId), - provider - ); - if (balance) { - const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; - setNativeAsset(assetWithOnchainBalance); - } else { - setNativeAsset(asset); - } - } - })(); - }, [accountInfo.address, currentChainId, provider]); - - useEffect(() => { - (async () => { - if (!isMessageRequest && !nonceForDisplay) { - try { - const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); - if (nonce || nonce === 0) { - const nonceAsString = nonce.toString(); - setNonceForDisplay(nonceAsString); - } - } catch (error) { - console.error('Failed to get nonce for display:', error); - } - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accountInfo.address, currentChainId, getNextNonce, isMessageRequest]); - - useEffect(() => { - const timeout = setTimeout(async () => { - try { - let simulationData; - if (isMessageRequest) { - // Message Signing - simulationData = await metadataPOSTClient.simulateMessage({ - address: accountAddress, - chainId: currentChainId, - message: { - method: transactionDetails?.payload?.method, - params: [request.message], - }, - domain: transactionDetails?.dappUrl, - }); - // Handle message simulation response - if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { - setSimulationError(simulationData?.simulateMessage?.error?.type); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - setSimulationData(undefined); - } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { - setSimulationData(simulationData.simulateMessage?.simulation); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } - } else { - // TX Signing - simulationData = await metadataPOSTClient.simulateTransactions({ - chainId: currentChainId, - currency: nativeCurrency?.toLowerCase(), - transactions: [ - { - from: req?.from, - to: req?.to, - data: req?.data, - value: req?.value || '0x0', - }, - ], - domain: transactionDetails?.dappUrl, - }); - // Handle TX simulation response - if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateTransactions?.[0]?.scanning?.result); - } else if (simulationData?.simulateTransactions?.[0]?.error) { - setSimulationError(simulationData?.simulateTransactions?.[0]?.error?.type); - setSimulationData(undefined); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } else if (simulationData.simulateTransactions?.[0]?.simulation) { - setSimulationData(simulationData.simulateTransactions[0]?.simulation); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); - } finally { - setIsLoading(false); - } - }, 750); - - return () => { - clearTimeout(timeout); - }; - }, [ - accountAddress, - currentChainId, - isMessageRequest, - isPersonalSign, - nativeCurrency, - req, - request.message, - simulationUnavailable, - transactionDetails, - ]); - const closeScreen = useCallback( (canceled: boolean) => performanceTracking.getState().executeFn({ @@ -526,80 +260,6 @@ export const SignTransactionSheet = () => { [accountInfo.isHardwareWallet, closeScreen, onCancelCallback, source, transactionDetails?.payload?.method] ); - const handleSignMessage = useCallback(async () => { - const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); - let response = null; - - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { - return; - } - - const existingWallet = await performanceTracking.getState().executeFn({ - fn: loadWallet, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.KeychainRead, - })({ - address: accountInfo.address, - provider, - timeTracking: { - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.Authentication, - }, - }); - - if (!existingWallet) { - return; - } - switch (transactionDetails?.payload?.method) { - case PERSONAL_SIGN: - response = await performanceTracking.getState().executeFn({ - fn: signPersonalMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - case SIGN_TYPED_DATA_V4: - case SIGN_TYPED_DATA: - response = await performanceTracking.getState().executeFn({ - fn: signTypedDataMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - default: - break; - } - - if (response?.result) { - analytics.track(event.txRequestApprove, { - source, - requestType: 'signature', - dappName: transactionDetails?.dappName, - dappUrl: transactionDetails?.dappUrl, - isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - }); - onSuccessCallback?.(response.result); - - closeScreen(false); - } else { - await onCancel(response?.error); - } - }, [ - transactionDetails?.payload?.params, - transactionDetails?.payload?.method, - transactionDetails?.dappName, - transactionDetails?.dappUrl, - currentChainId, - accountInfo.address, - accountInfo.isHardwareWallet, - source, - onSuccessCallback, - closeScreen, - onCancel, - ]); - const handleConfirmTransaction = useCallback(async () => { const sendInsteadOfSign = transactionDetails.payload.method === SEND_TRANSACTION; const txPayload = req; @@ -813,44 +473,94 @@ export const SignTransactionSheet = () => { onCancel, ]); - const onConfirm = useCallback(async () => { - if (isMessageRequest) { - return handleSignMessage(); - } - if (!isBalanceEnough || !isValidGas) return; - return handleConfirmTransaction(); - }, [handleConfirmTransaction, handleSignMessage, isBalanceEnough, isMessageRequest, isValidGas]); + const handleSignMessage = useCallback(async () => { + const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); + let response = null; - const onPressSend = useCallback(async () => { - if (isAuthorizing) return; - setIsAuthorizing(true); - try { - await onConfirm(); - setIsAuthorizing(false); - } catch (error) { - setIsAuthorizing(false); + const provider = getProvider({ chainId: currentChainId }); + if (!provider) { + return; } - }, [isAuthorizing, onConfirm]); - const submitFn = useCallback( - () => - performanceTracking.getState().executeFn({ - fn: async () => { - if (!isBalanceEnough) { - navigate(Routes.ADD_CASH_SHEET); - return; - } - if (accountInfo.isHardwareWallet) { - navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); - } else { - await onPressSend(); - } - }, - operation: TimeToSignOperation.CallToAction, + const existingWallet = await performanceTracking.getState().executeFn({ + fn: loadWallet, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.KeychainRead, + })({ + address: accountInfo.address, + provider, + timeTracking: { screen: SCREEN_FOR_REQUEST_SOURCE[source], - })(), - [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] - ); + operation: TimeToSignOperation.Authentication, + }, + }); + + if (!existingWallet) { + return; + } + switch (transactionDetails?.payload?.method) { + case PERSONAL_SIGN: + response = await performanceTracking.getState().executeFn({ + fn: signPersonalMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + case SIGN_TYPED_DATA_V4: + case SIGN_TYPED_DATA: + response = await performanceTracking.getState().executeFn({ + fn: signTypedDataMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + default: + break; + } + + if (response?.result) { + analytics.track(event.txRequestApprove, { + source, + requestType: 'signature', + dappName: transactionDetails?.dappName, + dappUrl: transactionDetails?.dappUrl, + isHardwareWallet: accountInfo.isHardwareWallet, + network: ethereumUtils.getNetworkFromChainId(currentChainId), + }); + onSuccessCallback?.(response.result); + + closeScreen(false); + } else { + await onCancel(response?.error); + } + }, [ + transactionDetails?.payload?.params, + transactionDetails?.payload?.method, + transactionDetails?.dappName, + transactionDetails?.dappUrl, + currentChainId, + accountInfo.address, + accountInfo.isHardwareWallet, + source, + onSuccessCallback, + closeScreen, + onCancel, + ]); + + const { onConfirm } = useConfirmTransaction({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, + }); + + const { submitFn } = useTransactionSubmission({ + isBalanceEnough, + accountInfo, + onConfirm, + source, + }); const onPressCancel = useCallback(() => onCancel(), [onCancel]); @@ -905,8 +615,9 @@ export const SignTransactionSheet = () => { { > {transactionDetails.dappName} - {source === 'browser' && } + {source === RequestSource.BROWSER && } {isMessageRequest @@ -927,33 +638,36 @@ export const SignTransactionSheet = () => { - {isMessageRequest ? ( - ) : ( - { /> { disabled={!canPressConfirm} size="big" weight="heavy" - // eslint-disable-next-line react/jsx-props-no-spreading - {...((simulationError || (simulationScanResult && simulationScanResult !== TransactionScanResultType.Ok)) && { - color: simulationScanResult === TransactionScanResultType.Warning ? 'orange' : colors.red, - })} + color={ + simulationResult?.simulationError || + (simulationResult?.simulationScanResult && simulationResult?.simulationScanResult !== TransactionScanResultType.Ok) + ? simulationResult?.simulationScanResult === TransactionScanResultType.Warning + ? 'orange' + : colors.red + : undefined + } /> @@ -1049,7 +767,7 @@ export const SignTransactionSheet = () => { )} - {source === 'browser' && ( + {source === RequestSource.BROWSER && ( { theme={'dark'} marginBottom={0} asset={undefined} - fallbackColor={simulationError ? colors.red : undefined} + fallbackColor={simulationResult?.simulationError ? colors.red : undefined} testID={undefined} showGasOptions={undefined} validateGasParams={undefined} @@ -1083,1221 +801,3 @@ export const SignTransactionSheet = () => { ); }; - -interface SimulationCardProps { - currentNetwork: Network; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - isPersonalSign: boolean; - noChanges: boolean; - simulation: TransactionSimulationResult | undefined; - simulationError: TransactionErrorType | undefined; - simulationScanResult: TransactionScanResultType | undefined; - walletBalance: { - amount: string | number; - display: string; - isLoaded: boolean; - symbol: string; - }; -} - -const SimulationCard = ({ - currentNetwork, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - isPersonalSign, - noChanges, - simulation, - simulationError, - simulationScanResult, - walletBalance, -}: SimulationCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const spinnerRotation = useSharedValue(0); - - const simulationUnavailable = isPersonalSign; - - const listStyle = useAnimatedStyle(() => ({ - opacity: noChanges - ? withTiming(1, timingConfig) - : interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const spinnerStyle = useAnimatedStyle(() => { - return { - transform: [{ rotate: `${spinnerRotation.value}deg` }], - }; - }); - - useAnimatedReaction( - () => ({ isLoading, simulationUnavailable }), - ({ isLoading, simulationUnavailable }, previous = { isLoading: false, simulationUnavailable: false }) => { - if (isLoading && !previous?.isLoading) { - spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); - } else if ( - (!isLoading && previous?.isLoading) || - (simulationUnavailable && !previous?.simulationUnavailable && previous?.isLoading) - ) { - spinnerRotation.value = withTiming(360, timingConfig); - } - }, - [isLoading, simulationUnavailable] - ); - const renderSimulationEventRows = useMemo(() => { - if (isBalanceEnough === false) return null; - - return ( - <> - {simulation?.approvals?.map(change => { - return ( - - ); - })} - {simulation?.out?.map(change => { - return ( - - ); - })} - {simulation?.in?.map(change => { - return ( - - ); - })} - - ); - }, [isBalanceEnough, simulation]); - - const titleColor: TextColor = useMemo(() => { - if (isLoading) { - return 'label'; - } - if (isBalanceEnough === false) { - return 'blue'; - } - if (noChanges || simulationUnavailable) { - return 'labelQuaternary'; - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return 'orange'; - } - if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { - return 'red'; - } - return 'label'; - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable]); - - const titleText = useMemo(() => { - if (isLoading) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); - } - if (isBalanceEnough === false) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); - } - if (simulationUnavailable) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); - } - if (simulationScanResult === TransactionScanResultType.Malicious) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); - } - if (noChanges) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); - } - if (simulationError) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); - } - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable, walletBalance?.symbol]); - - const isExpanded = useMemo(() => { - if (isLoading || isPersonalSign) { - return false; - } - const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; - return shouldExpandOnLoad; - }, [isBalanceEnough, isLoading, isPersonalSign, noChanges, simulation, simulationError]); - - return ( - - - - - {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( - - ) : ( - - {!isLoading && noChanges && !simulationUnavailable ? ( - - {/* The extra space avoids icon clipping */} - {'􀻾 '} - - ) : ( - - - 􀬨 - - - )} - - )} - - {titleText} - - - {/* TODO: Unhide once we add explainer sheets */} - {/* - - - - - 􀁜 - - - - - */} - - - - {isBalanceEnough === false ? ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { - symbol: walletBalance?.symbol, - network: getNetworkObj(currentNetwork).name, - })} - - ) : ( - <> - {simulationUnavailable && isPersonalSign && ( - - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} - - - )} - {simulationError && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} - - )} - {simulationScanResult === TransactionScanResultType.Warning && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} - - )} - {simulationScanResult === TransactionScanResultType.Malicious && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} - - )} - - )} - {renderSimulationEventRows} - - - - - ); -}; - -interface DetailsCardProps { - currentNetwork: Network; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - meta: TransactionSimulationMeta | undefined; - methodName: string; - noChanges: boolean; - nonce: string | undefined; - toAddress: string; -} - -const DetailsCard = ({ - currentNetwork, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - meta, - methodName, - noChanges, - nonce, - toAddress, -}: DetailsCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const [isExpanded, setIsExpanded] = useState(false); - - const listStyle = useAnimatedStyle(() => ({ - opacity: interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; - - const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); - const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; - const showTransferToRow = !!meta?.transferTo?.address; - // Hide DetailsCard if balance is insufficient once loaded - if (!isLoading && isBalanceEnough === false) { - return <>; - } - return ( - setIsExpanded(true)} - > - - - - - - 􁙠 - - - - {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} - - - - - - {} - {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - - ethereumUtils.openAddressInBlockExplorer( - meta?.to?.address || toAddress || meta?.transferTo?.address || '', - ethereumUtils.getChainIdFromNetwork(currentNetwork) - ) - } - value={ - meta?.to?.name || - abbreviations.address(meta?.to?.address || toAddress, 4, 6) || - meta?.to?.address || - toAddress || - meta?.transferTo?.address || - '' - } - /> - )} - {showFunctionRow && } - {!!meta?.to?.sourceCodeStatus && } - {!!meta?.to?.created && } - {nonce && } - - - - - ); -}; - -const MessageCard = ({ - expandedCardBottomInset, - message, - method, -}: { - expandedCardBottomInset: number; - message: string; - method: RPCMethod; -}) => { - const { setClipboard } = useClipboard(); - const [didCopy, setDidCopy] = useState(false); - - let displayMessage = message; - if (isSignTypedData(method)) { - try { - const parsedMessage = JSON.parse(message); - const sanitizedMessage = sanitizeTypedData(parsedMessage); - displayMessage = sanitizedMessage; - // eslint-disable-next-line no-empty - } catch (e) { - logger.warn('[SignTransactionSheet]: Error while parsing message'); - } - - displayMessage = JSON.stringify(displayMessage, null, 4); - } - - const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); - - const cardHeight = useSharedValue( - estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 - ); - const contentHeight = useSharedValue(estimatedMessageHeight); - - const handleCopyPress = useCallback( - (message: string) => { - if (didCopy) return; - setClipboard(message); - setDidCopy(true); - const copyTimer = setTimeout(() => { - setDidCopy(false); - }, 2000); - return () => clearTimeout(copyTimer); - }, - [didCopy, setClipboard] - ); - - return ( - MAX_CARD_HEIGHT} - isExpanded - skipCollapsedState - > - - - - - - 􀙤 - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} - - - - handleCopyPress(message)}> - - - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} - - - - - - - - - {displayMessage} - - - - ); -}; - -const SimulatedEventRow = ({ - amount, - asset, - eventType, - price, -}: { - amount: string | 'unlimited'; - asset: TransactionSimulationAsset | undefined; - eventType: EventType; - price?: number | undefined; -}) => { - const theme = useTheme(); - const { nativeCurrency } = useAccountSettings(); - const { data: externalAsset } = useExternalToken({ - address: asset?.assetCode || '', - chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), - currency: nativeCurrency, - }); - - const eventInfo: EventInfo = infoForEventType[eventType]; - - const formattedAmount = useMemo(() => { - if (!asset) return; - - const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; - const assetDisplayName = - asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; - const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; - - const displayAmount = - asset?.decimals === 0 - ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` - : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; - - const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; - - return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; - }, [amount, asset, eventInfo?.amountPrefix]); - - const url = maybeSignUri(asset?.iconURL, { - fm: 'png', - w: 16 * PixelRatio.get(), - }); - - const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; - - const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); - - return ( - - - - - - - {eventInfo.label} - - {showUSD && ( - - {formattedPrice} - - )} - - - - - {asset?.type !== TransactionAssetType.Nft ? ( - - ) : ( - - )} - - - {formattedAmount} - - - - - ); -}; - -const DetailRow = ({ - currentNetwork, - detailType, - onPress, - value, -}: { - currentNetwork?: Network; - detailType: DetailType; - onPress?: () => void; - value: string; -}) => { - const detailInfo: DetailInfo = infoForDetailType[detailType]; - - return ( - - - - - - {detailInfo.label} - - - - {detailType === 'function' && } - {detailType === 'sourceCodeVerification' && ( - - )} - {detailType === 'chain' && currentNetwork && ( - - )} - {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( - - {value} - - )} - {(detailType === 'contract' || detailType === 'to') && ( - - - - - 􀂄 - - - - - )} - - - - ); -}; - -const EventIcon = ({ eventType }: { eventType: EventType }) => { - const eventInfo: EventInfo = infoForEventType[eventType]; - - const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; - const isWarningIcon = - eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; - - return ( - - {!hideInnerFill && ( - - )} - - {eventInfo.icon} - - - ); -}; - -const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { - return ( - - - {detailInfo.icon} - - - ); -}; - -const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { - const { colors, isDarkMode } = useTheme(); - const separatorTertiary = useForegroundColor('separatorTertiary'); - - const infoForBadgeType: { - [key: string]: { - backgroundColor: string; - borderColor: string; - label?: string; - text: TextColor; - textOpacity?: number; - }; - } = { - function: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - text: 'labelQuaternary', - }, - unknown: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - label: 'Unknown', - text: 'labelQuaternary', - }, - unverified: { - backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, - borderColor: colors.alpha(colors.red, 0.02), - label: 'Unverified', - text: 'red', - textOpacity: 0.76, - }, - verified: { - backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, - borderColor: colors.alpha(colors.green, 0.02), - label: 'Verified', - text: 'green', - textOpacity: 0.76, - }, - }; - - return ( - - - - {infoForBadgeType[type].label || value} - - - - ); -}; - -const VerifiedBadge = () => { - return ( - - - - - 􀇻 - - - - ); -}; - -const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { - return ( - - {visible && ( - - - - - - 􀁣 - - - - - )} - - ); -}; - -const FadedScrollCard = ({ - cardHeight, - children, - contentHeight, - expandedCardBottomInset = 120, - expandedCardTopInset = 120, - initialScrollEnabled, - isExpanded, - onPressCollapsedCard, - skipCollapsedState, -}: { - cardHeight: SharedValue; - children: React.ReactNode; - contentHeight: SharedValue; - expandedCardBottomInset?: number; - expandedCardTopInset?: number; - initialScrollEnabled?: boolean; - isExpanded: boolean; - onPressCollapsedCard?: () => void; - skipCollapsedState?: boolean; -}) => { - const { height: deviceHeight, width: deviceWidth } = useDimensions(); - const { isDarkMode } = useTheme(); - - const cardRef = useAnimatedRef(); - - const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); - const [isFullyExpanded, setIsFullyExpanded] = useState(false); - - const yPosition = useSharedValue(0); - - const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); - - const containerStyle = useAnimatedStyle(() => { - return { - height: - cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState - ? interpolate( - cardHeight.value, - [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], - [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], - 'clamp' - ) - : undefined, - zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), - }; - }); - - const backdropStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const cardStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); - return { - borderColor: interpolateColor( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] - ), - height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, - position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', - transform: [ - { - translateY: interpolate( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - [ - 0, - 0, - -yPosition.value + - expandedCardTopInset + - (deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight) - - (yPosition.value + expandedCardHeight >= deviceHeight - expandedCardBottomInset - ? 0 - : deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight), - ] - ), - }, - ], - }; - }); - - const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { - return { - transform: skipCollapsedState - ? undefined - : [ - { - translateY: interpolate( - cardHeight.value, - [ - 0, - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT - ? MAX_CARD_HEIGHT - : contentHeight.value + CARD_BORDER_WIDTH * 2, - maxExpandedHeight, - ], - [-2, -2, 0, 0] - ), - }, - ], - }; - }); - - const shadowStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const handleContentSizeChange = useCallback( - (width: number, height: number) => { - contentHeight.value = Math.round(height); - }, - [contentHeight] - ); - - const handleOnLayout = useCallback(() => { - runOnUI(() => { - if (cardHeight.value === MAX_CARD_HEIGHT) { - const measurement = measure(cardRef); - if (measurement === null) { - return; - } - if (yPosition.value !== measurement.pageY) { - yPosition.value = measurement.pageY; - } - } - })(); - }, [cardHeight, cardRef, yPosition]); - - useAnimatedReaction( - () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), - ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { - if ( - isFullyExpanded !== previous?.isFullyExpanded || - isExpanded !== previous?.isExpanded || - contentHeight !== previous?.contentHeight - ) { - if (isFullyExpanded) { - const expandedCardHeight = - contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; - if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { - cardHeight.value = withTiming(expandedCardHeight, timingConfig); - } else { - runOnJS(setIsFullyExpanded)(false); - } - } else if (isExpanded) { - cardHeight.value = withTiming( - contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, - timingConfig - ); - } else { - cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); - } - - const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); - runOnJS(setScrollEnabled)(enableScroll); - } - } - ); - - return ( - - { - if (isFullyExpanded) { - setIsFullyExpanded(false); - } - }} - pointerEvents={isFullyExpanded ? 'auto' : 'none'} - style={[ - { - backgroundColor: 'rgba(0, 0, 0, 0.6)', - height: deviceHeight * 3, - left: -deviceWidth * 0.5, - position: 'absolute', - top: -deviceHeight, - width: deviceWidth * 2, - zIndex: -1, - }, - backdropStyle, - ]} - /> - - - - { - if (!isFullyExpanded) { - setIsFullyExpanded(true); - } else setIsFullyExpanded(false); - } - } - > - {children} - - - - - - - - ); -}; - -const FadeGradient = ({ side, style }: { side: 'top' | 'bottom'; style?: StyleProp>> }) => { - const { colors, isDarkMode } = useTheme(); - - const isTop = side === 'top'; - const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; - const transparentColor = colors.alpha(solidColor, 0); - - return ( - - - - ); -}; - -const IconContainer = ({ - children, - hitSlop, - opacity, - size = 20, -}: { - children: React.ReactNode; - hitSlop?: number; - opacity?: number; - size?: number; -}) => { - // Prevent wide icons from being clipped - const extraHorizontalSpace = 4; - - return ( - - - {children} - - - ); -}; - -type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; - -type EventInfo = { - amountPrefix: string; - icon: string; - iconColor: TextColor; - label: string; - textColor: TextColor; -}; - -const infoForEventType: { [key: string]: EventInfo } = { - send: { - amountPrefix: '- ', - icon: '􀁷', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), - textColor: 'red', - }, - receive: { - amountPrefix: '+ ', - icon: '􀁹', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), - textColor: 'green', - }, - approve: { - amountPrefix: '', - icon: '􀎤', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), - textColor: 'label', - }, - revoke: { - amountPrefix: '', - icon: '􀎠', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), - textColor: 'label', - }, - failed: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), - textColor: 'red', - }, - insufficientBalance: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'blue', - label: '', - textColor: 'blue', - }, - MALICIOUS: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: '', - textColor: 'red', - }, - WARNING: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'orange', - label: '', - textColor: 'orange', - }, -}; - -type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; - -type DetailInfo = { - icon: string; - label: string; -}; - -const infoForDetailType: { [key: string]: DetailInfo } = { - chain: { - icon: '􀤆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), - }, - contract: { - icon: '􀉆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), - }, - to: { - icon: '􀉩', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), - }, - function: { - icon: '􀡅', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), - }, - sourceCodeVerification: { - icon: '􀕹', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), - }, - dateCreated: { - icon: '􀉉', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), - }, - nonce: { - icon: '􀆃', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), - }, -}; - -const CHARACTERS_PER_LINE = 40; -const LINE_HEIGHT = 11; -const LINE_GAP = 9; - -const estimateMessageHeight = (message: string) => { - const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); - const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; - - return messageHeight; -}; - -const formatDate = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - const diffTime = Math.abs(now.getTime() - date.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - const diffWeeks = Math.floor(diffDays / 7); - const diffMonths = Math.floor(diffDays / 30.44); - - if (diffDays === 0) { - return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); - } else if (diffDays === 1) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; - } else if (diffDays < 7) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; - } else if (diffWeeks === 1) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; - } else if (diffDays < 30.44) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; - } else if (diffMonths === 1) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; - } else if (diffDays < 365.25) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; - } else { - return date.toLocaleString('default', { month: 'short', year: 'numeric' }); - } -}; diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 02ea352ec5c..6c4fd429c2c 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -33,6 +33,8 @@ import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; +import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; +import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; const WalletPage = styled(Page)({ ...position.sizeAsObject('100%'), @@ -106,6 +108,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { if (walletReady) { loadAccountLateData(); loadGlobalLateData(); + runWalletBackupStatusChecks(); } }, [loadAccountLateData, loadGlobalLateData, walletReady]); @@ -147,6 +150,9 @@ const WalletScreen: React.FC = ({ navigation, route }) => { + + {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} + ); diff --git a/src/state/performance/operations.ts b/src/state/performance/operations.ts index f30133ce5c6..a042200fc26 100644 --- a/src/state/performance/operations.ts +++ b/src/state/performance/operations.ts @@ -4,6 +4,7 @@ export enum Screens { SEND = 'Send', SEND_ENS = 'SendENS', WALLETCONNECT = 'WalletConnect', + MOBILE_WALLET_PROTOCOL = 'MobileWalletProtocol', } type RouteValues = (typeof Screens)[keyof typeof Screens]; diff --git a/src/storage/index.ts b/src/storage/index.ts index 824c7288ce7..c9d9639552a 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -3,6 +3,7 @@ import { MMKV } from 'react-native-mmkv'; import { Account, Cards, Campaigns, Device, Review } from '@/storage/schema'; import { EthereumAddress, RainbowTransaction } from '@/entities'; import { Network } from '@/networks/types'; +import { SecureStorage } from '@coinbase/mobile-wallet-protocol-host'; /** * Generic storage class. DO NOT use this directly. Instead, use the exported @@ -12,8 +13,8 @@ export class Storage { protected sep = ':'; protected store: MMKV; - constructor({ id }: { id: string }) { - this.store = new MMKV({ id }); + constructor({ id, encryptionKey }: { id: string; encryptionKey?: string }) { + this.store = new MMKV({ id, encryptionKey }); } /** @@ -50,6 +51,13 @@ export class Storage { this.store.delete(scopes.join(this.sep)); } + /** + * Clear all values from storage + */ + clear() { + this.store.clearAll(); + } + /** * Remove many values from the same storage scope by keys * @@ -59,6 +67,21 @@ export class Storage { removeMany(scopes: [...Scopes], keys: Key[]) { keys.forEach(key => this.remove([...scopes, key])); } + + /** + * Encrypt the storage with a new key + * @param newEncryptionKey - The new encryption key + */ + encrypt(newEncryptionKey: string): void { + this.store.recrypt(newEncryptionKey); + } + + /** + * Remove encryption from the storage + */ + removeEncryption(): void { + this.store.recrypt(undefined); + } } /** @@ -88,3 +111,27 @@ export const cards = new Storage<[], Cards>({ id: 'cards' }); export const identifier = new Storage<[], { identifier: string }>({ id: 'identifier', }); + +/** + * Mobile Wallet Protocol storage + * + * @todo - fix any type here + */ +const mwpStorage = new Storage<[], any>({ id: 'mwp', encryptionKey: process.env.MWP_ENCRYPTION_KEY }); + +export const mwp: SecureStorage = { + get: async function (key: string): Promise { + const dataJson = mwpStorage.get([key]); + if (dataJson === undefined) { + return undefined; + } + return Promise.resolve(JSON.parse(dataJson) as T); + }, + set: async function (key: string, value: T): Promise { + const encoded = JSON.stringify(value); + mwpStorage.set([key], encoded); + }, + remove: async function (key: string): Promise { + mwpStorage.remove([key]); + }, +}; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts new file mode 100644 index 00000000000..8bb40dc1897 --- /dev/null +++ b/src/utils/formatDate.ts @@ -0,0 +1,28 @@ +import * as i18n from '@/languages'; + +export const formatDate = (dateString: string) => { + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - date.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30.44); + + if (diffDays === 0) { + return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); + } else if (diffDays === 1) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; + } else if (diffDays < 7) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; + } else if (diffWeeks === 1) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; + } else if (diffDays < 30.44) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; + } else if (diffMonths === 1) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; + } else if (diffDays < 365.25) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; + } else { + return date.toLocaleString('default', { month: 'short', year: 'numeric' }); + } +}; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 8a6f193a480..6ecc8ad16fc 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -13,7 +13,7 @@ import { import { InteractionManager } from 'react-native'; import { SEND_TRANSACTION } from './signingMethods'; import { handleSessionRequestResponse } from '@/walletConnect'; -import ethereumUtils from './ethereumUtils'; +import ethereumUtils, { getNetworkFromChainId } from './ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers'; import { RainbowNetworks } from '@/networks'; import { maybeSignUri } from '@/handlers/imgix'; @@ -22,8 +22,249 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; +import { + EthereumAction, + isEthereumAction, + isHandshakeAction, + PersonalSignAction, + RequestMessage, + useMobileWalletProtocolHost, +} from '@coinbase/mobile-wallet-protocol-host'; +import { ChainId } from '@/__swaps__/types/chains'; +import { logger, RainbowError } from '@/logger'; +import { noop } from 'lodash'; +import { toUtf8String } from '@ethersproject/strings'; +import { BigNumber } from '@ethersproject/bignumber'; + +export enum RequestSource { + WALLETCONNECT = 'walletconnect', + BROWSER = 'browser', + MOBILE_WALLET_PROTOCOL = 'mobile-wallet-protocol', +} + +// Mobile Wallet Protocol + +interface HandleMobileWalletProtocolRequestProps + extends Omit, 'message' | 'handleRequestUrl' | 'sendFailureToClient'> { + request: RequestMessage; +} + +const constructEthereumActionPayload = (action: EthereumAction) => { + if (action.method === 'eth_sendTransaction') { + const { weiValue, fromAddress, toAddress, actionSource, gasPriceInWei, ...rest } = action.params; + return [ + { + ...rest, + from: fromAddress, + to: toAddress, + value: weiValue, + }, + ]; + } + + return Object.values(action.params); +}; + +const supportedMobileWalletProtocolActions: string[] = [ + 'eth_requestAccounts', + 'eth_sendTransaction', + 'eth_signTypedData_v4', + 'personal_sign', + 'wallet_switchEthereumChain', +]; + +export const handleMobileWalletProtocolRequest = async ({ + request, + fetchClientAppMetadata, + approveHandshake, + rejectHandshake, + approveAction, + rejectAction, + session, +}: HandleMobileWalletProtocolRequestProps): Promise => { + logger.debug(`Handling Mobile Wallet Protocol request: ${request.uuid}`); + + const { selected } = store.getState().wallets; + const { accountAddress } = store.getState().settings; + + const isReadOnlyWallet = selected?.type === walletTypes.readOnly; + if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { + logger.debug('Rejecting request due to read-only wallet'); + watchingAlert(); + return Promise.reject(new Error('This wallet is read-only.')); + } + + const handleAction = async (currentIndex: number): Promise => { + const action = request.actions[currentIndex]; + logger.debug(`Handling action: ${action.kind}`); + + if (isHandshakeAction(action)) { + logger.debug(`Processing handshake action for ${action.appId}`); + + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const receivedTimestamp = Date.now(); + + const dappMetadata = await fetchClientAppMetadata(); + return new Promise((resolve, reject) => { + const routeParams: WalletconnectApprovalSheetRouteParams = { + receivedTimestamp, + meta: { + chainIds, + dappName: dappMetadata?.appName || dappMetadata?.appUrl || action.appName || action.appIconUrl || action.appId || '', + dappUrl: dappMetadata?.appUrl || action.appId || '', + imageUrl: maybeSignUri(dappMetadata?.iconUrl || action.appIconUrl), + isWalletConnectV2: false, + peerId: '', + dappScheme: action.callback, + proposedChainId: request.account?.networkId ?? ChainId.mainnet, + proposedAddress: request.account?.address || accountAddress, + }, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + timedOut: false, + callback: async approved => { + if (approved) { + logger.debug(`Handshake approved for ${action.appId}`); + const success = await approveHandshake(dappMetadata); + resolve(success); + } else { + logger.debug(`Handshake rejected for ${action.appId}`); + await rejectHandshake('User rejected the handshake'); + reject('User rejected the handshake'); + } + }, + }; + + Navigation.handleAction( + Routes.WALLET_CONNECT_APPROVAL_SHEET, + routeParams, + getActiveRoute()?.name === Routes.WALLET_CONNECT_APPROVAL_SHEET + ); + }); + } else if (isEthereumAction(action)) { + logger.debug(`Processing ethereum action: ${action.method}`); + if (!supportedMobileWalletProtocolActions.includes(action.method)) { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type ${action.method}`)); + await rejectAction(action, { + message: 'Unsupported action type', + code: 4001, + }); + return false; + } + + if (action.method === 'wallet_switchEthereumChain') { + const isSupportedChain = RainbowNetworks.find(network => network.id === BigNumber.from(action.params.chainId).toNumber()); + if (!isSupportedChain) { + await rejectAction(action, { + message: 'Unsupported chain', + code: 4001, + }); + return false; + } + + await approveAction(action, { value: 'null' }); + return true; + } + + // NOTE: This is a workaround to approve the eth_requestAccounts action if the previous action was a handshake action. + const previousAction = request.actions[currentIndex - 1]; + if (previousAction && isHandshakeAction(previousAction)) { + logger.debug('Approving eth_requestAccounts'); + await approveAction(action, { + value: JSON.stringify({ + chain: request.account?.chain ?? 'eth', + networkId: request.account?.networkId ?? ChainId.mainnet, + address: accountAddress, + }), + }); + return true; + } + + const nativeCurrency = store.getState().settings.nativeCurrency; + + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + if ((action as PersonalSignAction).params.message && (action as PersonalSignAction).params.message.type === 'Buffer') { + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + const messageFromBuffer = toUtf8String(Buffer.from((action as PersonalSignAction).params.message.data, 'hex')); + (action as PersonalSignAction).params.message = messageFromBuffer; + } + + const payload = { + method: action.method, + params: constructEthereumActionPayload(action), + }; + + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, request.account?.networkId ?? ChainId.mainnet); + + const requestWithDetails: RequestData = { + dappName: session?.dappName ?? session?.dappId ?? '', + dappUrl: session?.dappURL ?? '', + imageUrl: session?.dappImageURL ?? '', + address: (action as PersonalSignAction).params.address ?? accountAddress, + chainId: request.account?.networkId ?? ChainId.mainnet, + payload, + displayDetails, + }; + + return new Promise((resolve, reject) => { + const onSuccess = async (result: string) => { + logger.debug(`Ethereum action approved: [${action.method}]: ${result}`); + const success = await approveAction(action, { value: JSON.stringify(result) }); + resolve(success); + }; -export type RequestSource = 'walletconnect' | 'browser'; + const onCancel = async (error?: Error) => { + if (error) { + logger.debug(`Ethereum action rejected: [${action.method}]: ${error.message}`); + await rejectAction(action, { + message: error.message, + code: 4001, + }); + reject(error.message); + } else { + logger.debug(`Ethereum action rejected: [${action.method}]: User rejected request`); + await rejectAction(action, { + message: 'User rejected request', + code: 4001, + }); + reject('User rejected request'); + } + }; + + Navigation.handleAction(Routes.CONFIRM_REQUEST, { + transactionDetails: requestWithDetails, + onSuccess, + onCancel, + onCloseScreen: noop, + chainId: request.account?.networkId ?? ChainId.mainnet, + address: accountAddress, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + }); + }); + } else { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type, ${action}`)); + return false; + } + }; + + const handleActions = async (actions: typeof request.actions, currentIndex: number = 0): Promise => { + if (currentIndex >= actions.length) { + logger.debug(`All actions completed successfully: ${actions.length}`); + return true; + } + + logger.debug(`Processing action ${currentIndex + 1} of ${actions.length}`); + const success = await handleAction(currentIndex); + if (success) { + return handleActions(actions, currentIndex + 1); + } else { + // stop processing if an action fails + return false; + } + }; + + // start processing actions starting at index 0 + return handleActions(request.actions); +}; // Dapp Browser @@ -52,7 +293,7 @@ export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): proposedChainId: dappData.chainId, proposedAddress: dappData.address, }, - source: 'browser', + source: RequestSource.BROWSER, timedOut: false, callback: async (approved, approvedChainId, accountAddress) => { if (approved) { @@ -77,16 +318,31 @@ export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): }); }; -export const handleDappBrowserRequest = async (request: Omit): Promise => { +const findWalletForAddress = async (address: string) => { + if (!address.trim()) { + return Promise.reject(new Error('Invalid address')); + } + const { wallets } = store.getState().wallets; - const selectedWallet = findWalletWithAccount(wallets!, request.address); - const isReadOnlyWallet = selectedWallet!.type === walletTypes.readOnly; + const selectedWallet = findWalletWithAccount(wallets!, address); + if (!selectedWallet) { + return Promise.reject(new Error('Wallet not found')); + } + + const isReadOnlyWallet = selectedWallet.type === walletTypes.readOnly; if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return Promise.reject(new Error('This wallet is read-only.')); } + + return selectedWallet; +}; + +export const handleDappBrowserRequest = async (request: Omit): Promise => { + await findWalletForAddress(request.address); + const nativeCurrency = store.getState().settings.nativeCurrency; - const displayDetails = getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); + const displayDetails = await getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); const requestWithDetails: RequestData = { ...request, @@ -118,7 +374,7 @@ export const handleDappBrowserRequest = async (request: Omit Date: Wed, 28 Aug 2024 17:29:34 -0400 Subject: [PATCH 65/78] Revert "Transaction Simulation Cleanup (#5977)" (#6057) This reverts commit 254e06db205c247d7472ba38278142de81c3d52e. --- globals.d.ts | 1 - ios/Podfile.lock | 12 - package.json | 1 - src/App.tsx | 167 +- src/components/AppStateChangeHandler.tsx | 38 - src/components/DeeplinkHandler.tsx | 65 - src/components/FadeGradient.tsx | 47 - src/components/FadedScrollCard.tsx | 281 --- .../MobileWalletProtocolListener.tsx | 51 - .../Transactions/TransactionDetailsCard.tsx | 127 -- .../Transactions/TransactionDetailsRow.tsx | 90 - .../Transactions/TransactionIcons.tsx | 196 -- .../Transactions/TransactionMessageCard.tsx | 113 - .../TransactionSimulatedEventRow.tsx | 109 - .../TransactionSimulationCard.tsx | 315 --- src/components/Transactions/constants.ts | 121 -- src/components/Transactions/types.ts | 18 - src/handlers/deeplinks.ts | 19 +- src/hooks/useApplicationSetup.ts | 55 - src/hooks/useCalculateGasLimit.ts | 74 - src/hooks/useConfirmTransaction.ts | 27 - src/hooks/useHasEnoughBalance.ts | 47 - src/hooks/useNonceForDisplay.ts | 32 - src/hooks/useProviderSetup.ts | 50 - src/hooks/useSubmitTransaction.ts | 55 - src/hooks/useTransactionSetup.ts | 76 - src/languages/en_US.json | 1 - src/navigation/Routes.android.tsx | 29 +- src/navigation/Routes.ios.tsx | 29 +- src/navigation/config.tsx | 3 +- src/notifications/tokens.ts | 2 +- src/parsers/requests.js | 6 +- src/redux/requests.ts | 6 +- src/redux/walletconnect.ts | 4 +- .../transactions/transactionSimulation.ts | 148 -- src/screens/SignTransactionSheet.tsx | 1920 +++++++++++++++-- src/screens/WalletScreen/index.tsx | 6 - src/state/performance/operations.ts | 1 - src/storage/index.ts | 51 +- src/utils/formatDate.ts | 28 - src/utils/requestNavigationHandlers.ts | 274 +-- src/walletConnect/index.tsx | 2 +- tsconfig.json | 1 - yarn.lock | 34 - 44 files changed, 1901 insertions(+), 2831 deletions(-) delete mode 100644 src/components/AppStateChangeHandler.tsx delete mode 100644 src/components/DeeplinkHandler.tsx delete mode 100644 src/components/FadeGradient.tsx delete mode 100644 src/components/FadedScrollCard.tsx delete mode 100644 src/components/MobileWalletProtocolListener.tsx delete mode 100644 src/components/Transactions/TransactionDetailsCard.tsx delete mode 100644 src/components/Transactions/TransactionDetailsRow.tsx delete mode 100644 src/components/Transactions/TransactionIcons.tsx delete mode 100644 src/components/Transactions/TransactionMessageCard.tsx delete mode 100644 src/components/Transactions/TransactionSimulatedEventRow.tsx delete mode 100644 src/components/Transactions/TransactionSimulationCard.tsx delete mode 100644 src/components/Transactions/constants.ts delete mode 100644 src/components/Transactions/types.ts delete mode 100644 src/hooks/useApplicationSetup.ts delete mode 100644 src/hooks/useCalculateGasLimit.ts delete mode 100644 src/hooks/useConfirmTransaction.ts delete mode 100644 src/hooks/useHasEnoughBalance.ts delete mode 100644 src/hooks/useNonceForDisplay.ts delete mode 100644 src/hooks/useProviderSetup.ts delete mode 100644 src/hooks/useSubmitTransaction.ts delete mode 100644 src/hooks/useTransactionSetup.ts delete mode 100644 src/resources/transactions/transactionSimulation.ts delete mode 100644 src/utils/formatDate.ts diff --git a/globals.d.ts b/globals.d.ts index 068c5a86e74..29d1107dba2 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -109,5 +109,4 @@ declare module 'react-native-dotenv' { export const REACT_NATIVE_RUDDERSTACK_WRITE_KEY: string; export const RUDDERSTACK_DATA_PLANE_URL: string; export const SILENCE_EMOJI_WARNINGS: boolean; - export const MWP_ENCRYPTION_KEY: string; } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f28ca0bae70..1c400b0f1c1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,9 +4,6 @@ PODS: - BVLinearGradient (2.8.3): - React-Core - CocoaAsyncSocket (7.6.5) - - CoinbaseWalletSDK/Client (1.1.0) - - CoinbaseWalletSDK/Host (1.1.0): - - CoinbaseWalletSDK/Client - DoubleConversion (1.1.6) - FasterImage (1.6.2): - FasterImage/Nuke (= 1.6.2) @@ -174,9 +171,6 @@ PODS: - MMKV (1.3.9): - MMKVCore (~> 1.3.9) - MMKVCore (1.3.9) - - mobile-wallet-protocol-host (0.1.7): - - CoinbaseWalletSDK/Host - - React-Core - MultiplatformBleAdapter (0.1.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -1832,7 +1826,6 @@ DEPENDENCIES: - GoogleUtilities - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libwebp - - "mobile-wallet-protocol-host (from `../node_modules/@coinbase/mobile-wallet-protocol-host`)" - nanopb - PanModal (from `https://github.com/rainbow-me/PanModal`, commit `ab97d74279ba28c2891b47a5dc767ed4dd7cf994`) - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`) @@ -1963,7 +1956,6 @@ SPEC REPOS: https://github.com/CocoaPods/Specs.git: - Branch - CocoaAsyncSocket - - CoinbaseWalletSDK - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -2015,8 +2007,6 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 - mobile-wallet-protocol-host: - :path: "../node_modules/@coinbase/mobile-wallet-protocol-host" PanModal: :commit: ab97d74279ba28c2891b47a5dc767ed4dd7cf994 :git: https://github.com/rainbow-me/PanModal @@ -2267,7 +2257,6 @@ SPEC CHECKSUMS: Branch: d99436c6f3d5b2529ba948d273e47e732830f207 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 @@ -2293,7 +2282,6 @@ SPEC CHECKSUMS: MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843 MMKV: 817ba1eea17421547e01e087285606eb270a8dcb MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 - mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d nanopb: 438bc412db1928dac798aa6fd75726007be04262 PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3 diff --git a/package.json b/package.json index 7ff3845f425..533313474f1 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "@bradgarropy/use-countdown": "1.4.1", "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", - "@coinbase/mobile-wallet-protocol-host": "0.1.7", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", "@ensdomains/eth-ens-namehash": "2.0.15", diff --git a/src/App.tsx b/src/App.tsx index 128324099b8..02c836a846e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,9 @@ import './languages'; import * as Sentry from '@sentry/react-native'; -import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react'; -import { AppRegistry, Dimensions, LogBox, StyleSheet, View } from 'react-native'; -import { MobileWalletProtocolProvider } from '@coinbase/mobile-wallet-protocol-host'; -import { DeeplinkHandler } from '@/components/DeeplinkHandler'; -import { AppStateChangeHandler } from '@/components/AppStateChangeHandler'; -import { useApplicationSetup } from '@/hooks/useApplicationSetup'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; +import branch from 'react-native-branch'; + import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; @@ -17,16 +15,26 @@ import { OfflineToast } from './components/toasts'; import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug'; import monitorNetwork from './debugging/network'; import { Playground } from './design-system/playground/Playground'; +import handleDeeplink from './handlers/deeplinks'; +import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; import RainbowContextWrapper from './helpers/RainbowContext'; +import isTestFlight from './helpers/isTestFlight'; import * as keychain from '@/model/keychain'; +import { loadAddress } from './model/wallet'; import { Navigation } from './navigation'; import RoutesComponent from './navigation/Routes'; +import { PerformanceContextMap } from './performance/PerformanceContextMap'; +import { PerformanceTracking } from './performance/tracking'; +import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetrics'; import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query'; import store from './redux/store'; +import { walletConnectLoadState } from './redux/walletconnect'; import { MainThemeProvider } from './theme/ThemeContext'; +import { branchListener } from './utils/branch'; import { addressKey } from './utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute'; +import Routes from '@/navigation/routesNames'; import { Portal } from '@/react-native-cool-modals/Portal'; import { NotificationsHandler } from '@/notifications/NotificationsHandler'; import { analyticsV2 } from '@/analytics'; @@ -34,17 +42,19 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; +import { initListeners as initWalletConnectListeners } from '@/walletConnect'; +import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { initializeRemoteConfig } from '@/model/remoteConfig'; import { NavigationContainerRef } from '@react-navigation/native'; import { RootStackParamList } from './navigation/types'; import { Address } from 'viem'; import { IS_DEV } from './env'; +import { checkIdentifierOnLaunch } from './model/backup'; import { prefetchDefaultFavorites } from './resources/favorites'; -const LazyRoutesComponent = lazy(() => import('./navigation/Routes')); - if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); (showNetworkRequests || showNetworkResponses) && monitorNetwork(showNetworkRequests, showNetworkResponses); @@ -52,40 +62,123 @@ if (IS_DEV) { enableScreens(); -const sx = StyleSheet.create({ - container: { - flex: 1, - }, -}); +const containerStyle = { flex: 1 }; interface AppProps { walletReady: boolean; } function App({ walletReady }: AppProps) { - const { initialRoute } = useApplicationSetup(); + const [appState, setAppState] = useState(AppState.currentState); + const [initialRoute, setInitialRoute] = useState(null); + const eventSubscription = useRef | null>(null); + const branchListenerRef = useRef | null>(null); + const navigatorRef = useRef | null>(null); + + const setupDeeplinking = useCallback(async () => { + const initialUrl = await Linking.getInitialURL(); + + branchListenerRef.current = await branchListener(url => { + logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); + try { + handleDeeplink(url, initialRoute); + } catch (error) { + if (error instanceof Error) { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { + message: error.message, + url, + }); + } else { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { + message: 'Unknown error', + url, + }); + } + } + }); + + if (initialUrl) { + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); + branch.openURL(initialUrl); + } + }, [initialRoute]); + + const identifyFlow = useCallback(async () => { + const address = await loadAddress(); + if (address) { + setTimeout(() => { + InteractionManager.runAfterInteractions(() => { + handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); + }); + }, 10_000); + + InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); + } + + setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + }, []); + + const handleAppStateChange = useCallback( + (nextAppState: AppStateStatus) => { + if (appState === 'background' && nextAppState === 'active') { + store.dispatch(walletConnectLoadState()); + } + setAppState(nextAppState); + analyticsV2.track(analyticsV2.event.appStateChange, { + category: 'app state', + label: nextAppState, + }); + }, + [appState] + ); const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { + navigatorRef.current = ref; Navigation.setTopLevelNavigator(ref); }, []); + useEffect(() => { + if (!__DEV__ && isTestFlight) { + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); + } + identifyFlow(); + eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + + const p1 = analyticsV2.initializeRudderstack(); + const p2 = setupDeeplinking(); + const p3 = saveFCMToken(); + Promise.all([p1, p2, p3]).then(() => { + initWalletConnectListeners(); + PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); + analyticsV2.track(analyticsV2.event.applicationDidMount); + }); + + return () => { + eventSubscription.current?.remove(); + branchListenerRef.current?.(); + }; + }, []); + + useEffect(() => { + if (walletReady) { + logger.debug(`[App]: ✅ Wallet ready!`); + runWalletBackupStatusChecks(); + } + }, [walletReady]); + return ( - + {initialRoute && ( - }> - {/* @ts-expect-error - Property 'ref' does not exist on the 'IntrinsicAttributes' object */} - - + )} - - ); } @@ -99,9 +192,9 @@ const AppWithRedux = connect(state => }))(App); function Root() { - const [initializing, setInitializing] = useState(true); + const [initializing, setInitializing] = React.useState(true); - useEffect(() => { + React.useEffect(() => { async function initializeApplication() { await initializeRemoteConfig(); await migrate(); @@ -207,21 +300,19 @@ function Root() { prefetchDefaultFavorites(); }} > - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/components/AppStateChangeHandler.tsx b/src/components/AppStateChangeHandler.tsx deleted file mode 100644 index ee8b29d2ebb..00000000000 --- a/src/components/AppStateChangeHandler.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { AppState, AppStateStatus, Linking } from 'react-native'; -import { analyticsV2 } from '@/analytics'; -import store from '@/redux/store'; -import { walletConnectLoadState } from '@/redux/walletconnect'; - -type AppStateChangeHandlerProps = { - walletReady: boolean; -}; - -export function AppStateChangeHandler({ walletReady }: AppStateChangeHandlerProps) { - const [appState, setAppState] = useState(AppState.currentState); - const eventSubscription = useRef | null>(null); - - const handleAppStateChange = useCallback( - (nextAppState: AppStateStatus) => { - if (appState === 'background' && nextAppState === 'active') { - store.dispatch(walletConnectLoadState()); - } - setAppState(nextAppState); - analyticsV2.track(analyticsV2.event.appStateChange, { - category: 'app state', - label: nextAppState, - }); - }, - [appState] - ); - - useEffect(() => { - if (!walletReady) return; - - eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); - - return () => eventSubscription.current?.remove(); - }, [handleAppStateChange]); - - return null; -} diff --git a/src/components/DeeplinkHandler.tsx b/src/components/DeeplinkHandler.tsx deleted file mode 100644 index e34f7ef4e8b..00000000000 --- a/src/components/DeeplinkHandler.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; -import { Linking } from 'react-native'; -import branch from 'react-native-branch'; -import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; -import handleDeeplink from '@/handlers/deeplinks'; -import { InitialRoute } from '@/navigation/initialRoute'; -import { logger, RainbowError } from '@/logger'; -import { branchListener } from '@/utils/branch'; - -type DeeplinkHandlerProps = { - initialRoute: InitialRoute; - walletReady: boolean; -}; - -export function DeeplinkHandler({ initialRoute, walletReady }: DeeplinkHandlerProps) { - const branchListenerRef = useRef | null>(null); - const { handleRequestUrl, sendFailureToClient } = useMobileWalletProtocolHost(); - - const setupDeeplinking = useCallback(async () => { - const initialUrl = await Linking.getInitialURL(); - - branchListenerRef.current = await branchListener(async url => { - logger.debug(`[App]: Branch listener called`, {}, logger.DebugContext.deeplinks); - - try { - handleDeeplink({ - url, - initialRoute, - handleRequestUrl, - sendFailureToClient, - }); - } catch (error) { - if (error instanceof Error) { - logger.error(new RainbowError('Error opening deeplink'), { - message: error.message, - url, - }); - } else { - logger.error(new RainbowError('Error opening deeplink'), { - message: 'Unknown error', - url, - }); - } - } - }); - - if (initialUrl) { - logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); - branch.openURL(initialUrl); - } - }, [handleRequestUrl, initialRoute, sendFailureToClient]); - - useEffect(() => { - if (!walletReady) return; - - setupDeeplinking(); - return () => { - if (branchListenerRef.current) { - branchListenerRef.current(); - } - }; - }, [setupDeeplinking, walletReady]); - - return null; -} diff --git a/src/components/FadeGradient.tsx b/src/components/FadeGradient.tsx deleted file mode 100644 index 88fdade67a7..00000000000 --- a/src/components/FadeGradient.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { StyleProp, ViewStyle } from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import Animated from 'react-native-reanimated'; - -import { Box, globalColors } from '@/design-system'; - -import { useTheme } from '@/theme'; - -type FadeGradientProps = { side: 'top' | 'bottom'; style?: StyleProp>> }; - -export const FadeGradient = ({ side, style }: FadeGradientProps) => { - const { colors, isDarkMode } = useTheme(); - - const isTop = side === 'top'; - const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; - const transparentColor = colors.alpha(solidColor, 0); - - return ( - - - - ); -}; diff --git a/src/components/FadedScrollCard.tsx b/src/components/FadedScrollCard.tsx deleted file mode 100644 index bf8a1ed9c39..00000000000 --- a/src/components/FadedScrollCard.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { TouchableWithoutFeedback } from 'react-native'; -import Animated, { - Easing, - SharedValue, - interpolate, - interpolateColor, - measure, - runOnJS, - runOnUI, - useAnimatedReaction, - useAnimatedRef, - useAnimatedStyle, - useSharedValue, - withTiming, -} from 'react-native-reanimated'; - -import { globalColors } from '@/design-system'; - -import { useTheme } from '@/theme'; - -import { useDimensions } from '@/hooks'; -import { FadeGradient } from '@/components/FadeGradient'; - -const COLLAPSED_CARD_HEIGHT = 56; -const MAX_CARD_HEIGHT = 176; - -const CARD_BORDER_WIDTH = 1.5; - -const timingConfig = { - duration: 300, - easing: Easing.bezier(0.2, 0, 0, 1), -}; - -type FadedScrollCardProps = { - cardHeight: SharedValue; - children: React.ReactNode; - contentHeight: SharedValue; - expandedCardBottomInset?: number; - expandedCardTopInset?: number; - initialScrollEnabled?: boolean; - isExpanded: boolean; - onPressCollapsedCard?: () => void; - skipCollapsedState?: boolean; -}; - -export const FadedScrollCard = ({ - cardHeight, - children, - contentHeight, - expandedCardBottomInset = 120, - expandedCardTopInset = 120, - initialScrollEnabled, - isExpanded, - onPressCollapsedCard, - skipCollapsedState, -}: FadedScrollCardProps) => { - const { height: deviceHeight, width: deviceWidth } = useDimensions(); - const { isDarkMode } = useTheme(); - - const cardRef = useAnimatedRef(); - - const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); - const [isFullyExpanded, setIsFullyExpanded] = useState(false); - - const yPosition = useSharedValue(0); - - const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); - - const containerStyle = useAnimatedStyle(() => { - return { - height: - cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState - ? interpolate( - cardHeight.value, - [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], - [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], - 'clamp' - ) - : undefined, - zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), - }; - }); - - const backdropStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const cardStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); - - const outputRange = [0, 0]; - - const yPos = -yPosition.value; - const offset = - deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight - (yPosition.value + expandedCardHeight); - - if (yPos + expandedCardTopInset + offset >= deviceHeight - expandedCardBottomInset) { - outputRange.push(0); - } else { - outputRange.push(deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight); - } - - return { - borderColor: interpolateColor( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] - ), - height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, - position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', - transform: [ - { - translateY: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, expandedCardHeight], outputRange), - }, - ], - }; - }); - - const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { - return { - transform: skipCollapsedState - ? undefined - : [ - { - translateY: interpolate( - cardHeight.value, - [ - 0, - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT - ? MAX_CARD_HEIGHT - : contentHeight.value + CARD_BORDER_WIDTH * 2, - maxExpandedHeight, - ], - [-2, -2, 0, 0] - ), - }, - ], - }; - }); - - const shadowStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const handleContentSizeChange = useCallback( - (width: number, height: number) => { - contentHeight.value = Math.round(height); - }, - [contentHeight] - ); - - const handleOnLayout = useCallback(() => { - runOnUI(() => { - if (cardHeight.value === MAX_CARD_HEIGHT) { - const measurement = measure(cardRef); - if (measurement === null) { - return; - } - if (yPosition.value !== measurement.pageY) { - yPosition.value = measurement.pageY; - } - } - })(); - }, [cardHeight, cardRef, yPosition]); - - useAnimatedReaction( - () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), - ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { - if ( - isFullyExpanded !== previous?.isFullyExpanded || - isExpanded !== previous?.isExpanded || - contentHeight !== previous?.contentHeight - ) { - if (isFullyExpanded) { - const expandedCardHeight = - contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; - if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { - cardHeight.value = withTiming(expandedCardHeight, timingConfig); - } else { - runOnJS(setIsFullyExpanded)(false); - } - } else if (isExpanded) { - cardHeight.value = withTiming( - contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, - timingConfig - ); - } else { - cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); - } - - const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); - runOnJS(setScrollEnabled)(enableScroll); - } - } - ); - - return ( - - { - if (isFullyExpanded) { - setIsFullyExpanded(false); - } - }} - pointerEvents={isFullyExpanded ? 'auto' : 'none'} - style={[ - { - backgroundColor: 'rgba(0, 0, 0, 0.6)', - height: deviceHeight * 3, - left: -deviceWidth * 0.5, - position: 'absolute', - top: -deviceHeight, - width: deviceWidth * 2, - zIndex: -1, - }, - backdropStyle, - ]} - /> - - - - { - if (!isFullyExpanded) { - setIsFullyExpanded(true); - } else setIsFullyExpanded(false); - } - } - > - {children} - - - - - - - - ); -}; diff --git a/src/components/MobileWalletProtocolListener.tsx b/src/components/MobileWalletProtocolListener.tsx deleted file mode 100644 index 395c58fd6b4..00000000000 --- a/src/components/MobileWalletProtocolListener.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { addDiagnosticLogListener, getAndroidIntentUrl, useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; -import { handleMobileWalletProtocolRequest } from '@/utils/requestNavigationHandlers'; -import { logger, RainbowError } from '@/logger'; -import { IS_ANDROID, IS_DEV } from '@/env'; - -export const MobileWalletProtocolListener = () => { - const { message, handleRequestUrl, sendFailureToClient, ...mwpProps } = useMobileWalletProtocolHost(); - const lastMessageUuidRef = useRef(null); - - useEffect(() => { - if (message && lastMessageUuidRef.current !== message.uuid) { - lastMessageUuidRef.current = message.uuid; - try { - handleMobileWalletProtocolRequest({ request: message, ...mwpProps }); - } catch (error) { - logger.error(new RainbowError('Error handling Mobile Wallet Protocol request'), { - error, - }); - } - } - }, [message, mwpProps]); - - useEffect(() => { - if (IS_DEV) { - const removeListener = addDiagnosticLogListener(event => { - console.log('Event:', JSON.stringify(event, null, 2)); - }); - - return () => removeListener(); - } - }, []); - - useEffect(() => { - if (IS_ANDROID) { - (async function handleAndroidIntent() { - const intentUrl = await getAndroidIntentUrl(); - if (intentUrl) { - const response = await handleRequestUrl(intentUrl); - if (response.error) { - // Return error to client app if session is expired or invalid - const { errorMessage, decodedRequest } = response.error; - await sendFailureToClient(errorMessage, decodedRequest); - } - } - })(); - } - }, [handleRequestUrl, sendFailureToClient]); - - return null; -}; diff --git a/src/components/Transactions/TransactionDetailsCard.tsx b/src/components/Transactions/TransactionDetailsCard.tsx deleted file mode 100644 index 8e00ee8e78a..00000000000 --- a/src/components/Transactions/TransactionDetailsCard.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useState } from 'react'; -import * as i18n from '@/languages'; -import Animated, { interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; - -import { Box, Inline, Stack, Text } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; - -import { abbreviations, ethereumUtils } from '@/utils'; -import { TransactionSimulationMeta } from '@/graphql/__generated__/metadataPOST'; -import { ChainId } from '@/__swaps__/types/chains'; - -import { getNetworkObj, getNetworkObject } from '@/networks'; -import { TransactionDetailsRow } from '@/components/Transactions/TransactionDetailsRow'; -import { FadedScrollCard } from '@/components/FadedScrollCard'; -import { IconContainer } from '@/components/Transactions/TransactionIcons'; -import { formatDate } from '@/utils/formatDate'; -import { - COLLAPSED_CARD_HEIGHT, - MAX_CARD_HEIGHT, - CARD_ROW_HEIGHT, - CARD_BORDER_WIDTH, - EXPANDED_CARD_TOP_INSET, -} from '@/components/Transactions/constants'; - -interface TransactionDetailsCardProps { - currentChainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - meta: TransactionSimulationMeta | undefined; - methodName: string; - noChanges: boolean; - nonce: string | undefined; - toAddress: string; -} - -export const TransactionDetailsCard = ({ - currentChainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - meta, - methodName, - noChanges, - nonce, - toAddress, -}: TransactionDetailsCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const [isExpanded, setIsExpanded] = useState(false); - - const currentNetwork = getNetworkObject({ chainId: currentChainId }); - - const listStyle = useAnimatedStyle(() => ({ - opacity: interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; - - const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); - const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; - const showTransferToRow = !!meta?.transferTo?.address; - // Hide DetailsCard if balance is insufficient once loaded - if (!isLoading && isBalanceEnough === false) { - return <>; - } - return ( - setIsExpanded(true)} - > - - - - - - 􁙠 - - - - {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} - - - - - - {} - {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - - ethereumUtils.openAddressInBlockExplorer( - meta?.to?.address || toAddress || meta?.transferTo?.address || '', - currentChainId - ) - } - value={ - meta?.to?.name || - abbreviations.address(meta?.to?.address || toAddress, 4, 6) || - meta?.to?.address || - toAddress || - meta?.transferTo?.address || - '' - } - /> - )} - {showFunctionRow && } - {!!meta?.to?.sourceCodeStatus && } - {!!meta?.to?.created && } - {nonce && } - - - - - ); -}; diff --git a/src/components/Transactions/TransactionDetailsRow.tsx b/src/components/Transactions/TransactionDetailsRow.tsx deleted file mode 100644 index bbe2a8394de..00000000000 --- a/src/components/Transactions/TransactionDetailsRow.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import * as i18n from '@/languages'; -import { TouchableWithoutFeedback } from 'react-native'; - -import { ButtonPressAnimation } from '@/components/animations'; -import { ChainImage } from '@/components/coin-icon/ChainImage'; -import { Box, Inline, Text } from '@/design-system'; - -import { DetailIcon, DetailBadge, IconContainer } from '@/components/Transactions/TransactionIcons'; -import { SMALL_CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; -import { DetailType, DetailInfo } from '@/components/Transactions/types'; -import { ChainId } from '@/__swaps__/types/chains'; - -interface TransactionDetailsRowProps { - currentChainId?: ChainId; - detailType: DetailType; - onPress?: () => void; - value: string; -} - -export const TransactionDetailsRow = ({ currentChainId, detailType, onPress, value }: TransactionDetailsRowProps) => { - const detailInfo: DetailInfo = infoForDetailType[detailType]; - - return ( - - - - - - {detailInfo.label} - - - - {detailType === 'function' && } - {detailType === 'sourceCodeVerification' && ( - - )} - {detailType === 'chain' && currentChainId && } - {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( - - {value} - - )} - {(detailType === 'contract' || detailType === 'to') && ( - - - - - 􀂄 - - - - - )} - - - - ); -}; - -const infoForDetailType: { [key: string]: DetailInfo } = { - chain: { - icon: '􀤆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), - }, - contract: { - icon: '􀉆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), - }, - to: { - icon: '􀉩', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), - }, - function: { - icon: '􀡅', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), - }, - sourceCodeVerification: { - icon: '􀕹', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), - }, - dateCreated: { - icon: '􀉉', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), - }, - nonce: { - icon: '􀆃', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), - }, -}; diff --git a/src/components/Transactions/TransactionIcons.tsx b/src/components/Transactions/TransactionIcons.tsx deleted file mode 100644 index 53ef21fcd6e..00000000000 --- a/src/components/Transactions/TransactionIcons.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React from 'react'; -import { AnimatePresence, MotiView } from 'moti'; - -import { Bleed, Box, Text, globalColors, useForegroundColor } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; -import { infoForEventType, motiTimingConfig } from '@/components/Transactions/constants'; - -import { useTheme } from '@/theme'; -import { DetailInfo, EventInfo, EventType } from '@/components/Transactions/types'; - -export const EventIcon = ({ eventType }: { eventType: EventType }) => { - const eventInfo: EventInfo = infoForEventType[eventType]; - - const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; - const isWarningIcon = - eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; - - return ( - - {!hideInnerFill && ( - - )} - - {eventInfo.icon} - - - ); -}; - -export const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { - return ( - - - {detailInfo.icon} - - - ); -}; - -export const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { - const { colors, isDarkMode } = useTheme(); - const separatorTertiary = useForegroundColor('separatorTertiary'); - - const infoForBadgeType: { - [key: string]: { - backgroundColor: string; - borderColor: string; - label?: string; - text: TextColor; - textOpacity?: number; - }; - } = { - function: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - text: 'labelQuaternary', - }, - unknown: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - label: 'Unknown', - text: 'labelQuaternary', - }, - unverified: { - backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, - borderColor: colors.alpha(colors.red, 0.02), - label: 'Unverified', - text: 'red', - textOpacity: 0.76, - }, - verified: { - backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, - borderColor: colors.alpha(colors.green, 0.02), - label: 'Verified', - text: 'green', - textOpacity: 0.76, - }, - }; - - return ( - - - - {infoForBadgeType[type].label || value} - - - - ); -}; - -export const VerifiedBadge = () => { - return ( - - - - - 􀇻 - - - - ); -}; - -export const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { - return ( - - {visible && ( - - - - - - 􀁣 - - - - - )} - - ); -}; - -export const IconContainer = ({ - children, - hitSlop, - opacity, - size = 20, -}: { - children: React.ReactNode; - hitSlop?: number; - opacity?: number; - size?: number; -}) => { - // Prevent wide icons from being clipped - const extraHorizontalSpace = 4; - - return ( - - - {children} - - - ); -}; diff --git a/src/components/Transactions/TransactionMessageCard.tsx b/src/components/Transactions/TransactionMessageCard.tsx deleted file mode 100644 index 3e946bf6d6d..00000000000 --- a/src/components/Transactions/TransactionMessageCard.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import * as i18n from '@/languages'; -import { TouchableWithoutFeedback } from 'react-native'; -import { useSharedValue } from 'react-native-reanimated'; - -import { ButtonPressAnimation } from '@/components/animations'; -import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; - -import { useClipboard } from '@/hooks'; -import { logger } from '@/logger'; -import { isSignTypedData } from '@/utils/signingMethods'; - -import { RPCMethod } from '@/walletConnect/types'; -import { sanitizeTypedData } from '@/utils/signingUtils'; -import { - estimateMessageHeight, - MAX_CARD_HEIGHT, - CARD_ROW_HEIGHT, - CARD_BORDER_WIDTH, - EXPANDED_CARD_TOP_INSET, -} from '@/components/Transactions/constants'; -import { FadedScrollCard } from '@/components/FadedScrollCard'; -import { AnimatedCheckmark, IconContainer } from '@/components/Transactions/TransactionIcons'; - -type TransactionMessageCardProps = { - expandedCardBottomInset: number; - message: string; - method: RPCMethod; -}; - -export const TransactionMessageCard = ({ expandedCardBottomInset, message, method }: TransactionMessageCardProps) => { - const { setClipboard } = useClipboard(); - const [didCopy, setDidCopy] = useState(false); - - let displayMessage = message; - if (isSignTypedData(method)) { - try { - const parsedMessage = JSON.parse(message); - const sanitizedMessage = sanitizeTypedData(parsedMessage); - displayMessage = sanitizedMessage; - } catch (error) { - logger.warn(`[TransactionMessageCard]: Error parsing signed typed data for ${method}`, { - error, - }); - } - - displayMessage = JSON.stringify(displayMessage, null, 4); - } - - const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); - - const cardHeight = useSharedValue( - estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 - ); - const contentHeight = useSharedValue(estimatedMessageHeight); - - const handleCopyPress = useCallback( - (message: string) => { - if (didCopy) return; - setClipboard(message); - setDidCopy(true); - const copyTimer = setTimeout(() => { - setDidCopy(false); - }, 2000); - return () => clearTimeout(copyTimer); - }, - [didCopy, setClipboard] - ); - - return ( - MAX_CARD_HEIGHT} - isExpanded - skipCollapsedState - > - - - - - - 􀙤 - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} - - - - handleCopyPress(message)}> - - - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} - - - - - - - - - {displayMessage} - - - - ); -}; diff --git a/src/components/Transactions/TransactionSimulatedEventRow.tsx b/src/components/Transactions/TransactionSimulatedEventRow.tsx deleted file mode 100644 index ce4d26052eb..00000000000 --- a/src/components/Transactions/TransactionSimulatedEventRow.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React, { useMemo } from 'react'; -import * as i18n from '@/languages'; -import { Image, PixelRatio } from 'react-native'; - -import { Bleed, Box, Inline, Text } from '@/design-system'; - -import { useTheme } from '@/theme'; -import { TransactionAssetType, TransactionSimulationAsset } from '@/graphql/__generated__/metadataPOST'; -import { Network } from '@/networks/types'; -import { convertAmountToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; - -import { useAccountSettings } from '@/hooks'; - -import { maybeSignUri } from '@/handlers/imgix'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; -import { EventInfo, EventType } from '@/components/Transactions/types'; -import { infoForEventType, CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; -import { EventIcon } from '@/components/Transactions/TransactionIcons'; -import { ethereumUtils } from '@/utils'; - -type TransactionSimulatedEventRowProps = { - amount: string | 'unlimited'; - asset: TransactionSimulationAsset | undefined; - eventType: EventType; - price?: number | undefined; -}; - -export const TransactionSimulatedEventRow = ({ amount, asset, eventType, price }: TransactionSimulatedEventRowProps) => { - const theme = useTheme(); - const { nativeCurrency } = useAccountSettings(); - - const chainId = ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet); - - const { data: externalAsset } = useExternalToken({ - address: asset?.assetCode || '', - chainId, - currency: nativeCurrency, - }); - - const eventInfo: EventInfo = infoForEventType[eventType]; - - const formattedAmount = useMemo(() => { - if (!asset) return; - - const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; - const assetDisplayName = - asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; - const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; - - const displayAmount = - asset?.decimals === 0 - ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` - : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; - - const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; - - return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; - }, [amount, asset, eventInfo?.amountPrefix]); - - const url = maybeSignUri(asset?.iconURL, { - fm: 'png', - w: 16 * PixelRatio.get(), - }); - - const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; - - const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); - - return ( - - - - - - - {eventInfo.label} - - {showUSD && ( - - {formattedPrice} - - )} - - - - - {asset?.type !== TransactionAssetType.Nft ? ( - - ) : ( - - )} - - - {formattedAmount} - - - - - ); -}; diff --git a/src/components/Transactions/TransactionSimulationCard.tsx b/src/components/Transactions/TransactionSimulationCard.tsx deleted file mode 100644 index 51244f5b030..00000000000 --- a/src/components/Transactions/TransactionSimulationCard.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import React, { useMemo } from 'react'; -import * as i18n from '@/languages'; -import Animated, { - interpolate, - useAnimatedReaction, - useAnimatedStyle, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; - -import { Box, Inline, Stack, Text } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; - -import { TransactionErrorType, TransactionSimulationResult, TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; -import { Network } from '@/networks/types'; - -import { getNetworkObj, getNetworkObject } from '@/networks'; -import { isEmpty } from 'lodash'; -import { TransactionSimulatedEventRow } from '@/components/Transactions/TransactionSimulatedEventRow'; -import { FadedScrollCard } from '@/components/FadedScrollCard'; -import { EventIcon, IconContainer } from '@/components/Transactions/TransactionIcons'; -import { - COLLAPSED_CARD_HEIGHT, - MAX_CARD_HEIGHT, - CARD_ROW_HEIGHT, - CARD_BORDER_WIDTH, - EXPANDED_CARD_TOP_INSET, - rotationConfig, - timingConfig, -} from '@/components/Transactions/constants'; -import { ChainId } from '@/__swaps__/types/chains'; - -interface TransactionSimulationCardProps { - currentChainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - txSimulationApiError: unknown; - isPersonalSignRequest: boolean; - noChanges: boolean; - simulation: TransactionSimulationResult | undefined; - simulationError: TransactionErrorType | undefined; - simulationScanResult: TransactionScanResultType | undefined; - walletBalance: { - amount: string | number; - display: string; - isLoaded: boolean; - symbol: string; - }; -} - -export const TransactionSimulationCard = ({ - currentChainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - txSimulationApiError, - isPersonalSignRequest, - noChanges, - simulation, - simulationError, - simulationScanResult, - walletBalance, -}: TransactionSimulationCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const spinnerRotation = useSharedValue(0); - - const listStyle = useAnimatedStyle(() => ({ - opacity: noChanges - ? withTiming(1, timingConfig) - : interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const spinnerStyle = useAnimatedStyle(() => { - return { - transform: [{ rotate: `${spinnerRotation.value}deg` }], - }; - }); - - useAnimatedReaction( - () => ({ isLoading, isPersonalSignRequest }), - ({ isLoading, isPersonalSignRequest }, previous = { isLoading: false, isPersonalSignRequest: false }) => { - if (isLoading && !previous?.isLoading) { - spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); - } else if ( - (!isLoading && previous?.isLoading) || - (isPersonalSignRequest && !previous?.isPersonalSignRequest && previous?.isLoading) - ) { - spinnerRotation.value = withTiming(360, timingConfig); - } - }, - [isLoading, isPersonalSignRequest] - ); - const renderSimulationEventRows = useMemo(() => { - if (isBalanceEnough === false) return null; - - return ( - <> - {simulation?.approvals?.map(change => { - return ( - - ); - })} - {simulation?.out?.map(change => { - return ( - - ); - })} - {simulation?.in?.map(change => { - return ( - - ); - })} - - ); - }, [isBalanceEnough, simulation]); - - const titleColor: TextColor = useMemo(() => { - if (isLoading) { - return 'label'; - } - if (isBalanceEnough === false) { - return 'blue'; - } - if (noChanges || isPersonalSignRequest || txSimulationApiError) { - return 'labelQuaternary'; - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return 'orange'; - } - if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { - return 'red'; - } - return 'label'; - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, isPersonalSignRequest, txSimulationApiError]); - - const titleText = useMemo(() => { - if (isLoading) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); - } - if (isBalanceEnough === false) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); - } - if (txSimulationApiError || isPersonalSignRequest) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); - } - if (simulationScanResult === TransactionScanResultType.Malicious) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); - } - if (noChanges) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); - } - if (simulationError) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); - } - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); - }, [ - isBalanceEnough, - isLoading, - noChanges, - simulationError, - simulationScanResult, - isPersonalSignRequest, - txSimulationApiError, - walletBalance?.symbol, - ]); - - const isExpanded = useMemo(() => { - if (isLoading || isPersonalSignRequest) { - return false; - } - const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; - return shouldExpandOnLoad; - }, [isBalanceEnough, isLoading, isPersonalSignRequest, noChanges, simulation, simulationError]); - - return ( - - - - - {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( - - ) : ( - - {!isLoading && noChanges && !isPersonalSignRequest ? ( - - {/* The extra space avoids icon clipping */} - {'􀻾 '} - - ) : ( - - - 􀬨 - - - )} - - )} - - {titleText} - - - {/* TODO: Unhide once we add explainer sheets */} - {/* - - - - - 􀁜 - - - - - */} - - - - {isBalanceEnough === false ? ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { - symbol: walletBalance?.symbol, - network: getNetworkObject({ chainId: currentChainId }).name, - })} - - ) : ( - <> - {isPersonalSignRequest && ( - - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} - - - )} - {txSimulationApiError && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.tx_api_error)} - - )} - {simulationError && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} - - )} - {simulationScanResult === TransactionScanResultType.Warning && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} - - )} - {simulationScanResult === TransactionScanResultType.Malicious && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} - - )} - - )} - {renderSimulationEventRows} - - - - - ); -}; diff --git a/src/components/Transactions/constants.ts b/src/components/Transactions/constants.ts deleted file mode 100644 index 79e6e0d8df5..00000000000 --- a/src/components/Transactions/constants.ts +++ /dev/null @@ -1,121 +0,0 @@ -import * as i18n from '@/languages'; -import { Screens } from '@/state/performance/operations'; -import { safeAreaInsetValues } from '@/utils'; -import { TransitionConfig } from 'moti'; -import { Easing } from 'react-native-reanimated'; -import { EventInfo } from '@/components/Transactions/types'; -import { RequestSource } from '@/utils/requestNavigationHandlers'; - -export const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; -export const GAS_BUTTON_SPACE = - 30 + // GasSpeedButton height - 24; // Between GasSpeedButton and bottom of sheet - -export const EXPANDED_CARD_BOTTOM_INSET = - SCREEN_BOTTOM_INSET + - 24 + // Between bottom of sheet and bottom of Cancel/Confirm - 52 + // Cancel/Confirm height - 24 + // Between Cancel/Confirm and wallet avatar row - 44 + // Wallet avatar row height - 24; // Between wallet avatar row and bottom of expandable area - -export const COLLAPSED_CARD_HEIGHT = 56; -export const MAX_CARD_HEIGHT = 176; - -export const CARD_ROW_HEIGHT = 12; -export const SMALL_CARD_ROW_HEIGHT = 10; -export const CARD_BORDER_WIDTH = 1.5; - -export const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; - -export const rotationConfig = { - duration: 2100, - easing: Easing.linear, -}; - -export const timingConfig = { - duration: 300, - easing: Easing.bezier(0.2, 0, 0, 1), -}; - -export const motiTimingConfig: TransitionConfig = { - duration: 225, - easing: Easing.bezier(0.2, 0, 0, 1), - type: 'timing', -}; - -export const SCREEN_FOR_REQUEST_SOURCE = { - [RequestSource.BROWSER]: Screens.DAPP_BROWSER, - [RequestSource.WALLETCONNECT]: Screens.WALLETCONNECT, - [RequestSource.MOBILE_WALLET_PROTOCOL]: Screens.MOBILE_WALLET_PROTOCOL, -}; - -export const CHARACTERS_PER_LINE = 40; -export const LINE_HEIGHT = 11; -export const LINE_GAP = 9; - -export const estimateMessageHeight = (message: string) => { - const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); - const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; - - return messageHeight; -}; - -export const infoForEventType: { [key: string]: EventInfo } = { - send: { - amountPrefix: '- ', - icon: '􀁷', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), - textColor: 'red', - }, - receive: { - amountPrefix: '+ ', - icon: '􀁹', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), - textColor: 'green', - }, - approve: { - amountPrefix: '', - icon: '􀎤', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), - textColor: 'label', - }, - revoke: { - amountPrefix: '', - icon: '􀎠', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), - textColor: 'label', - }, - failed: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), - textColor: 'red', - }, - insufficientBalance: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'blue', - label: '', - textColor: 'blue', - }, - MALICIOUS: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: '', - textColor: 'red', - }, - WARNING: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'orange', - label: '', - textColor: 'orange', - }, -}; diff --git a/src/components/Transactions/types.ts b/src/components/Transactions/types.ts deleted file mode 100644 index ce9eaa8b3c3..00000000000 --- a/src/components/Transactions/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TextColor } from '@/design-system/color/palettes'; - -export type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; - -export type EventInfo = { - amountPrefix: string; - icon: string; - iconColor: TextColor; - label: string; - textColor: TextColor; -}; - -export type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; - -export type DetailInfo = { - icon: string; - label: string; -}; diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index 33316a8c0a0..c8914e691bd 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -19,13 +19,6 @@ import { FiatProviderName } from '@/entities/f2c'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { queryClient } from '@/react-query'; import { pointsReferralCodeQueryKey } from '@/resources/points'; -import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; -import { InitialRoute } from '@/navigation/initialRoute'; - -interface DeeplinkHandlerProps extends Pick, 'handleRequestUrl' | 'sendFailureToClient'> { - url: string; - initialRoute: InitialRoute; -} /* * You can test these deeplinks with the following command: @@ -33,7 +26,7 @@ interface DeeplinkHandlerProps extends Pick(null); - - const identifyFlow = useCallback(async () => { - const address = await loadAddress(); - if (address) { - setTimeout(() => { - InteractionManager.runAfterInteractions(() => { - handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); - }); - }, 10_000); - - InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); - } - - setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - }, []); - - useEffect(() => { - if (!IS_DEV && isTestFlight) { - logger.debug(`[App]: Test flight usage - ${isTestFlight}`); - } - identifyFlow(); - - Promise.all([analyticsV2.initializeRudderstack(), saveFCMToken()]) - .catch(error => { - logger.error(new RainbowError('Failed to initialize rudderstack or save FCM token', error)); - }) - .finally(() => { - initWalletConnectListeners(); - PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); - analyticsV2.track(analyticsV2.event.applicationDidMount); - }); - }, [identifyFlow]); - - return { initialRoute }; -} diff --git a/src/hooks/useCalculateGasLimit.ts b/src/hooks/useCalculateGasLimit.ts deleted file mode 100644 index b0c6fb098bb..00000000000 --- a/src/hooks/useCalculateGasLimit.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useEffect, useRef, useCallback } from 'react'; -import { estimateGas, web3Provider, toHex } from '@/handlers/web3'; -import { convertHexToString, omitFlatten } from '@/helpers/utilities'; -import { logger, RainbowError } from '@/logger'; -import { getNetworkObject } from '@/networks'; -import { ethereumUtils } from '@/utils'; -import { hexToNumber, isHex } from 'viem'; -import { isEmpty } from 'lodash'; -import { InteractionManager } from 'react-native'; -import { GasFeeParamsBySpeed } from '@/entities'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; -import { useGas } from '@/hooks'; -import { ChainId } from '@/__swaps__/types/chains'; - -type CalculateGasLimitProps = { - isMessageRequest: boolean; - gasFeeParamsBySpeed: GasFeeParamsBySpeed; - provider: StaticJsonRpcProvider | null; - req: any; - updateTxFee: ReturnType['updateTxFee']; - currentChainId: ChainId; -}; - -export const useCalculateGasLimit = ({ - isMessageRequest, - gasFeeParamsBySpeed, - provider, - req, - updateTxFee, - currentChainId, -}: CalculateGasLimitProps) => { - const calculatingGasLimit = useRef(false); - - const calculateGasLimit = useCallback(async () => { - calculatingGasLimit.current = true; - const txPayload = req; - if (isHex(txPayload?.type)) { - txPayload.type = hexToNumber(txPayload?.type); - } - let gas = txPayload.gasLimit || txPayload.gas; - - try { - logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); - const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); - const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); - if (rawGasLimit) { - gas = toHex(rawGasLimit); - } - } catch (error) { - logger.error(new RainbowError('WC: error estimating gas'), { error }); - } finally { - logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); - - const networkObject = getNetworkObject({ chainId: currentChainId }); - if (currentChainId && networkObject.gas.OptimismTxFee) { - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider || web3Provider); - updateTxFee(gas, null, l1GasFeeOptimism); - } else { - updateTxFee(gas, null); - } - } - }, [currentChainId, req, updateTxFee, provider]); - - useEffect(() => { - if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { - InteractionManager.runAfterInteractions(() => { - calculateGasLimit(); - }); - } - }, [calculateGasLimit, gasFeeParamsBySpeed, isMessageRequest, provider]); - - return { calculateGasLimit }; -}; diff --git a/src/hooks/useConfirmTransaction.ts b/src/hooks/useConfirmTransaction.ts deleted file mode 100644 index 6600cead84b..00000000000 --- a/src/hooks/useConfirmTransaction.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback } from 'react'; - -type UseConfirmTransactionProps = { - isMessageRequest: boolean; - isBalanceEnough: boolean | undefined; - isValidGas: boolean; - handleSignMessage: () => void; - handleConfirmTransaction: () => void; -}; - -export const useConfirmTransaction = ({ - isMessageRequest, - isBalanceEnough, - isValidGas, - handleSignMessage, - handleConfirmTransaction, -}: UseConfirmTransactionProps) => { - const onConfirm = useCallback(async () => { - if (isMessageRequest) { - return handleSignMessage(); - } - if (!isBalanceEnough || !isValidGas) return; - return handleConfirmTransaction(); - }, [isMessageRequest, isBalanceEnough, isValidGas, handleConfirmTransaction, handleSignMessage]); - - return { onConfirm }; -}; diff --git a/src/hooks/useHasEnoughBalance.ts b/src/hooks/useHasEnoughBalance.ts deleted file mode 100644 index 56c5c766ae1..00000000000 --- a/src/hooks/useHasEnoughBalance.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useEffect, useState } from 'react'; -import { fromWei, greaterThanOrEqualTo } from '@/helpers/utilities'; -import BigNumber from 'bignumber.js'; -import { SelectedGasFee } from '@/entities'; -import { ChainId } from '@/__swaps__/types/chains'; - -type WalletBalance = { - amount: string | number; - display: string; - isLoaded: boolean; - symbol: string; -}; - -type BalanceCheckParams = { - isMessageRequest: boolean; - walletBalance: WalletBalance; - currentChainId: ChainId; - selectedGasFee: SelectedGasFee; - req: any; -}; - -export const useHasEnoughBalance = ({ isMessageRequest, walletBalance, currentChainId, selectedGasFee, req }: BalanceCheckParams) => { - const [isBalanceEnough, setIsBalanceEnough] = useState(); - - useEffect(() => { - if (isMessageRequest) { - setIsBalanceEnough(true); - return; - } - - const { gasFee } = selectedGasFee; - if (!walletBalance.isLoaded || !currentChainId || !gasFee?.estimatedFee) { - return; - } - - const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); - const balanceAmount = walletBalance.amount; - const value = req?.value ?? 0; - - const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); - const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); - - setIsBalanceEnough(isEnough); - }, [isMessageRequest, currentChainId, selectedGasFee, walletBalance, req]); - - return { isBalanceEnough }; -}; diff --git a/src/hooks/useNonceForDisplay.ts b/src/hooks/useNonceForDisplay.ts deleted file mode 100644 index 34ba719c5e9..00000000000 --- a/src/hooks/useNonceForDisplay.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useState } from 'react'; -import { getNextNonce } from '@/state/nonces'; -import { ChainId } from '@/__swaps__/types/chains'; -import { ethereumUtils } from '@/utils'; - -type UseNonceParams = { - isMessageRequest: boolean; - currentAddress: string; - currentChainId: ChainId; -}; - -export const useNonceForDisplay = ({ isMessageRequest, currentAddress, currentChainId }: UseNonceParams) => { - const [nonceForDisplay, setNonceForDisplay] = useState(); - - useEffect(() => { - if (!isMessageRequest && !nonceForDisplay) { - (async () => { - try { - const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); - if (nonce || nonce === 0) { - const nonceAsString = nonce.toString(); - setNonceForDisplay(nonceAsString); - } - } catch (error) { - console.error('Failed to get nonce for display:', error); - } - })(); - } - }, [currentAddress, currentChainId, isMessageRequest, nonceForDisplay]); - - return { nonceForDisplay }; -}; diff --git a/src/hooks/useProviderSetup.ts b/src/hooks/useProviderSetup.ts deleted file mode 100644 index 83b372bf1a0..00000000000 --- a/src/hooks/useProviderSetup.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useEffect, useState } from 'react'; -import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; -import { ethereumUtils } from '@/utils'; -import { getOnchainAssetBalance } from '@/handlers/assets'; -import { ParsedAddressAsset } from '@/entities'; -import { ChainId } from '@/__swaps__/types/chains'; - -export const useProviderSetup = (currentChainId: ChainId, accountAddress: string) => { - const [provider, setProvider] = useState(null); - const [nativeAsset, setNativeAsset] = useState(null); - - useEffect(() => { - const initProvider = async () => { - let p; - if (currentChainId === ChainId.mainnet) { - p = await getFlashbotsProvider(); - } else { - p = getProvider({ chainId: currentChainId }); - } - setProvider(p); - }; - initProvider(); - }, [currentChainId]); - - useEffect(() => { - const fetchNativeAsset = async () => { - if (provider) { - const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountAddress); - if (asset) { - const balance = await getOnchainAssetBalance( - asset, - accountAddress, - ethereumUtils.getNetworkFromChainId(currentChainId), - provider - ); - if (balance) { - const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; - setNativeAsset(assetWithOnchainBalance); - } else { - setNativeAsset(asset); - } - } - } - }; - fetchNativeAsset(); - }, [accountAddress, currentChainId, provider]); - - return { provider, nativeAsset }; -}; diff --git a/src/hooks/useSubmitTransaction.ts b/src/hooks/useSubmitTransaction.ts deleted file mode 100644 index 166f2f4004f..00000000000 --- a/src/hooks/useSubmitTransaction.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { useCallback, useState } from 'react'; -import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; -import Routes from '@/navigation/routesNames'; -import { useNavigation } from '@/navigation'; -import { RequestSource } from '@/utils/requestNavigationHandlers'; -import { SCREEN_FOR_REQUEST_SOURCE } from '@/components/Transactions/constants'; - -export const useTransactionSubmission = ({ - isBalanceEnough, - accountInfo, - onConfirm, - source, -}: { - isBalanceEnough: boolean | undefined; - accountInfo: { isHardwareWallet: boolean }; - onConfirm: () => Promise; - source: RequestSource; -}) => { - const [isAuthorizing, setIsAuthorizing] = useState(false); - const { navigate } = useNavigation(); - - const onPressSend = useCallback(async () => { - if (isAuthorizing) return; - try { - setIsAuthorizing(true); - await onConfirm(); - } catch (error) { - console.error('Error while sending transaction:', error); - } finally { - setIsAuthorizing(false); - } - }, [isAuthorizing, onConfirm]); - - const submitFn = useCallback( - () => - performanceTracking.getState().executeFn({ - fn: async () => { - if (!isBalanceEnough) { - navigate(Routes.ADD_CASH_SHEET); - return; - } - if (accountInfo.isHardwareWallet) { - navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); - } else { - await onPressSend(); - } - }, - operation: TimeToSignOperation.CallToAction, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - })(), - [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] - ); - - return { submitFn, isAuthorizing }; -}; diff --git a/src/hooks/useTransactionSetup.ts b/src/hooks/useTransactionSetup.ts deleted file mode 100644 index 6bc4f9528ab..00000000000 --- a/src/hooks/useTransactionSetup.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as i18n from '@/languages'; -import { useCallback, useEffect, useState } from 'react'; -import { InteractionManager } from 'react-native'; -import useGas from '@/hooks/useGas'; -import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; -import { analytics } from '@/analytics'; -import { event } from '@/analytics/event'; -import { RequestSource } from '@/utils/requestNavigationHandlers'; -import { ChainId } from '@/__swaps__/types/chains'; -import { ethereumUtils } from '@/utils'; - -type TransactionSetupParams = { - currentChainId: ChainId; - startPollingGasFees: ReturnType['startPollingGasFees']; - stopPollingGasFees: ReturnType['stopPollingGasFees']; - isMessageRequest: boolean; - transactionDetails: any; - source: RequestSource; -}; - -export const useTransactionSetup = ({ - currentChainId, - startPollingGasFees, - stopPollingGasFees, - isMessageRequest, - transactionDetails, - source, -}: TransactionSetupParams) => { - const [methodName, setMethodName] = useState(null); - - const fetchMethodName = useCallback( - async (data: string) => { - const methodSignaturePrefix = data.substr(0, 10); - try { - const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); - if (name) { - setMethodName(name); - } - } catch (e) { - setMethodName(data); - } - }, - [currentChainId] - ); - - useEffect(() => { - InteractionManager.runAfterInteractions(() => { - if (currentChainId) { - if (!isMessageRequest) { - const network = ethereumUtils.getNetworkFromChainId(currentChainId); - startPollingGasFees(network); - fetchMethodName(transactionDetails?.payload?.params?.[0].data); - } else { - setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); - } - analytics.track(event.txRequestShownSheet, { source }); - } - }); - - return () => { - if (!isMessageRequest) { - stopPollingGasFees(); - } - }; - }, [ - isMessageRequest, - currentChainId, - transactionDetails?.payload?.params, - source, - fetchMethodName, - startPollingGasFees, - stopPollingGasFees, - ]); - - return { methodName }; -}; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 4f157ecc5c2..5130d449fc6 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2835,7 +2835,6 @@ "unavailable_personal_sign": "Simulation for personal signs is not yet supported", "unavailable_zora_network": "Simulation on Zora is not yet supported", "failed_to_simulate": "The simulation failed, which suggests your transaction is likely to fail. This may be an issue with the app you’re using.", - "tx_api_error": "We are unable to determine whether or not your transaction will succeed or fail. Proceed with caution.", "warning": "No known malicious behavior was detected, but this transaction has characteristics that may pose a risk to your wallet.", "malicious": "Signing this transaction could result in losing access to everything in your wallet." }, diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 007652f2302..39d424b8c06 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; +import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -90,7 +90,6 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; -import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const OuterStack = createStackNavigator(); @@ -270,13 +269,25 @@ function AuthNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( - - - - - -)); +const AppContainerWithAnalytics = React.forwardRef( + ( + props: { + onReady: () => void; + }, + ref + ) => ( + + + + + + ) +); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index 4f1a8e96407..c468af96ecb 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; +import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -104,7 +104,6 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; -import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const NativeStack = createNativeStackNavigator(); @@ -293,13 +292,25 @@ function NativeStackNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( - - - - - -)); +const AppContainerWithAnalytics = React.forwardRef( + ( + props: { + onReady: () => void; + }, + ref + ) => ( + + + + + + ) +); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 8324c450a34..3e9de2383ff 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -29,7 +29,6 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; -import { RequestSource } from '@/utils/requestNavigationHandlers'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -278,7 +277,7 @@ export const signTransactionSheetConfig = { options: ({ route }: { route: SignTransactionSheetRouteProp }) => ({ ...buildCoolModalConfig({ ...route.params, - backgroundOpacity: route?.params?.source === RequestSource.WALLETCONNECT ? 1 : 0.7, + backgroundOpacity: route?.params?.source === 'walletconnect' ? 1 : 0.7, cornerRadius: 0, springDamping: 1, topOffset: 0, diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index 4bc8b78a88d..8c88b9ee1bf 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -2,7 +2,7 @@ import messaging from '@react-native-firebase/messaging'; import { getLocal, saveLocal } from '@/handlers/localstorage/common'; import { getPermissionStatus } from '@/notifications/permissions'; -import { logger } from '@/logger'; +import { logger, RainbowError } from '@/logger'; export const registerTokenRefreshListener = () => messaging().onTokenRefresh(fcmToken => { diff --git a/src/parsers/requests.js b/src/parsers/requests.js index a3fa2ac7dc6..ae7da2d87b6 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -9,7 +9,7 @@ import { isSignTypedData, SIGN, PERSONAL_SIGN, SEND_TRANSACTION, SIGN_TRANSACTIO import { isAddress } from '@ethersproject/address'; import { toUtf8String } from '@ethersproject/strings'; -export const getRequestDisplayDetails = async (payload, nativeCurrency, chainId) => { +export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { const timestampInMs = Date.now(); if (payload.method === SEND_TRANSACTION || payload.method === SIGN_TRANSACTION) { const transaction = Object.assign(payload?.params?.[0] ?? null); @@ -75,9 +75,9 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ timestampInMs, }); -const getTransactionDisplayDetails = async (transaction, nativeCurrency, timestampInMs, chainId) => { +const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index 05350697d48..aa5196b9cd4 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -71,7 +71,7 @@ export interface WalletconnectRequestData extends RequestData { /** * Display details loaded for a request. */ -export interface RequestDisplayDetails { +interface RequestDisplayDetails { /** * Data loaded for the request, depending on the type of request. */ @@ -154,7 +154,7 @@ export const addRequestToApprove = icons?: string[]; } ) => - async (dispatch: Dispatch, getState: AppGetState) => { + (dispatch: Dispatch, getState: AppGetState) => { const { requests } = getState().requests; const { walletConnectors } = getState().walletconnect; const { accountAddress, network, nativeCurrency } = getState().settings; @@ -163,7 +163,7 @@ export const addRequestToApprove = const chainId = walletConnector._chainId; // @ts-expect-error "_accounts" is private. const address = walletConnector._accounts[0]; - const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, chainId); + const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, chainId); const oneHourAgoTs = Date.now() - EXPIRATION_THRESHOLD_IN_MS; // @ts-expect-error This fails to compile as `displayDetails` does not // always return an object with `timestampInMs`. Still, the error thrown diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index fa1b19b631a..da0ea48400c 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -559,9 +559,7 @@ const listenOnNewMessages = return; } const { requests: pendingRequests } = getState().requests; - const request = !pendingRequests[requestId] - ? await dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) - : null; + const request = !pendingRequests[requestId] ? dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) : null; if (request) { handleWalletConnectRequest(request); InteractionManager.runAfterInteractions(() => { diff --git a/src/resources/transactions/transactionSimulation.ts b/src/resources/transactions/transactionSimulation.ts deleted file mode 100644 index 01d76df5de2..00000000000 --- a/src/resources/transactions/transactionSimulation.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { createQueryKey, QueryConfig, QueryFunctionArgs } from '@/react-query'; -import { useQuery } from '@tanstack/react-query'; -import { RainbowError, logger } from '@/logger'; -import { metadataPOSTClient } from '@/graphql'; -import { TransactionErrorType, TransactionScanResultType, TransactionSimulationResult } from '@/graphql/__generated__/metadataPOST'; -import { isNil } from 'lodash'; -import { RequestData } from '@/redux/requests'; -import { ChainId } from '@/__swaps__/types/chains'; - -type SimulationArgs = { - accountAddress: string; - currentChainId: ChainId; - isMessageRequest: boolean; - nativeCurrency: string; - req: any; // Replace 'any' with the correct type for 'req' - requestMessage: string; - simulationUnavailable: boolean; - transactionDetails: RequestData; -}; - -type SimulationResult = { - simulationData: TransactionSimulationResult | undefined; - simulationError: TransactionErrorType | undefined; - simulationScanResult: TransactionScanResultType | undefined; -}; - -const simulationQueryKey = ({ - accountAddress, - currentChainId, - isMessageRequest, - nativeCurrency, - req, - requestMessage, - simulationUnavailable, - transactionDetails, -}: SimulationArgs) => - createQueryKey( - 'txSimulation', - { - accountAddress, - currentChainId, - isMessageRequest, - nativeCurrency, - req, - requestMessage, - simulationUnavailable, - transactionDetails, - }, - { persisterVersion: 1 } - ); - -const fetchSimulation = async ({ - queryKey: [ - { accountAddress, currentChainId, isMessageRequest, nativeCurrency, req, requestMessage, simulationUnavailable, transactionDetails }, - ], -}: QueryFunctionArgs): Promise => { - try { - let simulationData; - - if (isMessageRequest) { - simulationData = await metadataPOSTClient.simulateMessage({ - address: accountAddress, - chainId: currentChainId, - message: { - method: transactionDetails?.payload?.method, - params: [requestMessage], - }, - domain: transactionDetails?.dappUrl, - }); - - if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { - return { - simulationData: { in: [], out: [], approvals: [] }, - simulationError: undefined, - simulationScanResult: simulationData?.simulateMessage?.scanning?.result, - }; - } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { - return { - simulationData: undefined, - simulationError: simulationData?.simulateMessage?.error?.type, - simulationScanResult: simulationData?.simulateMessage?.scanning?.result, - }; - } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { - return { - simulationData: simulationData.simulateMessage?.simulation, - simulationError: undefined, - simulationScanResult: simulationData?.simulateMessage?.scanning?.result, - }; - } - } else { - simulationData = await metadataPOSTClient.simulateTransactions({ - chainId: currentChainId, - currency: nativeCurrency?.toLowerCase(), - transactions: [ - { - from: req?.from, - to: req?.to, - data: req?.data || '0x', - value: req?.value || '0x0', - }, - ], - domain: transactionDetails?.dappUrl, - }); - - if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { - return { - simulationData: { in: [], out: [], approvals: [] }, - simulationError: undefined, - simulationScanResult: simulationData?.simulateTransactions?.[0]?.scanning?.result, - }; - } else if (simulationData?.simulateTransactions?.[0]?.error) { - return { - simulationData: undefined, - simulationError: simulationData?.simulateTransactions?.[0]?.error?.type, - simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, - }; - } else if (simulationData.simulateTransactions?.[0]?.simulation) { - return { - simulationData: simulationData.simulateTransactions[0]?.simulation, - simulationError: undefined, - simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, - }; - } - } - - return { - simulationData: undefined, - simulationError: undefined, - simulationScanResult: undefined, - }; - } catch (error) { - logger.error(new RainbowError('Error while simulating'), { error }); - throw error; - } -}; - -export const useSimulation = ( - args: SimulationArgs, - config: QueryConfig> = {} -) => { - return useQuery(simulationQueryKey(args), fetchSimulation, { - enabled: !!args.accountAddress && !!args.currentChainId, - retry: 3, - refetchOnWindowFocus: false, - staleTime: Infinity, - ...config, - }); -}; diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index c3961c962ef..49dfac6f483 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -1,31 +1,68 @@ -import React, { useCallback, useMemo } from 'react'; -import { AnimatePresence, MotiView } from 'moti'; +/* eslint-disable no-nested-ternary */ +import BigNumber from 'bignumber.js'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { AnimatePresence, MotiView, TransitionConfig } from 'moti'; import * as i18n from '@/languages'; -import { Image, InteractionManager, PixelRatio, ScrollView } from 'react-native'; -import Animated from 'react-native-reanimated'; +import { Image, InteractionManager, PixelRatio, ScrollView, StyleProp, TouchableWithoutFeedback, ViewStyle } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated, { + Easing, + SharedValue, + interpolate, + interpolateColor, + measure, + runOnJS, + runOnUI, + useAnimatedReaction, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; import { Transaction } from '@ethersproject/transactions'; +import { ButtonPressAnimation } from '@/components/animations'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { SheetActionButton } from '@/components/sheet'; import { Bleed, Box, Columns, Inline, Inset, Stack, Text, globalColors, useBackgroundColor, useForegroundColor } from '@/design-system'; -import { NewTransaction } from '@/entities'; +import { TextColor } from '@/design-system/color/palettes'; +import { NewTransaction, ParsedAddressAsset } from '@/entities'; import { useNavigation } from '@/navigation'; import { useTheme } from '@/theme'; -import { deviceUtils, ethereumUtils } from '@/utils'; +import { abbreviations, deviceUtils, ethereumUtils, safeAreaInsetValues } from '@/utils'; import { PanGestureHandler } from 'react-native-gesture-handler'; import { RouteProp, useRoute } from '@react-navigation/native'; -import { TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; +import { metadataPOSTClient } from '@/graphql'; +import { + TransactionAssetType, + TransactionErrorType, + TransactionSimulationAsset, + TransactionSimulationMeta, + TransactionSimulationResult, + TransactionScanResultType, +} from '@/graphql/__generated__/metadataPOST'; import { Network } from '@/networks/types'; -import { convertHexToString, delay, greaterThan, omitFlatten } from '@/helpers/utilities'; +import { + convertAmountToNativeDisplay, + convertHexToString, + convertRawAmountToBalance, + delay, + fromWei, + greaterThan, + greaterThanOrEqualTo, + omitFlatten, +} from '@/helpers/utilities'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { getAccountProfileInfo } from '@/helpers/accountInfo'; -import { useAccountSettings, useGas, useSwitchWallet, useWallets } from '@/hooks'; +import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWallet, useWallets } from '@/hooks'; import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; -import { estimateGasWithPadding, getProvider, getProviderForNetwork, toHex } from '@/handlers/web3'; +import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { GasSpeedButton } from '@/components/gas'; import { getNetworkObj, getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; @@ -35,45 +72,71 @@ import { SIGN_TYPED_DATA, SIGN_TYPED_DATA_V4, isMessageDisplayType, - isPersonalSign, + isPersonalSign as checkIsPersonalSign, + isSignTypedData, } from '@/utils/signingMethods'; -import { isNil } from 'lodash'; +import { isEmpty, isNil } from 'lodash'; +import Routes from '@/navigation/routesNames'; import { parseGasParamsForTransaction } from '@/parsers/gas'; import { loadWallet, sendTransaction, signPersonalMessage, signTransaction, signTypedDataMessage } from '@/model/wallet'; import { analyticsV2 as analytics } from '@/analytics'; import { maybeSignUri } from '@/handlers/imgix'; +import { RPCMethod } from '@/walletConnect/types'; import { isAddress } from '@ethersproject/address'; +import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; +import { sanitizeTypedData } from '@/utils/signingUtils'; import { hexToNumber, isHex } from 'viem'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNextNonce } from '@/state/nonces'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { RequestData } from '@/redux/requests'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; -import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; -import { useSimulation } from '@/resources/transactions/transactionSimulation'; -import { TransactionSimulationCard } from '@/components/Transactions/TransactionSimulationCard'; -import { TransactionDetailsCard } from '@/components/Transactions/TransactionDetailsCard'; -import { TransactionMessageCard } from '@/components/Transactions/TransactionMessageCard'; -import { VerifiedBadge } from '@/components/Transactions/TransactionIcons'; -import { - SCREEN_FOR_REQUEST_SOURCE, - EXPANDED_CARD_BOTTOM_INSET, - GAS_BUTTON_SPACE, - motiTimingConfig, - SCREEN_BOTTOM_INSET, - infoForEventType, -} from '@/components/Transactions/constants'; -import { useCalculateGasLimit } from '@/hooks/useCalculateGasLimit'; -import { useTransactionSetup } from '@/hooks/useTransactionSetup'; -import { useHasEnoughBalance } from '@/hooks/useHasEnoughBalance'; -import { useNonceForDisplay } from '@/hooks/useNonceForDisplay'; -import { useProviderSetup } from '@/hooks/useProviderSetup'; -import { useTransactionSubmission } from '@/hooks/useSubmitTransaction'; -import { useConfirmTransaction } from '@/hooks/useConfirmTransaction'; +import { getOnchainAssetBalance } from '@/handlers/assets'; +import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { ChainId } from '@/__swaps__/types/chains'; +const COLLAPSED_CARD_HEIGHT = 56; +const MAX_CARD_HEIGHT = 176; + +const CARD_ROW_HEIGHT = 12; +const SMALL_CARD_ROW_HEIGHT = 10; +const CARD_BORDER_WIDTH = 1.5; + +const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; +const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; + +const GAS_BUTTON_SPACE = + 30 + // GasSpeedButton height + 24; // Between GasSpeedButton and bottom of sheet + +const EXPANDED_CARD_BOTTOM_INSET = + SCREEN_BOTTOM_INSET + + 24 + // Between bottom of sheet and bottom of Cancel/Confirm + 52 + // Cancel/Confirm height + 24 + // Between Cancel/Confirm and wallet avatar row + 44 + // Wallet avatar row height + 24; // Between wallet avatar row and bottom of expandable area + +const rotationConfig = { + duration: 2100, + easing: Easing.linear, +}; + +const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +const motiTimingConfig: TransitionConfig = { + duration: 225, + easing: Easing.bezier(0.2, 0, 0, 1), + type: 'timing', +}; + type SignTransactionSheetParams = { transactionDetails: RequestData; onSuccess: (hash: string) => void; @@ -85,12 +148,20 @@ type SignTransactionSheetParams = { source: RequestSource; }; +const SCREEN_FOR_REQUEST_SOURCE = { + browser: Screens.DAPP_BROWSER, + walletconnect: Screens.WALLETCONNECT, +}; + export type SignTransactionSheetRouteProp = RouteProp<{ SignTransactionSheet: SignTransactionSheetParams }, 'SignTransactionSheet'>; export const SignTransactionSheet = () => { - const { goBack } = useNavigation(); + const { goBack, navigate } = useNavigation(); const { colors, isDarkMode } = useTheme(); const { accountAddress, nativeCurrency } = useAccountSettings(); + const [simulationData, setSimulationData] = useState(); + const [simulationError, setSimulationError] = useState(undefined); + const [simulationScanResult, setSimulationScanResult] = useState(undefined); const { params: routeParams } = useRoute(); const { wallets, walletNames } = useWallets(); @@ -106,14 +177,22 @@ export const SignTransactionSheet = () => { source, } = routeParams; - const { provider, nativeAsset } = useProviderSetup(currentChainId, accountAddress); - const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); - const isPersonalSignRequest = isPersonalSign(transactionDetails.payload.method); + + const isPersonalSign = checkIsPersonalSign(transactionDetails.payload.method); const label = useForegroundColor('label'); const surfacePrimary = useBackgroundColor('surfacePrimary'); + const [provider, setProvider] = useState(null); + const [isAuthorizing, setIsAuthorizing] = useState(false); + const [isLoading, setIsLoading] = useState(!isPersonalSign); + const [methodName, setMethodName] = useState(null); + const calculatingGasLimit = useRef(false); + const [isBalanceEnough, setIsBalanceEnough] = useState(); + const [nonceForDisplay, setNonceForDisplay] = useState(); + + const [nativeAsset, setNativeAsset] = useState(null); const formattedDappUrl = useMemo(() => { try { const { hostname } = new URL(transactionDetails?.dappUrl); @@ -123,16 +202,107 @@ export const SignTransactionSheet = () => { } }, [transactionDetails]); + const { + gasLimit, + isValidGas, + startPollingGasFees, + stopPollingGasFees, + isSufficientGas, + updateTxFee, + selectedGasFee, + gasFeeParamsBySpeed, + } = useGas(); + + const simulationUnavailable = isPersonalSign; + + const itemCount = (simulationData?.in?.length || 0) + (simulationData?.out?.length || 0) + (simulationData?.approvals?.length || 0); + + const noChanges = !!(simulationData && itemCount === 0) && simulationScanResult === TransactionScanResultType.Ok; + const req = transactionDetails?.payload?.params?.[0]; const request = useMemo(() => { return isMessageRequest - ? { message: transactionDetails?.displayDetails?.request || '' } + ? { message: transactionDetails?.displayDetails?.request } : { ...transactionDetails?.displayDetails?.request, - nativeAsset, + nativeAsset: nativeAsset, }; }, [isMessageRequest, transactionDetails?.displayDetails?.request, nativeAsset]); + const calculateGasLimit = useCallback(async () => { + calculatingGasLimit.current = true; + const txPayload = req; + if (isHex(txPayload?.type)) { + txPayload.type = hexToNumber(txPayload?.type); + } + // use the default + let gas = txPayload.gasLimit || txPayload.gas; + + const provider = getProvider({ chainId: currentChainId }); + try { + // attempt to re-run estimation + logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + // safety precaution: we want to ensure these properties are not used for gas estimation + const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); + const rawGasLimit = await estimateGas(cleanTxPayload, provider); + logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + if (rawGasLimit) { + gas = toHex(rawGasLimit); + } + } catch (error) { + logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); + } finally { + logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + const networkObject = getNetworkObject({ chainId: currentChainId }); + if (networkObject && networkObject.gas.OptimismTxFee) { + const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); + updateTxFee(gas, null, l1GasFeeOptimism); + } else { + updateTxFee(gas, null); + } + } + }, [currentChainId, req, updateTxFee]); + + const fetchMethodName = useCallback( + async (data: string) => { + const methodSignaturePrefix = data.substr(0, 10); + try { + const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); + if (name) { + setMethodName(name); + } + } catch (e) { + setMethodName(data); + } + }, + [currentChainId] + ); + + // start polling for gas and get fn name + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (currentChainId) { + if (!isMessageRequest) { + const network = ethereumUtils.getNetworkFromChainId(currentChainId); + startPollingGasFees(network); + fetchMethodName(transactionDetails?.payload?.params[0].data); + } else { + setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); + } + analytics.track(event.txRequestShownSheet), { source }; + } + }); + }, [isMessageRequest, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source, currentChainId]); + + // get gas limit + useEffect(() => { + if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { + InteractionManager.runAfterInteractions(() => { + calculateGasLimit(); + }); + } + }, [calculateGasLimit, gasLimit, gasFeeParamsBySpeed, isMessageRequest, provider, updateTxFee]); + const walletBalance = useMemo(() => { return { amount: nativeAsset?.balance?.amount || 0, @@ -142,67 +312,34 @@ export const SignTransactionSheet = () => { }; }, [nativeAsset?.balance?.amount, nativeAsset?.balance?.display, nativeAsset?.symbol]); - const { gasLimit, isValidGas, startPollingGasFees, stopPollingGasFees, updateTxFee, selectedGasFee, gasFeeParamsBySpeed } = useGas(); - - const { methodName } = useTransactionSetup({ - currentChainId, - startPollingGasFees, - stopPollingGasFees, - isMessageRequest, - transactionDetails, - source, - }); + // check native balance is sufficient + useEffect(() => { + if (isMessageRequest) { + setIsBalanceEnough(true); + return; + } - const { isBalanceEnough } = useHasEnoughBalance({ - isMessageRequest, - walletBalance, - currentChainId, - selectedGasFee, - req, - }); + const { gasFee } = selectedGasFee; + if (!walletBalance?.isLoaded || !currentChainId || !gasFee?.estimatedFee) { + return; + } - useCalculateGasLimit({ - isMessageRequest, - gasFeeParamsBySpeed, - provider, - req, - updateTxFee, - currentChainId, - }); + // Get the TX fee Amount + const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); - const { nonceForDisplay } = useNonceForDisplay({ - isMessageRequest, - currentAddress, - currentChainId, - }); + // Get the ETH balance + const balanceAmount = walletBalance?.amount ?? 0; - const { - data: simulationResult, - isLoading: txSimulationLoading, - error: txSimulationApiError, - } = useSimulation( - { - accountAddress, - currentChainId, - isMessageRequest, - nativeCurrency, - req, - requestMessage: request.message, - simulationUnavailable: isPersonalSignRequest, - transactionDetails, - }, - { - enabled: !isPersonalSignRequest, - } - ); + // Get the TX value + const txPayload = req; + const value = txPayload?.value ?? 0; - const itemCount = - (simulationResult?.simulationData?.in?.length || 0) + - (simulationResult?.simulationData?.out?.length || 0) + - (simulationResult?.simulationData?.approvals?.length || 0); + // Check that there's enough ETH to pay for everything! + const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); + const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); - const noChanges = - !!(simulationResult?.simulationData && itemCount === 0) && simulationResult?.simulationScanResult === TransactionScanResultType.Ok; + setIsBalanceEnough(isEnough); + }, [isMessageRequest, isSufficientGas, selectedGasFee, walletBalance, req, currentChainId]); const accountInfo = useMemo(() => { const selectedWallet = wallets ? findWalletWithAccount(wallets, currentAddress) : undefined; @@ -214,6 +351,135 @@ export const SignTransactionSheet = () => { }; }, [wallets, currentAddress, walletNames]); + useEffect(() => { + const initProvider = async () => { + let p; + // check on this o.O + if (currentChainId === ChainId.mainnet) { + p = await getFlashbotsProvider(); + } else { + p = getProvider({ chainId: currentChainId }); + } + + setProvider(p); + }; + initProvider(); + }, [currentChainId, setProvider]); + + useEffect(() => { + (async () => { + const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); + if (asset && provider) { + const balance = await getOnchainAssetBalance( + asset, + accountInfo.address, + ethereumUtils.getNetworkFromChainId(currentChainId), + provider + ); + if (balance) { + const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; + setNativeAsset(assetWithOnchainBalance); + } else { + setNativeAsset(asset); + } + } + })(); + }, [accountInfo.address, currentChainId, provider]); + + useEffect(() => { + (async () => { + if (!isMessageRequest && !nonceForDisplay) { + try { + const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + if (nonce || nonce === 0) { + const nonceAsString = nonce.toString(); + setNonceForDisplay(nonceAsString); + } + } catch (error) { + console.error('Failed to get nonce for display:', error); + } + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [accountInfo.address, currentChainId, getNextNonce, isMessageRequest]); + + useEffect(() => { + const timeout = setTimeout(async () => { + try { + let simulationData; + if (isMessageRequest) { + // Message Signing + simulationData = await metadataPOSTClient.simulateMessage({ + address: accountAddress, + chainId: currentChainId, + message: { + method: transactionDetails?.payload?.method, + params: [request.message], + }, + domain: transactionDetails?.dappUrl, + }); + // Handle message simulation response + if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { + setSimulationData({ in: [], out: [], approvals: [] }); + setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); + } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { + setSimulationError(simulationData?.simulateMessage?.error?.type); + setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); + setSimulationData(undefined); + } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { + setSimulationData(simulationData.simulateMessage?.simulation); + setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); + } + } else { + // TX Signing + simulationData = await metadataPOSTClient.simulateTransactions({ + chainId: currentChainId, + currency: nativeCurrency?.toLowerCase(), + transactions: [ + { + from: req?.from, + to: req?.to, + data: req?.data, + value: req?.value || '0x0', + }, + ], + domain: transactionDetails?.dappUrl, + }); + // Handle TX simulation response + if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { + setSimulationData({ in: [], out: [], approvals: [] }); + setSimulationScanResult(simulationData?.simulateTransactions?.[0]?.scanning?.result); + } else if (simulationData?.simulateTransactions?.[0]?.error) { + setSimulationError(simulationData?.simulateTransactions?.[0]?.error?.type); + setSimulationData(undefined); + setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); + } else if (simulationData.simulateTransactions?.[0]?.simulation) { + setSimulationData(simulationData.simulateTransactions[0]?.simulation); + setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); + } + } + } catch (error) { + logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); + } finally { + setIsLoading(false); + } + }, 750); + + return () => { + clearTimeout(timeout); + }; + }, [ + accountAddress, + currentChainId, + isMessageRequest, + isPersonalSign, + nativeCurrency, + req, + request.message, + simulationUnavailable, + transactionDetails, + ]); + const closeScreen = useCallback( (canceled: boolean) => performanceTracking.getState().executeFn({ @@ -260,6 +526,80 @@ export const SignTransactionSheet = () => { [accountInfo.isHardwareWallet, closeScreen, onCancelCallback, source, transactionDetails?.payload?.method] ); + const handleSignMessage = useCallback(async () => { + const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); + let response = null; + + const provider = getProvider({ chainId: currentChainId }); + if (!provider) { + return; + } + + const existingWallet = await performanceTracking.getState().executeFn({ + fn: loadWallet, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.KeychainRead, + })({ + address: accountInfo.address, + provider, + timeTracking: { + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.Authentication, + }, + }); + + if (!existingWallet) { + return; + } + switch (transactionDetails?.payload?.method) { + case PERSONAL_SIGN: + response = await performanceTracking.getState().executeFn({ + fn: signPersonalMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + case SIGN_TYPED_DATA_V4: + case SIGN_TYPED_DATA: + response = await performanceTracking.getState().executeFn({ + fn: signTypedDataMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + default: + break; + } + + if (response?.result) { + analytics.track(event.txRequestApprove, { + source, + requestType: 'signature', + dappName: transactionDetails?.dappName, + dappUrl: transactionDetails?.dappUrl, + isHardwareWallet: accountInfo.isHardwareWallet, + network: ethereumUtils.getNetworkFromChainId(currentChainId), + }); + onSuccessCallback?.(response.result); + + closeScreen(false); + } else { + await onCancel(response?.error); + } + }, [ + transactionDetails?.payload?.params, + transactionDetails?.payload?.method, + transactionDetails?.dappName, + transactionDetails?.dappUrl, + currentChainId, + accountInfo.address, + accountInfo.isHardwareWallet, + source, + onSuccessCallback, + closeScreen, + onCancel, + ]); + const handleConfirmTransaction = useCallback(async () => { const sendInsteadOfSign = transactionDetails.payload.method === SEND_TRANSACTION; const txPayload = req; @@ -473,94 +813,44 @@ export const SignTransactionSheet = () => { onCancel, ]); - const handleSignMessage = useCallback(async () => { - const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); - let response = null; - - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { - return; + const onConfirm = useCallback(async () => { + if (isMessageRequest) { + return handleSignMessage(); } + if (!isBalanceEnough || !isValidGas) return; + return handleConfirmTransaction(); + }, [handleConfirmTransaction, handleSignMessage, isBalanceEnough, isMessageRequest, isValidGas]); - const existingWallet = await performanceTracking.getState().executeFn({ - fn: loadWallet, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.KeychainRead, - })({ - address: accountInfo.address, - provider, - timeTracking: { - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.Authentication, - }, - }); - - if (!existingWallet) { - return; - } - switch (transactionDetails?.payload?.method) { - case PERSONAL_SIGN: - response = await performanceTracking.getState().executeFn({ - fn: signPersonalMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - case SIGN_TYPED_DATA_V4: - case SIGN_TYPED_DATA: - response = await performanceTracking.getState().executeFn({ - fn: signTypedDataMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - default: - break; - } - - if (response?.result) { - analytics.track(event.txRequestApprove, { - source, - requestType: 'signature', - dappName: transactionDetails?.dappName, - dappUrl: transactionDetails?.dappUrl, - isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - }); - onSuccessCallback?.(response.result); - - closeScreen(false); - } else { - await onCancel(response?.error); + const onPressSend = useCallback(async () => { + if (isAuthorizing) return; + setIsAuthorizing(true); + try { + await onConfirm(); + setIsAuthorizing(false); + } catch (error) { + setIsAuthorizing(false); } - }, [ - transactionDetails?.payload?.params, - transactionDetails?.payload?.method, - transactionDetails?.dappName, - transactionDetails?.dappUrl, - currentChainId, - accountInfo.address, - accountInfo.isHardwareWallet, - source, - onSuccessCallback, - closeScreen, - onCancel, - ]); + }, [isAuthorizing, onConfirm]); - const { onConfirm } = useConfirmTransaction({ - isMessageRequest, - isBalanceEnough, - isValidGas, - handleSignMessage, - handleConfirmTransaction, - }); - - const { submitFn } = useTransactionSubmission({ - isBalanceEnough, - accountInfo, - onConfirm, - source, - }); + const submitFn = useCallback( + () => + performanceTracking.getState().executeFn({ + fn: async () => { + if (!isBalanceEnough) { + navigate(Routes.ADD_CASH_SHEET); + return; + } + if (accountInfo.isHardwareWallet) { + navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); + } else { + await onPressSend(); + } + }, + operation: TimeToSignOperation.CallToAction, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + })(), + [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] + ); const onPressCancel = useCallback(() => onCancel(), [onCancel]); @@ -615,9 +905,8 @@ export const SignTransactionSheet = () => { { > {transactionDetails.dappName} - {source === RequestSource.BROWSER && } + {source === 'browser' && } {isMessageRequest @@ -638,36 +927,33 @@ export const SignTransactionSheet = () => { - {isMessageRequest ? ( - ) : ( - { /> { disabled={!canPressConfirm} size="big" weight="heavy" - color={ - simulationResult?.simulationError || - (simulationResult?.simulationScanResult && simulationResult?.simulationScanResult !== TransactionScanResultType.Ok) - ? simulationResult?.simulationScanResult === TransactionScanResultType.Warning - ? 'orange' - : colors.red - : undefined - } + // eslint-disable-next-line react/jsx-props-no-spreading + {...((simulationError || (simulationScanResult && simulationScanResult !== TransactionScanResultType.Ok)) && { + color: simulationScanResult === TransactionScanResultType.Warning ? 'orange' : colors.red, + })} /> @@ -767,7 +1049,7 @@ export const SignTransactionSheet = () => { )} - {source === RequestSource.BROWSER && ( + {source === 'browser' && ( { theme={'dark'} marginBottom={0} asset={undefined} - fallbackColor={simulationResult?.simulationError ? colors.red : undefined} + fallbackColor={simulationError ? colors.red : undefined} testID={undefined} showGasOptions={undefined} validateGasParams={undefined} @@ -801,3 +1083,1221 @@ export const SignTransactionSheet = () => { ); }; + +interface SimulationCardProps { + currentNetwork: Network; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + isPersonalSign: boolean; + noChanges: boolean; + simulation: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; + walletBalance: { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; + }; +} + +const SimulationCard = ({ + currentNetwork, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + isPersonalSign, + noChanges, + simulation, + simulationError, + simulationScanResult, + walletBalance, +}: SimulationCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const spinnerRotation = useSharedValue(0); + + const simulationUnavailable = isPersonalSign; + + const listStyle = useAnimatedStyle(() => ({ + opacity: noChanges + ? withTiming(1, timingConfig) + : interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const spinnerStyle = useAnimatedStyle(() => { + return { + transform: [{ rotate: `${spinnerRotation.value}deg` }], + }; + }); + + useAnimatedReaction( + () => ({ isLoading, simulationUnavailable }), + ({ isLoading, simulationUnavailable }, previous = { isLoading: false, simulationUnavailable: false }) => { + if (isLoading && !previous?.isLoading) { + spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); + } else if ( + (!isLoading && previous?.isLoading) || + (simulationUnavailable && !previous?.simulationUnavailable && previous?.isLoading) + ) { + spinnerRotation.value = withTiming(360, timingConfig); + } + }, + [isLoading, simulationUnavailable] + ); + const renderSimulationEventRows = useMemo(() => { + if (isBalanceEnough === false) return null; + + return ( + <> + {simulation?.approvals?.map(change => { + return ( + + ); + })} + {simulation?.out?.map(change => { + return ( + + ); + })} + {simulation?.in?.map(change => { + return ( + + ); + })} + + ); + }, [isBalanceEnough, simulation]); + + const titleColor: TextColor = useMemo(() => { + if (isLoading) { + return 'label'; + } + if (isBalanceEnough === false) { + return 'blue'; + } + if (noChanges || simulationUnavailable) { + return 'labelQuaternary'; + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return 'orange'; + } + if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { + return 'red'; + } + return 'label'; + }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable]); + + const titleText = useMemo(() => { + if (isLoading) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); + } + if (isBalanceEnough === false) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); + } + if (simulationUnavailable) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); + } + if (simulationScanResult === TransactionScanResultType.Malicious) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); + } + if (noChanges) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); + } + if (simulationError) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); + } + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); + }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable, walletBalance?.symbol]); + + const isExpanded = useMemo(() => { + if (isLoading || isPersonalSign) { + return false; + } + const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; + return shouldExpandOnLoad; + }, [isBalanceEnough, isLoading, isPersonalSign, noChanges, simulation, simulationError]); + + return ( + + + + + {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( + + ) : ( + + {!isLoading && noChanges && !simulationUnavailable ? ( + + {/* The extra space avoids icon clipping */} + {'􀻾 '} + + ) : ( + + + 􀬨 + + + )} + + )} + + {titleText} + + + {/* TODO: Unhide once we add explainer sheets */} + {/* + + + + + 􀁜 + + + + + */} + + + + {isBalanceEnough === false ? ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { + symbol: walletBalance?.symbol, + network: getNetworkObj(currentNetwork).name, + })} + + ) : ( + <> + {simulationUnavailable && isPersonalSign && ( + + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} + + + )} + {simulationError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} + + )} + {simulationScanResult === TransactionScanResultType.Warning && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} + + )} + {simulationScanResult === TransactionScanResultType.Malicious && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} + + )} + + )} + {renderSimulationEventRows} + + + + + ); +}; + +interface DetailsCardProps { + currentNetwork: Network; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + meta: TransactionSimulationMeta | undefined; + methodName: string; + noChanges: boolean; + nonce: string | undefined; + toAddress: string; +} + +const DetailsCard = ({ + currentNetwork, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + meta, + methodName, + noChanges, + nonce, + toAddress, +}: DetailsCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const [isExpanded, setIsExpanded] = useState(false); + + const listStyle = useAnimatedStyle(() => ({ + opacity: interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; + + const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); + const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; + const showTransferToRow = !!meta?.transferTo?.address; + // Hide DetailsCard if balance is insufficient once loaded + if (!isLoading && isBalanceEnough === false) { + return <>; + } + return ( + setIsExpanded(true)} + > + + + + + + 􁙠 + + + + {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} + + + + + + {} + {!!(meta?.to?.address || toAddress || showTransferToRow) && ( + + ethereumUtils.openAddressInBlockExplorer( + meta?.to?.address || toAddress || meta?.transferTo?.address || '', + ethereumUtils.getChainIdFromNetwork(currentNetwork) + ) + } + value={ + meta?.to?.name || + abbreviations.address(meta?.to?.address || toAddress, 4, 6) || + meta?.to?.address || + toAddress || + meta?.transferTo?.address || + '' + } + /> + )} + {showFunctionRow && } + {!!meta?.to?.sourceCodeStatus && } + {!!meta?.to?.created && } + {nonce && } + + + + + ); +}; + +const MessageCard = ({ + expandedCardBottomInset, + message, + method, +}: { + expandedCardBottomInset: number; + message: string; + method: RPCMethod; +}) => { + const { setClipboard } = useClipboard(); + const [didCopy, setDidCopy] = useState(false); + + let displayMessage = message; + if (isSignTypedData(method)) { + try { + const parsedMessage = JSON.parse(message); + const sanitizedMessage = sanitizeTypedData(parsedMessage); + displayMessage = sanitizedMessage; + // eslint-disable-next-line no-empty + } catch (e) { + logger.warn('[SignTransactionSheet]: Error while parsing message'); + } + + displayMessage = JSON.stringify(displayMessage, null, 4); + } + + const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); + + const cardHeight = useSharedValue( + estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 + ); + const contentHeight = useSharedValue(estimatedMessageHeight); + + const handleCopyPress = useCallback( + (message: string) => { + if (didCopy) return; + setClipboard(message); + setDidCopy(true); + const copyTimer = setTimeout(() => { + setDidCopy(false); + }, 2000); + return () => clearTimeout(copyTimer); + }, + [didCopy, setClipboard] + ); + + return ( + MAX_CARD_HEIGHT} + isExpanded + skipCollapsedState + > + + + + + + 􀙤 + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} + + + + handleCopyPress(message)}> + + + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} + + + + + + + + + {displayMessage} + + + + ); +}; + +const SimulatedEventRow = ({ + amount, + asset, + eventType, + price, +}: { + amount: string | 'unlimited'; + asset: TransactionSimulationAsset | undefined; + eventType: EventType; + price?: number | undefined; +}) => { + const theme = useTheme(); + const { nativeCurrency } = useAccountSettings(); + const { data: externalAsset } = useExternalToken({ + address: asset?.assetCode || '', + chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), + currency: nativeCurrency, + }); + + const eventInfo: EventInfo = infoForEventType[eventType]; + + const formattedAmount = useMemo(() => { + if (!asset) return; + + const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; + const assetDisplayName = + asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; + const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; + + const displayAmount = + asset?.decimals === 0 + ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` + : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; + + const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; + + return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; + }, [amount, asset, eventInfo?.amountPrefix]); + + const url = maybeSignUri(asset?.iconURL, { + fm: 'png', + w: 16 * PixelRatio.get(), + }); + + const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; + + const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); + + return ( + + + + + + + {eventInfo.label} + + {showUSD && ( + + {formattedPrice} + + )} + + + + + {asset?.type !== TransactionAssetType.Nft ? ( + + ) : ( + + )} + + + {formattedAmount} + + + + + ); +}; + +const DetailRow = ({ + currentNetwork, + detailType, + onPress, + value, +}: { + currentNetwork?: Network; + detailType: DetailType; + onPress?: () => void; + value: string; +}) => { + const detailInfo: DetailInfo = infoForDetailType[detailType]; + + return ( + + + + + + {detailInfo.label} + + + + {detailType === 'function' && } + {detailType === 'sourceCodeVerification' && ( + + )} + {detailType === 'chain' && currentNetwork && ( + + )} + {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( + + {value} + + )} + {(detailType === 'contract' || detailType === 'to') && ( + + + + + 􀂄 + + + + + )} + + + + ); +}; + +const EventIcon = ({ eventType }: { eventType: EventType }) => { + const eventInfo: EventInfo = infoForEventType[eventType]; + + const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; + const isWarningIcon = + eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; + + return ( + + {!hideInnerFill && ( + + )} + + {eventInfo.icon} + + + ); +}; + +const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { + return ( + + + {detailInfo.icon} + + + ); +}; + +const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { + const { colors, isDarkMode } = useTheme(); + const separatorTertiary = useForegroundColor('separatorTertiary'); + + const infoForBadgeType: { + [key: string]: { + backgroundColor: string; + borderColor: string; + label?: string; + text: TextColor; + textOpacity?: number; + }; + } = { + function: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + text: 'labelQuaternary', + }, + unknown: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + label: 'Unknown', + text: 'labelQuaternary', + }, + unverified: { + backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, + borderColor: colors.alpha(colors.red, 0.02), + label: 'Unverified', + text: 'red', + textOpacity: 0.76, + }, + verified: { + backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, + borderColor: colors.alpha(colors.green, 0.02), + label: 'Verified', + text: 'green', + textOpacity: 0.76, + }, + }; + + return ( + + + + {infoForBadgeType[type].label || value} + + + + ); +}; + +const VerifiedBadge = () => { + return ( + + + + + 􀇻 + + + + ); +}; + +const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { + return ( + + {visible && ( + + + + + + 􀁣 + + + + + )} + + ); +}; + +const FadedScrollCard = ({ + cardHeight, + children, + contentHeight, + expandedCardBottomInset = 120, + expandedCardTopInset = 120, + initialScrollEnabled, + isExpanded, + onPressCollapsedCard, + skipCollapsedState, +}: { + cardHeight: SharedValue; + children: React.ReactNode; + contentHeight: SharedValue; + expandedCardBottomInset?: number; + expandedCardTopInset?: number; + initialScrollEnabled?: boolean; + isExpanded: boolean; + onPressCollapsedCard?: () => void; + skipCollapsedState?: boolean; +}) => { + const { height: deviceHeight, width: deviceWidth } = useDimensions(); + const { isDarkMode } = useTheme(); + + const cardRef = useAnimatedRef(); + + const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); + const [isFullyExpanded, setIsFullyExpanded] = useState(false); + + const yPosition = useSharedValue(0); + + const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); + + const containerStyle = useAnimatedStyle(() => { + return { + height: + cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState + ? interpolate( + cardHeight.value, + [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], + [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], + 'clamp' + ) + : undefined, + zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), + }; + }); + + const backdropStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const cardStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); + return { + borderColor: interpolateColor( + cardHeight.value, + [0, MAX_CARD_HEIGHT, expandedCardHeight], + isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] + ), + height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, + position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', + transform: [ + { + translateY: interpolate( + cardHeight.value, + [0, MAX_CARD_HEIGHT, expandedCardHeight], + [ + 0, + 0, + -yPosition.value + + expandedCardTopInset + + (deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight) - + (yPosition.value + expandedCardHeight >= deviceHeight - expandedCardBottomInset + ? 0 + : deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight), + ] + ), + }, + ], + }; + }); + + const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { + return { + transform: skipCollapsedState + ? undefined + : [ + { + translateY: interpolate( + cardHeight.value, + [ + 0, + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT + ? MAX_CARD_HEIGHT + : contentHeight.value + CARD_BORDER_WIDTH * 2, + maxExpandedHeight, + ], + [-2, -2, 0, 0] + ), + }, + ], + }; + }); + + const shadowStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const handleContentSizeChange = useCallback( + (width: number, height: number) => { + contentHeight.value = Math.round(height); + }, + [contentHeight] + ); + + const handleOnLayout = useCallback(() => { + runOnUI(() => { + if (cardHeight.value === MAX_CARD_HEIGHT) { + const measurement = measure(cardRef); + if (measurement === null) { + return; + } + if (yPosition.value !== measurement.pageY) { + yPosition.value = measurement.pageY; + } + } + })(); + }, [cardHeight, cardRef, yPosition]); + + useAnimatedReaction( + () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), + ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { + if ( + isFullyExpanded !== previous?.isFullyExpanded || + isExpanded !== previous?.isExpanded || + contentHeight !== previous?.contentHeight + ) { + if (isFullyExpanded) { + const expandedCardHeight = + contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; + if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { + cardHeight.value = withTiming(expandedCardHeight, timingConfig); + } else { + runOnJS(setIsFullyExpanded)(false); + } + } else if (isExpanded) { + cardHeight.value = withTiming( + contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, + timingConfig + ); + } else { + cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); + } + + const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); + runOnJS(setScrollEnabled)(enableScroll); + } + } + ); + + return ( + + { + if (isFullyExpanded) { + setIsFullyExpanded(false); + } + }} + pointerEvents={isFullyExpanded ? 'auto' : 'none'} + style={[ + { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + height: deviceHeight * 3, + left: -deviceWidth * 0.5, + position: 'absolute', + top: -deviceHeight, + width: deviceWidth * 2, + zIndex: -1, + }, + backdropStyle, + ]} + /> + + + + { + if (!isFullyExpanded) { + setIsFullyExpanded(true); + } else setIsFullyExpanded(false); + } + } + > + {children} + + + + + + + + ); +}; + +const FadeGradient = ({ side, style }: { side: 'top' | 'bottom'; style?: StyleProp>> }) => { + const { colors, isDarkMode } = useTheme(); + + const isTop = side === 'top'; + const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; + const transparentColor = colors.alpha(solidColor, 0); + + return ( + + + + ); +}; + +const IconContainer = ({ + children, + hitSlop, + opacity, + size = 20, +}: { + children: React.ReactNode; + hitSlop?: number; + opacity?: number; + size?: number; +}) => { + // Prevent wide icons from being clipped + const extraHorizontalSpace = 4; + + return ( + + + {children} + + + ); +}; + +type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; + +type EventInfo = { + amountPrefix: string; + icon: string; + iconColor: TextColor; + label: string; + textColor: TextColor; +}; + +const infoForEventType: { [key: string]: EventInfo } = { + send: { + amountPrefix: '- ', + icon: '􀁷', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), + textColor: 'red', + }, + receive: { + amountPrefix: '+ ', + icon: '􀁹', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), + textColor: 'green', + }, + approve: { + amountPrefix: '', + icon: '􀎤', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), + textColor: 'label', + }, + revoke: { + amountPrefix: '', + icon: '􀎠', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), + textColor: 'label', + }, + failed: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), + textColor: 'red', + }, + insufficientBalance: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'blue', + label: '', + textColor: 'blue', + }, + MALICIOUS: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: '', + textColor: 'red', + }, + WARNING: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'orange', + label: '', + textColor: 'orange', + }, +}; + +type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; + +type DetailInfo = { + icon: string; + label: string; +}; + +const infoForDetailType: { [key: string]: DetailInfo } = { + chain: { + icon: '􀤆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), + }, + contract: { + icon: '􀉆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), + }, + to: { + icon: '􀉩', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), + }, + function: { + icon: '􀡅', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), + }, + sourceCodeVerification: { + icon: '􀕹', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), + }, + dateCreated: { + icon: '􀉉', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), + }, + nonce: { + icon: '􀆃', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), + }, +}; + +const CHARACTERS_PER_LINE = 40; +const LINE_HEIGHT = 11; +const LINE_GAP = 9; + +const estimateMessageHeight = (message: string) => { + const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); + const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; + + return messageHeight; +}; + +const formatDate = (dateString: string) => { + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - date.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30.44); + + if (diffDays === 0) { + return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); + } else if (diffDays === 1) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; + } else if (diffDays < 7) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; + } else if (diffWeeks === 1) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; + } else if (diffDays < 30.44) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; + } else if (diffMonths === 1) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; + } else if (diffDays < 365.25) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; + } else { + return date.toLocaleString('default', { month: 'short', year: 'numeric' }); + } +}; diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 6c4fd429c2c..02ea352ec5c 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -33,8 +33,6 @@ import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; -import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; -import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; const WalletPage = styled(Page)({ ...position.sizeAsObject('100%'), @@ -108,7 +106,6 @@ const WalletScreen: React.FC = ({ navigation, route }) => { if (walletReady) { loadAccountLateData(); loadGlobalLateData(); - runWalletBackupStatusChecks(); } }, [loadAccountLateData, loadGlobalLateData, walletReady]); @@ -150,9 +147,6 @@ const WalletScreen: React.FC = ({ navigation, route }) => { - - {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} - ); diff --git a/src/state/performance/operations.ts b/src/state/performance/operations.ts index a042200fc26..f30133ce5c6 100644 --- a/src/state/performance/operations.ts +++ b/src/state/performance/operations.ts @@ -4,7 +4,6 @@ export enum Screens { SEND = 'Send', SEND_ENS = 'SendENS', WALLETCONNECT = 'WalletConnect', - MOBILE_WALLET_PROTOCOL = 'MobileWalletProtocol', } type RouteValues = (typeof Screens)[keyof typeof Screens]; diff --git a/src/storage/index.ts b/src/storage/index.ts index c9d9639552a..824c7288ce7 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -3,7 +3,6 @@ import { MMKV } from 'react-native-mmkv'; import { Account, Cards, Campaigns, Device, Review } from '@/storage/schema'; import { EthereumAddress, RainbowTransaction } from '@/entities'; import { Network } from '@/networks/types'; -import { SecureStorage } from '@coinbase/mobile-wallet-protocol-host'; /** * Generic storage class. DO NOT use this directly. Instead, use the exported @@ -13,8 +12,8 @@ export class Storage { protected sep = ':'; protected store: MMKV; - constructor({ id, encryptionKey }: { id: string; encryptionKey?: string }) { - this.store = new MMKV({ id, encryptionKey }); + constructor({ id }: { id: string }) { + this.store = new MMKV({ id }); } /** @@ -51,13 +50,6 @@ export class Storage { this.store.delete(scopes.join(this.sep)); } - /** - * Clear all values from storage - */ - clear() { - this.store.clearAll(); - } - /** * Remove many values from the same storage scope by keys * @@ -67,21 +59,6 @@ export class Storage { removeMany(scopes: [...Scopes], keys: Key[]) { keys.forEach(key => this.remove([...scopes, key])); } - - /** - * Encrypt the storage with a new key - * @param newEncryptionKey - The new encryption key - */ - encrypt(newEncryptionKey: string): void { - this.store.recrypt(newEncryptionKey); - } - - /** - * Remove encryption from the storage - */ - removeEncryption(): void { - this.store.recrypt(undefined); - } } /** @@ -111,27 +88,3 @@ export const cards = new Storage<[], Cards>({ id: 'cards' }); export const identifier = new Storage<[], { identifier: string }>({ id: 'identifier', }); - -/** - * Mobile Wallet Protocol storage - * - * @todo - fix any type here - */ -const mwpStorage = new Storage<[], any>({ id: 'mwp', encryptionKey: process.env.MWP_ENCRYPTION_KEY }); - -export const mwp: SecureStorage = { - get: async function (key: string): Promise { - const dataJson = mwpStorage.get([key]); - if (dataJson === undefined) { - return undefined; - } - return Promise.resolve(JSON.parse(dataJson) as T); - }, - set: async function (key: string, value: T): Promise { - const encoded = JSON.stringify(value); - mwpStorage.set([key], encoded); - }, - remove: async function (key: string): Promise { - mwpStorage.remove([key]); - }, -}; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts deleted file mode 100644 index 8bb40dc1897..00000000000 --- a/src/utils/formatDate.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as i18n from '@/languages'; - -export const formatDate = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - const diffTime = Math.abs(now.getTime() - date.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - const diffWeeks = Math.floor(diffDays / 7); - const diffMonths = Math.floor(diffDays / 30.44); - - if (diffDays === 0) { - return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); - } else if (diffDays === 1) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; - } else if (diffDays < 7) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; - } else if (diffWeeks === 1) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; - } else if (diffDays < 30.44) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; - } else if (diffMonths === 1) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; - } else if (diffDays < 365.25) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; - } else { - return date.toLocaleString('default', { month: 'short', year: 'numeric' }); - } -}; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 6ecc8ad16fc..8a6f193a480 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -13,7 +13,7 @@ import { import { InteractionManager } from 'react-native'; import { SEND_TRANSACTION } from './signingMethods'; import { handleSessionRequestResponse } from '@/walletConnect'; -import ethereumUtils, { getNetworkFromChainId } from './ethereumUtils'; +import ethereumUtils from './ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers'; import { RainbowNetworks } from '@/networks'; import { maybeSignUri } from '@/handlers/imgix'; @@ -22,249 +22,8 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; -import { - EthereumAction, - isEthereumAction, - isHandshakeAction, - PersonalSignAction, - RequestMessage, - useMobileWalletProtocolHost, -} from '@coinbase/mobile-wallet-protocol-host'; -import { ChainId } from '@/__swaps__/types/chains'; -import { logger, RainbowError } from '@/logger'; -import { noop } from 'lodash'; -import { toUtf8String } from '@ethersproject/strings'; -import { BigNumber } from '@ethersproject/bignumber'; - -export enum RequestSource { - WALLETCONNECT = 'walletconnect', - BROWSER = 'browser', - MOBILE_WALLET_PROTOCOL = 'mobile-wallet-protocol', -} - -// Mobile Wallet Protocol - -interface HandleMobileWalletProtocolRequestProps - extends Omit, 'message' | 'handleRequestUrl' | 'sendFailureToClient'> { - request: RequestMessage; -} - -const constructEthereumActionPayload = (action: EthereumAction) => { - if (action.method === 'eth_sendTransaction') { - const { weiValue, fromAddress, toAddress, actionSource, gasPriceInWei, ...rest } = action.params; - return [ - { - ...rest, - from: fromAddress, - to: toAddress, - value: weiValue, - }, - ]; - } - - return Object.values(action.params); -}; - -const supportedMobileWalletProtocolActions: string[] = [ - 'eth_requestAccounts', - 'eth_sendTransaction', - 'eth_signTypedData_v4', - 'personal_sign', - 'wallet_switchEthereumChain', -]; - -export const handleMobileWalletProtocolRequest = async ({ - request, - fetchClientAppMetadata, - approveHandshake, - rejectHandshake, - approveAction, - rejectAction, - session, -}: HandleMobileWalletProtocolRequestProps): Promise => { - logger.debug(`Handling Mobile Wallet Protocol request: ${request.uuid}`); - - const { selected } = store.getState().wallets; - const { accountAddress } = store.getState().settings; - - const isReadOnlyWallet = selected?.type === walletTypes.readOnly; - if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { - logger.debug('Rejecting request due to read-only wallet'); - watchingAlert(); - return Promise.reject(new Error('This wallet is read-only.')); - } - - const handleAction = async (currentIndex: number): Promise => { - const action = request.actions[currentIndex]; - logger.debug(`Handling action: ${action.kind}`); - - if (isHandshakeAction(action)) { - logger.debug(`Processing handshake action for ${action.appId}`); - - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); - const receivedTimestamp = Date.now(); - - const dappMetadata = await fetchClientAppMetadata(); - return new Promise((resolve, reject) => { - const routeParams: WalletconnectApprovalSheetRouteParams = { - receivedTimestamp, - meta: { - chainIds, - dappName: dappMetadata?.appName || dappMetadata?.appUrl || action.appName || action.appIconUrl || action.appId || '', - dappUrl: dappMetadata?.appUrl || action.appId || '', - imageUrl: maybeSignUri(dappMetadata?.iconUrl || action.appIconUrl), - isWalletConnectV2: false, - peerId: '', - dappScheme: action.callback, - proposedChainId: request.account?.networkId ?? ChainId.mainnet, - proposedAddress: request.account?.address || accountAddress, - }, - source: RequestSource.MOBILE_WALLET_PROTOCOL, - timedOut: false, - callback: async approved => { - if (approved) { - logger.debug(`Handshake approved for ${action.appId}`); - const success = await approveHandshake(dappMetadata); - resolve(success); - } else { - logger.debug(`Handshake rejected for ${action.appId}`); - await rejectHandshake('User rejected the handshake'); - reject('User rejected the handshake'); - } - }, - }; - - Navigation.handleAction( - Routes.WALLET_CONNECT_APPROVAL_SHEET, - routeParams, - getActiveRoute()?.name === Routes.WALLET_CONNECT_APPROVAL_SHEET - ); - }); - } else if (isEthereumAction(action)) { - logger.debug(`Processing ethereum action: ${action.method}`); - if (!supportedMobileWalletProtocolActions.includes(action.method)) { - logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type ${action.method}`)); - await rejectAction(action, { - message: 'Unsupported action type', - code: 4001, - }); - return false; - } - - if (action.method === 'wallet_switchEthereumChain') { - const isSupportedChain = RainbowNetworks.find(network => network.id === BigNumber.from(action.params.chainId).toNumber()); - if (!isSupportedChain) { - await rejectAction(action, { - message: 'Unsupported chain', - code: 4001, - }); - return false; - } - - await approveAction(action, { value: 'null' }); - return true; - } - - // NOTE: This is a workaround to approve the eth_requestAccounts action if the previous action was a handshake action. - const previousAction = request.actions[currentIndex - 1]; - if (previousAction && isHandshakeAction(previousAction)) { - logger.debug('Approving eth_requestAccounts'); - await approveAction(action, { - value: JSON.stringify({ - chain: request.account?.chain ?? 'eth', - networkId: request.account?.networkId ?? ChainId.mainnet, - address: accountAddress, - }), - }); - return true; - } - - const nativeCurrency = store.getState().settings.nativeCurrency; - - // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} - if ((action as PersonalSignAction).params.message && (action as PersonalSignAction).params.message.type === 'Buffer') { - // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} - const messageFromBuffer = toUtf8String(Buffer.from((action as PersonalSignAction).params.message.data, 'hex')); - (action as PersonalSignAction).params.message = messageFromBuffer; - } - - const payload = { - method: action.method, - params: constructEthereumActionPayload(action), - }; - - const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, request.account?.networkId ?? ChainId.mainnet); - - const requestWithDetails: RequestData = { - dappName: session?.dappName ?? session?.dappId ?? '', - dappUrl: session?.dappURL ?? '', - imageUrl: session?.dappImageURL ?? '', - address: (action as PersonalSignAction).params.address ?? accountAddress, - chainId: request.account?.networkId ?? ChainId.mainnet, - payload, - displayDetails, - }; - - return new Promise((resolve, reject) => { - const onSuccess = async (result: string) => { - logger.debug(`Ethereum action approved: [${action.method}]: ${result}`); - const success = await approveAction(action, { value: JSON.stringify(result) }); - resolve(success); - }; - const onCancel = async (error?: Error) => { - if (error) { - logger.debug(`Ethereum action rejected: [${action.method}]: ${error.message}`); - await rejectAction(action, { - message: error.message, - code: 4001, - }); - reject(error.message); - } else { - logger.debug(`Ethereum action rejected: [${action.method}]: User rejected request`); - await rejectAction(action, { - message: 'User rejected request', - code: 4001, - }); - reject('User rejected request'); - } - }; - - Navigation.handleAction(Routes.CONFIRM_REQUEST, { - transactionDetails: requestWithDetails, - onSuccess, - onCancel, - onCloseScreen: noop, - chainId: request.account?.networkId ?? ChainId.mainnet, - address: accountAddress, - source: RequestSource.MOBILE_WALLET_PROTOCOL, - }); - }); - } else { - logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type, ${action}`)); - return false; - } - }; - - const handleActions = async (actions: typeof request.actions, currentIndex: number = 0): Promise => { - if (currentIndex >= actions.length) { - logger.debug(`All actions completed successfully: ${actions.length}`); - return true; - } - - logger.debug(`Processing action ${currentIndex + 1} of ${actions.length}`); - const success = await handleAction(currentIndex); - if (success) { - return handleActions(actions, currentIndex + 1); - } else { - // stop processing if an action fails - return false; - } - }; - - // start processing actions starting at index 0 - return handleActions(request.actions); -}; +export type RequestSource = 'walletconnect' | 'browser'; // Dapp Browser @@ -293,7 +52,7 @@ export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): proposedChainId: dappData.chainId, proposedAddress: dappData.address, }, - source: RequestSource.BROWSER, + source: 'browser', timedOut: false, callback: async (approved, approvedChainId, accountAddress) => { if (approved) { @@ -318,31 +77,16 @@ export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): }); }; -const findWalletForAddress = async (address: string) => { - if (!address.trim()) { - return Promise.reject(new Error('Invalid address')); - } - +export const handleDappBrowserRequest = async (request: Omit): Promise => { const { wallets } = store.getState().wallets; - const selectedWallet = findWalletWithAccount(wallets!, address); - if (!selectedWallet) { - return Promise.reject(new Error('Wallet not found')); - } - - const isReadOnlyWallet = selectedWallet.type === walletTypes.readOnly; + const selectedWallet = findWalletWithAccount(wallets!, request.address); + const isReadOnlyWallet = selectedWallet!.type === walletTypes.readOnly; if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return Promise.reject(new Error('This wallet is read-only.')); } - - return selectedWallet; -}; - -export const handleDappBrowserRequest = async (request: Omit): Promise => { - await findWalletForAddress(request.address); - const nativeCurrency = store.getState().settings.nativeCurrency; - const displayDetails = await getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); + const displayDetails = getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); const requestWithDetails: RequestData = { ...request, @@ -374,7 +118,7 @@ export const handleDappBrowserRequest = async (request: Omit Date: Wed, 28 Aug 2024 18:12:08 -0400 Subject: [PATCH 66/78] use chainId instead of network: part 2 (#5997) * use chainid in --- src/__swaps__/screens/Swap/Swap.tsx | 2 +- .../components/AnimatedChainImage.android.tsx | 2 +- .../components/AnimatedChainImage.ios.tsx | 2 +- .../screens/Swap/components/CoinRow.tsx | 20 +- .../Swap/components/FastSwapCoinIconImage.tsx | 88 ------ .../screens/Swap/components/FlipButton.tsx | 2 +- .../screens/Swap/components/GasButton.tsx | 2 +- .../screens/Swap/components/GasPanel.tsx | 2 +- .../screens/Swap/components/ReviewPanel.tsx | 7 +- .../screens/Swap/components/SwapCoinIcon.tsx | 2 +- .../Swap/components/SwapOutputAsset.tsx | 8 +- .../components/TokenList/ChainSelection.tsx | 6 +- .../components/TokenList/TokenToBuyList.tsx | 2 +- .../Swap/hooks/useAnimatedSwapStyles.ts | 2 +- .../screens/Swap/hooks/useCustomGas.ts | 2 +- .../screens/Swap/hooks/useEstimatedGasFee.ts | 2 +- .../Swap/hooks/useNativeAssetForChain.ts | 6 +- .../Swap/hooks/useSearchCurrencyLists.ts | 2 +- .../screens/Swap/hooks/useSelectedGas.ts | 2 +- .../Swap/hooks/useSwapEstimatedGasLimit.ts | 2 +- .../Swap/hooks/useSwapInputsController.ts | 2 +- .../SyncSwapStateAndSharedValues.tsx | 2 +- .../screens/Swap/providers/swap-provider.tsx | 2 +- .../Swap/resources/_selectors/assets.ts | 2 +- .../screens/Swap/resources/assets/assets.ts | 2 +- .../Swap/resources/assets/userAssets.ts | 2 +- .../resources/assets/userAssetsByChain.ts | 2 +- .../Swap/resources/search/discovery.ts | 2 +- .../screens/Swap/resources/search/search.ts | 2 +- .../screens/Swap/resources/search/utils.ts | 2 +- src/__swaps__/types/assets.ts | 2 +- src/__swaps__/types/chains.ts | 215 --------------- src/__swaps__/types/refraction.ts | 2 +- src/__swaps__/types/search.ts | 2 +- src/__swaps__/utils/address.ts | 2 +- src/__swaps__/utils/assets.ts | 3 +- src/__swaps__/utils/chains.ts | 2 +- src/__swaps__/utils/gasUtils.ts | 2 +- src/__swaps__/utils/meteorology.ts | 6 +- src/__swaps__/utils/swaps.ts | 2 +- src/__swaps__/utils/userChains.ts | 2 +- src/analytics/event.ts | 3 +- src/appIcons/appIcons.ts | 2 +- src/components/AddFundsInterstitial.js | 4 +- src/components/ChainLogo.js | 18 +- src/components/ContactRowInfoButton.js | 18 +- .../control-panel/ControlPanel.tsx | 61 ++--- .../DappBrowser/handleProviderRequest.ts | 60 ++-- src/components/L2Disclaimer.js | 8 +- src/components/activity-list/ActivityList.js | 5 +- .../FastComponents/FastBalanceCoinRow.tsx | 12 +- .../FastComponents/FastCoinBadge.tsx | 2 +- .../FastCurrencySelectionRow.tsx | 2 +- .../RecyclerAssetList2/NFTEmptyState.tsx | 4 +- .../core/RawRecyclerList.tsx | 4 +- .../ProfileActionButtonsRow.tsx | 4 +- src/components/cards/EthCard.tsx | 4 +- src/components/cards/FeaturedMintCard.tsx | 4 +- .../cards/MintsCard/CollectionCell.tsx | 5 +- src/components/cards/NFTOffersCard/Offer.tsx | 3 +- src/components/cards/OpRewardsCard.tsx | 3 +- src/components/change-wallet/WalletList.tsx | 4 +- src/components/coin-icon/ChainBadge.js | 3 +- src/components/coin-icon/ChainImage.tsx | 3 +- src/components/coin-icon/EthCoinIcon.tsx | 2 +- src/components/coin-icon/RainbowCoinIcon.tsx | 2 +- .../coin-icon/RequestVendorLogoIcon.js | 6 +- src/components/coin-icon/TwoCoinsIcon.tsx | 2 +- src/components/coin-row/CoinRowInfoButton.js | 6 +- .../coin-row/FastTransactionCoinRow.tsx | 5 +- src/components/coin-row/SendCoinRow.js | 6 +- src/components/contacts/ContactRow.js | 6 +- .../context-menu-buttons/ChainContextMenu.tsx | 2 +- .../discover/DiscoverSearchInput.js | 7 +- .../ens-profile/ActionButtons/MoreButton.tsx | 4 +- .../exchange/ConfirmExchangeButton.js | 6 +- src/components/exchange/ExchangeField.tsx | 3 +- .../exchange/ExchangeInputField.tsx | 3 +- .../exchange/ExchangeOutputField.tsx | 2 +- src/components/exchange/ExchangeTokenRow.tsx | 12 +- src/components/exchange/NetworkSwitcher.js | 9 +- src/components/exchange/NetworkSwitcherv2.tsx | 18 +- .../exchangeAssetRowContextMenuProps.ts | 6 +- .../expanded-state/AvailableNetworks.js | 28 +- .../expanded-state/AvailableNetworksv2.tsx | 87 +++--- .../expanded-state/ContactProfileState.js | 8 +- .../expanded-state/CustomGasState.js | 4 +- .../UniqueTokenExpandedState.tsx | 14 +- .../asset/ChartExpandedState.js | 31 ++- .../expanded-state/asset/SocialLinks.js | 4 +- .../chart/ChartContextButton.js | 4 +- .../chart/ChartExpandedStateHeader.js | 6 +- .../expanded-state/custom-gas/FeesPanel.tsx | 14 +- .../swap-details/CurrencyTile.js | 2 +- .../swap-details/SwapDetailsContent.js | 2 +- .../swap-details/SwapDetailsContractRow.js | 12 +- .../swap-details/SwapDetailsExchangeRow.js | 5 +- .../swap-details/SwapDetailsRewardRow.tsx | 2 +- .../swap-settings/MaxToleranceInput.tsx | 256 +++++++++--------- .../swap-settings/SwapSettingsState.js | 4 +- .../unique-token/NFTBriefTokenInfoRow.tsx | 2 +- .../UniqueTokenExpandedStateHeader.tsx | 12 +- src/components/gas/GasSpeedButton.js | 6 +- src/components/positions/PositionsCard.tsx | 3 +- .../check-fns/hasNonZeroAssetBalance.ts | 32 --- .../check-fns/hasSwapTxn.ts | 4 +- .../remote-promo-sheet/check-fns/index.ts | 1 - .../sheet-action-buttons/SwapActionButton.tsx | 2 +- src/components/toasts/OfflineToast.js | 4 +- src/components/toasts/TestnetToast.js | 20 +- .../token-info/TokenInfoBalanceValue.js | 4 +- .../WalletConnectListItem.js | 6 +- .../WalletConnectV2ListItem.tsx | 2 +- src/entities/tokens.ts | 17 +- src/entities/transactions/transaction.ts | 3 +- src/entities/uniqueAssets.ts | 3 +- src/featuresToUnlock/tokenGatedUtils.ts | 8 +- src/handlers/__mocks__/web3.ts | 2 +- src/handlers/assets.ts | 14 +- src/handlers/ens.ts | 19 +- src/handlers/gasFees.ts | 5 +- src/handlers/localstorage/globalSettings.ts | 8 +- src/handlers/localstorage/removeWallet.ts | 4 +- src/handlers/swap.ts | 10 +- src/handlers/tokenSearch.ts | 7 +- src/handlers/web3.ts | 143 ++++------ src/helpers/RainbowContext.tsx | 14 +- src/helpers/ens.ts | 13 +- src/helpers/gas.ts | 12 +- src/helpers/index.ts | 1 - src/helpers/networkInfo.ts | 26 +- src/helpers/networkTypes.ts | 31 --- src/helpers/validators.ts | 6 +- src/helpers/walletConnectNetworks.ts | 11 +- src/hooks/charts/useChartInfo.ts | 20 +- src/hooks/charts/useChartThrottledPoints.ts | 2 +- src/hooks/useAccountAsset.ts | 3 +- src/hooks/useAccountTransactions.ts | 26 +- src/hooks/useAdditionalAssetData.ts | 20 +- src/hooks/useAsset.ts | 5 +- src/hooks/useContacts.ts | 10 +- src/hooks/useENSRegistrationActionHandler.ts | 30 +- src/hooks/useENSRegistrationCosts.ts | 9 +- src/hooks/useENSRegistrationStepHandler.tsx | 7 +- src/hooks/useENSSearch.ts | 4 +- src/hooks/useGas.ts | 44 ++- src/hooks/useImportingWallet.ts | 5 +- src/hooks/useOnAvatarPress.ts | 4 +- src/hooks/usePriceImpactDetails.ts | 5 +- src/hooks/useRainbowFee.js | 2 +- src/hooks/useSearchCurrencyList.ts | 7 +- src/hooks/useSwapCurrencyHandlers.ts | 3 - src/hooks/useSwapCurrencyList.ts | 136 +++++----- src/hooks/useSwapDerivedOutputs.ts | 12 +- src/hooks/useSwapInputHandlers.ts | 6 +- src/hooks/useSwapRefuel.ts | 28 +- src/hooks/useSwappableUserAssets.ts | 9 +- src/hooks/useUserAccounts.ts | 14 +- src/hooks/useWatchPendingTxs.ts | 42 +-- src/languages/en_US.json | 1 + src/migrations/migrations/migrateFavorites.ts | 3 +- src/model/remoteConfig.ts | 8 +- src/model/wallet.ts | 2 +- src/navigation/SwipeNavigator.tsx | 5 +- src/navigation/config.tsx | 7 +- src/networks/README.md | 8 +- src/networks/arbitrum.ts | 5 +- src/networks/avalanche.ts | 5 +- src/networks/base.ts | 5 +- src/networks/blast.ts | 5 +- src/networks/bsc.ts | 5 +- src/networks/degen.ts | 5 +- src/networks/gnosis.ts | 5 +- src/networks/goerli.ts | 5 +- src/networks/index.ts | 64 ++--- src/networks/mainnet.ts | 10 +- src/networks/optimism.ts | 5 +- src/networks/polygon.ts | 5 +- src/networks/types.ts | 199 +++++++++++++- src/networks/zora.ts | 5 +- src/notifications/NotificationsHandler.tsx | 7 +- src/parsers/accounts.js | 72 ----- src/parsers/accounts.ts | 32 +++ src/parsers/gas.ts | 4 +- src/parsers/index.ts | 2 +- src/parsers/requests.js | 2 +- src/parsers/transactions.ts | 2 +- src/raps/actions/claimBridge.ts | 16 +- src/raps/actions/crosschainSwap.ts | 14 +- src/raps/actions/ens.ts | 8 +- src/raps/actions/swap.ts | 17 +- src/raps/actions/unlock.ts | 24 +- src/raps/references.ts | 2 +- src/raps/unlockAndSwap.ts | 2 +- src/raps/utils.ts | 3 +- src/redux/contacts.ts | 10 +- src/redux/ensRegistration.ts | 8 +- src/redux/explorer.ts | 10 +- src/redux/gas.ts | 135 ++++----- src/redux/requests.ts | 2 +- src/redux/settings.ts | 25 +- src/redux/showcaseTokens.ts | 4 +- src/redux/swap.ts | 4 +- src/redux/walletconnect.ts | 20 +- src/references/chain-assets.json | 6 +- src/references/gasUnits.ts | 2 +- src/references/index.ts | 3 +- src/references/rainbow-token-list/index.ts | 3 +- src/references/testnet-assets-by-chain.ts | 9 +- src/resources/assets/UserAssetsQuery.ts | 8 +- src/resources/assets/assetSelectors.ts | 6 +- src/resources/assets/assets.ts | 7 +- src/resources/assets/externalAssetsQuery.ts | 18 +- src/resources/assets/hardhatAssets.ts | 26 +- src/resources/assets/types.ts | 4 +- src/resources/assets/useUserAsset.ts | 2 +- src/resources/defi/PositionsQuery.ts | 4 +- src/resources/ens/ensAddressQuery.ts | 5 +- src/resources/favorites.ts | 3 +- src/resources/nfts/index.ts | 19 +- src/resources/nfts/simplehash/index.ts | 42 +-- src/resources/nfts/simplehash/types.ts | 2 +- src/resources/nfts/simplehash/utils.ts | 3 +- src/resources/nfts/types.ts | 2 +- src/resources/nfts/utils.ts | 2 +- src/resources/reservoir/mints.ts | 15 +- src/resources/reservoir/utils.ts | 2 +- .../transactions/consolidatedTransactions.ts | 4 +- src/resources/transactions/transaction.ts | 35 ++- .../AddCash/components/ProviderCard.tsx | 22 +- src/screens/AddCash/utils.ts | 16 +- src/screens/CurrencySelectModal.tsx | 25 +- src/screens/ENSConfirmRegisterSheet.tsx | 2 +- src/screens/ExchangeModal.tsx | 27 +- src/screens/ExplainSheet.js | 126 ++++----- src/screens/MintsSheet/card/Card.tsx | 11 +- src/screens/NFTOffersSheet/OfferRow.tsx | 3 +- src/screens/NFTSingleOfferSheet/index.tsx | 14 +- src/screens/SendConfirmationSheet.tsx | 26 +- src/screens/SendSheet.js | 39 ++- .../components/CurrencySection.tsx | 2 +- .../SettingsSheet/components/DevSection.tsx | 29 +- .../components/NetworkSection.tsx | 26 +- .../components/NotificationsSection.tsx | 6 +- .../components/SettingsSection.tsx | 21 -- src/screens/SignTransactionSheet.tsx | 60 ++-- src/screens/SpeedUpAndCancelSheet.js | 54 ++-- src/screens/WalletConnectApprovalSheet.js | 49 ++-- src/screens/WalletScreen/index.tsx | 31 +-- .../discover/components/DiscoverHome.tsx | 6 +- .../discover/components/DiscoverSearch.js | 5 +- src/screens/mints/MintSheet.tsx | 23 +- .../points/claim-flow/ClaimRewardsPanel.tsx | 13 +- .../points/components/LeaderboardRow.tsx | 5 +- src/screens/points/content/PointsContent.tsx | 2 +- .../points/contexts/PointsProfileContext.tsx | 6 +- ...ransactionDetailsHashAndActionsSection.tsx | 2 +- .../TransactionDetailsValueAndFeeSection.tsx | 4 +- .../components/TransactionMasthead.tsx | 4 +- src/state/appSessions/index.test.ts | 38 +-- src/state/appSessions/index.ts | 68 +++-- src/state/assets/userAssets.ts | 2 +- src/state/connectedToHardhat/index.ts | 28 ++ src/state/internal/createRainbowStore.ts | 8 +- src/state/nonces/index.ts | 61 +++-- src/state/pendingTransactions/index.ts | 16 +- src/state/swaps/swapsStore.ts | 2 +- src/state/sync/UserAssetsSync.tsx | 3 +- src/styles/colors.ts | 49 ++-- src/utils/ethereumUtils.ts | 86 +++--- src/utils/getTokenMetadata.ts | 7 - src/utils/getUrlForTrustIconFallback.ts | 13 +- src/utils/index.ts | 1 - src/utils/requestNavigationHandlers.ts | 12 +- src/walletConnect/index.tsx | 11 +- tsconfig.json | 1 - 276 files changed, 1944 insertions(+), 2297 deletions(-) delete mode 100644 src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx delete mode 100644 src/__swaps__/types/chains.ts delete mode 100644 src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts delete mode 100644 src/helpers/networkTypes.ts delete mode 100644 src/parsers/accounts.js create mode 100644 src/parsers/accounts.ts create mode 100644 src/state/connectedToHardhat/index.ts delete mode 100644 src/utils/getTokenMetadata.ts diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 7f005ef3095..b3b8f05f449 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -18,7 +18,7 @@ import { SwapInputAsset } from '@/__swaps__/screens/Swap/components/SwapInputAss import { SwapNavbar } from '@/__swaps__/screens/Swap/components/SwapNavbar'; import { SwapOutputAsset } from '@/__swaps__/screens/Swap/components/SwapOutputAsset'; import { SwapSheetGestureBlocker } from '@/__swaps__/screens/Swap/components/SwapSheetGestureBlocker'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; import { AbsolutePortalRoot } from '@/components/AbsolutePortal'; diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx index 96873dce266..689f9374507 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx @@ -13,7 +13,7 @@ const AvalancheBadge = require('@/assets/badges/avalanche.png'); const BlastBadge = require('@/assets/badges/blast.png'); const DegenBadge = require('@/assets/badges/degen.png'); -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { globalColors } from '@/design-system'; import { PIXEL_RATIO } from '@/utils/deviceUtils'; import { useSwapsStore } from '@/state/swaps/swapsStore'; diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index ceae4f7d750..3ed30eca49b 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -11,7 +11,7 @@ import ZoraBadge from '@/assets/badges/zora.png'; import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { useAnimatedProps } from 'react-native-reanimated'; import { AddressOrEth } from '@/__swaps__/types/assets'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 0728370c7aa..37c7feb897c 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -1,14 +1,14 @@ import { BalancePill } from '@/__swaps__/screens/Swap/components/BalancePill'; import { CoinRowButton } from '@/__swaps__/screens/Swap/components/CoinRowButton'; import { AddressOrEth, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ButtonPressAnimation } from '@/components/animations'; import { ContextMenuButton } from '@/components/context-menu'; import { Box, Column, Columns, HitSlop, Inline, Text } from '@/design-system'; import { setClipboard } from '@/hooks/useClipboard'; import * as i18n from '@/languages'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { BASE_DEGEN_ADDRESS, DEGEN_CHAIN_DEGEN_ADDRESS, ETH_ADDRESS } from '@/references'; import { toggleFavorite } from '@/resources/favorites'; import { userAssetsStore } from '@/state/assets/userAssets'; @@ -185,7 +185,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...asse } const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) => { - const network = RainbowNetworks.find(network => network.id === chainId)?.value; + const networkObject = RainbowNetworkObjects.find(networkObject => networkObject.id === chainId)?.value; const handleCopy = useCallback(() => { haptics.selection(); @@ -197,11 +197,11 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) title: i18n.t(i18n.l.exchange.coin_row.copy_contract_address), action: handleCopy, }, - ...(network + ...(networkObject ? { blockExplorer: { - title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)) }), - action: () => ethereumUtils.openAddressInBlockExplorer(address, chainId), + title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })) }), + action: () => ethereumUtils.openAddressInBlockExplorer({ address, chainId }), }, } : {}), @@ -217,7 +217,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) iconValue: 'doc.on.doc', }, }, - ...(network + ...(networkObject ? [ { actionKey: 'blockExplorer', @@ -236,7 +236,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const handlePressMenuItem = async ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => { if (actionKey === 'copyAddress') { options.copy.action(); - } else if (actionKey === 'blockExplorer' && network) { + } else if (actionKey === 'blockExplorer' && networkObject) { options.blockExplorer?.action(); } }; @@ -244,14 +244,14 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const onPressAndroid = () => showActionSheetWithOptions( { - options: [options.copy.title, ...(network ? [options.blockExplorer?.title] : [])], + options: [options.copy.title, ...(networkObject ? [options.blockExplorer?.title] : [])], showSeparators: true, }, (idx: number) => { if (idx === 0) { options.copy.action(); } - if (idx === 1 && network) { + if (idx === 1 && networkObject) { options.blockExplorer?.action(); } } diff --git a/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx b/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx deleted file mode 100644 index ed659118401..00000000000 --- a/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { ImgixImage } from '@/components/images'; -import { Network } from '@/networks/types'; -import { getUrlForTrustIconFallback } from '@/utils'; - -export const FastSwapCoinIconImage = React.memo(function FastSwapCoinIconImage({ - address, - disableShadow = true, - network, - shadowColor, - size, -}: { - address: string; - children: () => React.ReactNode; - disableShadow?: boolean; - network: Network; - shadowColor: string; - size?: number; -}) { - const imageUrl = getUrlForTrustIconFallback(address, network); - - return ( - - - - ); -}); - -const sx = StyleSheet.create({ - coinIconContainer: { - alignItems: 'center', - borderRadius: 20, - height: 40, - justifyContent: 'center', - overflow: 'visible', - width: 40, - }, - coinIconFallback: { - borderRadius: 20, - height: 40, - overflow: 'hidden', - width: 40, - }, - container: { - elevation: 6, - height: 59, - overflow: 'visible', - paddingTop: 9, - }, - contract: { - height: 40, - width: 40, - }, - fallbackWrapper: { - left: 0, - position: 'absolute', - top: 0, - }, - reactCoinIconContainer: { - alignItems: 'center', - justifyContent: 'center', - }, - reactCoinIconImage: { - height: '100%', - width: '100%', - }, - withShadow: { - elevation: 6, - shadowOffset: { - height: 4, - width: 0, - }, - shadowOpacity: 0.2, - shadowRadius: 6, - }, -}); diff --git a/src/__swaps__/screens/Swap/components/FlipButton.tsx b/src/__swaps__/screens/Swap/components/FlipButton.tsx index 1bc69c1df28..0b71487dfcf 100644 --- a/src/__swaps__/screens/Swap/components/FlipButton.tsx +++ b/src/__swaps__/screens/Swap/components/FlipButton.tsx @@ -14,7 +14,7 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { GestureHandlerButton } from './GestureHandlerButton'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const FlipButton = () => { const { isDarkMode } = useColorMode(); diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index bdef2deda67..6f96e02db53 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,4 +1,3 @@ -import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -24,6 +23,7 @@ import { NavigationSteps, useSwapContext } from '../providers/swap-provider'; import { EstimatedSwapGasFee, EstimatedSwapGasFeeSlot } from './EstimatedSwapGasFee'; import { GestureHandlerButton } from './GestureHandlerButton'; import { UnmountOnAnimatedReaction } from './UnmountOnAnimatedReaction'; +import { ChainId } from '@/networks/types'; const { SWAP_GAS_ICONS } = gasUtils; const GAS_BUTTON_HIT_SLOP = 16; diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 77f6a7dbf0a..85167b3e817 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -4,7 +4,7 @@ import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, withDelay, wi import { MIN_FLASHBOTS_PRIORITY_FEE, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; import { diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 9441ba77056..029407d9310 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -2,8 +2,7 @@ import { AnimatedChainImage } from '@/__swaps__/screens/Swap/components/Animated import { ReviewGasButton } from '@/__swaps__/screens/Swap/components/GasButton'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { useNativeAssetForChain } from '@/__swaps__/screens/Swap/hooks/useNativeAssetForChain'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; -import { chainNameFromChainId } from '@/__swaps__/utils/chains'; +import { ChainNameDisplay, ChainId } from '@/networks/types'; import { useEstimatedTime } from '@/__swaps__/utils/meteorology'; import { convertRawAmountToBalance, @@ -364,10 +363,10 @@ export function ReviewPanel() { }); const openGasExplainer = useCallback(async () => { - const nativeAsset = await getNativeAssetForNetwork(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet); + const nativeAsset = await getNativeAssetForNetwork({ chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet }); navigate(Routes.EXPLAIN_SHEET, { - network: chainNameFromChainId(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet), + chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet, type: 'gas', nativeAsset, }); diff --git a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx index e206a0d0a85..7e26e38f4b8 100644 --- a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx +++ b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx @@ -10,7 +10,7 @@ import { FallbackIcon as CoinIconTextFallback, isETH } from '@/utils'; import { FastFallbackCoinIconImage } from '@/components/asset-list/RecyclerAssetList2/FastComponents/FastFallbackCoinIconImage'; import Animated from 'react-native-reanimated'; import FastImage, { Source } from 'react-native-fast-image'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; // TODO: Delete this and replace with RainbowCoinIcon // ⚠️ When replacing this component with RainbowCoinIcon, make sure diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index 74ffb1cc923..b3546fef19c 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -15,7 +15,7 @@ import { TokenList } from '@/__swaps__/screens/Swap/components/TokenList/TokenLi import { BASE_INPUT_WIDTH, INPUT_INNER_WIDTH, INPUT_PADDING, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { IS_ANDROID, IS_IOS } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; @@ -59,15 +59,13 @@ function SwapOutputAmount() { const { inputAsset, outputAsset } = useSwapsStore.getState(); const inputTokenSymbol = inputAsset?.symbol; const outputTokenSymbol = outputAsset?.symbol; - const inputNetwork = ethereumUtils.getNetworkFromChainId(inputAsset?.chainId ?? ChainId.mainnet); - const outputNetwork = ethereumUtils.getNetworkFromChainId(outputAsset?.chainId ?? ChainId.mainnet); const isCrosschainSwap = inputAsset?.chainId !== outputAsset?.chainId; const isBridgeSwap = inputTokenSymbol === outputTokenSymbol; navigate(Routes.EXPLAIN_SHEET, { inputToken: inputTokenSymbol, - fromNetwork: inputNetwork, - toNetwork: outputNetwork, + fromChainId: inputAsset?.chainId ?? ChainId.mainnet, + toChainId: outputAsset?.chainId ?? ChainId.mainnet, isCrosschainSwap, isBridgeSwap, outputToken: outputTokenSymbol, diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index 5a4ade623e2..00c53e26585 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -6,7 +6,7 @@ import { Text as RNText, StyleSheet } from 'react-native'; import Animated, { useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId, ChainName, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; import { opacity } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; @@ -78,7 +78,7 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: return { actionKey: `${chainId}`, - actionTitle: displayName, + actionTitle: displayName as string, icon: { iconType: 'ASSET', iconValue: `${networkName}Badge${chainId === ChainId.mainnet ? '' : 'NoShadow'}`, @@ -89,7 +89,7 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: if (!output) { supportedChains.unshift({ actionKey: 'all', - actionTitle: i18n.t(i18n.l.exchange.all_networks) as ChainName, + actionTitle: i18n.t(i18n.l.exchange.all_networks), icon: { iconType: 'icon', iconValue: '􀆪', diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index bb4d3b75c80..fd78a631b8c 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -2,7 +2,7 @@ import { COIN_ROW_WITH_PADDING_HEIGHT, CoinRow } from '@/__swaps__/screens/Swap/ import { ListEmpty } from '@/__swaps__/screens/Swap/components/TokenList/ListEmpty'; import { AssetToBuySectionId, useSearchCurrencyLists } from '@/__swaps__/screens/Swap/hooks/useSearchCurrencyLists'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; diff --git a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts index b07a99dfdd1..2c2c791ad81 100644 --- a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts @@ -23,7 +23,7 @@ import { safeAreaInsetValues } from '@/utils'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; import { DerivedValue, SharedValue, interpolate, useAnimatedStyle, useDerivedValue, withSpring, withTiming } from 'react-native-reanimated'; import { NavigationSteps } from './useSwapNavigation'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; const INSET_BOTTOM = IS_ANDROID ? getSoftMenuBarHeight() - 24 : safeAreaInsetValues.bottom + 16; diff --git a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts index b6cfa892e40..13aa6388d50 100644 --- a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; export type EIP1159GasSettings = { diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index ab3729c448c..9f8af50f901 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { add, convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/__swaps__/utils/numbers'; import { useNativeAsset } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index 1f03494ed2c..95c886d31d0 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SharedValue, runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { ParsedAddressAsset } from '@/entities'; @@ -9,11 +9,11 @@ import { ethereumUtils } from '@/utils'; export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? ChainId.mainnet); - const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset(chainId.value)); + const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset({ chainId: chainId.value })); const getNativeAssetForNetwork = useCallback( (chainId: ChainId) => { - const asset = ethereumUtils.getNetworkNativeAsset(chainId); + const asset = ethereumUtils.getNetworkNativeAsset({ chainId }); nativeAsset.value = asset; }, [nativeAsset] diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index bb6a5c2ff3b..a1ba4437ff2 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -1,6 +1,6 @@ import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search/search'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset, TokenSearchAssetKey, TokenSearchThreshold } from '@/__swaps__/types/search'; import { addHexPrefix } from '@/__swaps__/utils/hex'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; diff --git a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts index 3cf7f91cd48..40f066fec79 100644 --- a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { getCachedGasSuggestions, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts index 9149ee472bb..bffed48cf60 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts @@ -2,7 +2,7 @@ import { CrosschainQuote, Quote, QuoteError, SwapType } from '@rainbow-me/swaps' import { useQuery } from '@tanstack/react-query'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { estimateUnlockAndCrosschainSwap } from '@/raps/unlockAndCrosschainSwap'; import { estimateUnlockAndSwap } from '@/raps/unlockAndSwap'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey } from '@/react-query'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 6aa89017e0a..1953277fb87 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,7 +1,7 @@ import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { valueBasedDecimalFormatter } from '@/__swaps__/utils/decimalFormatter'; import { getInputValuesForSliderPositionWorklet, updateInputValuesAfterFlip } from '@/__swaps__/utils/flipAssets'; diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 474f44943a7..090ec7a11a7 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -10,7 +10,7 @@ import { toScaledIntegerWorklet, } from '@/__swaps__/safe-math/SafeMath'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { add } from '@/__swaps__/utils/numbers'; import { ParsedAddressAsset } from '@/entities'; import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset'; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 5ae640688a2..917e3ee5fa2 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -24,7 +24,7 @@ import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextSty import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; import { getDefaultSlippageWorklet, isUnwrapEthWorklet, isWrapEthWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; diff --git a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts index 9e2e29c05cf..f8c829cc06b 100644 --- a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts +++ b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts @@ -1,5 +1,5 @@ import { ParsedAssetsDict, ParsedAssetsDictByChain, ParsedUserAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { deriveAddressAndChainWithUniqueId } from '@/__swaps__/utils/address'; import { add } from '@/__swaps__/utils/numbers'; diff --git a/src/__swaps__/screens/Swap/resources/assets/assets.ts b/src/__swaps__/screens/Swap/resources/assets/assets.ts index 070c11577ee..85d800e5be5 100644 --- a/src/__swaps__/screens/Swap/resources/assets/assets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/assets.ts @@ -4,7 +4,7 @@ import { requestMetadata } from '@/graphql'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { AddressOrEth, AssetMetadata, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { chunkArray, createAssetQuery, parseAssetMetadata } from '@/__swaps__/utils/assets'; import { RainbowError, logger } from '@/logger'; export const ASSETS_TIMEOUT_DURATION = 10000; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index d6d689682e2..4b14f675c49 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -9,7 +9,7 @@ import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; import { SupportedCurrencyKey, SUPPORTED_CHAIN_IDS } from '@/references'; import { ParsedAssetsDictByChain, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { parseUserAsset } from '@/__swaps__/utils/assets'; import { greaterThan } from '@/__swaps__/utils/numbers'; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts index cb5a12e1e45..b2b130aea98 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { ParsedAssetsDictByChain, ParsedUserAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { RainbowError, logger } from '@/logger'; diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts index f3b0a9ae468..269f44441b9 100644 --- a/src/__swaps__/screens/Swap/resources/search/discovery.ts +++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts index 9ac2e5b678e..8f29f7eb1d9 100644 --- a/src/__swaps__/screens/Swap/resources/search/search.ts +++ b/src/__swaps__/screens/Swap/resources/search/search.ts @@ -1,5 +1,5 @@ /* eslint-disable no-nested-ternary */ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; diff --git a/src/__swaps__/screens/Swap/resources/search/utils.ts b/src/__swaps__/screens/Swap/resources/search/utils.ts index 0ed74aeb80e..4c649d04cae 100644 --- a/src/__swaps__/screens/Swap/resources/search/utils.ts +++ b/src/__swaps__/screens/Swap/resources/search/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ARBITRUM_ETH_ADDRESS, diff --git a/src/__swaps__/types/assets.ts b/src/__swaps__/types/assets.ts index 05f78261ff1..73946574888 100644 --- a/src/__swaps__/types/assets.ts +++ b/src/__swaps__/types/assets.ts @@ -1,7 +1,7 @@ import type { Address } from 'viem'; import { ETH_ADDRESS } from '@/references'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ResponseByTheme } from '../utils/swaps'; diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts deleted file mode 100644 index ff59ca43b05..00000000000 --- a/src/__swaps__/types/chains.ts +++ /dev/null @@ -1,215 +0,0 @@ -import * as chain from 'viem/chains'; -import type { Chain } from 'viem/chains'; - -const HARDHAT_CHAIN_ID = 1337; -const HARDHAT_OP_CHAIN_ID = 1338; - -export const chainHardhat: Chain = { - id: HARDHAT_CHAIN_ID, - name: 'Hardhat', - nativeCurrency: { - decimals: 18, - name: 'Hardhat', - symbol: 'ETH', - }, - rpcUrls: { - public: { http: ['http://127.0.0.1:8545'] }, - default: { http: ['http://127.0.0.1:8545'] }, - }, - testnet: true, -}; - -export const chainHardhatOptimism: Chain = { - id: HARDHAT_OP_CHAIN_ID, - name: 'Hardhat OP', - nativeCurrency: { - decimals: 18, - name: 'Hardhat OP', - symbol: 'ETH', - }, - rpcUrls: { - public: { http: ['http://127.0.0.1:8545'] }, - default: { http: ['http://127.0.0.1:8545'] }, - }, - testnet: true, -}; - -export enum ChainName { - arbitrum = 'arbitrum', - arbitrumNova = 'arbitrum-nova', - arbitrumSepolia = 'arbitrum-sepolia', - avalanche = 'avalanche', - avalancheFuji = 'avalanche-fuji', - base = 'base', - blast = 'blast', - blastSepolia = 'blast-sepolia', - bsc = 'bsc', - celo = 'celo', - degen = 'degen', - gnosis = 'gnosis', - goerli = 'goerli', - linea = 'linea', - manta = 'manta', - optimism = 'optimism', - polygon = 'polygon', - polygonZkEvm = 'polygon-zkevm', - rari = 'rari', - scroll = 'scroll', - zora = 'zora', - mainnet = 'mainnet', - holesky = 'holesky', - hardhat = 'hardhat', - hardhatOptimism = 'hardhat-optimism', - sepolia = 'sepolia', - optimismSepolia = 'optimism-sepolia', - bscTestnet = 'bsc-testnet', - polygonMumbai = 'polygon-mumbai', - baseSepolia = 'base-sepolia', - zoraSepolia = 'zora-sepolia', - polygonAmoy = 'polygon-amoy', -} - -export enum ChainId { - arbitrum = chain.arbitrum.id, - arbitrumNova = chain.arbitrumNova.id, - arbitrumSepolia = chain.arbitrumSepolia.id, - avalanche = chain.avalanche.id, - avalancheFuji = chain.avalancheFuji.id, - base = chain.base.id, - baseSepolia = chain.baseSepolia.id, - blast = chain.blast.id, - blastSepolia = chain.blastSepolia.id, - bsc = chain.bsc.id, - bscTestnet = chain.bscTestnet.id, - celo = chain.celo.id, - degen = chain.degen.id, - gnosis = chain.gnosis.id, - goerli = chain.goerli.id, - hardhat = HARDHAT_CHAIN_ID, - hardhatOptimism = chainHardhatOptimism.id, - holesky = chain.holesky.id, - linea = chain.linea.id, - mainnet = chain.mainnet.id, - manta = chain.manta.id, - optimism = chain.optimism.id, - optimismSepolia = chain.optimismSepolia.id, - polygon = chain.polygon.id, - polygonAmoy = chain.polygonAmoy.id, - polygonMumbai = chain.polygonMumbai.id, - polygonZkEvm = chain.polygonZkEvm.id, - rari = 1380012617, - scroll = chain.scroll.id, - sepolia = chain.sepolia.id, - zora = chain.zora.id, - zoraSepolia = chain.zoraSepolia.id, -} - -export const chainNameToIdMapping: { - [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; -} = { - ['ethereum']: ChainId.mainnet, - [ChainName.arbitrum]: ChainId.arbitrum, - [ChainName.arbitrumNova]: ChainId.arbitrumNova, - [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, - [ChainName.avalanche]: ChainId.avalanche, - [ChainName.avalancheFuji]: ChainId.avalancheFuji, - [ChainName.base]: ChainId.base, - [ChainName.bsc]: ChainId.bsc, - [ChainName.celo]: ChainId.celo, - [ChainName.degen]: ChainId.degen, - [ChainName.gnosis]: ChainId.gnosis, - [ChainName.goerli]: chain.goerli.id, - [ChainName.linea]: ChainId.linea, - [ChainName.manta]: ChainId.manta, - [ChainName.optimism]: ChainId.optimism, - [ChainName.polygon]: ChainId.polygon, - [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, - [ChainName.rari]: ChainId.rari, - [ChainName.scroll]: ChainId.scroll, - [ChainName.zora]: ChainId.zora, - [ChainName.mainnet]: ChainId.mainnet, - [ChainName.holesky]: ChainId.holesky, - [ChainName.hardhat]: ChainId.hardhat, - [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, - ['ethereum-sepolia']: ChainId.sepolia, - [ChainName.sepolia]: ChainId.sepolia, - [ChainName.optimismSepolia]: ChainId.optimismSepolia, - [ChainName.bscTestnet]: ChainId.bscTestnet, - [ChainName.polygonMumbai]: ChainId.polygonMumbai, - [ChainName.baseSepolia]: ChainId.baseSepolia, - [ChainName.zoraSepolia]: ChainId.zoraSepolia, - [ChainName.blast]: ChainId.blast, - [ChainName.blastSepolia]: ChainId.blastSepolia, - [ChainName.polygonAmoy]: ChainId.polygonAmoy, -}; - -export const chainIdToNameMapping: { - [key in ChainId]: ChainName; -} = { - [ChainId.arbitrum]: ChainName.arbitrum, - [ChainId.arbitrumNova]: ChainName.arbitrumNova, - [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, - [ChainId.avalanche]: ChainName.avalanche, - [ChainId.avalancheFuji]: ChainName.avalancheFuji, - [ChainId.base]: ChainName.base, - [ChainId.blast]: ChainName.blast, - [ChainId.blastSepolia]: ChainName.blastSepolia, - [ChainId.bsc]: ChainName.bsc, - [ChainId.celo]: ChainName.celo, - [ChainId.degen]: ChainName.degen, - [ChainId.gnosis]: ChainName.gnosis, - [chain.goerli.id]: ChainName.goerli, - [ChainId.linea]: ChainName.linea, - [ChainId.manta]: ChainName.manta, - [ChainId.optimism]: ChainName.optimism, - [ChainId.polygon]: ChainName.polygon, - [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, - [ChainId.rari]: ChainName.rari, - [ChainId.scroll]: ChainName.scroll, - [ChainId.zora]: ChainName.zora, - [ChainId.mainnet]: ChainName.mainnet, - [ChainId.holesky]: ChainName.holesky, - [ChainId.hardhat]: ChainName.hardhat, - [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, - [ChainId.sepolia]: ChainName.sepolia, - [ChainId.optimismSepolia]: ChainName.optimismSepolia, - [ChainId.bscTestnet]: ChainName.bscTestnet, - [ChainId.polygonMumbai]: ChainName.polygonMumbai, - [ChainId.baseSepolia]: ChainName.baseSepolia, - [ChainId.zoraSepolia]: ChainName.zoraSepolia, - [ChainId.polygonAmoy]: ChainName.polygonAmoy, -}; - -export const ChainNameDisplay = { - [ChainId.arbitrum]: 'Arbitrum', - [ChainId.arbitrumNova]: chain.arbitrumNova.name, - [ChainId.avalanche]: 'Avalanche', - [ChainId.avalancheFuji]: 'Avalanche Fuji', - [ChainId.base]: 'Base', - [ChainId.blast]: 'Blast', - [ChainId.blastSepolia]: 'Blast Sepolia', - [ChainId.bsc]: 'BSC', - [ChainId.celo]: chain.celo.name, - [ChainId.degen]: 'Degen Chain', - [ChainId.linea]: 'Linea', - [ChainId.manta]: 'Manta', - [ChainId.optimism]: 'Optimism', - [ChainId.polygon]: 'Polygon', - [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, - [ChainId.rari]: 'RARI Chain', - [ChainId.scroll]: chain.scroll.name, - [ChainId.zora]: 'Zora', - [ChainId.mainnet]: 'Ethereum', - [chain.goerli.id]: 'Goerli', - [ChainId.hardhat]: 'Hardhat', - [ChainId.hardhatOptimism]: chainHardhatOptimism.name, - [ChainId.sepolia]: chain.sepolia.name, - [ChainId.holesky]: chain.holesky.name, - [ChainId.optimismSepolia]: chain.optimismSepolia.name, - [ChainId.bscTestnet]: 'BSC Testnet', - [ChainId.polygonMumbai]: chain.polygonMumbai.name, - [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, - [ChainId.baseSepolia]: chain.baseSepolia.name, - [ChainId.zoraSepolia]: 'Zora Sepolia', - [ChainId.polygonAmoy]: 'Polygon Amoy', -} as const; diff --git a/src/__swaps__/types/refraction.ts b/src/__swaps__/types/refraction.ts index e18be684eb0..9afc8ce9cf3 100644 --- a/src/__swaps__/types/refraction.ts +++ b/src/__swaps__/types/refraction.ts @@ -1,5 +1,5 @@ import { ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { PaginatedTransactionsApiResponse } from '@/resources/transactions/types'; /** diff --git a/src/__swaps__/types/search.ts b/src/__swaps__/types/search.ts index 5acf427de4e..9e0aff40e3d 100644 --- a/src/__swaps__/types/search.ts +++ b/src/__swaps__/types/search.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, AssetType, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AssetToBuySectionId } from '../screens/Swap/hooks/useSearchCurrencyLists'; export type TokenSearchAssetKey = keyof ParsedAsset; diff --git a/src/__swaps__/utils/address.ts b/src/__swaps__/utils/address.ts index 39fd20091c1..1b56b2ba57e 100644 --- a/src/__swaps__/utils/address.ts +++ b/src/__swaps__/utils/address.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function truncateAddress(address?: AddressOrEth) { if (!address) return ''; diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts index 8b888c36fc2..420aee6a9b8 100644 --- a/src/__swaps__/utils/assets.ts +++ b/src/__swaps__/utils/assets.ts @@ -1,5 +1,4 @@ import { AddressZero } from '@ethersproject/constants'; -import isValidDomain from 'is-valid-domain'; import { ETH_ADDRESS, SupportedCurrencyKey } from '@/references'; import { @@ -13,7 +12,7 @@ import { ZerionAsset, ZerionAssetPrice, } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import * as i18n from '@/languages'; import { SearchAsset } from '@/__swaps__/types/search'; diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts index 1b9ca2bd496..63f821fc1b7 100644 --- a/src/__swaps__/utils/chains.ts +++ b/src/__swaps__/utils/chains.ts @@ -1,7 +1,7 @@ import { celo, fantom, harmonyOne, moonbeam } from 'viem/chains'; import { NATIVE_ASSETS_PER_CHAIN } from '@/references'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/__swaps__/types/chains'; +import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/networks/types'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/utils/gasUtils.ts b/src/__swaps__/utils/gasUtils.ts index 7e4ff881a7b..5039a93eef1 100644 --- a/src/__swaps__/utils/gasUtils.ts +++ b/src/__swaps__/utils/gasUtils.ts @@ -12,7 +12,7 @@ import { OVM_GAS_PRICE_ORACLE, gasUnits, supportedNativeCurrencies, optimismGasO import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/__swaps__/utils/meteorology'; import { ParsedAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { BlocksToConfirmation, GasFeeLegacyParams, GasFeeParam, GasFeeParams, GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index de0740f4069..c12164ef6d4 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -1,12 +1,11 @@ import { useQuery } from '@tanstack/react-query'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { rainbowMeteorologyGetData } from '@/handlers/gasFees'; import { abs, lessThan, subtract } from '@/helpers/utilities'; import { gweiToWei } from '@/parsers'; import { QueryConfig, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { useCallback } from 'react'; import { MIN_FLASHBOTS_PRIORITY_FEE } from '../screens/Swap/constants'; import { GasSettings } from '../screens/Swap/hooks/useCustomGas'; @@ -72,8 +71,7 @@ type MeteorologyQueryKey = ReturnType; // Query Function async function meteorologyQueryFunction({ queryKey: [{ chainId }] }: QueryFunctionArgs) { - const network = getNetworkFromChainId(chainId); - const parsedResponse = await rainbowMeteorologyGetData(network); + const parsedResponse = await rainbowMeteorologyGetData(chainId); const meteorologyData = parsedResponse.data as MeteorologyResponse | MeteorologyLegacyResponse; return meteorologyData; } diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 33071792138..10f55dae1ad 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -9,7 +9,7 @@ import { SLIDER_WIDTH, STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { chainNameFromChainId, chainNameFromChainIdWorklet } from '@/__swaps__/utils/chains'; import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; import { globalColors } from '@/design-system'; diff --git a/src/__swaps__/utils/userChains.ts b/src/__swaps__/utils/userChains.ts index 0a9a35369c1..cff0ccf7f6e 100644 --- a/src/__swaps__/utils/userChains.ts +++ b/src/__swaps__/utils/userChains.ts @@ -21,7 +21,7 @@ import { sepolia, } from 'viem/chains'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; export const chainIdMap: Record< ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.base | ChainId.bsc | ChainId.zora | ChainId.avalanche, diff --git a/src/analytics/event.ts b/src/analytics/event.ts index ebb6849433e..22c3c8c5e93 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -1,13 +1,12 @@ import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { UnlockableAppIconKey } from '@/appIcons/appIcons'; import { CardType } from '@/components/cards/GenericCard'; import { LearnCategory } from '@/components/cards/utils/types'; import { FiatProviderName } from '@/entities/f2c'; -import { Network } from '@/networks/types'; import { RapSwapActionParameters } from '@/raps/references'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; diff --git a/src/appIcons/appIcons.ts b/src/appIcons/appIcons.ts index 025ed695e05..407b688f21b 100644 --- a/src/appIcons/appIcons.ts +++ b/src/appIcons/appIcons.ts @@ -1,5 +1,4 @@ import { EthereumAddress } from '@/entities'; -import { Network } from '@/helpers'; import { ImageSourcePropType } from 'react-native'; import AppIconFiniliar from '@/assets/appIconFiniliar.png'; import AppIconGoldDoge from '@/assets/appIconGoldDoge.png'; @@ -15,6 +14,7 @@ import AppIconPoolboy from '@/assets/appIconPoolboy.png'; import AppIconAdworld from '@/assets/appIconAdworld.png'; import AppIconFarcaster from '@/assets/appIconFarcaster.png'; import { TokenGateCheckerNetwork } from '@/featuresToUnlock/tokenGatedUtils'; +import { Network } from '@/networks/types'; // optimism app icon unlocking NFTs const OPTIMISTIC_EXPLORER_NFT_ADDRESS: EthereumAddress = '0x81b30ff521D1fEB67EDE32db726D95714eb00637'; diff --git a/src/components/AddFundsInterstitial.js b/src/components/AddFundsInterstitial.js index 34312f9026a..23f430fd577 100644 --- a/src/components/AddFundsInterstitial.js +++ b/src/components/AddFundsInterstitial.js @@ -3,7 +3,6 @@ import lang from 'i18n-js'; import React, { Fragment, useCallback } from 'react'; import { Linking, View } from 'react-native'; import networkInfo from '../helpers/networkInfo'; -import networkTypes from '../helpers/networkTypes'; import showWalletErrorAlert from '../helpers/support'; import { useNavigation } from '../navigation/Navigation'; import { useTheme } from '../theme/ThemeContext'; @@ -20,6 +19,7 @@ import styled from '@/styled-thing'; import { padding, position } from '@/styles'; import ShadowStack from '@/react-native-shadow-stack'; import { useRoute } from '@react-navigation/native'; +import { Network } from '@/networks/types'; const ContainerWidth = 261; @@ -213,7 +213,7 @@ const AddFundsInterstitial = ({ network }) => { return ( - {network === networkTypes.mainnet ? ( + {network === Network.mainnet ? ( {ios ? lang.t('add_funds.to_get_started_ios') : lang.t('add_funds.to_get_started_android')} diff --git a/src/components/ChainLogo.js b/src/components/ChainLogo.js index ef03bb368c7..ae21dddd071 100644 --- a/src/components/ChainLogo.js +++ b/src/components/ChainLogo.js @@ -19,9 +19,9 @@ import BaseBadge from '../assets/badges/baseBadge.png'; import BaseBadgeDark from '../assets/badges/baseBadgeDark.png'; import BaseBadgeNoShadow from '../assets/badges/baseBadgeNoShadow.png'; import { Centered } from './layout'; -import networkTypes from '@/helpers/networkTypes'; import styled from '@/styled-thing'; import { position } from '@/styles'; +import { ChainId } from '@/networks/types'; const ChainIcon = styled(FastImage)({ height: ({ size }) => size, @@ -34,25 +34,25 @@ const Content = styled(Centered)(({ size }) => ({ overflow: 'visible', })); -export default function ChainLogo({ network, size = 44, withShadows = true, ...props }) { +export default function ChainLogo({ chainId, size = 44, withShadows = true, ...props }) { const { isDarkMode } = useTheme(); const source = useMemo(() => { let val = null; - if (network === networkTypes.arbitrum) { + if (chainId === ChainId.arbitrum) { val = withShadows ? (isDarkMode ? ArbitrumBadgeDark : ArbitrumBadge) : ArbitrumBadgeNoShadow; - } else if (network === networkTypes.optimism) { + } else if (chainId === ChainId.optimism) { val = withShadows ? (isDarkMode ? OptimismBadgeDark : OptimismBadge) : OptimismBadgeNoShadow; - } else if (network === networkTypes.polygon) { + } else if (chainId === ChainId.polygon) { val = withShadows ? (isDarkMode ? PolygonBadgeDark : PolygonBadge) : PolygonBadgeNoShadow; - } else if (network === networkTypes.bsc) { + } else if (chainId === ChainId.bsc) { val = withShadows ? (isDarkMode ? BscBadgeDark : BscBadge) : BscBadgeNoShadow; - } else if (network === networkTypes.zora) { + } else if (chainId === ChainId.zora) { val = withShadows ? (isDarkMode ? ZoraBadgeDark : ZoraBadge) : ZoraBadgeNoShadow; - } else if (network === networkTypes.base) { + } else if (chainId === ChainId.base) { val = withShadows ? (isDarkMode ? BaseBadgeDark : BaseBadge) : BaseBadgeNoShadow; } return val; - }, [isDarkMode, network, withShadows]); + }, [isDarkMode, chainId, withShadows]); if (!source) return null; diff --git a/src/components/ContactRowInfoButton.js b/src/components/ContactRowInfoButton.js index 60c58c64a07..28ce8a1f159 100644 --- a/src/components/ContactRowInfoButton.js +++ b/src/components/ContactRowInfoButton.js @@ -69,7 +69,7 @@ const ContactRowActions = { const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { @@ -82,7 +82,7 @@ const buildBlockExplorerAction = chainId => { }; }; -const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { +const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { const { setClipboard } = useClipboard(); const handleCopyAddress = useCallback( address => { @@ -93,7 +93,7 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId }))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( { @@ -107,14 +107,14 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { handleCopyAddress(item?.address); } if (idx === 1) { - ethereumUtils.openAddressInBlockExplorer(item?.address, network); + ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); } } ); - }, [item?.network, item?.name, item?.address, handleCopyAddress, network]); + }, [item?.name, item?.address, handleCopyAddress, chainId]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); + const blockExplorerAction = buildBlockExplorerAction(chainId); return { menuItems: [ blockExplorerAction, @@ -125,17 +125,17 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { ], menuTitle: `${item?.name}`, }; - }, [item]); + }, [chainId, item?.address, item?.name]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContactRowActionsEnum.copyAddress) { handleCopyAddress(item?.address); } else if (actionKey === ContactRowActionsEnum.blockExplorer) { - ethereumUtils.openAddressInBlockExplorer(item?.address); + ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); } }, - [item, handleCopyAddress] + [handleCopyAddress, item?.address, chainId] ); const Container = children ? Centered : InfoButton; diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index 34a8421d1e8..55de3b4bf14 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -30,7 +30,6 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBalancesAndNames } from '@/hooks'; import { useSyncSharedValue } from '@/hooks/reanimated/useSyncSharedValue'; -import { Network } from '@/networks/types'; import { useBrowserStore } from '@/state/browser/browserStore'; import { colors } from '@/styles'; import { deviceUtils, watchingAlert } from '@/utils'; @@ -41,7 +40,7 @@ import { TOP_INSET } from '../Dimensions'; import { formatUrl } from '../utils'; import { RouteProp, useRoute } from '@react-navigation/native'; import { toHex } from 'viem'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import * as i18n from '@/languages'; import { useDispatch } from 'react-redux'; import store from '@/redux/store'; @@ -63,7 +62,7 @@ import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const PAGES = { HOME: 'home', @@ -102,7 +101,7 @@ export const ControlPanel = () => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null, [hostSessions] @@ -112,7 +111,7 @@ export const ControlPanel = () => { const [currentAddress, setCurrentAddress] = useState( currentSession?.address || hostSessions?.activeSessionAddress || accountAddress ); - const [currentNetwork, setCurrentNetwork] = useState(currentSession?.network || Network.mainnet); + const [currentChainId, setCurrentChainId] = useState(currentSession?.chainId || ChainId.mainnet); // listens to the current active tab and sets the account useEffect(() => { @@ -128,8 +127,8 @@ export const ControlPanel = () => { setCurrentAddress(accountAddress); } - if (currentSession?.network) { - setCurrentNetwork(currentSession?.network); + if (currentSession?.chainId) { + setCurrentChainId(currentSession?.chainId); } } }, [accountAddress, activeTabHost, currentSession]); @@ -184,28 +183,27 @@ export const ControlPanel = () => { const { testnetsEnabled } = store.getState().settings; const allNetworkItems = useMemo(() => { - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ networkType, features: { walletconnect } }) => walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => { return { IconComponent: , label: network.name, secondaryLabel: i18n.t( - isConnected && network.value === currentNetwork + isConnected && network.id === currentChainId ? i18n.l.dapp_browser.control_panel.connected : i18n.l.dapp_browser.control_panel.not_connected ), - uniqueId: network.value, - selected: network.value === currentNetwork, - chainId: network.id, + uniqueId: String(network.id), + selected: network.id === currentChainId, }; }); - }, [currentNetwork, isConnected, testnetsEnabled]); + }, [currentChainId, isConnected, testnetsEnabled]); const selectedWallet = allWalletItems.find(item => item.selected); const animatedAccentColor = useSharedValue(selectedWallet?.color || globalColors.blue10); - const selectedNetworkId = useSharedValue(currentNetwork?.toString() || RainbowNetworks[0].value); + const selectedNetworkId = useSharedValue(currentChainId?.toString() || RainbowNetworkObjects[0].value); const selectedWalletId = useSharedValue(selectedWallet?.uniqueId || accountAddress); const handleSwitchWallet = useCallback( @@ -213,21 +211,21 @@ export const ControlPanel = () => { const address = selectedItemId; updateActiveSession({ host: activeTabHost, address: address as `0x${string}` }); if (isConnected) { - updateActiveSessionNetwork({ host: activeTabHost, network: currentNetwork }); + updateActiveSessionNetwork({ host: activeTabHost, chainId: currentChainId }); // need to emit these events to the dapp activeTabRef.current?.injectJavaScript(`window.ethereum.emit('accountsChanged', ['${address}']); true;`); } setCurrentAddress(address); }, - [activeTabHost, activeTabRef, currentNetwork, isConnected, updateActiveSession, updateActiveSessionNetwork] + [activeTabHost, activeTabRef, currentChainId, isConnected, updateActiveSession, updateActiveSessionNetwork] ); const handleNetworkSwitch = useCallback( (selectedItemId: string) => { - updateActiveSessionNetwork({ host: activeTabHost, network: selectedItemId as Network }); - const chainId = RainbowNetworks.find(({ value }) => value === (selectedItemId as Network))?.id as number; + updateActiveSessionNetwork({ host: activeTabHost, chainId: Number(selectedItemId) as ChainId }); + const chainId = RainbowNetworkObjects.find(({ id }) => id === (Number(selectedItemId) as ChainId))?.id as number; activeTabRef.current?.injectJavaScript(`window.ethereum.emit('chainChanged', ${toHex(chainId)}); true;`); - setCurrentNetwork(selectedItemId as Network); + setCurrentChainId(Number(selectedItemId) as ChainId); }, [activeTabHost, activeTabRef, updateActiveSessionNetwork] ); @@ -235,23 +233,21 @@ export const ControlPanel = () => { const handleConnect = useCallback(async () => { const activeTabHost = getDappHost(activeTabUrl || ''); const address = selectedWalletId.value; - const network = selectedNetworkId.value as Network; + const chainId = Number(selectedNetworkId.value); addSession({ host: activeTabHost || '', address: address as `0x${string}`, - network, + chainId, url: activeTabUrl || '', }); - const chainId = ethereumUtils.getChainIdFromNetwork(network); - activeTabRef.current?.injectJavaScript( `window.ethereum.emit('accountsChanged', ['${address}']); window.ethereum.emit('connect', { address: '${address}', chainId: '${toHex(chainId)}' }); true;` ); setIsConnected(true); setCurrentAddress(address); - setCurrentNetwork(network); + setCurrentChainId(chainId); }, [activeTabUrl, selectedWalletId.value, selectedNetworkId.value, addSession, activeTabRef]); const handleDisconnect = useCallback(() => { @@ -273,7 +269,7 @@ export const ControlPanel = () => { ; goToPage: (pageId: string) => void; - selectedNetwork: string; + selectedChainId: ChainId; selectedWallet: ControlPanelMenuItemProps | undefined; allNetworkItems: ControlPanelMenuItemProps[]; isConnected: boolean; @@ -393,8 +389,8 @@ const HomePanel = ({ const walletLabel = selectedWallet?.label || ''; const walletSecondaryLabel = selectedWallet?.secondaryLabel || ''; - const network = allNetworkItems.find(item => item.uniqueId === selectedNetwork); - const networkIcon = ; + const network = allNetworkItems.find(item => item.uniqueId === String(selectedChainId)); + const networkIcon = ; const networkLabel = network?.label || ''; const networkSecondaryLabel = network?.secondaryLabel || ''; @@ -420,7 +416,7 @@ const HomePanel = ({ /> ); - }, [allNetworkItems, animatedAccentColor, goToPage, selectedNetwork, selectedWallet]); + }, [allNetworkItems, animatedAccentColor, goToPage, selectedChainId, selectedWallet]); const runWalletChecksBeforeSwapOrBridge = useCallback(async () => { if (!selectedWallet || !wallets) return false; @@ -460,7 +456,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -488,7 +484,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -737,7 +733,6 @@ interface ControlPanelMenuItemProps { label: string; labelColor?: TextColor; imageUrl?: string; - chainId?: ChainId; color?: string; onPress?: () => void; secondaryLabel?: string; diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index 3554f3ce19f..d0ce9356952 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -4,20 +4,18 @@ import * as lang from '@/languages'; import { Provider } from '@ethersproject/providers'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { getProviderForNetwork } from '@/handlers/web3'; -import ethereumUtils, { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { RainbowNetworkObjects, RainbowSupportedChainIds } from '@/networks'; +import { getProvider } from '@/handlers/web3'; import { UserRejectedRequestError } from 'viem'; import { convertHexToString } from '@/helpers/utilities'; import { logger } from '@/logger'; import { ActiveSession } from '@rainbow-me/provider/dist/references/appSession'; -import { Network } from '@/helpers'; import { handleDappBrowserConnectionPrompt, handleDappBrowserRequest } from '@/utils/requestNavigationHandlers'; import { Tab } from '@rainbow-me/provider/dist/references/messengers'; import { getDappMetadata } from '@/resources/metadata/dapp'; import { useAppSessionsStore } from '@/state/appSessions'; import { BigNumber } from '@ethersproject/bignumber'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export type ProviderRequestPayload = RequestArguments & { id: number; @@ -123,56 +121,51 @@ const messengerProviderRequestFn = async (messenger: Messenger, request: Provide hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; - // Wait for response from the popup. - let response: unknown | null; - if (request.method === 'eth_requestAccounts') { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - chainId is not defined in the type const chainId = request.params?.[0]?.chainId ? BigNumber.from(request.params?.[0]?.chainId).toNumber() : undefined; - response = await handleDappBrowserConnectionPrompt({ + const response = await handleDappBrowserConnectionPrompt({ dappName: dappData?.appName || request.meta?.sender.title || '', dappUrl: request.meta?.sender.url || '', chainId, address: hostSessions?.activeSessionAddress || undefined, }); + if (!response || response instanceof Error) { + throw new UserRejectedRequestError(Error('User rejected the request.')); + } + useAppSessionsStore.getState().addSession({ host: getDappHost(request.meta?.sender.url) || '', - // @ts-ignore - address: response.address, - // @ts-ignore - network: getNetworkFromChainId(response.chainId), - // @ts-ignore + address: response?.address, + chainId: response.chainId, url: request.meta?.sender.url || '', }); + return response; } else { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); - response = await handleDappBrowserRequest({ + const response = await handleDappBrowserRequest({ dappName: dappData?.appName || request.meta?.sender.title || request.meta?.sender.url || '', imageUrl: dappData?.appLogo || '', address: appSession?.address || '', dappUrl: request.meta?.sender.url || '', payload: request, - chainId: appSession?.network ? ethereumUtils.getChainIdFromNetwork(appSession.network) : ChainId.mainnet, + chainId: appSession?.chainId ? appSession?.chainId : ChainId.mainnet, }); + return response as object; } - - if (!response) { - throw new UserRejectedRequestError(Error('User rejected the request.')); - } - return response; }; const isSupportedChainId = (chainId: number | string) => { const numericChainId = BigNumber.from(chainId).toNumber(); - return !!RainbowNetworks.find(network => Number(network.id) === numericChainId); + return !!RainbowSupportedChainIds.find(chainId => chainId === numericChainId); }; const getActiveSession = ({ host }: { host: string }): ActiveSession => { const hostSessions = useAppSessionsStore.getState().getActiveSession({ host }); @@ -180,26 +173,19 @@ const getActiveSession = ({ host }: { host: string }): ActiveSession => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; if (!appSession) return null; return { address: appSession?.address || '', - chainId: getChainIdByNetwork(appSession.network), + chainId: appSession.chainId, }; // return null; }; -const getChainIdByNetwork = (network: Network) => getNetworkObj(network).id; - -const getChain = (chainId: number) => RainbowNetworks.find(network => Number(network.id) === chainId); - -const getProvider = ({ chainId }: { chainId?: number | undefined }) => { - const network = getNetworkFromChainId(chainId || 1); - return getProviderForNetwork(network) as unknown as Provider; -}; +const getChain = (chainId: number) => RainbowNetworkObjects.find(network => Number(network.id) === chainId); const checkRateLimitFn = async (host: string) => { // try { @@ -284,7 +270,7 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }): { chainAlreadyAdded: boolean } => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { // TODO - Open add / switch ethereum chain @@ -341,14 +327,14 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }) => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); const supportedChainId = supportedChains.includes(numericChainId); if (supportedChainId) { const host = getDappHost(callbackOptions?.sender.url) || ''; const activeSession = getActiveSession({ host }); if (activeSession) { - useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, network: getNetworkFromChainId(Number(numericChainId)) }); + useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, chainId: Number(numericChainId) }); messenger.send(`chainChanged:${host}`, Number(numericChainId)); } console.warn('PROVIDER TODO: TODO SEND NOTIFICATION'); @@ -364,7 +350,7 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: checkRateLimit, onSwitchEthereumChainNotSupported, onSwitchEthereumChainSupported, - getProvider, + getProvider: chainId => getProvider({ chainId: chainId as number }) as unknown as Provider, getActiveSession, getChain, }); diff --git a/src/components/L2Disclaimer.js b/src/components/L2Disclaimer.js index 162084f1dec..aa5361e6970 100644 --- a/src/components/L2Disclaimer.js +++ b/src/components/L2Disclaimer.js @@ -7,14 +7,13 @@ import { Column, Row } from './layout'; import { Text } from './text'; import { padding, position } from '@/styles'; import { darkModeThemeColors } from '@/styles/colors'; -import { getNetworkObj } from '@/networks'; import * as lang from '@/languages'; import { isL2Chain } from '@/handlers/web3'; import { EthCoinIcon } from './coin-icon/EthCoinIcon'; -import { ethereumUtils } from '@/utils'; +import { chainIdToNameMapping } from '@/networks/types'; const L2Disclaimer = ({ - network, + chainId, colors, hideDivider, isNft = false, @@ -37,7 +36,6 @@ const L2Disclaimer = ({ }, }; - const chainId = ethereumUtils.getChainIdFromNetwork(network); const isL2 = isL2Chain({ chainId }); return ( @@ -59,7 +57,7 @@ const L2Disclaimer = ({ ? customText : lang.t(lang.l.expanded_state.asset.l2_disclaimer, { symbol, - network: getNetworkObj(network).name, + network: chainIdToNameMapping[chainId], })} diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index b57e625fe12..da1f88d403d 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -2,7 +2,6 @@ import * as lang from '@/languages'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SectionList, StyleSheet, View } from 'react-native'; import sectionListGetItemLayout from 'react-native-section-list-get-item-layout'; -import networkTypes from '../../helpers/networkTypes'; import ActivityIndicator from '../ActivityIndicator'; import Spinner from '../Spinner'; import { ButtonPressAnimation } from '../animations'; @@ -14,6 +13,7 @@ import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { useSectionListScrollToTopContext } from '@/navigation/SectionListScrollToTopContext'; import { safeAreaInsetValues } from '@/utils'; +import { Network } from '@/networks/types'; const sx = StyleSheet.create({ sectionHeader: { @@ -106,7 +106,8 @@ const ActivityList = ({ if (!ref) return; setScrollToTopRef(ref); }; - if (network === networkTypes.mainnet) { + + if (network === Network.mainnet) { return ( remainingItemsLabel && } diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index adb20230da8..f6a4885e514 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -12,6 +12,8 @@ import Routes from '@/navigation/routesNames'; import { borders, colors, padding, shadow } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; +import { NativeCurrencyKey } from '@/entities'; +import { ChainId } from '@/networks/types'; interface CoinCheckButtonProps { isHidden: boolean; @@ -55,7 +57,7 @@ const formatPercentageString = (percentString?: string) => (percentString ? perc interface MemoizedBalanceCoinRowProps { uniqueId: string; - nativeCurrency: string; + nativeCurrency: NativeCurrencyKey; theme: any; navigate: any; nativeCurrencySymbol: string; @@ -65,7 +67,7 @@ interface MemoizedBalanceCoinRowProps { const MemoizedBalanceCoinRow = React.memo( ({ uniqueId, nativeCurrency, theme, navigate, nativeCurrencySymbol, isHidden, maybeCallback }: MemoizedBalanceCoinRowProps) => { - const item = useAccountAsset(uniqueId, nativeCurrency) as any; + const item = useAccountAsset(uniqueId, nativeCurrency); const handlePress = useCallback(() => { if (maybeCallback.current) { @@ -80,7 +82,7 @@ const MemoizedBalanceCoinRow = React.memo( } }, [navigate, item, maybeCallback]); - const percentChange = item?.native?.change; + const percentChange = item?.native?.change || undefined; const percentageChangeDisplay = formatPercentageString(percentChange); const isPositive = percentChange && percentageChangeDisplay.charAt(0) !== '-'; @@ -91,7 +93,7 @@ const MemoizedBalanceCoinRow = React.memo( const valueColor = nativeDisplay ? theme.colors.dark : theme.colors.blueGreyLight; - const chainId = ethereumUtils.getChainIdFromNetwork(item?.network); + const chainId = item?.chainId || ChainId.mainnet; return ( @@ -102,7 +104,7 @@ const MemoizedBalanceCoinRow = React.memo( size={40} icon={item?.icon_url} chainId={chainId} - symbol={item?.symbol} + symbol={item?.symbol || ''} theme={theme} colors={item?.colors} /> diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx index 40262984048..01092ef5462 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx @@ -18,7 +18,7 @@ import BlastBadge from '@/assets/badges/blastBadge.png'; import BlastBadgeDark from '@/assets/badges/blastBadgeDark.png'; import DegenBadge from '@/assets/badges/degenBadge.png'; import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; interface FastChainBadgeProps { chainId: ChainId; diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 15923fff565..0da7a18422e 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -13,7 +13,7 @@ import { colors, fonts, fontWithWidth, getFontSize } from '@/styles'; import { deviceUtils } from '@/utils'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const SafeRadialGradient = (IS_TESTING === 'true' ? View : RadialGradient) as typeof RadialGradient; diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index a23be055a48..26f131b8638 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -13,7 +13,6 @@ import { StyleSheet } from 'react-native'; import { LIGHT_SEPARATOR_COLOR, SEPARATOR_COLOR } from '@/__swaps__/screens/Swap/constants'; import { analyticsV2 } from '@/analytics'; import { convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; type LaunchFeaturedMintButtonProps = { @@ -32,8 +31,7 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); } }, [featuredMint]); diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index 3f063c6a23d..f1ce574bb24 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -13,7 +13,7 @@ import rowRenderer from './RowRenderer'; import { BaseCellType, CellTypes, RecyclerListViewRef } from './ViewTypes'; import getLayoutProvider from './getLayoutProvider'; import useLayoutItemAnimator from './useLayoutItemAnimator'; -import { UniqueAsset } from '@/entities'; +import { NativeCurrencyKey, UniqueAsset } from '@/entities'; import { useRecyclerListViewScrollToTopContext } from '@/navigation/RecyclerListViewScrollToTopContext'; import { useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks'; import { useNavigation } from '@/navigation'; @@ -29,7 +29,7 @@ const dataProvider = new DataProvider((r1, r2) => { export type ExtendedState = { theme: any; nativeCurrencySymbol: string; - nativeCurrency: string; + nativeCurrency: NativeCurrencyKey; navigate: any; isCoinListEdited: boolean; hiddenCoins: BooleanMap; diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index 2378b7c6f5d..9dd8a5ba71c 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -20,7 +20,7 @@ import { useAccountAccentColor } from '@/hooks/useAccountAccentColor'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const ProfileActionButtonsRowHeight = 80; @@ -191,7 +191,7 @@ function SwapButton() { android && delayNext(); - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, accountAddress); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: accountAddress }); navigate(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { diff --git a/src/components/cards/EthCard.tsx b/src/components/cards/EthCard.tsx index 0e11d03b334..693ff97b0f8 100644 --- a/src/components/cards/EthCard.tsx +++ b/src/components/cards/EthCard.tsx @@ -22,10 +22,9 @@ import * as i18n from '@/languages'; import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import assetTypes from '@/entities/assetTypes'; -import { Network } from '@/networks/types'; +import { Network, ChainId } from '@/networks/types'; import { getUniqueId } from '@/utils/ethereumUtils'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; export const ETH_CARD_HEIGHT = 284.3; @@ -45,6 +44,7 @@ export const EthCard = () => { ...externalEthAsset, address: ETH_ADDRESS, network: Network.mainnet, + chainId: ChainId.mainnet, uniqueId: getUniqueId(ETH_ADDRESS, ChainId.mainnet), }), [externalEthAsset] diff --git a/src/components/cards/FeaturedMintCard.tsx b/src/components/cards/FeaturedMintCard.tsx index 1b6e2e1cf12..b2738022f5c 100644 --- a/src/components/cards/FeaturedMintCard.tsx +++ b/src/components/cards/FeaturedMintCard.tsx @@ -27,7 +27,6 @@ import { Media } from '../Media'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; -import { ethereumUtils } from '@/utils'; const IMAGE_SIZE = 111; @@ -74,8 +73,7 @@ export function FeaturedMintCard() { mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); } }, [featuredMint]); diff --git a/src/components/cards/MintsCard/CollectionCell.tsx b/src/components/cards/MintsCard/CollectionCell.tsx index 01053b1a852..072057e5ce3 100644 --- a/src/components/cards/MintsCard/CollectionCell.tsx +++ b/src/components/cards/MintsCard/CollectionCell.tsx @@ -6,7 +6,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { useTheme } from '@/theme'; import { View } from 'react-native'; import { MintableCollection } from '@/graphql/__generated__/arc'; -import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { IS_IOS } from '@/env'; @@ -58,8 +58,7 @@ export function CollectionCell({ collection }: { collection: MintableCollection priceInEth: amount, }); - const network = ethereumUtils.getNetworkFromChainId(collection.chainId); - navigateToMintCollection(collection.contract, collection.mintStatus.price, network); + navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); }, [amount, collection.chainId, collection.contract, collection.contractAddress, collection.mintStatus.price]); return ( diff --git a/src/components/cards/NFTOffersCard/Offer.tsx b/src/components/cards/NFTOffersCard/Offer.tsx index dd2bcbc943f..b20da3382f8 100644 --- a/src/components/cards/NFTOffersCard/Offer.tsx +++ b/src/components/cards/NFTOffersCard/Offer.tsx @@ -23,6 +23,7 @@ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useAccountSettings } from '@/hooks'; import { Network } from '@/networks/types'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; const TWO_HOURS_MS = 2 * 60 * 60 * 1000; export const CELL_HORIZONTAL_PADDING = 7; @@ -69,7 +70,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { const { nativeCurrency } = useAccountSettings(); const offerChainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address, + address: offer.paymentToken.address as AddressOrEth, chainId: offerChainId, currency: nativeCurrency, }); diff --git a/src/components/cards/OpRewardsCard.tsx b/src/components/cards/OpRewardsCard.tsx index d53a421d187..89a307ff16e 100644 --- a/src/components/cards/OpRewardsCard.tsx +++ b/src/components/cards/OpRewardsCard.tsx @@ -8,6 +8,7 @@ import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; +import { ChainId } from '@/networks/types'; const GRADIENT: Gradient = { colors: ['#520907', '#B22824'], @@ -23,7 +24,7 @@ export const OpRewardsCard: React.FC = () => { }; return ( - + onChangeAccount(wallet?.id, account.address), rowType: RowTypes.ADDRESS, walletId: wallet?.id, diff --git a/src/components/coin-icon/ChainBadge.js b/src/components/coin-icon/ChainBadge.js index 19cce6700b8..b5d25a2f67f 100644 --- a/src/components/coin-icon/ChainBadge.js +++ b/src/components/coin-icon/ChainBadge.js @@ -40,8 +40,7 @@ import { Centered } from '../layout'; import styled from '@/styled-thing'; import { position as positions } from '@/styles'; import { ChainBadgeSizeConfigs } from '@/components/coin-icon/ChainBadgeSizeConfigs'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ChainIcon = styled(FastImage)({ height: ({ containerSize }) => containerSize, diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx index 844b96a2d18..1b2184ceb56 100644 --- a/src/components/coin-icon/ChainImage.tsx +++ b/src/components/coin-icon/ChainImage.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import ArbitrumBadge from '@/assets/badges/arbitrum.png'; import BaseBadge from '@/assets/badges/base.png'; @@ -12,7 +12,6 @@ import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; import FastImage, { Source } from 'react-native-fast-image'; -import { ChainId } from '@/__swaps__/types/chains'; export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) { const source = useMemo(() => { diff --git a/src/components/coin-icon/EthCoinIcon.tsx b/src/components/coin-icon/EthCoinIcon.tsx index 4207a9e6333..53346a7afd0 100644 --- a/src/components/coin-icon/EthCoinIcon.tsx +++ b/src/components/coin-icon/EthCoinIcon.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@/theme'; import { useNativeAsset } from '@/utils/ethereumUtils'; import RainbowCoinIcon from './RainbowCoinIcon'; import { ETH_SYMBOL } from '@/references'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type EthCoinIconProps = { size?: number; diff --git a/src/components/coin-icon/RainbowCoinIcon.tsx b/src/components/coin-icon/RainbowCoinIcon.tsx index bcd086c3f39..d01e0e78b42 100644 --- a/src/components/coin-icon/RainbowCoinIcon.tsx +++ b/src/components/coin-icon/RainbowCoinIcon.tsx @@ -7,7 +7,7 @@ import { FallbackIcon as CoinIconTextFallback } from '@/utils'; import { FastFallbackCoinIconImage } from '../asset-list/RecyclerAssetList2/FastComponents/FastFallbackCoinIconImage'; import { FastChainBadge } from '../asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const fallbackTextStyles = { fontFamily: fonts.family.SFProRounded, diff --git a/src/components/coin-icon/RequestVendorLogoIcon.js b/src/components/coin-icon/RequestVendorLogoIcon.js index d858f7522dd..cedf992c177 100644 --- a/src/components/coin-icon/RequestVendorLogoIcon.js +++ b/src/components/coin-icon/RequestVendorLogoIcon.js @@ -1,7 +1,7 @@ import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { useTheme } from '../../theme/ThemeContext'; -import { ethereumUtils, initials } from '../../utils'; +import { initials } from '../../utils'; import ChainBadge from './ChainBadge'; import { Centered } from '../layout'; import { Text } from '../text'; @@ -33,7 +33,7 @@ export default function RequestVendorLogoIcon({ shouldPrioritizeImageLoading, showLargeShadow, size = CoinIconSize, - network, + chainId, ...props }) { const [error, setError] = useState(null); @@ -71,7 +71,7 @@ export default function RequestVendorLogoIcon({ )} - + ); } diff --git a/src/components/coin-icon/TwoCoinsIcon.tsx b/src/components/coin-icon/TwoCoinsIcon.tsx index a6537bb131c..d6827ad1942 100644 --- a/src/components/coin-icon/TwoCoinsIcon.tsx +++ b/src/components/coin-icon/TwoCoinsIcon.tsx @@ -4,7 +4,7 @@ import { ParsedAddressAsset } from '@/entities'; import { useTheme } from '@/theme'; import ChainBadge from './ChainBadge'; import RainbowCoinIcon from './RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function TwoCoinsIcon({ size = 45, diff --git a/src/components/coin-row/CoinRowInfoButton.js b/src/components/coin-row/CoinRowInfoButton.js index 63a58a9aa5b..59ad34022ad 100644 --- a/src/components/coin-row/CoinRowInfoButton.js +++ b/src/components/coin-row/CoinRowInfoButton.js @@ -93,7 +93,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.chainId))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -115,7 +115,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) }, [item, handleCopyContractAddress]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); + const blockExplorerAction = buildBlockExplorerAction(item?.chainId); return { menuItems: [ blockExplorerAction, @@ -126,7 +126,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ], menuTitle: `${item?.name} (${item?.symbol})`, }; - }, [item?.address, item?.name, item?.network, item?.symbol]); + }, [item?.address, item?.chainId, item?.name, item?.symbol]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index 9ee6cb3bb10..5adaba45b02 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -10,7 +10,7 @@ import Routes from '@rainbow-me/routes'; import { ImgixImage } from '../images'; import { CardSize } from '../unique-token/CardSize'; import { ChainBadge } from '../coin-icon'; -import { Network } from '@/networks/types'; +import { ChainId } from '@/networks/types'; import { address } from '@/utils/abbreviations'; import { TransactionType } from '@/resources/transactions/types'; import { @@ -26,7 +26,6 @@ import Spinner from '../Spinner'; import * as lang from '@/languages'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { checkForPendingSwap } from '@/screens/transaction-details/helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; export const getApprovalLabel = ({ approvalAmount, asset, type }: Pick) => { if (!approvalAmount || !asset) return; @@ -408,7 +407,7 @@ export default React.memo(function TransactionCoinRow({ const [topValue] = activityValues(item, nativeCurrency) ?? []; return ( - + diff --git a/src/components/coin-row/SendCoinRow.js b/src/components/coin-row/SendCoinRow.js index 4795baafa14..8a0515f2422 100644 --- a/src/components/coin-row/SendCoinRow.js +++ b/src/components/coin-row/SendCoinRow.js @@ -3,7 +3,7 @@ import { TouchableWithoutFeedback } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { buildAssetUniqueIdentifier } from '../../helpers/assets'; import { useTheme } from '../../theme/ThemeContext'; -import { deviceUtils, ethereumUtils } from '../../utils'; +import { deviceUtils } from '../../utils'; import { ButtonPressAnimation } from '../animations'; import { Text } from '../text'; import CoinName from './CoinName'; @@ -105,9 +105,7 @@ const SendCoinRow = ({ const Wrapper = disablePressAnimation ? TouchableWithoutFeedback : ButtonPressAnimation; - const isL2 = useMemo(() => { - return isL2Chain({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }); - }, [item?.network]); + const isL2 = useMemo(() => isL2Chain({ chainId: item?.chainId }), [item?.chainId]); const containerSelectedStyles = { height: selectedHeight, diff --git a/src/components/contacts/ContactRow.js b/src/components/contacts/ContactRow.js index b213d33dd23..e52b5f8251e 100644 --- a/src/components/contacts/ContactRow.js +++ b/src/components/contacts/ContactRow.js @@ -58,7 +58,7 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const { width: deviceWidth } = useDimensions(); const { onAddOrUpdateContacts } = useContacts(); const { colors } = useTheme(); - const { accountType, balances, ens, image, label, network, onPress, showcaseItem, testID } = props; + const { accountType, balances, ens, image, label, onPress, showcaseItem, testID } = props; const balanceText = balances ? balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); @@ -80,12 +80,12 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const name = await fetchReverseRecord(address); if (name !== ensName) { setENSName(name); - onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, network, name); + onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, name); } }; fetchENSName(); } - }, [accountType, onAddOrUpdateContacts, address, color, ensName, network, nickname, profilesEnabled, setENSName]); + }, [accountType, onAddOrUpdateContacts, address, color, ensName, nickname, profilesEnabled, setENSName]); let cleanedUpLabel = null; if (label) { diff --git a/src/components/context-menu-buttons/ChainContextMenu.tsx b/src/components/context-menu-buttons/ChainContextMenu.tsx index 09828c27eb0..5f79b99c36e 100644 --- a/src/components/context-menu-buttons/ChainContextMenu.tsx +++ b/src/components/context-menu-buttons/ChainContextMenu.tsx @@ -3,7 +3,7 @@ import { ChainImage } from '@/components/coin-icon/ChainImage'; import { ContextMenuButton } from '@/components/context-menu'; import { Bleed, Box, Inline, Text, TextProps } from '@/design-system'; import * as i18n from '@/languages'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; import { showActionSheetWithOptions } from '@/utils'; import { userAssetsStore } from '@/state/assets/userAssets'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; diff --git a/src/components/discover/DiscoverSearchInput.js b/src/components/discover/DiscoverSearchInput.js index b7284d3c7f2..99913ebfef1 100644 --- a/src/components/discover/DiscoverSearchInput.js +++ b/src/components/discover/DiscoverSearchInput.js @@ -11,9 +11,9 @@ import { analytics } from '@/analytics'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { margin, padding } from '@/styles'; -import { deviceUtils, ethereumUtils } from '@/utils'; +import { deviceUtils } from '@/utils'; import DiscoverSheetContext from '@/screens/discover/DiscoverScreenContext'; -import { getNetworkObj } from '@/networks'; +import { chainIdToNameMapping } from '@/networks/types'; export const ExchangeSearchHeight = 40; const ExchangeSearchWidth = deviceUtils.dimensions.width - 30; @@ -131,9 +131,8 @@ const ExchangeSearch = ( const placeholder = useMemo(() => { if (!currentChainId) return placeholderText; - const network = getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)); return lang.t('button.exchange_search_placeholder_network', { - network: network.name, + network: chainIdToNameMapping[currentChainId], }); }, [currentChainId, placeholderText]); diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index d22be293a06..dd695ea4e9e 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -12,7 +12,7 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils } from '@/utils'; import { formatAddressForDisplay } from '@/utils/abbreviations'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -112,7 +112,7 @@ export default function MoreButton({ address, ensName }: { address?: string; ens setClipboard(address!); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); + ethereumUtils.openAddressInBlockExplorer({ address, chainId: ChainId.mainnet }); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/components/exchange/ConfirmExchangeButton.js b/src/components/exchange/ConfirmExchangeButton.js index 05b438e0acd..455517866ee 100644 --- a/src/components/exchange/ConfirmExchangeButton.js +++ b/src/components/exchange/ConfirmExchangeButton.js @@ -14,12 +14,12 @@ import Routes from '@/navigation/routesNames'; import { lightModeThemeColors } from '@/styles'; import { useTheme } from '@/theme'; import handleSwapErrorCodes from '@/utils/exchangeErrorCodes'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; const NOOP = () => null; export default function ConfirmExchangeButton({ - currentNetwork, + chainId, disabled, loading, isHighPriceImpact, @@ -95,7 +95,7 @@ export default function ConfirmExchangeButton({ label = lang.t('button.confirm_exchange.insufficient_funds'); } else if (isSufficientGas != null && !isSufficientGas) { label = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: getNetworkObj(currentNetwork).nativeCurrency.symbol, + tokenName: getNetworkObject({ chainId }).nativeCurrency.symbol, }); } else if (!isValidGas && isGasReady) { label = lang.t('button.confirm_exchange.invalid_fee'); diff --git a/src/components/exchange/ExchangeField.tsx b/src/components/exchange/ExchangeField.tsx index a31dc8e749b..f72ed6fe138 100644 --- a/src/components/exchange/ExchangeField.tsx +++ b/src/components/exchange/ExchangeField.tsx @@ -4,14 +4,13 @@ import { TokenSelectionButton } from '../buttons'; import { ChainBadge, CoinIconSize } from '../coin-icon'; import { EnDash } from '../text'; import ExchangeInput from './ExchangeInput'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import styled from '@/styled-thing'; import { borders } from '@/styles'; import { useTheme } from '@/theme'; import { AccentColorProvider, Box, Space } from '@/design-system'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; const ExchangeFieldHeight = android ? 64 : 38; const ExchangeFieldPadding: Space = android ? '15px (Deprecated)' : '19px (Deprecated)'; diff --git a/src/components/exchange/ExchangeInputField.tsx b/src/components/exchange/ExchangeInputField.tsx index 490cc202a04..438955417c8 100644 --- a/src/components/exchange/ExchangeInputField.tsx +++ b/src/components/exchange/ExchangeInputField.tsx @@ -4,10 +4,9 @@ import { ColumnWithMargins, Row } from '../layout'; import ExchangeField from './ExchangeField'; import ExchangeMaxButton from './ExchangeMaxButton'; import ExchangeNativeField from './ExchangeNativeField'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import styled from '@/styled-thing'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; const Container = styled(ColumnWithMargins).attrs({ margin: 5 })({ paddingTop: android ? 0 : 6, diff --git a/src/components/exchange/ExchangeOutputField.tsx b/src/components/exchange/ExchangeOutputField.tsx index 479862c16f6..0ada42075e3 100644 --- a/src/components/exchange/ExchangeOutputField.tsx +++ b/src/components/exchange/ExchangeOutputField.tsx @@ -3,7 +3,7 @@ import { TextInput } from 'react-native'; import ExchangeField from './ExchangeField'; import { Box } from '@rainbow-me/design-system'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; interface ExchangeOutputFieldProps { color: string; diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index b1280c79fcc..5a1f9254bf5 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -9,7 +9,8 @@ import { IS_IOS } from '@/env'; import { FavStar, Info } from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; import { View } from 'react-native'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { ParsedAddressAsset } from '@/entities'; interface ExchangeTokenRowProps { item: any; @@ -48,7 +49,6 @@ export default React.memo(function ExchangeTokenRow({ {name ?? item?.name} - {showBalance && item?.balance?.display && ( + {showBalance && (item as ParsedAddressAsset)?.balance?.display && ( - {item?.balance?.display ?? ''} + {(item as ParsedAddressAsset)?.balance?.display ?? ''} )} {!showBalance && ( @@ -92,7 +92,7 @@ export default React.memo(function ExchangeTokenRow({ {showBalance && ( - {item?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} + {(item as ParsedAddressAsset)?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} )} diff --git a/src/components/exchange/NetworkSwitcher.js b/src/components/exchange/NetworkSwitcher.js index 44870fb6286..e9b3a1812d7 100644 --- a/src/components/exchange/NetworkSwitcher.js +++ b/src/components/exchange/NetworkSwitcher.js @@ -8,11 +8,12 @@ import { Column, Row } from '../layout'; import { Text } from '../text'; import { padding, position } from '@/styles'; import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { chainIdToNameMapping } from '@/networks/types'; const networkMenuItems = () => { - return RainbowNetworks.filter(network => network.features.swaps).map(network => ({ + return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => ({ actionKey: network.value, actionTitle: network.name, icon: { @@ -22,7 +23,7 @@ const networkMenuItems = () => { })); }; const androidNetworkMenuItems = () => { - return RainbowNetworks.filter(network => network.features.swaps).map(network => network.name); + return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => network.name); }; const NetworkSwitcherv1 = ({ @@ -93,7 +94,7 @@ const NetworkSwitcherv1 = ({ weight={prominent ? 'heavy' : 'bold'} > {lang.t('expanded_state.swap.network_switcher', { - network: getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)).name, + network: chainIdToNameMapping[currentChainId], })} diff --git a/src/components/exchange/NetworkSwitcherv2.tsx b/src/components/exchange/NetworkSwitcherv2.tsx index 2ee33c53529..a16693c7cbb 100644 --- a/src/components/exchange/NetworkSwitcherv2.tsx +++ b/src/components/exchange/NetworkSwitcherv2.tsx @@ -4,11 +4,11 @@ import RadialGradient from 'react-native-radial-gradient'; import { ButtonPressAnimation } from '../animations'; import ChainBadge from '../coin-icon/ChainBadge'; import { Bleed, Box, Columns, Inline, Text } from '@/design-system'; -import { Network } from '@/helpers'; import { position } from '@rainbow-me/styles'; import { useTheme } from '@/theme'; import { sortNetworks } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId } from '@/networks/types'; const NetworkSwitcherv2 = ({ currentChainId, @@ -31,10 +31,10 @@ const NetworkSwitcherv2 = ({ })); }, []); - const radialGradientProps = (network: Network) => { + const radialGradientProps = (chainId: ChainId) => { return { center: [0, 1], - colors: [colors.alpha(colors.networkColors[network], 0.1), colors.alpha(colors.networkColors[network], 0.02)], + colors: [colors.alpha(colors.networkColors[chainId], 0.1), colors.alpha(colors.networkColors[chainId], 0.02)], pointerEvents: 'none', style: { ...position.coverAsObject, @@ -55,7 +55,7 @@ const NetworkSwitcherv2 = ({ testID={'network-switcher-scroll-view'} > - {networkMenuItems.map(({ chainId, title, network }) => { + {networkMenuItems.map(({ chainId, title }) => { const isSelected = currentChainId === chainId; return ( setCurrentChainId(chainId)} padding="8px" - testID={`${testID}-${network}`} + testID={`${testID}-${chainId}`} > {isSelected && ( )} - {network === Network.mainnet ? ( + {chainId === ChainId.mainnet ? ( ) : ( )} {title} diff --git a/src/components/exchange/exchangeAssetRowContextMenuProps.ts b/src/components/exchange/exchangeAssetRowContextMenuProps.ts index 57d0c2dc12f..e1de9563887 100644 --- a/src/components/exchange/exchangeAssetRowContextMenuProps.ts +++ b/src/components/exchange/exchangeAssetRowContextMenuProps.ts @@ -3,11 +3,11 @@ import { startCase } from 'lodash'; import { NativeSyntheticEvent } from 'react-native'; import { setClipboard } from '../../hooks/useClipboard'; import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const buildBlockExplorerAction = (chainId: ChainId) => { const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { actionKey: CoinRowActionsEnum.blockExplorer, @@ -43,7 +43,7 @@ export default function contextMenuProps(item: any, onCopySwapDetailsText: (addr }; const onPressAndroid = () => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( diff --git a/src/components/expanded-state/AvailableNetworks.js b/src/components/expanded-state/AvailableNetworks.js index 72cba5b9315..f6e96c439ab 100644 --- a/src/components/expanded-state/AvailableNetworks.js +++ b/src/components/expanded-state/AvailableNetworks.js @@ -3,11 +3,9 @@ import React from 'react'; import { Linking } from 'react-native'; import RadialGradient from 'react-native-radial-gradient'; import { Box } from '@/design-system'; -import networkInfo from '@/helpers/networkInfo'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { padding, position } from '@/styles'; -import { ethereumUtils } from '@/utils'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import { Column, Row } from '../layout'; @@ -15,11 +13,11 @@ import { ChainBadge } from '../coin-icon'; import Divider from '../Divider'; import { Text } from '../text'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, marginHorizontal = 19, prominent }) => { const { colors } = useTheme(); const { navigate } = useNavigation(); - const radialGradientProps = { center: [0, 1], colors: colors.gradients.lightGreyWhite, @@ -30,9 +28,7 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, }, }; - const availableNetworks = Object.keys(networks).map(network => { - return ethereumUtils.getNetworkFromChainId(Number(network)); - }); + const availableChainIds = Object.keys(networks).map(network => Number(network)); const linkToHop = useCallback(() => { Linking.openURL('https://app.hop.exchange/#/send'); @@ -40,12 +36,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, const handleAvailableNetworksPress = useCallback(() => { navigate(Routes.EXPLAIN_SHEET, { - networks: availableNetworks, + chainIds: availableChainIds, onClose: linkToHop, tokenSymbol: asset.symbol, type: 'availableNetworks', }); - }, [navigate, availableNetworks, linkToHop, asset.symbol]); + }, [navigate, availableChainIds, linkToHop, asset.symbol]); return ( <> @@ -53,12 +49,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, - {availableNetworks?.map((network, index) => { + {availableChainIds?.map((chainId, index) => { return ( - {network !== 'mainnet' ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( )} @@ -85,12 +81,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, size="smedium" weight={prominent ? 'heavy' : 'bold'} > - {availableNetworks?.length > 1 + {availableChainIds?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableNetworks?.length, + availableNetworks: availableChainIds?.length, }) : lang.t('expanded_state.asset.available_network', { - availableNetwork: networkInfo[availableNetworks?.[0]]?.name, + availableNetwork: chainIdToNameMapping[availableChainIds[0]]?.name, })} diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index 496a8bacd3a..b1345505800 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -8,14 +8,14 @@ import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { position } from '@/styles'; import { ethereumUtils, watchingAlert } from '@/utils'; -import { CurrencySelectionTypes, ExchangeModalTypes, Network } from '@/helpers'; +import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; import { useSwapCurrencyHandlers, useWallets } from '@/hooks'; import { RainbowToken } from '@/entities'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { implementation } from '@/entities/dispersion'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { SWAPS_V2, enableActionsOnReadOnlyWallet, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; @@ -25,7 +25,8 @@ import { AddressOrEth, AssetType } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; +import { getUniqueId } from '@/utils/ethereumUtils'; const NOOP = () => null; @@ -56,11 +57,11 @@ const AvailableNetworksv2 = ({ }, }; - const availableNetworks = useMemo(() => { + const availableChainIds = useMemo(() => { // we dont want to show mainnet return Object.keys(networks) - .map(network => ethereumUtils.getNetworkFromChainId(Number(network))) - .filter(network => network !== Network.mainnet); + .filter(chainId => Number(chainId) !== ChainId.mainnet) + .map(chainId => Number(chainId)); }, [networks]); const { updateInputCurrency } = useSwapCurrencyHandlers({ @@ -68,7 +69,7 @@ const AvailableNetworksv2 = ({ type: ExchangeModalTypes.swap, }); const convertAssetAndNavigate = useCallback( - (chosenNetwork: Network) => { + (chainId: ChainId) => { if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return; @@ -77,15 +78,14 @@ const AvailableNetworksv2 = ({ const newAsset = asset; // we need to convert the mainnet asset to the selected network's - newAsset.mainnet_address = networks?.[ethereumUtils.getChainIdFromNetwork(Network.mainnet)]?.address ?? asset.address; - newAsset.address = networks?.[ethereumUtils.getChainIdFromNetwork(chosenNetwork)].address; - newAsset.network = chosenNetwork; + newAsset.mainnet_address = networks?.[ChainId.mainnet]?.address ?? asset.address; + newAsset.address = networks?.[chainId].address; + newAsset.chainId = chainId; goBack(); if (swapsV2Enabled || swaps_v2) { - const chainId = ethereumUtils.getChainIdFromNetwork(newAsset.network); - const uniqueId = `${newAsset.address}_${chainId}`; + const uniqueId = `${newAsset.address}_${asset.chainId}`; const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); const parsedAsset = parseSearchAsset({ @@ -94,16 +94,16 @@ const AvailableNetworksv2 = ({ uniqueId, address: newAsset.address as AddressOrEth, type: newAsset.type as AssetType, - chainId, - chainName: chainNameFromChainId(chainId), + chainId: asset.chainId, + chainName: chainNameFromChainId(asset.chainId), isNativeAsset: false, native: {}, }, searchAsset: { ...newAsset, uniqueId, - chainId, - chainName: chainNameFromChainId(chainId), + chainId: asset.chainId, + chainName: chainNameFromChainId(asset.chainId), address: newAsset.address as AddressOrEth, highLiquidity: newAsset.highLiquidity ?? false, isRainbowCurated: newAsset.isRainbowCurated ?? false, @@ -118,7 +118,7 @@ const AvailableNetworksv2 = ({ const largestBalanceSameChainUserAsset = userAssetsStore .getState() .getUserAssets() - .find(userAsset => userAsset.chainId === chainId && userAsset.address !== newAsset.address); + .find(userAsset => userAsset.chainId === asset.chainId && userAsset.address !== newAsset.address); if (largestBalanceSameChainUserAsset) { swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); } else { @@ -133,8 +133,8 @@ const AvailableNetworksv2 = ({ return; } - newAsset.uniqueId = `${asset.address}_${chosenNetwork}`; - newAsset.type = chosenNetwork; + newAsset.uniqueId = getUniqueId(asset.address, chainId); + newAsset.type = ethereumUtils.getNetworkFromChainId(chainId); navigate(Routes.EXCHANGE_MODAL, { params: { @@ -151,37 +151,35 @@ const AvailableNetworksv2 = ({ screen: Routes.CURRENCY_SELECT_SCREEN, }); }, - [asset, goBack, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] + [asset, goBack, isReadOnlyWallet, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] ); const handlePressContextMenu = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly - ({ nativeEvent: { actionKey: network } }) => { - convertAssetAndNavigate(network); + ({ nativeEvent: { actionKey: chainId } }) => { + convertAssetAndNavigate(chainId); }, [convertAssetAndNavigate] ); const handlePressButton = useCallback(() => { - convertAssetAndNavigate(availableNetworks[0]); - }, [availableNetworks, convertAssetAndNavigate]); + convertAssetAndNavigate(availableChainIds[0]); + }, [availableChainIds, convertAssetAndNavigate]); const networkMenuItems = useMemo(() => { - return RainbowNetworks.filter(({ features, value, id }) => features.swaps && value !== Network.mainnet && !!networks[id]).map( - network => ({ - actionKey: network.value, - actionTitle: network.name, - icon: { - iconType: 'ASSET', - iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, - }, - }) - ); + return RainbowNetworkObjects.filter(({ features, id }) => features.swaps && id !== ChainId.mainnet && !!networks[id]).map(network => ({ + actionKey: `${network.id}`, + actionTitle: network.name, + icon: { + iconType: 'ASSET', + iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, + }, + })); }, [networks]); - const MenuWrapper = availableNetworks.length > 1 ? ContextMenuButton : Box; + const MenuWrapper = availableChainIds.length > 1 ? ContextMenuButton : Box; - if (availableNetworks.length === 0) return null; + if (availableChainIds.length === 0) return null; return ( <> @@ -208,16 +206,15 @@ const AvailableNetworksv2 = ({ - {availableNetworks?.map((network, index) => { - const chainId = ethereumUtils.getChainIdFromNetwork(network); + {availableChainIds?.map((chainId, index) => { return ( @@ -233,18 +230,18 @@ const AvailableNetworksv2 = ({ - {availableNetworks?.length > 1 + {availableChainIds?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableNetworks?.length, + availableNetworks: availableChainIds?.length, }) : lang.t('expanded_state.asset.available_networkv2', { - availableNetwork: getNetworkObj(availableNetworks?.[0])?.name, + availableNetwork: chainIdToNameMapping[availableChainIds[0]], })} - {availableNetworks?.length > 1 ? '􀁱' : '􀯻'} + {availableChainIds?.length > 1 ? '􀁱' : '􀯻'} diff --git a/src/components/expanded-state/ContactProfileState.js b/src/components/expanded-state/ContactProfileState.js index b6bde10d0de..9a895e6dedc 100644 --- a/src/components/expanded-state/ContactProfileState.js +++ b/src/components/expanded-state/ContactProfileState.js @@ -7,7 +7,7 @@ import { magicMemo } from '../../utils'; import ProfileModal from './profile/ProfileModal'; import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; -import { useAccountSettings, useContacts, useENSAvatar } from '@/hooks'; +import { useContacts, useENSAvatar } from '@/hooks'; import { addressHashedColorIndex, addressHashedEmoji } from '@/utils/profileUtils'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; @@ -24,16 +24,14 @@ const ContactProfileState = ({ address, color, contact, ens, nickname }) => { const colorIndex = useMemo(() => color || addressHashedColorIndex(address) || 0, [address, color]); - const { network } = useAccountSettings(); - const handleAddContact = useCallback(() => { const nickname = profilesEnabled ? value : (emoji ? `${emoji} ${value}` : value).trim(); if (value?.length > 0) { - onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], network, ens); + onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], ens); goBack(); } android && Keyboard.dismiss(); - }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, network, onAddOrUpdateContacts, profilesEnabled, value]); + }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, onAddOrUpdateContacts, profilesEnabled, value]); const handleCancel = useCallback(() => { goBack(); diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index eed7e54ba95..d19af128e10 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -40,7 +40,7 @@ export default function CustomGasState({ asset }) { const { height: deviceHeight } = useDimensions(); const keyboardHeight = useKeyboardHeight(); const colorForAsset = useColorForAsset(asset || {}, fallbackColor, false, true); - const { selectedGasFee, currentBlockParams, txNetwork } = useGas(); + const { selectedGasFee, currentBlockParams, chainId } = useGas(); const [canGoBack, setCanGoBack] = useState(true); const { tradeDetails } = useSelector(state => state.swap); @@ -93,7 +93,7 @@ export default function CustomGasState({ asset }) { { - switch (network) { - case Network.mainnet: +const getIsSupportedOnRainbowWeb = (chainId: ChainId) => { + switch (chainId) { + case ChainId.mainnet: return true; default: return false; @@ -251,7 +251,7 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE [offer] ); - const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.network); + const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.chainId); const [isRefreshMetadataToastActive, setIsRefreshMetadataToastActive] = useState(false); const [isReportSpamToastActive, setIsReportSpamToastActive] = useState(false); @@ -548,10 +548,10 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE /> )} - {asset.network !== Network.mainnet ? ( + {asset.chainId !== ChainId.mainnet ? ( // @ts-expect-error JavaScript component isL2Chain({ chainId: assetChainId }), [assetChainId]); - const isTestnet = isTestnetNetwork(currentNetwork); + const isL2 = useMemo(() => isL2Chain({ chainId: asset?.chainId }), [asset?.chainId]); + const isTestnet = isTestnetChain({ chainId: currentChainId }); const { data, isLoading: additionalAssetDataLoading } = useAdditionalAssetData({ address: asset?.address, - network: asset?.network, + chainId: asset?.chainId, currency: nativeCurrency, }); @@ -235,12 +235,13 @@ export default function ChartExpandedState({ asset }) { const { colors } = useTheme(); const crosschainEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); + const AvailableNetworks = !crosschainEnabled ? AvailableNetworksv1 : AvailableNetworksv2; - const assetNetwork = assetWithPrice.network; + const assetChainId = assetWithPrice.chainId; const { swagg_enabled, f2c_enabled } = useRemoteConfig(); - const swapEnabled = swagg_enabled && getNetworkObj(assetNetwork).features.swaps; + const swapEnabled = swagg_enabled && getNetworkObject({ chainId: assetChainId }).features.swaps; const addCashEnabled = f2c_enabled; const format = useCallback( @@ -314,7 +315,7 @@ export default function ChartExpandedState({ asset }) { ) : null} {!data?.networks && isL2 && ( - + )} {data?.networks && !hasBalance && ( @@ -375,7 +376,7 @@ export default function ChartExpandedState({ asset }) { isNativeAsset={assetWithPrice?.isNativeAsset} links={data?.links} marginTop={!delayedDescriptions && 19} - chainId={ethereumUtils.getChainIdFromNetwork(asset?.network)} + chainId={asset?.chainId} /> diff --git a/src/components/expanded-state/asset/SocialLinks.js b/src/components/expanded-state/asset/SocialLinks.js index 79d2706a75a..f5199f6480d 100644 --- a/src/components/expanded-state/asset/SocialLinks.js +++ b/src/components/expanded-state/asset/SocialLinks.js @@ -28,8 +28,8 @@ const CommunityLink = styled(Link).attrs({ }); export default function SocialLinks({ address, color, isNativeAsset, links, marginTop, chainId }) { - const etherscanURL = ethereumUtils.getEtherscanHostForNetwork(chainId); - const blockExplorerName = ethereumUtils.getBlockExplorer(chainId); + const etherscanURL = ethereumUtils.getEtherscanHostForNetwork({ chainId }); + const blockExplorerName = ethereumUtils.getBlockExplorer({ chainId }); return ( <> diff --git a/src/components/expanded-state/chart/ChartContextButton.js b/src/components/expanded-state/chart/ChartContextButton.js index 8ce84794e84..19044bc6fe1 100644 --- a/src/components/expanded-state/chart/ChartContextButton.js +++ b/src/components/expanded-state/chart/ChartContextButton.js @@ -46,12 +46,12 @@ export default function ChartContextButton({ asset, color }) { ? [] : [ `🔍 ${emojiSpacing}${lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), })}`, ]), ...(ios ? [lang.t('wallet.action.cancel')] : []), ], - [asset?.isNativeAsset, asset?.network, currentAction] + [asset?.chainId, asset?.isNativeAsset, currentAction] ); return ; diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 624255db57e..2d23e3ca37c 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -11,7 +11,6 @@ import { useAccountSettings, useBooleanState } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ethereumUtils } from '@/utils'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -52,9 +51,6 @@ export default function ChartExpandedStateHeader({ }) { const theme = useTheme(); const color = givenColors || theme.colors.dark; - const tokens = useMemo(() => { - return isPool ? asset.tokens : [asset]; - }, [asset, isPool]); const { nativeCurrency } = useAccountSettings(); const tabularNums = useTabularNumsWhileScrubbing(); @@ -114,7 +110,7 @@ export default function ChartExpandedStateHeader({ (null); const [maxPriorityFeeError, setMaxPriorityFeeError] = useState(null); @@ -248,12 +248,12 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack ); const addMinerTip = useCallback(() => { - updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, txNetwork)); - }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); + updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, chainId)); + }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); const substMinerTip = useCallback(() => { - updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, txNetwork)); - }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); + updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, chainId)); + }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); const addMaxFee = useCallback(() => { updateFeePerGas(isL2 ? GAS_FEE_L2_INCREMENT : GAS_FEE_INCREMENT); diff --git a/src/components/expanded-state/swap-details/CurrencyTile.js b/src/components/expanded-state/swap-details/CurrencyTile.js index dee048e8fe9..62072cc8930 100644 --- a/src/components/expanded-state/swap-details/CurrencyTile.js +++ b/src/components/expanded-state/swap-details/CurrencyTile.js @@ -78,7 +78,7 @@ export default function CurrencyTile({ { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { actionKey: ContractActionsEnum.blockExplorer, @@ -105,7 +105,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const [menuVisible, setMenuVisible] = useState(false); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(asset?.network)); + const blockExplorerAction = buildBlockExplorerAction(asset?.chainId); return { menuItems: [ blockExplorerAction, @@ -116,14 +116,14 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . ], menuTitle: `${asset?.name} (${asset?.symbol})`, }; - }, [asset?.address, asset?.name, asset?.symbol, asset?.network]); + }, [asset?.chainId, asset?.address, asset?.name, asset?.symbol]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContractActionsEnum.copyAddress) { handleCopyContractAddress(asset?.address); } else if (actionKey === ContractActionsEnum.blockExplorer) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); } }, [asset, handleCopyContractAddress] @@ -131,7 +131,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const onPressAndroid = useCallback(() => { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), }); const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -146,7 +146,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . handleCopyContractAddress(asset?.address); } if (idx === 1) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); } } ); diff --git a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js index fb9a5852652..67efaa8db35 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js @@ -1,7 +1,6 @@ import lang from 'i18n-js'; import { capitalize } from 'lodash'; import React, { Fragment, useMemo } from 'react'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { convertAmountToPercentageDisplay } from '../../../helpers/utilities'; import Pill from '../../Pill'; import { ButtonPressAnimation } from '../../animations'; @@ -11,10 +10,10 @@ import { usePrevious, useStepper } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { getExchangeIconUrl, magicMemo } from '@/utils'; import { SocketBridges } from '@/references/swap/bridges'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; const parseExchangeName = name => { - const networks = RainbowNetworks.map(network => network.name.toLowerCase()); + const networks = RainbowNetworkObjects.map(network => network.name.toLowerCase()); const removeNetworks = name => networks.some(network => name.toLowerCase().includes(network)) ? name.slice(name.indexOf('_') + 1, name.length) : name; diff --git a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx index 657c529d1a5..2dd81818208 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx +++ b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx @@ -9,7 +9,7 @@ import { ChainBadge } from '@/components/coin-icon'; import { getNetworkObject } from '@/networks'; import { useTheme } from '@/theme'; import * as i18n from '@/languages'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function SwapDetailsRewardRow({ reward }: { reward: Reward }) { const { navigate } = useNavigation(); diff --git a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx index 98f5ceeb63d..d2413fed5f1 100644 --- a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx +++ b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx @@ -6,13 +6,13 @@ import { ButtonPressAnimation } from '../../animations'; import { Icon } from '../../icons'; import StepButtonInput from './StepButtonInput'; import { AccentColorProvider, Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; -import { Network } from '@/helpers'; import { add, convertNumberToString, greaterThan } from '@/helpers/utilities'; import { useMagicAutofocus, useSwapSettings } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; import { ethereumUtils } from '@/utils'; +import { ChainId } from '@/networks/types'; const convertBipsToPercent = (bips: number) => (bips / 100).toString(); const convertPercentToBips = (percent: number) => (percent * 100).toString(); @@ -20,133 +20,131 @@ const convertPercentToBips = (percent: number) => (percent * 100).toString(); const SLIPPAGE_INCREMENT = 0.1; // eslint-disable-next-line react/display-name -export const MaxToleranceInput = forwardRef( - ({ colorForAsset, currentNetwork }: { colorForAsset: string; currentNetwork: Network }, ref) => { - const { slippageInBips, updateSwapSlippage } = useSwapSettings(); - const { navigate } = useNavigation(); - - const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); - - const slippageRef = useRef(null); - - const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); - - const { hasPriceImpact, priceImpactColor } = useMemo(() => { - const hasPriceImpact = Number(slippageValue) >= 3; - const priceImpactColor = hasPriceImpact ? colors.orange : null; - return { hasPriceImpact, priceImpactColor }; - }, [slippageValue]); - - useImperativeHandle(ref, () => ({ - blur: () => { - slippageRef?.current?.blur(); - }, - reset: () => { - const slippage = getDefaultSlippageFromConfig(ethereumUtils.getChainIdFromNetwork(currentNetwork)) as unknown as number; - onSlippageChange(convertBipsToPercent(slippage)); - }, - })); - - const updateSlippage = useCallback( - (increment: any) => { - const newSlippage = add(slippageValue, increment); - const newSlippageValue = convertNumberToString(newSlippage); - if (greaterThan(0, newSlippageValue)) return; - - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); - setSlippageValue(newSlippageValue); - }, - [slippageValue, updateSwapSlippage] - ); - - const addSlippage = useCallback(() => { - updateSlippage(SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const minusSlippage = useCallback(() => { - updateSlippage(-SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const onSlippageChange = useCallback( - (value: any) => { - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(value)); - setSlippageValue(value); - }, - [updateSwapSlippage, setSlippageValue] - ); - - const openSlippageExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'slippage', - }); - }; - - return ( - - - - - - - {`${lang.t('exchange.slippage_tolerance')} `} - {!hasPriceImpact && ( - - {' 􀅵'} - - )} - - {hasPriceImpact && ( - - - +export const MaxToleranceInput = forwardRef(({ colorForAsset, chainId }: { colorForAsset: string; chainId: ChainId }, ref) => { + const { slippageInBips, updateSwapSlippage } = useSwapSettings(); + const { navigate } = useNavigation(); + + const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); + + const slippageRef = useRef(null); + + const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); + + const { hasPriceImpact, priceImpactColor } = useMemo(() => { + const hasPriceImpact = Number(slippageValue) >= 3; + const priceImpactColor = hasPriceImpact ? colors.orange : null; + return { hasPriceImpact, priceImpactColor }; + }, [slippageValue]); + + useImperativeHandle(ref, () => ({ + blur: () => { + slippageRef?.current?.blur(); + }, + reset: () => { + const slippage = getDefaultSlippageFromConfig(chainId) as unknown as number; + onSlippageChange(convertBipsToPercent(slippage)); + }, + })); + + const updateSlippage = useCallback( + (increment: any) => { + const newSlippage = add(slippageValue, increment); + const newSlippageValue = convertNumberToString(newSlippage); + if (greaterThan(0, newSlippageValue)) return; + + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); + setSlippageValue(newSlippageValue); + }, + [slippageValue, updateSwapSlippage] + ); + + const addSlippage = useCallback(() => { + updateSlippage(SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const minusSlippage = useCallback(() => { + updateSlippage(-SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const onSlippageChange = useCallback( + (value: any) => { + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(value)); + setSlippageValue(value); + }, + [updateSwapSlippage, setSlippageValue] + ); + + const openSlippageExplainer = () => { + Keyboard.dismiss(); + navigate(Routes.EXPLAIN_SHEET, { + type: 'slippage', + }); + }; + + return ( + + + + + + + {`${lang.t('exchange.slippage_tolerance')} `} + {!hasPriceImpact && ( + + {' 􀅵'} + )} - + + {hasPriceImpact && ( + + + + )} + + + {hasPriceImpact && ( + + + + + {lang.t('exchange.high')} + + + {` · ${lang.t('exchange.price_impact.label')}`} + - {hasPriceImpact && ( - - - - - {lang.t('exchange.high')} - - - {` · ${lang.t('exchange.price_impact.label')}`} - - - )} - - - - - - - ); - } -); + )} + + + + + + + ); +}); diff --git a/src/components/expanded-state/swap-settings/SwapSettingsState.js b/src/components/expanded-state/swap-settings/SwapSettingsState.js index 13d2ae8899e..7432ebece10 100644 --- a/src/components/expanded-state/swap-settings/SwapSettingsState.js +++ b/src/components/expanded-state/swap-settings/SwapSettingsState.js @@ -32,7 +32,7 @@ function useAndroidDisableGesturesOnFocus() { export default function SwapSettingsState({ asset }) { const { flashbotsEnabled, settingsChangeFlashbotsEnabled } = useAccountSettings(); const { - params: { swapSupportsFlashbots = false, network }, + params: { swapSupportsFlashbots = false, chainId }, } = useRoute(); const { colors } = useTheme(); const { setParams, goBack } = useNavigation(); @@ -151,7 +151,7 @@ export default function SwapSettingsState({ asset }) { )} - + diff --git a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx index 21f6e2cd2f5..8247be503b0 100644 --- a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx +++ b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx @@ -54,7 +54,7 @@ export default function NFTBriefTokenInfoRow({ asset }: { asset: UniqueAsset }) const { data: listing } = useNFTListing({ contractAddress: asset?.asset_contract?.address ?? '', tokenId: asset?.id, - network: asset?.network, + chainId: asset?.chainId, }); const listingValue = listing && convertRawAmountToRoundedDecimal(listing?.price, listing?.payment_token?.decimals, 3); diff --git a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx index 08fb711b91e..205b88c4bff 100644 --- a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx +++ b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx @@ -9,7 +9,6 @@ import saveToCameraRoll from './saveToCameraRoll'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Bleed, Column, Columns, Heading, Inline, Inset, Space, Stack, Text } from '@/design-system'; import { UniqueAsset } from '@/entities'; -import { Network } from '@/helpers'; import { useClipboard, useDimensions, useHiddenTokens, useShowcaseTokens } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { useNavigation } from '@/navigation/Navigation'; @@ -23,6 +22,7 @@ import { refreshNFTContractMetadata, reportNFT } from '@/resources/nfts/simpleha import { ContextCircleButton } from '@/components/context-menu'; import { IS_ANDROID, IS_IOS } from '@/env'; import { MenuActionConfig, MenuConfig } from 'react-native-ios-context-menu'; +import { ChainId } from '@/networks/types'; const AssetActionsEnum = { copyTokenID: 'copyTokenID', @@ -36,7 +36,7 @@ const AssetActionsEnum = { report: 'report', } as const; -const getAssetActions = (network: Network) => +const getAssetActions = ({ chainId }: { chainId: ChainId }) => ({ [AssetActionsEnum.copyTokenID]: { actionKey: AssetActionsEnum.copyTokenID, @@ -57,7 +57,7 @@ const getAssetActions = (network: Network) => [AssetActionsEnum.etherscan]: { actionKey: AssetActionsEnum.etherscan, actionTitle: lang.t('expanded_state.unique_expanded.view_on_block_explorer', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }), icon: { iconType: 'SYSTEM', @@ -263,7 +263,7 @@ const UniqueTokenExpandedStateHeader = ({ const isPhotoDownloadAvailable = !isSVG && !isENS; const assetMenuConfig: MenuConfig = useMemo(() => { - const AssetActions = getAssetActions(asset.network); + const AssetActions = getAssetActions({ chainId: asset.chainId }); return { menuItems: [ @@ -309,7 +309,7 @@ const UniqueTokenExpandedStateHeader = ({ { ...AssetActions[AssetActionsEnum.etherscan], }, - ...(asset.network === Network.mainnet + ...(asset.chainId === ChainId.mainnet ? [ { menuTitle: lang.t('expanded_state.unique_expanded.view_on_marketplace'), @@ -320,7 +320,7 @@ const UniqueTokenExpandedStateHeader = ({ ], menuTitle: '', }; - }, [asset.id, asset?.network, isPhotoDownloadAvailable, isHiddenAsset, isModificationActionsEnabled, isSupportedOnRainbowWeb]); + }, [asset.id, asset.chainId, isModificationActionsEnabled, isHiddenAsset, isPhotoDownloadAvailable, isSupportedOnRainbowWeb]); const handlePressFamilyMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 8ba909abe83..a8851d7d387 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -28,7 +28,7 @@ import { getNetworkObject } from '@/networks'; import { IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const { GAS_EMOJIS, GAS_ICONS, GasSpeedOrder, CUSTOM, URGENT, NORMAL, FAST, getGasLabel } = gasUtils; @@ -310,9 +310,9 @@ const GasSpeedButton = ({ type: 'crossChainGas', }); } else { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId }); navigate(Routes.EXPLAIN_SHEET, { - network: ethereumUtils.getNetworkFromChainId(chainId), + chainId, type: 'gas', nativeAsset, }); diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx index 366e9c3577d..c1faa16a794 100644 --- a/src/components/positions/PositionsCard.tsx +++ b/src/components/positions/PositionsCard.tsx @@ -18,6 +18,7 @@ import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useAccountSettings } from '@/hooks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; type PositionCardProps = { position: RainbowPosition; @@ -33,7 +34,7 @@ function CoinIconForStack({ token }: { token: CoinStackToken }) { const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const chainId = ethereumUtils.getChainIdFromNetwork(token.network); - const { data: externalAsset } = useExternalToken({ address: token.address, chainId, currency: nativeCurrency }); + const { data: externalAsset } = useExternalToken({ address: token.address as AddressOrEth, chainId, currency: nativeCurrency }); return ( = async ({ assetAddress, network }) => { - const { accountAddress, nativeCurrency } = store.getState().settings; - - const assets = await fetchUserAssets({ - address: accountAddress, - currency: nativeCurrency, - connectedToHardhat: false, - }); - if (!assets || Object.keys(assets).length === 0) return false; - - const desiredAsset = Object.values(assets).find(asset => { - if (!network) { - return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase(); - } - - return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase() && asset.network === network; - }); - if (!desiredAsset) return false; - - return Number(desiredAsset.balance?.amount) > 0; -}; diff --git a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts index b6133f9f4af..0c7fe5d7fa2 100644 --- a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts +++ b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts @@ -1,5 +1,5 @@ import type { EthereumAddress, RainbowTransaction } from '@/entities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import store from '@/redux/store'; import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; @@ -13,7 +13,7 @@ const isSwapTx = (tx: RainbowTransaction): boolean => tx.to?.toLowerCase() === R export const hasSwapTxn = async (): Promise => { const { accountAddress, nativeCurrency } = store.getState().settings; - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, diff --git a/src/components/remote-promo-sheet/check-fns/index.ts b/src/components/remote-promo-sheet/check-fns/index.ts index 94ec08d36ec..b3fdb065edb 100644 --- a/src/components/remote-promo-sheet/check-fns/index.ts +++ b/src/components/remote-promo-sheet/check-fns/index.ts @@ -1,5 +1,4 @@ export * from './hasNftOffers'; -export * from './hasNonZeroAssetBalance'; export * from './hasNonZeroTotalBalance'; export * from './hasSwapTxn'; export * from './isAfterCampaignLaunch'; diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx index 810bb9e7890..af2a29de48b 100644 --- a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx +++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx @@ -85,7 +85,7 @@ function SwapActionButton({ asset, color: givenColor, inputType, label, fromDisc if (inputType === assetInputTypes.in) { swapsStore.setState({ inputAsset: userAsset || parsedAsset }); - const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork({ chainId }); if (nativeAssetForChain && !isSameAsset({ address: nativeAssetForChain.address as AddressOrEth, chainId }, parsedAsset)) { const userOutputAsset = userAssetsStore.getState().getUserAsset(`${nativeAssetForChain.address}_${chainId}`); diff --git a/src/components/toasts/OfflineToast.js b/src/components/toasts/OfflineToast.js index defab4a11ee..ec4565c57e9 100644 --- a/src/components/toasts/OfflineToast.js +++ b/src/components/toasts/OfflineToast.js @@ -1,15 +1,15 @@ import lang from 'i18n-js'; import React from 'react'; import { web3Provider } from '../../handlers/web3'; -import networkTypes from '../../helpers/networkTypes'; import Toast from './Toast'; import { useAccountSettings, useInternetStatus } from '@/hooks'; +import { Network } from '@/networks/types'; const OfflineToast = () => { const isConnected = useInternetStatus(); const { network } = useAccountSettings(); const providerUrl = web3Provider?.connection?.url; - const isMainnet = network === networkTypes.mainnet && !providerUrl?.startsWith('http://'); + const isMainnet = network === Network.mainnet && !providerUrl?.startsWith('http://'); return ; }; diff --git a/src/components/toasts/TestnetToast.js b/src/components/toasts/TestnetToast.js index ec370b3e51b..acbf9afb4ab 100644 --- a/src/components/toasts/TestnetToast.js +++ b/src/components/toasts/TestnetToast.js @@ -1,22 +1,22 @@ import React, { useEffect, useState } from 'react'; -import networkTypes from '../../helpers/networkTypes'; import { Icon } from '../icons'; import { Nbsp, Text } from '../text'; import Toast from './Toast'; -import { isHardHat } from '@/handlers/web3'; import { useInternetStatus } from '@/hooks'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -const TestnetToast = ({ network, web3Provider }) => { +const TestnetToast = ({ chainId }) => { + const { connectedToHardhat } = useConnectedToHardhatStore(); const isConnected = useInternetStatus(); - const providerUrl = web3Provider?.connection?.url; - const { name, colors: networkColors } = getNetworkObj(network); - const [visible, setVisible] = useState(!network === networkTypes.mainnet); + const { name, colors: networkColors } = getNetworkObject({ chainId }); + const [visible, setVisible] = useState(chainId !== ChainId.mainnet); const [networkName, setNetworkName] = useState(name); useEffect(() => { - if (network === networkTypes.mainnet) { - if (isHardHat(providerUrl)) { + if (chainId === ChainId.mainnet) { + if (connectedToHardhat) { setVisible(true); setNetworkName('Hardhat'); } else { @@ -26,7 +26,7 @@ const TestnetToast = ({ network, web3Provider }) => { setVisible(true); setNetworkName(name + (isConnected ? '' : ' (offline)')); } - }, [name, network, providerUrl, isConnected]); + }, [name, isConnected, chainId, connectedToHardhat]); const { colors, isDarkMode } = useTheme(); diff --git a/src/components/token-info/TokenInfoBalanceValue.js b/src/components/token-info/TokenInfoBalanceValue.js index e69519c8811..f9e42c44412 100644 --- a/src/components/token-info/TokenInfoBalanceValue.js +++ b/src/components/token-info/TokenInfoBalanceValue.js @@ -3,7 +3,7 @@ import { RowWithMargins } from '../layout'; import TokenInfoValue from './TokenInfoValue'; import { useColorForAsset } from '@/hooks'; import styled from '@/styled-thing'; -import { ethereumUtils, magicMemo } from '@/utils'; +import { magicMemo } from '@/utils'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; import { View } from 'react-native'; @@ -28,7 +28,7 @@ const TokenInfoBalanceValue = ({ align, asset, ...props }) => { { - const networkObj = getNetworkObj(ethereumUtils.getNetworkFromChainId(Number(chainId))); + const networkObj = getNetworkObject({ chainId }); return { chainId, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, @@ -195,7 +195,7 @@ export default function WalletConnectListItem({ account, chainId, dappIcon, dapp > - + { - const p = await getProviderForNetwork(network); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + const p = await getProvider({ chainId }); const contractInstance = new Contract(TOKEN_GATE_CHECKER_ADDRESS[network], tokenGateCheckerAbi, p); diff --git a/src/handlers/__mocks__/web3.ts b/src/handlers/__mocks__/web3.ts index dbd6cbf1714..4b6ff17ecf0 100644 --- a/src/handlers/__mocks__/web3.ts +++ b/src/handlers/__mocks__/web3.ts @@ -1,3 +1,3 @@ import { jest } from '@jest/globals'; -export const getProviderForNetwork = jest.fn(); +export const getProvider = jest.fn(); diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 48044fac0a0..164ae1c436a 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -2,22 +2,16 @@ import { Contract } from '@ethersproject/contracts'; import { erc20ABI } from '@/references'; import { convertAmountToBalanceDisplay, convertRawAmountToDecimalFormat } from '@/helpers/utilities'; -import { getNetworkObj, getNetworkObject } from '@/networks'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; -import { ethereumUtils } from '@/utils'; - -export function isL2Asset(network: Network) { - return getNetworkObj(network).networkType === 'layer2'; -} +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; export function isNativeAsset(address: string, chainId: ChainId) { return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } -export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, network: any, provider: any) { +export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: any, provider: any) { // Check if it's the native chain asset - if (isNativeAsset(address, ethereumUtils.getChainIdFromNetwork(network))) { + if (isNativeAsset(address, chainId)) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); } return getOnchainTokenBalance({ address, decimals, symbol }, userAddress, provider); diff --git a/src/handlers/ens.ts b/src/handlers/ens.ts index 5277ac98919..aa039d13a05 100644 --- a/src/handlers/ens.ts +++ b/src/handlers/ens.ts @@ -10,9 +10,8 @@ import { prefetchENSCover } from '../hooks/useENSCover'; import { prefetchENSRecords } from '../hooks/useENSRecords'; import { ENSActionParameters, ENSRapActionType } from '@/raps/common'; import { getENSData, getNameFromLabelhash, saveENSData } from './localstorage/ens'; -import { estimateGasWithPadding, getProviderForNetwork, TokenStandard } from './web3'; +import { estimateGasWithPadding, getProvider, TokenStandard } from './web3'; import { ENSRegistrationRecords, Records, UniqueAsset } from '@/entities'; -import { Network } from '@/helpers'; import { ENS_DOMAIN, ENS_RECORDS, ENSRegistrationTransactionType, generateSalt, getENSExecutionDetails, getNameOwner } from '@/helpers/ens'; import { add } from '@/helpers/utilities'; import { ImgixImage } from '@/components/images'; @@ -25,6 +24,7 @@ import { prefetchENSAddress } from '@/resources/ens/ensAddressQuery'; import { MimeType, handleNFTImages } from '@/utils/handleNFTImages'; import store from '@/redux/store'; import { logger, RainbowError } from '@/logger'; +import { ChainId, Network } from '@/networks/types'; const DUMMY_RECORDS = { description: 'description', @@ -51,6 +51,7 @@ const buildEnsToken = ({ }); return { acquisition_date: undefined, + chainId: ChainId.mainnet, animation_url: null, asset_contract: { address: contractAddress, @@ -321,7 +322,7 @@ export const fetchAccountDomains = async (address: string) => { export const fetchImage = async (imageType: 'avatar' | 'header', ensName: string) => { let imageUrl; - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); try { const avatarResolver = new AvatarResolver(provider); imageUrl = await avatarResolver.getImage(ensName, { @@ -346,7 +347,7 @@ export const fetchRecords = async (ensName: string, { supportedOnly = true }: { const data = response.domains[0] || {}; const rawRecordKeys = data.resolver?.texts || []; - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const supportedRecords = Object.values(ENS_RECORDS); const recordKeys = (rawRecordKeys as ENS_RECORDS[]).filter(key => (supportedOnly ? supportedRecords.includes(key) : true)); @@ -368,7 +369,7 @@ export const fetchCoinAddresses = async ( const response = await ensClient.getCoinTypesByName({ name: ensName }); const data = response.domains[0] || {}; const supportedRecords = Object.values(ENS_RECORDS); - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const rawCoinTypes: number[] = data.resolver?.coinTypes || []; const rawCoinTypesNames: string[] = rawCoinTypes.map(type => formatsByCoinType[type].name); @@ -401,7 +402,7 @@ export const fetchCoinAddresses = async ( }; export const fetchContenthash = async (ensName: string) => { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const contenthash = await resolver?.getContentHash(); return contenthash; @@ -448,7 +449,7 @@ export const fetchRegistration = async (ensName: string) => { }; export const fetchPrimary = async (ensName: string) => { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const address = await provider.resolveName(ensName); return { address, @@ -887,7 +888,7 @@ export const getRapActionTypeForTxType = (txType: ENSRegistrationTransactionType export const fetchReverseRecord = async (address: string) => { try { const checksumAddress = getAddress(address); - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const reverseRecord = await provider.lookupAddress(checksumAddress); return reverseRecord ?? ''; } catch (e) { @@ -897,7 +898,7 @@ export const fetchReverseRecord = async (address: string) => { export const fetchResolver = async (ensName: string) => { try { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); return resolver ?? ({} as Resolver); } catch (e) { diff --git a/src/handlers/gasFees.ts b/src/handlers/gasFees.ts index aeffb8cddac..6d85e0d1b59 100644 --- a/src/handlers/gasFees.ts +++ b/src/handlers/gasFees.ts @@ -1,5 +1,5 @@ -import { Network } from '@/helpers'; import { RainbowFetchClient } from '../rainbow-fetch'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const rainbowMeteorologyApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -10,4 +10,5 @@ const rainbowMeteorologyApi = new RainbowFetchClient({ timeout: 30000, // 30 secs }); -export const rainbowMeteorologyGetData = (network: Network) => rainbowMeteorologyApi.get(`/meteorology/v1/gas/${network}`, {}); +export const rainbowMeteorologyGetData = (chainId: ChainId) => + rainbowMeteorologyApi.get(`/meteorology/v1/gas/${chainIdToNameMapping[chainId]}`, {}); diff --git a/src/handlers/localstorage/globalSettings.ts b/src/handlers/localstorage/globalSettings.ts index a9e3769bfbf..c54370a7cd1 100644 --- a/src/handlers/localstorage/globalSettings.ts +++ b/src/handlers/localstorage/globalSettings.ts @@ -1,6 +1,6 @@ +import { ChainId } from '@/networks/types'; import { getGlobal, saveGlobal } from './common'; import { NativeCurrencyKeys } from '@/entities'; -import networkTypes from '@/helpers/networkTypes'; import { Language } from '@/languages'; export const IMAGE_METADATA = 'imageMetadata'; @@ -8,7 +8,7 @@ const KEYBOARD_HEIGHT = 'keyboardHeight'; const APP_ICON = 'appIcon'; const LANGUAGE = 'language'; const NATIVE_CURRENCY = 'nativeCurrency'; -const NETWORK = 'network'; +const CHAIN_ID = 'chainId'; const KEYCHAIN_INTEGRITY_STATE = 'keychainIntegrityState'; const AUTH_TIMELOCK = 'authTimelock'; const PIN_AUTH_ATTEMPTS_LEFT = 'pinAuthAttemptsLeft'; @@ -32,9 +32,9 @@ export const getLanguage = () => getGlobal(LANGUAGE, Language.EN_US); export const saveLanguage = (language: any) => saveGlobal(LANGUAGE, language); -export const getNetwork = () => getGlobal(NETWORK, networkTypes.mainnet); +export const getChainId = () => getGlobal(CHAIN_ID, ChainId.mainnet); -export const saveNetwork = (network: any) => saveGlobal(NETWORK, network); +export const saveChainId = (chainId: ChainId) => saveGlobal(CHAIN_ID, chainId); export const getKeyboardHeight = () => getGlobal(KEYBOARD_HEIGHT, null); diff --git a/src/handlers/localstorage/removeWallet.ts b/src/handlers/localstorage/removeWallet.ts index 7364f02e0cc..1a2934127be 100644 --- a/src/handlers/localstorage/removeWallet.ts +++ b/src/handlers/localstorage/removeWallet.ts @@ -1,17 +1,17 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { keys } from 'lodash'; -import NetworkTypes from '../../helpers/networkTypes'; import { accountLocalKeys } from './accountLocal'; import { getKey } from './common'; import { walletConnectAccountLocalKeys } from './walletconnectRequests'; import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet } from '@/notifications/settings'; +import { Network } from '@/networks/types'; export const removeWalletData = async (accountAddress: any) => { logger.debug('[localstorage/removeWallet]: removing wallet data', { accountAddress }); const allPrefixes = accountLocalKeys.concat(walletConnectAccountLocalKeys); logger.debug('[localstorage/removeWallet]: all prefixes', { allPrefixes }); - const networks = keys(NetworkTypes); + const networks = keys(Network); const allKeysWithNetworks = allPrefixes.map(prefix => networks.map(network => getKey(prefix, accountAddress, network))); const allKeys = allKeysWithNetworks.flat(); try { diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index 51c4b9de402..d68138be447 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -15,14 +15,14 @@ import { Contract } from '@ethersproject/contracts'; import { MaxUint256 } from '@ethersproject/constants'; import { IS_TESTING } from 'react-native-dotenv'; import { Token } from '../entities/tokens'; -import { estimateGasWithPadding, getProviderForNetwork, toHexNoLeadingZeros } from './web3'; +import { estimateGasWithPadding, getProvider, toHexNoLeadingZeros } from './web3'; import { getRemoteConfig } from '@/model/remoteConfig'; import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; import { erc20ABI, ethUnits } from '@/references'; import { ethereumUtils } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; import { logger, RainbowError } from '@/logger'; +import { ChainId } from '@/networks/types'; export enum Field { INPUT = 'INPUT', @@ -234,8 +234,7 @@ export const estimateSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: Quote | null; }): Promise => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = await getProviderForNetwork(network); + const provider = await getProvider({ chainId }); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } @@ -311,8 +310,7 @@ export const estimateCrosschainSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: CrosschainQuote; }): Promise => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = await getProviderForNetwork(network); + const provider = await getProvider({ chainId }); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index 848fc0cd770..00a54669b06 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -93,12 +93,13 @@ export const tokenSearch = async (searchParams: { return tokenSearch.data.data.map(token => { const networkKeys = Object.keys(token.networks); - const network = ethereumUtils.getNetworkFromChainId(Number(networkKeys[0])); + const chainId = Number(networkKeys[0]); + const network = ethereumUtils.getNetworkFromChainId(chainId); return { ...token, - address: token.networks['1']?.address || token.networks[Number(networkKeys[0])]?.address, + chainId, + address: token.networks['1']?.address || token.networks[chainId]?.address, network, - chainId: searchParams.chainId, mainnet_address: token.networks['1']?.address, }; }); diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 23996995cf2..75c1dd85022 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -10,7 +10,6 @@ import { startsWith } from 'lodash'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AssetType, NewTransaction, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers/networkTypes'; import { isUnstoppableAddressFormat } from '@/helpers/validators'; import { ARBITRUM_ETH_ADDRESS, @@ -36,17 +35,16 @@ import { import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; import { IS_IOS, RPC_PROXY_API_KEY, RPC_PROXY_BASE_URL } from '@/env'; -import { getNetworkObj, getNetworkObject } from '@/networks'; +import { getNetworkObject } from '@/networks'; import store from '@/redux/store'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum TokenStandard { ERC1155 = 'ERC1155', ERC721 = 'ERC721', } -export const networkProviders = new Map(); +export const chainsProviders = new Map(); /** * Creates an rpc endpoint for a given chain id using the Rainbow rpc proxy. @@ -73,30 +71,29 @@ export const proxyRpcEndpoint = (chainId: number, customEndpoint?: string) => { }`; } else { if (customEndpoint) return customEndpoint; - const network = ethereumUtils.getNetworkFromChainId(chainId); - switch (network) { - case Network.arbitrum: + switch (chainId) { + case ChainId.arbitrum: return arbitrum_mainnet_rpc; - case Network.goerli: + case ChainId.goerli: return ethereum_goerli_rpc; - case Network.optimism: + case ChainId.optimism: return optimism_mainnet_rpc; - case Network.polygon: + case ChainId.polygon: return polygon_mainnet_rpc; - case Network.base: + case ChainId.base: return base_mainnet_rpc; - case Network.bsc: + case ChainId.bsc: return bsc_mainnet_rpc; - case Network.zora: + case ChainId.zora: return zora_mainnet_rpc; - case Network.avalanche: + case ChainId.avalanche: return avalanche_mainnet_rpc; - case Network.blast: + case ChainId.blast: return blast_mainnet_rpc; - case Network.degen: + case ChainId.degen: return degen_mainnet_rpc; - case Network.gnosis: - case Network.mainnet: + case ChainId.gnosis: + case ChainId.mainnet: default: return ethereum_mainnet_rpc; } @@ -119,7 +116,7 @@ type GasParamsInput = { gasPrice: BigNumberish } & { /** * The input data provied to `getTxDetails`. */ -type TransactionDetailsInput = Pick & +type TransactionDetailsInput = Pick & Pick & GasParamsInput; @@ -130,7 +127,7 @@ type TransactionDetailsReturned = { data?: TransactionRequest['data']; from?: TransactionRequest['from']; gasLimit?: string; - network?: Network | string; + chainId?: ChainId | string; to?: TransactionRequest['to']; value?: TransactionRequest['value']; nonce?: TransactionRequest['nonce']; @@ -150,23 +147,13 @@ type NewTransactionNonNullable = { */ export let web3Provider: StaticJsonRpcProvider = null as unknown as StaticJsonRpcProvider; -/** - * @desc Checks whether or not a `Network | string` union type should be - * treated as a `Network` based on its prefix, as opposed to a `string` type. - * @param network The network to check. - * @return A type predicate of `network is Network`. - */ -const isNetworkEnum = (network: Network | string): network is Network => { - return !network.startsWith('http://'); -}; - /** * @desc Sets a different web3 provider. * @param network The network to set. * @return A promise that resolves with an Ethers Network when the provider is ready. */ -export const web3SetHttpProvider = async (network: Network | string): Promise => { - web3Provider = await getProviderForNetwork(network); +export const web3SetHttpProvider = async (chainId: ChainId): Promise => { + web3Provider = await getProvider({ chainId }); return web3Provider.ready; }; @@ -193,67 +180,37 @@ export const isHardHat = (providerUrl: string): boolean => { * @param network The network to check. * @return Whether or not the network is a testnet. */ -export const isTestnetNetwork = (network: Network): boolean => { - return getNetworkObj(network as Network).networkType === 'testnet'; +export const isTestnetChain = ({ chainId }: { chainId: ChainId }): boolean => { + return getNetworkObject({ chainId }).networkType === 'testnet'; }; // shoudl figure out better way to include this in networks export const getFlashbotsProvider = async () => { return new StaticJsonRpcProvider( proxyRpcEndpoint( - 1, + ChainId.mainnet, 'https://rpc.flashbots.net/?hint=hash&builder=flashbots&builder=f1b.io&builder=rsync&builder=beaverbuild.org&builder=builder0x69&builder=titan&builder=eigenphi&builder=boba-builder' - ), - Network.mainnet + ) ); }; -export const getCachedProviderForNetwork = (network: Network = Network.mainnet): StaticJsonRpcProvider | undefined => { - return networkProviders.get(network); +export const getCachedProviderForNetwork = (chainId: ChainId = ChainId.mainnet): StaticJsonRpcProvider | undefined => { + return chainsProviders.get(chainId); }; -/** - * @desc Gets or constructs a web3 provider for the specified network. - * @param network The network as a `Network` or string. - * @return The provider for the network. - */ -export const getProviderForNetwork = (network: Network | string = Network.mainnet): StaticJsonRpcProvider => { - const isSupportedNetwork = isNetworkEnum(network); - const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; +export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { + const cachedProvider = chainsProviders.get(chainId); - if (isSupportedNetwork && cachedProvider) { + if (cachedProvider) { return cachedProvider; } - if (!isSupportedNetwork) { - const provider = new StaticJsonRpcProvider(network, Network.mainnet); - networkProviders.set(Network.mainnet, provider); - return provider; - } else { - const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); - networkProviders.set(network, provider); - return provider; - } -}; - -export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { - const network = getNetworkFromChainId(chainId); - const isSupportedNetwork = isNetworkEnum(network); - const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; + const networkObject = getNetworkObject({ chainId }); - if (isSupportedNetwork && cachedProvider) { - return cachedProvider; - } + const provider = new StaticJsonRpcProvider(networkObject.rpc(), networkObject.id); + chainsProviders.set(chainId, provider); - if (!isSupportedNetwork) { - const provider = new StaticJsonRpcProvider(network, Network.mainnet); - networkProviders.set(Network.mainnet, provider); - return provider; - } else { - const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); - networkProviders.set(network, provider); - return provider; - } + return provider; }; /** @@ -261,8 +218,8 @@ export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProv * @returns boolean: `true` if connected to Hardhat. */ export const getIsHardhatConnected = (): boolean => { - const currentNetwork = store.getState().settings.network; - const currentProviderUrl = getCachedProviderForNetwork(currentNetwork)?.connection?.url; + const currentChainId = store.getState().settings.chainId; + const currentProviderUrl = getCachedProviderForNetwork(currentChainId)?.connection?.url; const connectedToHardhat = !!currentProviderUrl && isHardHat(currentProviderUrl); return connectedToHardhat; }; @@ -496,8 +453,8 @@ export const getTransactionCount = async (address: string): Promise & GasParamsInput): GasParamsReturned => { - return getNetworkObj(transaction.network).gas.gasType === 'legacy' +export const getTransactionGasParams = (transaction: Pick & GasParamsInput): GasParamsReturned => { + return getNetworkObject({ chainId: transaction.chainId }).gas.gasType === 'legacy' ? { gasPrice: toHex(transaction.gasPrice), } @@ -573,7 +530,7 @@ export const resolveNameOrAddress = async (nameOrAddress: string): Promise ): Promise => { const recipient = await resolveNameOrAddress(transaction.to); @@ -608,7 +565,7 @@ export const getTransferNftTransaction = async ( data, from, gasLimit: transaction.gasLimit?.toString(), - network: transaction.network, + chainId: transaction.chainId, nonce, to: contractAddress, ...gasParams, @@ -624,7 +581,7 @@ export const getTransferNftTransaction = async ( export const getTransferTokenTransaction = async ( transaction: Pick< NewTransactionNonNullable, - 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'network' | 'maxFeePerGas' | 'maxPriorityFeePerGas' + 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'chainId' | 'maxFeePerGas' | 'maxPriorityFeePerGas' > ): Promise => { const value = convertAmountToRawAmount(transaction.amount, transaction.asset.decimals); @@ -635,7 +592,7 @@ export const getTransferTokenTransaction = async ( data, from: transaction.from, gasLimit: transaction.gasLimit?.toString(), - network: transaction.network, + chainId: transaction.chainId, to: transaction.asset.address, ...gasParams, }; @@ -708,10 +665,10 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd const lowercasedContractAddress = asset.asset_contract.address.toLowerCase(); const standard = asset.asset_contract?.schema_name; let data: string | undefined; - if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.network === Network.mainnet) { + if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { const transferMethod = smartContractMethods.token_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); - } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.network === Network.mainnet) { + } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { const transferMethod = smartContractMethods.punk_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); } else if (standard === TokenStandard.ERC1155) { @@ -740,7 +697,7 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd * @param [{address, amount, asset, gasLimit, recipient}] The transaction * initialization details. * @param provider The RCP provider to use. - * @param network The network for the transaction + * @param chainId The chainId for the transaction * @return The transaction request. */ export const buildTransaction = async ( @@ -758,7 +715,7 @@ export const buildTransaction = async ( gasLimit?: string; }, provider: StaticJsonRpcProvider | null, - network: Network + chainId: ChainId ): Promise => { const _amount = amount && Number(amount) ? convertAmountToRawAmount(amount, asset.decimals) : estimateAssetBalancePortion(asset); const value = _amount.toString(); @@ -777,7 +734,7 @@ export const buildTransaction = async ( from: address, to: contractAddress, }; - } else if (!isNativeAsset(asset.address, ethereumUtils.getChainIdFromNetwork(network))) { + } else if (!isNativeAsset(asset.address, chainId)) { const transferData = getDataForTokenTransfer(value, _recipient); txData = { data: transferData, @@ -797,7 +754,7 @@ export const buildTransaction = async ( * to `false`. * @param provider If provided, a provider to use instead of the default * cached `web3Provider`. - * @param network The network to use, defaulting to `Network.mainnet`. + * @param chainId The chainId to use, defaulting to `ChainId.mainnet`. * @returns The estimated gas limit. */ export const estimateGasLimit = async ( @@ -814,9 +771,9 @@ export const estimateGasLimit = async ( }, addPadding = false, provider: StaticJsonRpcProvider | null = null, - network: Network = Network.mainnet + chainId: ChainId = ChainId.mainnet ): Promise => { - const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, network); + const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, chainId); if (addPadding) { return estimateGasWithPadding(estimateGasData, null, null, provider); diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index f38c5ad79c2..44dca36510b 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -10,12 +10,11 @@ import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; import { STORAGE_IDS } from '@/model/mmkv'; import { IS_TESTING } from 'react-native-dotenv'; -import { web3SetHttpProvider } from '@/handlers/web3'; import { logger, RainbowError } from '@/logger'; -import networkTypes from '@/helpers/networkTypes'; import { explorerInit } from '@/redux/explorer'; import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const RainbowContext = createContext({}); const storageKey = 'config'; @@ -28,6 +27,7 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { // This value is hold here to prevent JS VM from shutting down // on unmounting all shared values. useSharedValue(0); + const setConnectedToHardhat = useConnectedToHardhatStore.getState().setConnectedToHardhat; const [config, setConfig] = useState>( Object.entries(defaultConfig).reduce((acc, [key, { value }]) => ({ ...acc, [key]: value }), {}) ); @@ -63,17 +63,17 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { const connectToHardhat = useCallback(async () => { try { - const ready = await web3SetHttpProvider('http://127.0.0.1:8545'); - logger.debug('[RainbowContext]: connected to hardhat', { ready }); + setConnectedToHardhat(true); + logger.debug('connected to hardhat'); } catch (e: any) { - await web3SetHttpProvider(networkTypes.mainnet); - logger.error(new RainbowError('[RainbowContext]: error connecting to hardhat'), { + setConnectedToHardhat(false); + logger.error(new RainbowError('error connecting to hardhat'), { message: e.message, }); } dispatch(explorerInit()); Navigation.handleAction(Routes.WALLET_SCREEN, {}); - }, [dispatch]); + }, [dispatch, setConnectedToHardhat]); return ( diff --git a/src/helpers/ens.ts b/src/helpers/ens.ts index 8a1e81f8e7d..f6be6fdf771 100644 --- a/src/helpers/ens.ts +++ b/src/helpers/ens.ts @@ -8,7 +8,7 @@ import { atom } from 'recoil'; import { InlineFieldProps } from '../components/inputs/InlineField'; import { add, addBuffer, convertAmountAndPriceToNativeDisplay, divide, fromWei, handleSignificantDecimals, multiply } from './utilities'; import { ENSRegistrationRecords, EthereumAddress } from '@/entities'; -import { getProviderForNetwork, toHex } from '@/handlers/web3'; +import { getProvider, toHex } from '@/handlers/web3'; import { gweiToWei } from '@/parsers'; import { ENSBaseRegistrarImplementationABI, @@ -25,6 +25,7 @@ import { import { colors } from '@/styles'; import { labelhash } from '@/utils'; import { encodeContenthash, isValidContenthash } from '@/utils/contenthash'; +import { ChainId } from '@/networks/types'; export const ENS_SECONDS_WAIT = 60; export const ENS_SECONDS_PADDING = 5; @@ -367,27 +368,27 @@ export const deprecatedTextRecordFields = { export const ENS_DOMAIN = '.eth'; const getENSRegistrarControllerContract = async (wallet?: Signer, registrarAddress?: string) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(registrarAddress || ensETHRegistrarControllerAddress, ENSETHRegistrarControllerABI, signerOrProvider); }; const getENSPublicResolverContract = async (wallet?: Signer, resolverAddress?: EthereumAddress) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(resolverAddress || ensPublicResolverAddress, ENSPublicResolverABI, signerOrProvider); }; const getENSReverseRegistrarContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensReverseRegistrarAddress, ENSReverseRegistrarABI, signerOrProvider); }; const getENSBaseRegistrarImplementationContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensBaseRegistrarImplementationAddress, ENSBaseRegistrarImplementationABI, signerOrProvider); }; const getENSRegistryContract = async (wallet?: Signer) => { - const signerOrProvider = wallet ?? (await getProviderForNetwork()); + const signerOrProvider = wallet ?? (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensRegistryAddress, ENSRegistryWithFallbackABI, signerOrProvider); }; diff --git a/src/helpers/gas.ts b/src/helpers/gas.ts index 540cf9752ad..69b7a786578 100644 --- a/src/helpers/gas.ts +++ b/src/helpers/gas.ts @@ -1,7 +1,7 @@ -import { Network } from '@/networks/types'; import { memoFn } from '../utils/memoFn'; import { gasUtils } from '@/utils'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; const { GasTrends } = gasUtils; const { FALLING, NO_TREND, RISING, STABLE, SURGING } = GasTrends; @@ -25,8 +25,8 @@ export const getTrendKey = memoFn((trend: number) => { return NO_TREND; }); -export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { - const networkObject = getNetworkObj(txNetwork); +export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { + const networkObject = getNetworkObject({ chainId }); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; @@ -38,8 +38,8 @@ export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, tx } }); -export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { - const networkObject = getNetworkObj(txNetwork); +export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { + const networkObject = getNetworkObject({ chainId }); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index eb9b7a803b2..4a38d19659d 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -5,6 +5,5 @@ export { default as CurrencySelectionTypes } from './currencySelectionTypes'; export { default as ExchangeModalTypes } from './exchangeModalTypes'; export { default as isKeyboardOpen } from './isKeyboardOpen'; export { default as isReanimatedAvailable } from './isReanimatedAvailable'; -export { default as NetworkTypes, Network } from './networkTypes'; export { default as TokenSectionTypes } from './tokenSectionTypes'; export { StatusBarHelper }; diff --git a/src/helpers/networkInfo.ts b/src/helpers/networkInfo.ts index 2bda8506ad8..be81a8bad48 100644 --- a/src/helpers/networkInfo.ts +++ b/src/helpers/networkInfo.ts @@ -1,8 +1,8 @@ -import networkTypes from './networkTypes'; +import { Network } from '@/networks/types'; // TODO: networkInfo is DEPRECATED after the new network support changes const networkInfo = { - [`${networkTypes.mainnet}`]: { + [`${Network.mainnet}`]: { balance_checker_contract_address: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', color: '#0E76FD', disabled: false, @@ -10,9 +10,9 @@ const networkInfo = { faucet_url: null, name: 'Ethereum', gasToken: 'ETH', - value: networkTypes.mainnet, + value: Network.mainnet, }, - [`${networkTypes.goerli}`]: { + [`${Network.goerli}`]: { balance_checker_contract_address: '0xf3352813b612a2d198e437691557069316b84ebe', color: '#f6c343', disabled: false, @@ -21,9 +21,9 @@ const networkInfo = { name: 'Goerli', gasToken: 'ETH', testnet: true, - value: networkTypes.goerli, + value: Network.goerli, }, - [`${networkTypes.arbitrum}`]: { + [`${Network.arbitrum}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#2D374B', disabled: false, @@ -32,9 +32,9 @@ const networkInfo = { layer2: true, name: 'Arbitrum', gasToken: 'ETH', - value: networkTypes.arbitrum, + value: Network.arbitrum, }, - [`${networkTypes.optimism}`]: { + [`${Network.optimism}`]: { balance_checker_contract_address: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', color: '#FF4040', disabled: false, @@ -43,9 +43,9 @@ const networkInfo = { layer2: true, name: 'Optimism', gasToken: 'ETH', - value: networkTypes.optimism, + value: Network.optimism, }, - [`${networkTypes.polygon}`]: { + [`${Network.polygon}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#8247E5', disabled: false, @@ -55,9 +55,9 @@ const networkInfo = { longName: 'Polygon (Matic)', name: 'Polygon', gasToken: 'MATIC', - value: networkTypes.polygon, + value: Network.polygon, }, - [`${networkTypes.bsc}`]: { + [`${Network.bsc}`]: { balance_checker_contract_address: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', color: '#F0B90B', disabled: false, @@ -67,7 +67,7 @@ const networkInfo = { longName: 'Binance Smart Chain', name: 'BSC', gasToken: 'BNB', - value: networkTypes.bsc, + value: Network.bsc, }, }; diff --git a/src/helpers/networkTypes.ts b/src/helpers/networkTypes.ts deleted file mode 100644 index 62e425cda73..00000000000 --- a/src/helpers/networkTypes.ts +++ /dev/null @@ -1,31 +0,0 @@ -export enum Network { - arbitrum = 'arbitrum', - goerli = 'goerli', - mainnet = 'mainnet', - optimism = 'optimism', - polygon = 'polygon', - base = 'base', - bsc = 'bsc', - zora = 'zora', - gnosis = 'gnosis', - avalanche = 'avalanche', - blast = 'blast', - degen = 'degen', -} - -// We need to keep this one until -// we have typescript everywhere -export default { - arbitrum: 'arbitrum' as Network, - goerli: 'goerli' as Network, - mainnet: 'mainnet' as Network, - optimism: 'optimism' as Network, - polygon: 'polygon' as Network, - base: 'base' as Network, - bsc: 'bsc' as Network, - zora: 'zora' as Network, - gnosis: 'gnosis' as Network, - avalanche: 'avalanche' as Network, - blast: 'blast' as Network, - degen: 'degen' as Network, -}; diff --git a/src/helpers/validators.ts b/src/helpers/validators.ts index 72aceda6c26..3016e5c7950 100644 --- a/src/helpers/validators.ts +++ b/src/helpers/validators.ts @@ -1,8 +1,8 @@ import { isValidAddress } from 'ethereumjs-util'; import { memoFn } from '../utils/memoFn'; -import { Network } from './networkTypes'; -import { getProviderForNetwork, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; +import { getProvider, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; import { sanitizeSeedPhrase } from '@/utils/formatters'; +import { ChainId } from '@/networks/types'; // Currently supported Top Level Domains from Unstoppable Domains const supportedUnstoppableDomains = ['888', 'bitcoin', 'blockchain', 'coin', 'crypto', 'dao', 'nft', 'wallet', 'x', 'zil']; @@ -68,7 +68,7 @@ export const checkIsValidAddressOrDomainFormat = (address: any) => { * @return {Boolean} */ export const checkIsValidAddressOrDomain = async (address: any) => { - const provider = getProviderForNetwork(Network.mainnet); + const provider = getProvider({ chainId: ChainId.mainnet }); if (isENSAddressFormat(address)) { try { const resolvedAddress = await provider.resolveName(address); diff --git a/src/helpers/walletConnectNetworks.ts b/src/helpers/walletConnectNetworks.ts index 326399fd897..cb02ab79f2c 100644 --- a/src/helpers/walletConnectNetworks.ts +++ b/src/helpers/walletConnectNetworks.ts @@ -1,12 +1,12 @@ -import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { Network } from '@/networks/types'; +import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; import store from '@/redux/store'; import { showActionSheetWithOptions } from '@/utils'; import * as i18n from '@/languages'; +import { ChainId } from '@/networks/types'; const androidNetworkActions = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => network.name); }; @@ -15,7 +15,7 @@ export const NETWORK_MENU_ACTION_KEY_FILTER = 'switch-to-network-'; export const networksMenuItems = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => ({ actionKey: `${NETWORK_MENU_ACTION_KEY_FILTER}${network.value}`, @@ -76,7 +76,8 @@ export const androidShowNetworksActionSheet = (callback: any) => { (idx: any) => { if (idx !== undefined) { const networkActions = androidNetworkActions(); - const networkObj = RainbowNetworks.find(network => network.name === networkActions[idx]) || getNetworkObj(Network.mainnet); + const networkObj = + RainbowNetworkObjects.find(network => network.name === networkActions[idx]) || getNetworkObject({ chainId: ChainId.mainnet }); callback({ chainId: networkObj.id, network: networkObj.value }); } } diff --git a/src/hooks/charts/useChartInfo.ts b/src/hooks/charts/useChartInfo.ts index 05efb97f142..17b2adf72c8 100644 --- a/src/hooks/charts/useChartInfo.ts +++ b/src/hooks/charts/useChartInfo.ts @@ -1,18 +1,10 @@ import { useNavigation, useRoute } from '@react-navigation/native'; -import { isEmpty } from 'lodash'; -import { useCallback, useEffect, useState } from 'react'; -import isEqual from 'react-fast-compare'; -import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import { useCallbackOne } from 'use-memo-one'; -import { disableCharts } from '../../config/debug'; +import { useCallback } from 'react'; import { DEFAULT_CHART_TYPE } from '../../redux/charts'; import { metadataClient } from '@/graphql'; import { useQuery } from '@tanstack/react-query'; import { createQueryKey } from '@/react-query'; -import { getNetworkObj } from '@/networks'; -import { NetworkProperties } from '@/networks/types'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; const chartTimes = ['hour', 'day', 'week', 'month', 'year'] as const; type ChartTime = (typeof chartTimes)[number]; @@ -23,7 +15,7 @@ const getChartTimeArg = (selected: ChartTime) => export type ChartData = { x: number; y: number }; -const fetchPriceChart = async (time: ChartTime, chainId: NetworkProperties['id'], address: string) => { +const fetchPriceChart = async (time: ChartTime, chainId: ChainId, address: string) => { const priceChart = await metadataClient .priceChart({ address, chainId, ...getChartTimeArg(time) }) .then(d => d.token?.priceCharts[time] as PriceChartTimeData); @@ -33,7 +25,7 @@ const fetchPriceChart = async (time: ChartTime, chainId: NetworkProperties['id'] }, [] as ChartData[]); }; -export const usePriceChart = ({ mainnetAddress, address, network }: { mainnetAddress?: string; address: string; network: Network }) => { +export const usePriceChart = ({ mainnetAddress, address, chainId }: { mainnetAddress?: string; address: string; chainId: ChainId }) => { const { setParams } = useNavigation(); const updateChartType = useCallback( (type: ChartTime) => { @@ -47,12 +39,10 @@ export const usePriceChart = ({ mainnetAddress, address, network }: { mainnetAdd params: any; }>(); const chartType = params?.chartType ?? DEFAULT_CHART_TYPE; - const chainId = getNetworkObj(network).id; - const mainnetChainId = getNetworkObj(Network.mainnet).id; const query = useQuery({ queryFn: async () => { const chart = await fetchPriceChart(chartType, chainId, address); - if (!chart && mainnetAddress) return fetchPriceChart(chartType, mainnetChainId, mainnetAddress); + if (!chart && mainnetAddress) return fetchPriceChart(chartType, ChainId.mainnet, mainnetAddress); return chart || null; }, queryKey: createQueryKey('price chart', { address, chainId, chartType }), diff --git a/src/hooks/charts/useChartThrottledPoints.ts b/src/hooks/charts/useChartThrottledPoints.ts index a88dc3b111c..713f6fe8122 100644 --- a/src/hooks/charts/useChartThrottledPoints.ts +++ b/src/hooks/charts/useChartThrottledPoints.ts @@ -98,7 +98,7 @@ export default function useChartThrottledPoints({ updateChartType, } = usePriceChart({ address: asset.address, - network: asset.network, + chainId: asset.chainId, mainnetAddress: asset?.mainnet_address || asset?.mainnetAddress, }); const [throttledPoints, setThrottledPoints] = useState(() => traverseData({ nativePoints: [], points: [] }, chart)); diff --git a/src/hooks/useAccountAsset.ts b/src/hooks/useAccountAsset.ts index 146e6412ff9..2d2bccc2699 100644 --- a/src/hooks/useAccountAsset.ts +++ b/src/hooks/useAccountAsset.ts @@ -1,9 +1,10 @@ +import { NativeCurrencyKey } from '@/entities'; import useAccountSettings from './useAccountSettings'; import { parseAssetNative } from '@/parsers'; import { useUserAsset } from '@/resources/assets/useUserAsset'; // this is meant to be used for assets contained in the current wallet -export default function useAccountAsset(uniqueId: string, nativeCurrency: string | undefined = undefined) { +export default function useAccountAsset(uniqueId: string, nativeCurrency: NativeCurrencyKey | undefined = undefined) { const { data: accountAsset } = useUserAsset(uniqueId); // this is temporary for FastBalanceCoinRow to make a tiny bit faster diff --git a/src/hooks/useAccountTransactions.ts b/src/hooks/useAccountTransactions.ts index f7dc48cdac1..9fab4b41ae9 100644 --- a/src/hooks/useAccountTransactions.ts +++ b/src/hooks/useAccountTransactions.ts @@ -8,9 +8,9 @@ import { useTheme } from '@/theme'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import { RainbowTransaction } from '@/entities'; import { pendingTransactionsStore, usePendingTransactionsStore } from '@/state/pendingTransactions'; -import { RainbowNetworks } from '@/networks'; -import { Network } from '@/networks/types'; +import { RainbowNetworkObjects } from '@/networks'; import { nonceStore } from '@/state/nonces'; +import { ChainId } from '@/networks/types'; export const NOE_PAGE = 30; @@ -34,16 +34,16 @@ export default function useAccountTransactions() { .filter(t => t.from?.toLowerCase() === accountAddress?.toLowerCase()) .reduce( (latestTxMap, currentTx) => { - const currentNetwork = currentTx?.network; - if (currentNetwork) { - const latestTx = latestTxMap.get(currentNetwork); + const currentChainId = currentTx?.chainId; + if (currentChainId) { + const latestTx = latestTxMap.get(currentChainId); if (!latestTx) { - latestTxMap.set(currentNetwork, currentTx); + latestTxMap.set(currentChainId, currentTx); } } return latestTxMap; }, - new Map(RainbowNetworks.map(chain => [chain.value, null as RainbowTransaction | null])) + new Map(RainbowNetworkObjects.map(chain => [chain.id, null as RainbowTransaction | null])) ); watchForPendingTransactionsReportedByRainbowBackend({ currentAddress: accountAddress, @@ -56,17 +56,17 @@ export default function useAccountTransactions() { latestTransactions, }: { currentAddress: string; - latestTransactions: Map; + latestTransactions: Map; }) { const { setNonce } = nonceStore.getState(); const { setPendingTransactions, pendingTransactions: storePendingTransactions } = pendingTransactionsStore.getState(); const pendingTransactions = storePendingTransactions[currentAddress] || []; - const networks = RainbowNetworks.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); + const networks = RainbowNetworkObjects.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); for (const network of networks) { - const latestTxConfirmedByBackend = latestTransactions.get(network.value); + const latestTxConfirmedByBackend = latestTransactions.get(network.id); if (latestTxConfirmedByBackend) { const latestNonceConfirmedByBackend = latestTxConfirmedByBackend.nonce || 0; - const [latestPendingTx] = pendingTransactions.filter(tx => tx?.network === network.value); + const [latestPendingTx] = pendingTransactions.filter(tx => tx?.chainId === network.id); let currentNonce; if (latestPendingTx) { @@ -79,7 +79,7 @@ export default function useAccountTransactions() { setNonce({ address: currentAddress, - network: network.value, + chainId: network.id, currentNonce, latestConfirmedNonce: latestNonceConfirmedByBackend, }); @@ -88,7 +88,7 @@ export default function useAccountTransactions() { const updatedPendingTransactions = pendingTransactions?.filter(tx => { const txNonce = tx.nonce || 0; - const latestTx = latestTransactions.get(tx.network); + const latestTx = latestTransactions.get(tx.chainId); const latestTxNonce = latestTx?.nonce || 0; // still pending or backend is not returning confirmation yet // if !latestTx means that is the first tx of the wallet diff --git a/src/hooks/useAdditionalAssetData.ts b/src/hooks/useAdditionalAssetData.ts index cf8b8569890..df16a1dcd74 100644 --- a/src/hooks/useAdditionalAssetData.ts +++ b/src/hooks/useAdditionalAssetData.ts @@ -1,9 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { NativeCurrencyKey } from '@/entities'; -import { Network } from '@/networks/types'; import { metadataClient } from '@/graphql'; -import { ethereumUtils } from '@/utils'; import { Token } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/networks/types'; // Types type TokenMetadata = Pick< @@ -14,21 +13,20 @@ type TokenMetadata = Pick< // Types for the query arguments type AdditionalAssetDataArgs = { address: string; - network: Network; + chainId: ChainId; currency: NativeCurrencyKey; }; // Query Key function -const createAdditionalAssetDataQueryKey = ({ address, network, currency }: AdditionalAssetDataArgs) => [ +const createAdditionalAssetDataQueryKey = ({ address, chainId, currency }: AdditionalAssetDataArgs) => [ 'additionalAssetData', address, - network, + chainId, currency, ]; // Refactor the getAdditionalAssetData function to accept the new parameters -async function getAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs): Promise { - const chainId = ethereumUtils.getChainIdFromNetwork(network); +async function getAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs): Promise { const data = await metadataClient.tokenMetadata({ address, chainId, @@ -42,12 +40,12 @@ async function getAdditionalAssetData({ address, network, currency }: Additional } // Usage of the useQuery hook -export default function useAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs) { +export default function useAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs) { return useQuery( - createAdditionalAssetDataQueryKey({ address, network, currency }), - () => getAdditionalAssetData({ address, network, currency }), + createAdditionalAssetDataQueryKey({ address, chainId, currency }), + () => getAdditionalAssetData({ address, chainId, currency }), { - enabled: !!address && !!network && !!currency, // Ensure all parameters are provided + enabled: !!address && !!chainId && !!currency, // Ensure all parameters are provided } ); } diff --git a/src/hooks/useAsset.ts b/src/hooks/useAsset.ts index 9a5e3180397..91a77b25563 100644 --- a/src/hooks/useAsset.ts +++ b/src/hooks/useAsset.ts @@ -4,11 +4,12 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useSelector } from 'react-redux'; import { AppState } from '@/redux/store'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { Address } from 'viem'; // To fetch an asset from account assets, // generic assets, and uniqueTokens -export default function useAsset({ address, chainId }: { address: string; chainId: ChainId }) { +export default function useAsset({ address, chainId }: { address: Address; chainId: ChainId }) { const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); const uniqueId = getUniqueId(address, chainId); const accountAsset = useAccountAsset(uniqueId); diff --git a/src/hooks/useContacts.ts b/src/hooks/useContacts.ts index 277a5f77e1c..73aaac697f7 100644 --- a/src/hooks/useContacts.ts +++ b/src/hooks/useContacts.ts @@ -2,7 +2,6 @@ import { sortBy, values } from 'lodash'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createSelector } from 'reselect'; -import networkTypes from '../helpers/networkTypes'; import { contactsAddOrUpdate, removeContact } from '../redux/contacts'; import { AppState } from '@/redux/store'; @@ -16,9 +15,6 @@ const contactsSelector = createSelector( export default function useContacts() { const dispatch = useDispatch(); - const { network } = useSelector(({ settings: { network } }: AppState) => ({ - network, - })); const { contacts, sortedContacts } = useSelector(contactsSelector); const onAddOrUpdateContacts = useCallback( @@ -29,13 +25,9 @@ export default function useContacts() { const onRemoveContact = useCallback((data: string) => dispatch(removeContact(data)), [dispatch]); - const filteredContacts = sortedContacts.filter(contact => - contact.network === network || (!contact.network && network === networkTypes.mainnet) ? contact : false - ); - return { contacts, - filteredContacts, + filteredContacts: sortedContacts, onAddOrUpdateContacts, onRemoveContact, sortedContacts, diff --git a/src/hooks/useENSRegistrationActionHandler.ts b/src/hooks/useENSRegistrationActionHandler.ts index 473f059b441..6216645ff4c 100644 --- a/src/hooks/useENSRegistrationActionHandler.ts +++ b/src/hooks/useENSRegistrationActionHandler.ts @@ -12,20 +12,20 @@ import { Records, RegistrationParameters } from '@/entities'; import { fetchResolver } from '@/handlers/ens'; import { saveNameFromLabelhash } from '@/handlers/localstorage/ens'; import { uploadImage } from '@/handlers/pinata'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { ENS_DOMAIN, generateSalt, getRentPrice, REGISTRATION_STEPS } from '@/helpers/ens'; import { loadWallet } from '@/model/wallet'; import { timeUnits } from '@/references'; import Routes from '@/navigation/routesNames'; import { labelhash } from '@/utils'; import { getNextNonce } from '@/state/nonces'; -import { Network } from '@/networks/types'; import { Hex } from 'viem'; import { executeENSRap } from '@/raps/actions/ens'; import store from '@/redux/store'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { noop } from 'lodash'; import { logger, RainbowError } from '@/logger'; +import { ChainId } from '@/networks/types'; // Generic type for action functions type ActionFunction

= (...params: P) => Promise; @@ -115,7 +115,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step }; (() => { - provider = getProviderForNetwork(); + provider = getProvider({ chainId: ChainId.mainnet }); provider.on('block', updateAvatars); })(); return () => { @@ -128,7 +128,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { updateAvatarsOnNextBlock.current = true; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -139,7 +139,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const salt = generateSalt(); const [nonce, rentPrice] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), getRentPrice(registrationParameters.name.replace(ENS_DOMAIN, ''), duration), ]); @@ -186,7 +186,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name, duration } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -196,7 +196,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, rentPrice, changedRecords] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), getRentPrice(name.replace(ENS_DOMAIN, ''), duration), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, @@ -225,7 +225,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -234,7 +234,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const rentPrice = await getRentPrice(name.replace(ENS_DOMAIN, ''), duration); const registerEnsRegistrationParameters: ENSActionParameters = { @@ -253,7 +253,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -262,7 +262,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const registerEnsRegistrationParameters: ENSActionParameters = { ...formatENSActionParams(registrationParameters), @@ -278,7 +278,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const setRecordsAction: ActionTypes[typeof REGISTRATION_STEPS.EDIT] = useCallback( async (callback = noop) => { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -288,7 +288,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, changedRecords, resolver] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, header: coverMetadata, @@ -316,7 +316,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async ({ clearRecords, records, name, setAddress, toAddress, transferControl, wallet: walletOverride }, callback = noop) => { let wallet = walletOverride; if (!wallet) { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -326,7 +326,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const transferEnsParameters: ENSActionParameters = { ...formatENSActionParams({ diff --git a/src/hooks/useENSRegistrationCosts.ts b/src/hooks/useENSRegistrationCosts.ts index d1143975b3e..75fc975a023 100644 --- a/src/hooks/useENSRegistrationCosts.ts +++ b/src/hooks/useENSRegistrationCosts.ts @@ -24,11 +24,10 @@ import { REGISTRATION_MODES, REGISTRATION_STEPS, } from '@/helpers/ens'; -import { Network } from '@/helpers/networkTypes'; import { add, addBuffer, addDisplay, fromWei, greaterThanOrEqualTo, multiply } from '@/helpers/utilities'; import { ethUnits, timeUnits } from '@/references'; import { ethereumUtils, gasUtils } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; enum QUERY_KEYS { GET_COMMIT_GAS_LIMIT = 'GET_COMMIT_GAS_LIMIT', @@ -93,7 +92,7 @@ export default function useENSRegistrationCosts({ const rentPriceInWei = rentPrice?.wei?.toString(); const checkIfSufficientEth = useCallback((wei: string) => { - const nativeAsset = ethereumUtils.getNetworkNativeAsset(ChainId.mainnet); + const nativeAsset = ethereumUtils.getNetworkNativeAsset({ chainId: ChainId.mainnet }); const balanceAmount = nativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(wei); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); @@ -248,7 +247,7 @@ export default function useENSRegistrationCosts({ ); const estimatedFee = useMemo(() => { - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); const { gasFeeParamsBySpeed, currentBaseFee } = gasFeeParams; let estimatedGasLimit = ''; @@ -334,7 +333,7 @@ export default function useENSRegistrationCosts({ const data = useMemo(() => { const rentPricePerYearInWei = rentPrice?.perYear?.wei?.toString(); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); if (rentPricePerYearInWei) { const rentPriceInWei = multiply(rentPricePerYearInWei, yearsDuration); diff --git a/src/hooks/useENSRegistrationStepHandler.tsx b/src/hooks/useENSRegistrationStepHandler.tsx index 77653dca970..5110a579106 100644 --- a/src/hooks/useENSRegistrationStepHandler.tsx +++ b/src/hooks/useENSRegistrationStepHandler.tsx @@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'; import usePrevious from './usePrevious'; import { useENSRegistration, useInterval } from '.'; import { RegistrationParameters } from '@/entities'; -import { getProviderForNetwork, isHardHat, web3Provider } from '@/handlers/web3'; +import { getProvider, isHardHat, web3Provider } from '@/handlers/web3'; import { ENS_SECONDS_PADDING, ENS_SECONDS_WAIT, @@ -14,6 +14,7 @@ import { REGISTRATION_STEPS, } from '@/helpers/ens'; import { updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; +import { ChainId } from '@/networks/types'; const checkRegisterBlockTimestamp = async ({ registrationParameters, @@ -25,7 +26,7 @@ const checkRegisterBlockTimestamp = async ({ isTestingHardhat: boolean; }) => { try { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const block = await provider.getBlock('latest'); const msBlockTimestamp = getBlockMsTimestamp(block); const secs = differenceInSeconds(msBlockTimestamp, registrationParameters?.commitTransactionConfirmedAt || msBlockTimestamp); @@ -90,7 +91,7 @@ export default function useENSRegistrationStepHandler(observer = true) { const watchCommitTransaction = useCallback(async () => { if (observer) return; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); let confirmed = false; const tx = await provider.getTransaction(commitTransactionHash || ''); if (!tx?.blockHash) return confirmed; diff --git a/src/hooks/useENSSearch.ts b/src/hooks/useENSSearch.ts index 29cc46b6722..b492ad0749f 100644 --- a/src/hooks/useENSSearch.ts +++ b/src/hooks/useENSSearch.ts @@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useAccountSettings, useENSLocalTransactions } from '.'; import { fetchRegistrationDate } from '@/handlers/ens'; import { ENS_DOMAIN, formatRentPrice, getAvailable, getENSRegistrarControllerContract, getNameExpires, getRentPrice } from '@/helpers/ens'; -import { Network } from '@/helpers/networkTypes'; import { timeUnits } from '@/references'; import { ethereumUtils, validateENS } from '@/utils'; +import { ChainId } from '@/networks/types'; const formatTime = (timestamp: string, abbreviated = true) => { const style = abbreviated ? 'MMM d, y' : 'MMMM d, y'; @@ -51,7 +51,7 @@ export default function useENSSearch({ yearsDuration = 1, name: inputName }: { y } const [isAvailable, rentPrice] = await Promise.all([getAvailable(name, contract), getRentPrice(name, duration, contract)]); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); const formattedRentPrice = formatRentPrice(rentPrice, yearsDuration, nativeCurrency, nativeAssetPrice); if (isAvailable) { diff --git a/src/hooks/useGas.ts b/src/hooks/useGas.ts index 224e25ebc8e..4c6c7f3c2ce 100644 --- a/src/hooks/useGas.ts +++ b/src/hooks/useGas.ts @@ -13,7 +13,6 @@ import { ParsedAddressAsset, SelectedGasFee, } from '@/entities'; -import networkTypes, { Network } from '@/helpers/networkTypes'; import { fromWei, greaterThan, greaterThanOrEqualTo } from '@/helpers/utilities'; import { gasPricesStartPolling, @@ -24,25 +23,24 @@ import { gasUpdateTxFee, } from '@/redux/gas'; import { ethereumUtils } from '@/utils'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { BNB_MAINNET_ADDRESS, ETH_ADDRESS, MATIC_MAINNET_ADDRESS } from '@/references'; import useAccountSettings from './useAccountSettings'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; -const checkSufficientGas = (txFee: LegacyGasFee | GasFee, network: Network, nativeAsset?: ParsedAddressAsset) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkSufficientGas = (txFee: LegacyGasFee | GasFee, chainId: ChainId, nativeAsset?: ParsedAddressAsset) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; - const chainId = ethereumUtils.getChainIdFromNetwork(network); - const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset(chainId); + const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset({ chainId }); const balanceAmount = networkNativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(txFeeValue?.value?.amount); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); return isSufficientGas; }; -const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -50,8 +48,8 @@ const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, net return isValidGas; }; -const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -88,7 +86,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit: string; selectedGasFee: SelectedGasFee; selectedGasFeeOption: string; - txNetwork: Network; + chainId: ChainId; l1GasFeeOptimism: string; } = useSelector( ({ @@ -100,7 +98,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit, l1GasFeeOptimism, selectedGasFee, - txNetwork, + chainId, }, }: AppState) => ({ currentBlockParams, @@ -111,29 +109,29 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss l1GasFeeOptimism, selectedGasFee, selectedGasFeeOption: selectedGasFee.option, - txNetwork, + chainId, }) ); const prevSelectedGasFee = usePrevious(gasData?.selectedGasFee); const isSufficientGas = useMemo( - () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset), - [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset] + () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset), + [gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset] ); const isValidGas = useMemo( - () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), - [gasData?.selectedGasFee, gasData?.txNetwork] + () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), + [gasData?.selectedGasFee, gasData?.chainId] ); const isGasReady = useMemo( - () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), - [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork] + () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), + [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId] ); const startPollingGasFees = useCallback( - (network = networkTypes.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(network, flashbots)), + (chainId = ChainId.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(chainId, flashbots)), [dispatch] ); const stopPollingGasFees = useCallback(() => dispatch(gasPricesStopPolling()), [dispatch]); @@ -153,12 +151,12 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss const getTotalGasPrice = useCallback(() => { const txFee = gasData?.selectedGasFee?.gasFee; - const isLegacyGasNetwork = getNetworkObj(gasData?.txNetwork).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObject({ chainId: gasData?.chainId }).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; const txFeeAmount = fromWei(txFeeValue?.value?.amount); return txFeeAmount; - }, [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork]); + }, [gasData?.selectedGasFee?.gasFee, gasData?.chainId]); return { isGasReady, diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 828dee3225c..d2fce5085a5 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -17,7 +17,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { PROFILES, useExperimentalFlag } from '@/config'; import { fetchReverseRecord } from '@/handlers/ens'; -import { getProviderForNetwork, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; +import { getProvider, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; import { isENSAddressFormat, isUnstoppableAddressFormat, isValidWallet } from '@/helpers/validators'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { walletInit } from '@/model/wallet'; @@ -31,6 +31,7 @@ import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils'; import walletBackupTypes from '@/helpers/walletBackupTypes'; +import { ChainId } from '@/networks/types'; export default function useImportingWallet({ showImportModal = true } = {}) { const { accountAddress } = useAccountSettings(); @@ -122,7 +123,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { // Validate ENS if (isENSAddressFormat(input)) { try { - const web3Provider = getProviderForNetwork(); + const web3Provider = getProvider({ chainId: ChainId.mainnet }); const [address, avatar] = await Promise.all([ web3Provider.resolveName(input), !avatarUrl && profilesEnabled && fetchENSAvatar(input, { swallowError: true }), diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index 4c7323bcf24..bda2d6f20f5 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -1,5 +1,5 @@ import lang from 'i18n-js'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback } from 'react'; import { Linking } from 'react-native'; import { ImageOrVideo } from 'react-native-image-crop-picker'; import { useDispatch } from 'react-redux'; @@ -139,7 +139,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { const isReadOnly = isReadOnlyWallet && !enableActionsOnReadOnlyWallet; const isENSProfile = profilesEnabled && profileEnabled && isOwner; - const isZeroETH = isZero(accountAsset?.balance?.amount); + const isZeroETH = isZero(accountAsset?.balance?.amount || 0); const callback = useCallback( async (buttonIndex: number) => { diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts index f6789c38b76..b077d816a9b 100644 --- a/src/hooks/usePriceImpactDetails.ts +++ b/src/hooks/usePriceImpactDetails.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import { SwappableAsset } from '@/entities'; -import { Network } from '@/helpers'; import { useTheme } from '@/theme'; import { @@ -14,9 +13,9 @@ import { } from '@/helpers/utilities'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; -import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum SwapPriceImpactType { none = 'none', diff --git a/src/hooks/useRainbowFee.js b/src/hooks/useRainbowFee.js index b80d489a6be..6cc0d416bef 100644 --- a/src/hooks/useRainbowFee.js +++ b/src/hooks/useRainbowFee.js @@ -47,7 +47,7 @@ export default function useRainbowFee({ tradeDetails, chainId }) { useEffect(() => { const getNativeAsset = async () => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); setNativeAsset(nativeAsset); }; !nativeAsset && getNativeAsset(); diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 59b1320d29b..16c8bf2149d 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -18,7 +18,7 @@ import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type swapCurrencyListType = | 'verifiedAssets' @@ -86,7 +86,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); const [verifiedAssets, setVerifiedAssets] = useState([]); - const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ [ChainId.mainnet]: [], @@ -387,7 +386,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -430,7 +429,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), diff --git a/src/hooks/useSwapCurrencyHandlers.ts b/src/hooks/useSwapCurrencyHandlers.ts index 3fce074a3cb..9b835ff7a2d 100644 --- a/src/hooks/useSwapCurrencyHandlers.ts +++ b/src/hooks/useSwapCurrencyHandlers.ts @@ -119,8 +119,6 @@ export default function useSwapCurrencyHandlers({ } : null; - // prefetchExternalToken({address: newInputCurrency.address, network: newInputCurrency.network, currency: nativeCurrency}) - dispatch(updateSwapInputCurrency(newInputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newInputCurrency); @@ -136,7 +134,6 @@ export default function useSwapCurrencyHandlers({ } : null; - // prefetchExternalToken({address: newOutputCurrency.address, network: newOutputCurrency.network, currency: nativeCurrency}) dispatch(updateSwapOutputCurrency(newOutputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newOutputCurrency); diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index 12b8cf47df0..02bd432f880 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -6,22 +6,20 @@ import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; -import { RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; +import { RainbowToken, TokenSearchTokenListId } from '@/entities'; import { swapSearch } from '@/handlers/tokenSearch'; -import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; +import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; -import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; import { logger } from '@/logger'; +import { ChainId, Network } from '@/networks/types'; -const MAINNET_CHAINID = 1; type swapCurrencyListType = | 'verifiedAssets' | 'highLiquidityAssets' @@ -30,13 +28,10 @@ type swapCurrencyListType = | 'curatedAssets' | 'importedAssets'; -type CrosschainVerifiedAssets = { - [Network.mainnet]: RT[]; - [Network.optimism]: RT[]; - [Network.polygon]: RT[]; - [Network.bsc]: RT[]; - [Network.arbitrum]: RT[]; -}; +type CrosschainVerifiedAssets = Record< + ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.bsc | ChainId.arbitrum, + RainbowToken[] +>; const abcSort = (list: any[], key?: string) => { return list.sort((a, b) => { @@ -47,16 +42,16 @@ const abcSort = (list: any[], key?: string) => { const searchCurrencyList = async (searchParams: { chainId: number; fromChainId?: number | ''; - searchList: RT[] | TokenSearchTokenListId; + searchList: RainbowToken[] | TokenSearchTokenListId; query: string; }) => { const { searchList, query, chainId, fromChainId } = searchParams; const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - const keys: (keyof RT)[] = isAddress ? ['address'] : ['symbol', 'name']; + const keys: (keyof RainbowToken)[] = isAddress ? ['address'] : ['symbol', 'name']; const formattedQuery = isAddress ? addHexPrefix(query).toLowerCase() : query; if (typeof searchList === 'string') { const threshold = isAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'; - if (chainId === MAINNET_CHAINID && !formattedQuery && searchList !== 'verifiedAssets') { + if (chainId === ChainId.mainnet && !formattedQuery && searchList !== 'verifiedAssets') { return []; } return swapSearch({ @@ -74,10 +69,10 @@ const searchCurrencyList = async (searchParams: { } }; -const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINID, isDiscover = false) => { +const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainnet, isDiscover = false) => { const previousChainId = usePrevious(searchChainId); - const searching = useMemo(() => searchQuery !== '' || MAINNET_CHAINID !== searchChainId, [searchChainId, searchQuery]); + const searching = useMemo(() => searchQuery !== '' || ChainId.mainnet !== searchChainId, [searchChainId, searchQuery]); const { favorites: favoriteAddresses, favoritesMetadata: favoriteMap } = useFavorites(); @@ -85,24 +80,24 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const unfilteredFavorites = Object.values(favoriteMap); const [loading, setLoading] = useState(true); - const [favoriteAssets, setFavoriteAssets] = useState([]); - const [importedAssets, setImportedAssets] = useState([]); - const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); - const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); - const [verifiedAssets, setVerifiedAssets] = useState([]); + const [favoriteAssets, setFavoriteAssets] = useState([]); + const [importedAssets, setImportedAssets] = useState([]); + const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); + const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); + const [verifiedAssets, setVerifiedAssets] = useState([]); const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ - [Network.mainnet]: [], - [Network.optimism]: [], - [Network.polygon]: [], - [Network.bsc]: [], - [Network.arbitrum]: [], + [ChainId.mainnet]: [], + [ChainId.optimism]: [], + [ChainId.polygon]: [], + [ChainId.bsc]: [], + [ChainId.arbitrum]: [], }); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyNetwork = usePrevious(inputCurrency?.network); - const inputChainId = useMemo(() => ethereumUtils.getChainIdFromNetwork(inputCurrency?.network), [inputCurrency?.network]); + const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); + const inputChainId = inputCurrency?.chainId; const isCrosschainSearch = useMemo(() => { if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { return true; @@ -114,17 +109,16 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI [favoriteAddresses] ); const handleSearchResponse = useCallback( - (tokens: RT[], crosschainNetwork?: Network) => { + (tokens: RainbowToken[], chainId?: ChainId) => { // These transformations are necessary for L2 tokens to match our spec - const activeChainId = crosschainNetwork ? ethereumUtils.getChainIdFromNetwork(crosschainNetwork) : searchChainId; + const activeChainId = chainId ? chainId : searchChainId; return (tokens || []) .map(token => { token.address = token.networks?.[activeChainId]?.address || token.address; - const network = crosschainNetwork || ethereumUtils.getNetworkFromChainId(searchChainId); - token.network = network; - if (token.networks[MAINNET_CHAINID]) { - token.mainnet_address = token.networks[MAINNET_CHAINID].address; + token.chainId = activeChainId; + if (token.networks[ChainId.mainnet]) { + token.mainnet_address = token.networks[ChainId.mainnet].address; } token.uniqueId = getUniqueId(token.address, activeChainId); @@ -159,6 +153,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI return { ...token, network: Network.mainnet, + chainId: ChainId.mainnet, uniqueId: getUniqueId(token.address, ChainId.mainnet), }; }); @@ -175,15 +170,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }, [searchChainId, searchQuery, searching, unfilteredFavorites]); const getImportedAsset = useCallback( - async (searchQuery: string, chainId: number): Promise => { + async (searchQuery: string, chainId: number): Promise => { if (searching) { if (isAddress(searchQuery)) { const tokenListEntry = rainbowTokenList.RAINBOW_TOKEN_LIST[searchQuery.toLowerCase()]; if (tokenListEntry) { return [tokenListEntry]; } - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); const tokenContract = new Contract(searchQuery, erc20ABI, provider); try { const [name, symbol, decimals, address] = await Promise.all([ @@ -192,10 +186,11 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI tokenContract.decimals(), getAddress(searchQuery), ]); - const uniqueId = `${address}_${network}`; + const uniqueId = getUniqueId(address, chainId); return [ { address, + chainId, decimals, favorite: false, highLiquidity: false, @@ -209,7 +204,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }, }, symbol, - network, + network: ethereumUtils.getNetworkFromChainId(chainId), uniqueId, } as RainbowToken, ]; @@ -225,18 +220,17 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI ); const getCrosschainVerifiedAssetsForNetwork = useCallback( - async (network: Network) => { - const crosschainId = ethereumUtils.getChainIdFromNetwork(network); - const fromChainId = inputChainId !== crosschainId ? inputChainId : ''; + async (chainId: ChainId) => { + const fromChainId = inputChainId !== chainId ? inputChainId : ''; const results = await searchCurrencyList({ searchList: 'verifiedAssets', query: '', - chainId: crosschainId, + chainId, fromChainId, }); setCrosschainVerifiedAssets(state => ({ ...state, - [network]: handleSearchResponse(results, network), + [chainId]: handleSearchResponse(results, chainId), })); }, [handleSearchResponse, inputChainId] @@ -244,8 +238,8 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const getCrosschainVerifiedAssets = useCallback(async () => { const crosschainAssetRequests: Promise[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(network as Network)); + Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { + crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(Number(chainIdKey))); }); await Promise.all(crosschainAssetRequests); }, [crosschainVerifiedAssets, getCrosschainVerifiedAssetsForNetwork]); @@ -306,7 +300,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const search = useCallback(async () => { const categories: swapCurrencyListType[] = - searchChainId === MAINNET_CHAINID + searchChainId === ChainId.mainnet ? ['favoriteAssets', 'highLiquidityAssets', 'verifiedAssets', 'importedAssets'] : ['verifiedAssets', 'importedAssets']; setLoading(true); @@ -351,9 +345,9 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI (searching && !wasSearching) || (searching && previousSearchQuery !== searchQuery) || searchChainId !== previousChainId || - inputCurrency?.network !== previousInputCurrencyNetwork + inputCurrency?.chainId !== previousInputCurrencyChainId ) { - if (searchChainId === MAINNET_CHAINID) { + if (searchChainId === ChainId.mainnet) { search(); slowSearch(); } else { @@ -368,14 +362,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }; doSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.network]); + }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); const { colors } = useTheme(); const currencyList = useMemo(() => { const list = []; let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.network !== inputCurrency?.network) + ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) : null; if (searching) { const importedAsset = importedAssets?.[0]; @@ -400,14 +394,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), }); } } - if (favoriteAssets?.length && searchChainId === MAINNET_CHAINID) { + if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { list.push({ color: colors.yellowFavorite, data: abcSort(favoriteAssets, 'name'), @@ -438,12 +432,12 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }); } } else { - const curatedAssets = searchChainId === MAINNET_CHAINID && getCurated(); + const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -469,39 +463,39 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI } return list; }, [ + isCrosschainSearch, + verifiedAssets, searching, + inputCurrency?.name, + inputCurrency?.chainId, importedAssets, - favoriteAssets, - verifiedAssets, highLiquidityAssets, lowLiquidityAssets, - colors.yellowFavorite, - unfilteredFavorites, - searchChainId, - getCurated, isFavorite, - inputCurrency?.name, + favoriteAssets, + searchChainId, colors.networkColors, - isCrosschainSearch, - inputCurrency?.network, + colors.yellowFavorite, + getCurated, + unfilteredFavorites, ]); const crosschainExactMatches = useMemo(() => { if (currencyList.length) return []; if (!searchQuery) return []; - const exactMatches: RT[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - const currentNetworkChainId = ethereumUtils.getChainIdFromNetwork(network as Network); - if (currentNetworkChainId !== searchChainId) { + const exactMatches: RainbowToken[] = []; + Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { + const chainId = Number(chainIdKey); + if (chainId !== searchChainId) { // including goerli in our networks type is causing this type issue // @ts-ignore - const exactMatch = crosschainVerifiedAssets[network as Network].find((asset: RT) => { + const exactMatch = crosschainVerifiedAssets[chainId].find((asset: RainbowToken) => { const symbolMatch = isLowerCaseMatch(asset?.symbol, searchQuery); const nameMatch = isLowerCaseMatch(asset?.name, searchQuery); return symbolMatch || nameMatch; }); if (exactMatch) { - exactMatches.push({ ...exactMatch, network }); + exactMatches.push({ ...exactMatch, chainId }); } } }); diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index 72c76856c54..dfcd744ea3c 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -65,13 +65,13 @@ const getInputAmount = async ( if (!inputToken || !outputAmount || isZero(outputAmount) || !outputToken) return null; try { - const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken?.network); + const outputChainId = outputToken.chainId; - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken?.network); + const inputChainId = inputToken.chainId; - const inputTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; + const inputTokenAddress = isNativeAsset(inputToken.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken.address; - const outputTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; + const outputTokenAddress = isNativeAsset(outputToken.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken.address; const isCrosschainSwap = inputChainId !== outputChainId; if (isCrosschainSwap) return null; @@ -164,10 +164,10 @@ const getOutputAmount = async ( if (!inputAmount || isZero(inputAmount) || !outputToken) return null; try { - const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken.network); + const outputChainId = outputToken.chainId; const buyTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken.network); + const inputChainId = inputToken.chainId; const sellTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); diff --git a/src/hooks/useSwapInputHandlers.ts b/src/hooks/useSwapInputHandlers.ts index b6c4478bae2..ac7684de5c2 100644 --- a/src/hooks/useSwapInputHandlers.ts +++ b/src/hooks/useSwapInputHandlers.ts @@ -19,12 +19,12 @@ export default function useSwapInputHandlers() { const updateMaxInputAmount = useCallback(() => { const inputCurrencyAddress = inputCurrency?.address; const inputCurrencyUniqueId = inputCurrency?.uniqueId; - const inputCurrencyNetwork = inputCurrency?.network; + const inputCurrencyChainId = inputCurrency?.chainId; const accountAsset = ethereumUtils.getAccountAsset(inputCurrencyUniqueId); const oldAmount = accountAsset?.balance?.amount ?? '0'; let newAmount = oldAmount; - if (isNativeAsset(inputCurrencyAddress, ethereumUtils.getChainIdFromNetwork(inputCurrencyNetwork)) && accountAsset) { + if (isNativeAsset(inputCurrencyAddress, inputCurrencyChainId) && accountAsset) { // this subtracts gas from the balance of the asset newAmount = toFixedDecimals(ethereumUtils.getBalanceAmount(selectedGasFee, accountAsset, l1GasFeeOptimism), 6); @@ -39,7 +39,7 @@ export default function useSwapInputHandlers() { } } dispatch(updateSwapInputAmount(newAmount, true)); - }, [dispatch, inputCurrency?.address, inputCurrency?.network, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); + }, [dispatch, inputCurrency?.address, inputCurrency?.chainId, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); const updateInputAmount = useCallback( (value: string | null) => { diff --git a/src/hooks/useSwapRefuel.ts b/src/hooks/useSwapRefuel.ts index 488edca8bdc..d2455ee51ee 100644 --- a/src/hooks/useSwapRefuel.ts +++ b/src/hooks/useSwapRefuel.ts @@ -7,8 +7,8 @@ import { useEffect, useMemo, useState } from 'react'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { useAccountSettings, useGas } from '.'; import { isNativeAsset } from '@/handlers/assets'; -import { NetworkTypes } from '@/helpers'; import { toWei } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; export enum RefuelState { 'Add' = 'Add', @@ -32,22 +32,17 @@ export default function useSwapRefuel({ const [outputNativeAsset, setOutputNativeAsset] = useState(); const [inputNativeAsset, setInputNativeAsset] = useState(); - const { inputNetwork, outputNetwork, chainId, toChainId, isCrosschainSwap } = useMemo(() => { - const inputNetwork = inputCurrency.network; - const outputNetwork = outputCurrency.network; - const chainId = ethereumUtils.getChainIdFromNetwork(inputNetwork); - - const toChainId = ethereumUtils.getChainIdFromNetwork(outputNetwork); - const isCrosschainSwap = crosschainSwapsEnabled && inputNetwork !== outputNetwork; + const { chainId, toChainId, isCrosschainSwap } = useMemo(() => { + const chainId = inputCurrency.chainId; + const toChainId = outputCurrency.chainId; + const isCrosschainSwap = crosschainSwapsEnabled && chainId !== toChainId; return { - inputNetwork, - outputNetwork, chainId, toChainId, isCrosschainSwap, }; - }, [crosschainSwapsEnabled, inputCurrency.network, outputCurrency.network]); + }, [crosschainSwapsEnabled, inputCurrency.chainId, outputCurrency.chainId]); const { data: minRefuelAmount } = useMinRefuelAmount( { @@ -59,14 +54,14 @@ export default function useSwapRefuel({ useEffect(() => { const getNativeInputOutputAssets = async () => { - if (!outputNetwork || !inputNetwork || !accountAddress) return; - const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(toChainId, accountAddress); - const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); + if (!chainId || !toChainId || !accountAddress) return; + const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId: toChainId, address: accountAddress }); + const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); setOutputNativeAsset(outputNativeAsset); setInputNativeAsset(inputNativeAsset); }; getNativeInputOutputAssets(); - }, [outputNetwork, inputNetwork, accountAddress, toChainId, chainId]); + }, [accountAddress, toChainId, chainId]); const { showRefuelSheet, refuelState } = useMemo(() => { const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, toChainId); @@ -79,7 +74,7 @@ export default function useSwapRefuel({ return { showRefuelSheet: false, refuelState: null }; } // If we are swapping to mainnet then ignore - if (outputNetwork === NetworkTypes.mainnet) return { showRefuelSheet: false, refuelState: null }; + if (toChainId === ChainId.mainnet) return { showRefuelSheet: false, refuelState: null }; // Does the user have an existing balance on the output native asset const hasZeroOutputNativeAssetBalance = isZero(outputNativeAsset?.balance?.amount || 0); @@ -130,7 +125,6 @@ export default function useSwapRefuel({ minRefuelAmount, outputCurrency?.address, outputNativeAsset?.balance?.amount, - outputNetwork, selectedGasFee?.gasFee?.estimatedFee?.value?.amount, toChainId, tradeDetails?.sellAmount, diff --git a/src/hooks/useSwappableUserAssets.ts b/src/hooks/useSwappableUserAssets.ts index f83282af7bd..7e4338f042d 100644 --- a/src/hooks/useSwappableUserAssets.ts +++ b/src/hooks/useSwappableUserAssets.ts @@ -1,13 +1,13 @@ import { SwappableAsset } from '@/entities'; import { walletFilter } from '@/handlers/tokenSearch'; -import { Network } from '@/helpers'; import { useCoinListEditOptions } from '@/hooks'; import { ETH_ADDRESS } from '@/references'; import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import { EthereumAddress, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS } from '@rainbow-me/swaps'; import { ethereumUtils } from '@/utils'; import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { RainbowNetworks, getNetworkObj, getSwappableNetworks } from '@/networks'; +import { RainbowNetworkObjects, getNetworkObject, getSwappableNetworks } from '@/networks'; +import { Network } from '@/networks/types'; type SwappableAddresses = Record; @@ -29,8 +29,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset if (hiddenCoinsObj[asset.uniqueId]) return true; // filter out networks where swaps are not enabled - const assetNetwork = asset.network; - if (getNetworkObj(assetNetwork).features.swaps) return true; + if (getNetworkObject({ chainId: asset.chainId }).features.swaps) return true; return false; }); @@ -60,7 +59,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset ); const getSwappableAddressesInWallet = useCallback(async () => { - const networks = RainbowNetworks.filter(({ features }) => features.swaps).map(({ value }) => value); + const networks = RainbowNetworkObjects.filter(({ features }) => features.swaps).map(({ value }) => value); const walletFilterRequests: Promise[] = []; networks.forEach(network => { diff --git a/src/hooks/useUserAccounts.ts b/src/hooks/useUserAccounts.ts index 4ad20883a51..eafa2508ee9 100644 --- a/src/hooks/useUserAccounts.ts +++ b/src/hooks/useUserAccounts.ts @@ -1,43 +1,37 @@ import { values } from 'lodash'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; import walletTypes from '@/helpers/walletTypes'; -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; import { useMemo } from 'react'; import { RainbowAccount } from '@/model/wallet'; -import { Network } from '@/helpers'; export default function useUserAccounts() { const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); - const network = useSelector((state: AppState) => state.settings.network); const userAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type !== walletTypes.readOnly); - const addresses: (RainbowAccount & { network: Network })[] = []; + const addresses: RainbowAccount[] = []; filteredWallets.forEach(wallet => { wallet.addresses?.forEach(account => { addresses.push({ ...account, - network, }); }); }); return addresses; - }, [network, walletsWithBalancesAndNames]); + }, [walletsWithBalancesAndNames]); const watchedAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type === walletTypes.readOnly); - const addresses: (RainbowAccount & { network: Network })[] = []; + const addresses: RainbowAccount[] = []; filteredWallets.forEach(wallet => { wallet.addresses?.forEach(account => { addresses.push({ ...account, - network, }); }); }); return addresses; - }, [network, walletsWithBalancesAndNames]); + }, [walletsWithBalancesAndNames]); return { userAccounts, diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index de1c0ca9bf4..8e22b715a99 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -5,10 +5,9 @@ import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { RainbowError, logger } from '@/logger'; -import { Network } from '@/networks/types'; -import { getIsHardhatConnected, getProviderForNetwork } from '@/handlers/web3'; +import { getIsHardhatConnected, getProvider } from '@/handlers/web3'; import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import { getTransactionFlashbotStatus } from '@/handlers/transactions'; import { usePendingTransactionsStore } from '@/state/pendingTransactions'; @@ -16,6 +15,7 @@ import { useNonceStore } from '@/state/nonces'; import { Address } from 'viem'; import { nftsQueryKey } from '@/resources/nfts'; import { getNftSortForAddress } from './useNFTsSortBy'; +import { ChainId } from '@/networks/types'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ @@ -72,7 +72,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { const transaction = await transactionFetchQuery({ hash: tx.hash!, - network: tx.network, + chainId: tx.chainId, address, currency: nativeCurrency, }); @@ -89,7 +89,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { let updatedTransaction: RainbowTransaction = { ...tx }; try { - if (tx.network && tx.hash && address) { + if (tx.chainId && tx.hash && address) { updatedTransaction = await processSupportedNetworkTransaction(updatedTransaction); // if flashbots tx and no blockNumber, check if it failed if (!(tx as any).blockNumber && tx.flashbots) { @@ -115,46 +115,46 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const processNonces = useCallback( (txs: RainbowTransaction[]) => { const userTxs = txs.filter(tx => address?.toLowerCase() === tx.from?.toLowerCase()); - const networks = [ + const chainIds = [ ...new Set( userTxs.reduce((acc, tx) => { - acc.add(tx.network); + acc.add(tx.chainId); return acc; - }, new Set()) + }, new Set()) ), ]; let flashbotsTxFailed = false; const highestNoncePerChainId = userTxs.reduce((acc, tx) => { // if tx is not on mainnet, we don't care about the nonce - if (tx.network !== Network.mainnet) { - acc.set(tx.network, tx.nonce); + if (tx.chainId !== ChainId.mainnet) { + acc.set(tx.chainId, tx.nonce); return acc; } // if tx is flashbots and failed, we want to use the lowest nonce if (tx.flashbots && (tx as any)?.flashbotsStatus === 'FAILED' && tx?.nonce) { // if we already have a failed flashbots tx, we want to use the lowest nonce - if (flashbotsTxFailed && tx.nonce < acc.get(tx.network)) { - acc.set(tx.network, tx.nonce); + if (flashbotsTxFailed && tx.nonce < acc.get(tx.chainId)) { + acc.set(tx.chainId, tx.nonce); } else { - acc.set(tx.network, tx.nonce); + acc.set(tx.chainId, tx.nonce); flashbotsTxFailed = true; } // if tx succeeded, we want to use the highest nonce - } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.network)) { - acc.set(tx.network, tx.nonce); + } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.chainId)) { + acc.set(tx.chainId, tx.nonce); } return acc; }, new Map()); - networks.map(async network => { - const provider = getProviderForNetwork(network); + chainIds.map(async chainId => { + const provider = getProvider({ chainId }); const providerTransactionCount = await provider.getTransactionCount(address, 'latest'); const currentProviderNonce = providerTransactionCount - 1; - const currentNonceForChainId = highestNoncePerChainId.get(network) - 1; + const currentNonceForChainId = highestNoncePerChainId.get(chainId) - 1; setNonce({ address, - network: network, + chainId, currentNonce: currentProviderNonce > currentNonceForChainId ? currentProviderNonce : currentNonceForChainId, latestConfirmedNonce: currentProviderNonce, }); @@ -187,7 +187,9 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); if (minedTransactions.length) { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(networkObject => networkObject.enabled && networkObject.networkType !== 'testnet').map( + networkObject => networkObject.id + ); await queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ address, diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 5130d449fc6..3b0836c40a4 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -487,6 +487,7 @@ "clear_local_storage": "Clear local storage", "clear_mmkv_storage": "Clear MMKV storage", "connect_to_hardhat": "Connect to hardhat", + "disconnect_to_hardhat": "Disconnect from hardhat", "crash_app_render_error": "Crash app (render error)", "enable_testnets": "Enable Testnets", "installing_update": "Installing update", diff --git a/src/migrations/migrations/migrateFavorites.ts b/src/migrations/migrations/migrateFavorites.ts index 46845e56225..4ffa4c52259 100644 --- a/src/migrations/migrations/migrateFavorites.ts +++ b/src/migrations/migrations/migrateFavorites.ts @@ -3,7 +3,6 @@ import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { EthereumAddress, RainbowToken } from '@/entities'; import { createQueryKey, persistOptions, queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; -import { ethereumUtils } from '@/utils'; import { persistQueryClientRestore, persistQueryClientSave } from '@tanstack/react-query-persist-client'; import { Migration, MigrationName } from '../types'; @@ -25,7 +24,7 @@ export function migrateFavoritesV2(): Migration { for (const favorite of Object.values(v1Data)) { const uniqueId = getStandardizedUniqueIdWorklet({ address: favorite.address as AddressOrEth, - chainId: ethereumUtils.getChainIdFromNetwork(favorite.network), + chainId: favorite.chainId, }); favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network migratedFavorites[uniqueId] = favorite; diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index 9974e62ed2e..bf79c65490d 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -1,4 +1,4 @@ -import { getNetwork, saveNetwork } from '@/handlers/localstorage/globalSettings'; +import { getChainId, saveChainId } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; import { createQueryKey, queryClient } from '@/react-query'; @@ -245,9 +245,9 @@ export async function fetchRemoteConfig(): Promise { throw e; } finally { logger.debug(`[remoteConfig]: Current remote config:\n${JSON.stringify(config, null, 2)}`); - const currentNetwork = await getNetwork(); - web3SetHttpProvider(currentNetwork); - saveNetwork(currentNetwork); + const currentChainId = await getChainId(); + web3SetHttpProvider(currentChainId); + saveChainId(currentChainId); } } diff --git a/src/model/wallet.ts b/src/model/wallet.ts index 3271e2f55b6..d35c72f8b48 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -56,8 +56,8 @@ import { IS_ANDROID } from '@/env'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { Signer } from '@ethersproject/abstract-signer'; import { sanitizeTypedData } from '@/utils/signingUtils'; -import { Network } from '@/helpers'; import { ExecuteFnParamsWithoutFn, performanceTracking, Screen } from '@/state/performance/performance'; +import { Network } from '@/networks/types'; export type EthereumPrivateKey = string; type EthereumMnemonic = string; diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index 45fd589fcc6..e08d2197300 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -7,7 +7,6 @@ import { TestnetToast } from '@/components/toasts'; import { DAPP_BROWSER, POINTS, useExperimentalFlag } from '@/config'; import { Box, Columns, globalColors, Stack, useForegroundColor, Text, Cover, useColorMode } from '@/design-system'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import { web3Provider } from '@/handlers/web3'; import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; import { useAccountAccentColor, useAccountSettings, useCoinListEdited, useDimensions, usePendingTransactions } from '@/hooks'; import { useRemoteConfig } from '@/model/remoteConfig'; @@ -446,7 +445,7 @@ function SwipeNavigatorScreens() { } export function SwipeNavigator() { - const { network } = useAccountSettings(); + const { chainId } = useAccountSettings(); const { colors } = useTheme(); return ( @@ -462,7 +461,7 @@ export function SwipeNavigator() { - + ); } diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 3e9de2383ff..182550aa6c8 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { Keyboard, StatusBar } from 'react-native'; +import { Keyboard } from 'react-native'; import { useTheme } from '@/theme/ThemeContext'; import colors from '@/theme/currentColors'; import styled from '@/styled-thing'; import { fonts } from '@/styles'; -import networkTypes from '@/helpers/networkTypes'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; -import { getNetworkObj } from '@/networks'; import { getPositionSheetHeight } from '@/screens/positions/PositionSheet'; import { Icon } from '@/components/icons'; @@ -29,6 +27,7 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -490,7 +489,7 @@ export const ensAdditionalRecordsSheetConfig: PartialNavigatorConfigOptions = { }; export const explainSheetConfig: PartialNavigatorConfigOptions = { - options: ({ route: { params = { network: getNetworkObj(networkTypes.mainnet).name } } }) => { + options: ({ route: { params = { network: chainIdToNameMapping[ChainId.mainnet] } } }) => { // @ts-ignore const explainerConfig = explainers(params.network)[params?.type]; return buildCoolModalConfig({ diff --git a/src/networks/README.md b/src/networks/README.md index 47dd785beaa..678269fb209 100644 --- a/src/networks/README.md +++ b/src/networks/README.md @@ -3,9 +3,9 @@ Handling for networks throughout the codebase. ```typescript -import { getNetworkObj, Networks } from '@/networks'; +import { getNetworkObject } from '@/networks'; -const networkObj = getNetworkObj(Networks.mainnet); +const networkObj = getNetworkObject({ chainId: ChainId.mainnet }); // Get static properties based on network const networkName = networkObj.name; @@ -19,10 +19,10 @@ const gasPrices = networkObj.getGasPrices(); // Getting a subset of network objects -const layer2s = RainbowNetworks.filter(network => network.networkType === 'layer2'); +const layer2s = RainbowNetworkObjects.filter(network => network.networkType === 'layer2'); // Or networks that match specific properties -const walletconnectNetworks = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.value); +const walletconnectNetworks = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.value); ``` ## Network Objects diff --git a/src/networks/arbitrum.ts b/src/networks/arbitrum.ts index 72c95c4cad5..35c33af556b 100644 --- a/src/networks/arbitrum.ts +++ b/src/networks/arbitrum.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { arbitrum } from '@wagmi/chains'; import { ARBITRUM_ETH_ADDRESS } from '@/references'; import { getArbitrumGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getArbitrumNetworkObject = (): NetworkProperties => { const { arbitrum_enabled, arbitrum_tx_enabled } = getRemoteConfig(); @@ -25,7 +26,7 @@ export const getArbitrumNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(arbitrum.id), - getProvider: () => getProviderForNetwork(Network.arbitrum), + getProvider: () => getProvider({ chainId: ChainId.arbitrum }), balanceCheckerAddress: '0x54A4E5800345c01455a7798E0D96438364e22723', // features diff --git a/src/networks/avalanche.ts b/src/networks/avalanche.ts index ecb8b628d99..cc411357c81 100644 --- a/src/networks/avalanche.ts +++ b/src/networks/avalanche.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { avalanche } from '@wagmi/chains'; import { AVAX_AVALANCHE_ADDRESS } from '@/references'; import { getAvalancheGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getAvalancheNetworkObject = (): NetworkProperties => { const { avalanche_enabled, avalanche_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getAvalancheNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(avalanche.id), - getProvider: () => getProviderForNetwork(Network.avalanche), + getProvider: () => getProvider({ chainId: ChainId.avalanche }), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/base.ts b/src/networks/base.ts index ea2b6163d5a..7a978ddca6d 100644 --- a/src/networks/base.ts +++ b/src/networks/base.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { base } from '@wagmi/chains'; import { BASE_ETH_ADDRESS } from '@/references'; import { getBaseGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getBaseNetworkObject = (): NetworkProperties => { const { base_enabled, base_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getBaseNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(base.id), - getProvider: () => getProviderForNetwork(Network.base), + getProvider: () => getProvider({ chainId: ChainId.base }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/blast.ts b/src/networks/blast.ts index f7b168d3e72..6d4d482fc87 100644 --- a/src/networks/blast.ts +++ b/src/networks/blast.ts @@ -1,4 +1,4 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { blast } from 'viem/chains'; @@ -6,6 +6,7 @@ import { getBlastGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { BLAST_MAINNET_RPC } from 'react-native-dotenv'; import { BLAST_ETH_ADDRESS } from '@/references'; +import { ChainId } from '@/networks/types'; export const getBlastNetworkObject = (): NetworkProperties => { const { blast_enabled, blast_tx_enabled } = getRemoteConfig(); @@ -29,7 +30,7 @@ export const getBlastNetworkObject = (): NetworkProperties => { balanceCheckerAddress: '', rpc: () => proxyRpcEndpoint(blast.id), - getProvider: () => getProviderForNetwork(Network.blast), + getProvider: () => getProvider({ chainId: ChainId.blast }), // features features: { diff --git a/src/networks/bsc.ts b/src/networks/bsc.ts index 3e38d897969..89aa63c17d8 100644 --- a/src/networks/bsc.ts +++ b/src/networks/bsc.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { bsc } from '@wagmi/chains'; import { BNB_BSC_ADDRESS, BNB_MAINNET_ADDRESS } from '@/references'; import { getBscGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getBSCNetworkObject = (): NetworkProperties => { const { bsc_enabled, bsc_tx_enabled } = getRemoteConfig(); @@ -28,7 +29,7 @@ export const getBSCNetworkObject = (): NetworkProperties => { // this should be refactored to have less deps rpc: () => proxyRpcEndpoint(bsc.id), - getProvider: () => getProviderForNetwork(Network.bsc), + getProvider: () => getProvider({ chainId: ChainId.bsc }), balanceCheckerAddress: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', // features diff --git a/src/networks/degen.ts b/src/networks/degen.ts index bc7c43aad2d..044e3e6baba 100644 --- a/src/networks/degen.ts +++ b/src/networks/degen.ts @@ -1,4 +1,4 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { degen } from 'viem/chains'; @@ -6,6 +6,7 @@ import { DEGEN_CHAIN_DEGEN_ADDRESS } from '@/references'; import { getDegenGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { DEGEN_MAINNET_RPC } from 'react-native-dotenv'; +import { ChainId } from '@/networks/types'; export const getDegenNetworkObject = (): NetworkProperties => { const { degen_enabled, degen_tx_enabled } = getRemoteConfig(); @@ -28,7 +29,7 @@ export const getDegenNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(degen.id), - getProvider: () => getProviderForNetwork(Network.degen), + getProvider: () => getProvider({ chainId: ChainId.degen }), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/gnosis.ts b/src/networks/gnosis.ts index f6e18c742b7..cac594c0e13 100644 --- a/src/networks/gnosis.ts +++ b/src/networks/gnosis.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { gnosis } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; +import { ChainId } from '@/networks/types'; export const getGnosisNetworkObject = (): NetworkProperties => { return { @@ -24,7 +25,7 @@ export const getGnosisNetworkObject = (): NetworkProperties => { }, rpc: () => '', - getProvider: () => getProviderForNetwork(Network.optimism), + getProvider: () => getProvider({ chainId: ChainId.gnosis }), balanceCheckerAddress: '', // features diff --git a/src/networks/goerli.ts b/src/networks/goerli.ts index 2d9423a7ac2..71d3e19aeca 100644 --- a/src/networks/goerli.ts +++ b/src/networks/goerli.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { goerli } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getGoerliNetworkObject = (): NetworkProperties => { const { goerli_enabled, goerli_tx_enabled } = getRemoteConfig(); @@ -25,7 +26,7 @@ export const getGoerliNetworkObject = (): NetworkProperties => { }, // this should be refactored to have less deps - getProvider: () => getProviderForNetwork(Network.goerli), + getProvider: () => getProvider({ chainId: ChainId.goerli }), rpc: () => proxyRpcEndpoint(goerli.id), balanceCheckerAddress: '0xf3352813b612a2d198e437691557069316b84ebe', diff --git a/src/networks/index.ts b/src/networks/index.ts index 79c9089f23a..b4c5dd2f1f6 100644 --- a/src/networks/index.ts +++ b/src/networks/index.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import store from '@/redux/store'; import * as ls from '@/storage'; import { getArbitrumNetworkObject } from './arbitrum'; @@ -12,14 +12,14 @@ import { getGoerliNetworkObject } from './goerli'; import { getMainnetNetworkObject } from './mainnet'; import { getOptimismNetworkObject } from './optimism'; import { getPolygonNetworkObject } from './polygon'; -import { Network, NetworkProperties } from './types'; +import { NetworkProperties } from './types'; import { getZoraNetworkObject } from './zora'; /** * Array of all Rainbow Networks * the ordering is the default sorting */ -export const RainbowNetworks = [ +export const RainbowNetworkObjects = [ getMainnetNetworkObject(), getArbitrumNetworkObject(), getBaseNetworkObject(), @@ -34,46 +34,24 @@ export const RainbowNetworks = [ getDegenNetworkObject(), ]; +export const RainbowSupportedChainIds = [ + ChainId.mainnet, + ChainId.arbitrum, + ChainId.base, + ChainId.optimism, + ChainId.polygon, + ChainId.zora, + ChainId.gnosis, + ChainId.goerli, + ChainId.bsc, + ChainId.avalanche, + ChainId.blast, + ChainId.degen, +]; + /** * Helper function to get specific Rainbow Network's Object */ -export function getNetworkObj(network: Network): NetworkProperties { - switch (network) { - // Mainnet - case Network.mainnet: - return getMainnetNetworkObject(); - - // L2s - case Network.arbitrum: - return getArbitrumNetworkObject(); - case Network.base: - return getBaseNetworkObject(); - case Network.bsc: - return getBSCNetworkObject(); - case Network.optimism: - return getOptimismNetworkObject(); - case Network.polygon: - return getPolygonNetworkObject(); - case Network.zora: - return getZoraNetworkObject(); - case Network.gnosis: - return getGnosisNetworkObject(); - case Network.avalanche: - return getAvalancheNetworkObject(); - case Network.blast: - return getBlastNetworkObject(); - case Network.degen: - return getDegenNetworkObject(); - // Testnets - case Network.goerli: - return getGoerliNetworkObject(); - - // Fallback - default: - return getMainnetNetworkObject(); - } -} - export function getNetworkObject({ chainId }: { chainId: ChainId }): NetworkProperties { switch (chainId) { // Mainnet @@ -125,14 +103,14 @@ export function sortNetworks(): NetworkProperties[] { return count1 > count2 ? -1 : 1; }; - return RainbowNetworks.sort(tokenSort); + return RainbowNetworkObjects.sort(tokenSort); } export function getSwappableNetworks(): NetworkProperties[] { - return RainbowNetworks.filter(network => network.features.swaps); + return RainbowNetworkObjects.filter(network => network.features.swaps); } -export const RainbowNetworkByChainId = RainbowNetworks.reduce( +export const RainbowNetworkByChainId = RainbowNetworkObjects.reduce( (acc, network) => { acc[network.id] = network; return acc; diff --git a/src/networks/mainnet.ts b/src/networks/mainnet.ts index 2c3644511ec..e185b5c1b25 100644 --- a/src/networks/mainnet.ts +++ b/src/networks/mainnet.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; -import { Network, NetworkProperties } from './types'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { Network, NetworkProperties, ChainId } from './types'; import { gasUtils } from '@/utils'; import { mainnet } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const getMainnetNetworkObject = (): NetworkProperties => { const { mainnet_enabled, mainnet_tx_enabled } = getRemoteConfig(); @@ -24,9 +25,8 @@ export const getMainnetNetworkObject = (): NetworkProperties => { address: ETH_ADDRESS, }, - // this should be refactored to have less deps - getProvider: () => getProviderForNetwork(Network.mainnet), - rpc: () => proxyRpcEndpoint(mainnet.id), + getProvider: () => getProvider({ chainId: ChainId.mainnet }), + rpc: () => (useConnectedToHardhatStore.getState().connectedToHardhat ? 'http://127.0.0.1:8545' : proxyRpcEndpoint(mainnet.id)), balanceCheckerAddress: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', // features diff --git a/src/networks/optimism.ts b/src/networks/optimism.ts index b2d6ce8c8a6..2a9bf3c4884 100644 --- a/src/networks/optimism.ts +++ b/src/networks/optimism.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { optimism } from '@wagmi/chains'; import { OPTIMISM_ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getOptimismNetworkObject = (): NetworkProperties => { const { optimism_enabled, optimism_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getOptimismNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(optimism.id), - getProvider: () => getProviderForNetwork(Network.optimism), + getProvider: () => getProvider({ chainId: ChainId.optimism }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/polygon.ts b/src/networks/polygon.ts index 7866a77a4c4..49f4feba581 100644 --- a/src/networks/polygon.ts +++ b/src/networks/polygon.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { polygon } from '@wagmi/chains'; import { MATIC_MAINNET_ADDRESS, MATIC_POLYGON_ADDRESS } from '@/references'; import { getPolygonGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getPolygonNetworkObject = (): NetworkProperties => { const { polygon_tx_enabled } = getRemoteConfig(); @@ -27,7 +28,7 @@ export const getPolygonNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(polygon.id), - getProvider: () => getProviderForNetwork(Network.polygon), + getProvider: () => getProvider({ chainId: ChainId.polygon }), balanceCheckerAddress: '0x54A4E5800345c01455a77798E0D96438364e22723', // features diff --git a/src/networks/types.ts b/src/networks/types.ts index 052080ce5c7..6e94851fc28 100644 --- a/src/networks/types.ts +++ b/src/networks/types.ts @@ -2,7 +2,11 @@ import { EthereumAddress } from '@/entities'; import { GasPricesAPIData } from '@/entities/gas'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { Chain } from '@wagmi/chains'; -// network.ts +import * as chain from 'viem/chains'; + +const HARDHAT_CHAIN_ID = 1337; +const HARDHAT_OP_CHAIN_ID = 1338; + export enum Network { arbitrum = 'arbitrum', goerli = 'goerli', @@ -18,6 +22,199 @@ export enum Network { degen = 'degen', } +export enum ChainId { + arbitrum = chain.arbitrum.id, + arbitrumNova = chain.arbitrumNova.id, + arbitrumSepolia = chain.arbitrumSepolia.id, + avalanche = chain.avalanche.id, + avalancheFuji = chain.avalancheFuji.id, + base = chain.base.id, + baseSepolia = chain.baseSepolia.id, + blast = chain.blast.id, + blastSepolia = chain.blastSepolia.id, + bsc = chain.bsc.id, + bscTestnet = chain.bscTestnet.id, + celo = chain.celo.id, + degen = chain.degen.id, + gnosis = chain.gnosis.id, + goerli = chain.goerli.id, + hardhat = HARDHAT_CHAIN_ID, + hardhatOptimism = HARDHAT_OP_CHAIN_ID, + holesky = chain.holesky.id, + linea = chain.linea.id, + mainnet = chain.mainnet.id, + manta = chain.manta.id, + optimism = chain.optimism.id, + optimismSepolia = chain.optimismSepolia.id, + polygon = chain.polygon.id, + polygonAmoy = chain.polygonAmoy.id, + polygonMumbai = chain.polygonMumbai.id, + polygonZkEvm = chain.polygonZkEvm.id, + rari = 1380012617, + scroll = chain.scroll.id, + sepolia = chain.sepolia.id, + zora = chain.zora.id, + zoraSepolia = chain.zoraSepolia.id, +} + +export enum ChainName { + arbitrum = 'arbitrum', + arbitrumNova = 'arbitrum-nova', + arbitrumSepolia = 'arbitrum-sepolia', + avalanche = 'avalanche', + avalancheFuji = 'avalanche-fuji', + base = 'base', + blast = 'blast', + blastSepolia = 'blast-sepolia', + bsc = 'bsc', + celo = 'celo', + degen = 'degen', + gnosis = 'gnosis', + goerli = 'goerli', + linea = 'linea', + manta = 'manta', + optimism = 'optimism', + polygon = 'polygon', + polygonZkEvm = 'polygon-zkevm', + rari = 'rari', + scroll = 'scroll', + zora = 'zora', + mainnet = 'mainnet', + holesky = 'holesky', + hardhat = 'hardhat', + hardhatOptimism = 'hardhat-optimism', + sepolia = 'sepolia', + optimismSepolia = 'optimism-sepolia', + bscTestnet = 'bsc-testnet', + polygonMumbai = 'polygon-mumbai', + baseSepolia = 'base-sepolia', + zoraSepolia = 'zora-sepolia', + polygonAmoy = 'polygon-amoy', +} + +export const networkToIdMapping: { [key in Network]: ChainId } = { + [Network.arbitrum]: ChainId.arbitrum, + [Network.goerli]: ChainId.goerli, + [Network.mainnet]: ChainId.mainnet, + [Network.optimism]: ChainId.optimism, + [Network.polygon]: ChainId.polygon, + [Network.base]: ChainId.base, + [Network.bsc]: ChainId.bsc, + [Network.zora]: ChainId.zora, + [Network.gnosis]: ChainId.gnosis, + [Network.avalanche]: ChainId.avalanche, + [Network.blast]: ChainId.blast, + [Network.degen]: ChainId.degen, +}; + +export const chainNameToIdMapping: { + [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; +} = { + ['ethereum']: ChainId.mainnet, + [ChainName.arbitrum]: ChainId.arbitrum, + [ChainName.arbitrumNova]: ChainId.arbitrumNova, + [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, + [ChainName.avalanche]: ChainId.avalanche, + [ChainName.avalancheFuji]: ChainId.avalancheFuji, + [ChainName.base]: ChainId.base, + [ChainName.bsc]: ChainId.bsc, + [ChainName.celo]: ChainId.celo, + [ChainName.degen]: ChainId.degen, + [ChainName.gnosis]: ChainId.gnosis, + [ChainName.linea]: ChainId.linea, + [ChainName.manta]: ChainId.manta, + [ChainName.optimism]: ChainId.optimism, + [ChainName.goerli]: ChainId.goerli, + [ChainName.polygon]: ChainId.polygon, + [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, + [ChainName.rari]: ChainId.rari, + [ChainName.scroll]: ChainId.scroll, + [ChainName.zora]: ChainId.zora, + [ChainName.mainnet]: ChainId.mainnet, + [ChainName.holesky]: ChainId.holesky, + [ChainName.hardhat]: ChainId.hardhat, + [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, + ['ethereum-sepolia']: ChainId.sepolia, + [ChainName.sepolia]: ChainId.sepolia, + [ChainName.optimismSepolia]: ChainId.optimismSepolia, + [ChainName.bscTestnet]: ChainId.bscTestnet, + [ChainName.polygonMumbai]: ChainId.polygonMumbai, + [ChainName.baseSepolia]: ChainId.baseSepolia, + [ChainName.zoraSepolia]: ChainId.zoraSepolia, + [ChainName.blast]: ChainId.blast, + [ChainName.blastSepolia]: ChainId.blastSepolia, + [ChainName.polygonAmoy]: ChainId.polygonAmoy, +}; + +export const chainIdToNameMapping: { + [key in ChainId]: ChainName; +} = { + [ChainId.arbitrum]: ChainName.arbitrum, + [ChainId.arbitrumNova]: ChainName.arbitrumNova, + [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, + [ChainId.avalanche]: ChainName.avalanche, + [ChainId.avalancheFuji]: ChainName.avalancheFuji, + [ChainId.base]: ChainName.base, + [ChainId.blast]: ChainName.blast, + [ChainId.blastSepolia]: ChainName.blastSepolia, + [ChainId.bsc]: ChainName.bsc, + [ChainId.celo]: ChainName.celo, + [ChainId.degen]: ChainName.degen, + [ChainId.gnosis]: ChainName.gnosis, + [ChainId.linea]: ChainName.linea, + [ChainId.manta]: ChainName.manta, + [ChainId.optimism]: ChainName.optimism, + [ChainId.polygon]: ChainName.polygon, + [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, + [ChainId.rari]: ChainName.rari, + [ChainId.scroll]: ChainName.scroll, + [ChainId.zora]: ChainName.zora, + [ChainId.mainnet]: ChainName.mainnet, + [ChainId.holesky]: ChainName.holesky, + [ChainId.hardhat]: ChainName.hardhat, + [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, + [ChainId.sepolia]: ChainName.sepolia, + [ChainId.optimismSepolia]: ChainName.optimismSepolia, + [ChainId.bscTestnet]: ChainName.bscTestnet, + [ChainId.polygonMumbai]: ChainName.polygonMumbai, + [ChainId.baseSepolia]: ChainName.baseSepolia, + [ChainId.zoraSepolia]: ChainName.zoraSepolia, + [ChainId.polygonAmoy]: ChainName.polygonAmoy, +}; + +export const ChainNameDisplay = { + [ChainId.arbitrum]: 'Arbitrum', + [ChainId.arbitrumNova]: chain.arbitrumNova.name, + [ChainId.avalanche]: 'Avalanche', + [ChainId.avalancheFuji]: 'Avalanche Fuji', + [ChainId.base]: 'Base', + [ChainId.blast]: 'Blast', + [ChainId.blastSepolia]: 'Blast Sepolia', + [ChainId.bsc]: 'BSC', + [ChainId.celo]: chain.celo.name, + [ChainId.degen]: 'Degen Chain', + [ChainId.linea]: 'Linea', + [ChainId.manta]: 'Manta', + [ChainId.optimism]: 'Optimism', + [ChainId.polygon]: 'Polygon', + [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, + [ChainId.rari]: 'RARI Chain', + [ChainId.scroll]: chain.scroll.name, + [ChainId.zora]: 'Zora', + [ChainId.mainnet]: 'Ethereum', + [ChainId.hardhat]: 'Hardhat', + [ChainId.hardhatOptimism]: 'Hardhat OP', + [ChainId.sepolia]: chain.sepolia.name, + [ChainId.holesky]: chain.holesky.name, + [ChainId.optimismSepolia]: chain.optimismSepolia.name, + [ChainId.bscTestnet]: 'BSC Testnet', + [ChainId.polygonMumbai]: chain.polygonMumbai.name, + [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, + [ChainId.baseSepolia]: chain.baseSepolia.name, + [ChainId.zoraSepolia]: 'Zora Sepolia', + [ChainId.polygonAmoy]: 'Polygon Amoy', +} as const; + export type NetworkTypes = 'layer1' | 'layer2' | 'testnet'; export interface NetworkProperties extends Chain { diff --git a/src/networks/zora.ts b/src/networks/zora.ts index e8c5da58ffd..58d95526fdc 100644 --- a/src/networks/zora.ts +++ b/src/networks/zora.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { zora } from '@wagmi/chains'; import { ZORA_ETH_ADDRESS } from '@/references'; import { getZoraGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getZoraNetworkObject = (): NetworkProperties => { const { zora_enabled, zora_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getZoraNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(zora.id), - getProvider: () => getProviderForNetwork(Network.zora), + getProvider: () => getProvider({ chainId: ChainId.arbitrum }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/notifications/NotificationsHandler.tsx b/src/notifications/NotificationsHandler.tsx index 5c0d5a41af0..bb5240980db 100644 --- a/src/notifications/NotificationsHandler.tsx +++ b/src/notifications/NotificationsHandler.tsx @@ -22,7 +22,7 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { AppState as ApplicationState, AppStateStatus, NativeEventSubscription } from 'react-native'; import notifee, { Event as NotifeeEvent, EventType } from '@notifee/react-native'; -import { ethereumUtils, isLowerCaseMatch } from '@/utils'; +import { isLowerCaseMatch } from '@/utils'; import walletTypes from '@/helpers/walletTypes'; import { NotificationSubscriptionChangesListener, @@ -165,10 +165,11 @@ export const NotificationsHandler = ({ walletReady }: Props) => { } Navigation.handleAction(Routes.PROFILE_SCREEN, {}); - const network = ethereumUtils.getNetworkFromChainId(parseInt(data.chain, 10)); + const chainId = parseInt(data.chain, 10); + const transaction = await transactionFetchQuery({ hash: data.hash, - network: network, + chainId, address: walletAddress, currency: nativeCurrency, }); diff --git a/src/parsers/accounts.js b/src/parsers/accounts.js deleted file mode 100644 index aa31cce971b..00000000000 --- a/src/parsers/accounts.js +++ /dev/null @@ -1,72 +0,0 @@ -import isNil from 'lodash/isNil'; -import toUpper from 'lodash/toUpper'; -import { isNativeAsset } from '@/handlers/assets'; -import * as i18n from '@/languages'; -import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; -import { getTokenMetadata, isLowerCaseMatch } from '@/utils'; -import { memoFn } from '@/utils/memoFn'; -import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; - -// eslint-disable-next-line no-useless-escape -const sanitize = memoFn(s => s.replace(/[^a-z0-9áéíóúñü \.,_@:-]/gim, '')); - -export const parseAssetName = (metadata, name) => { - if (metadata?.name) return metadata?.name; - return name ? sanitize(name) : i18n.t(i18n.l.assets.unkown_token); -}; - -export const parseAssetSymbol = (metadata, symbol) => { - if (metadata?.symbol) return metadata?.symbol; - return symbol ? toUpper(sanitize(symbol)) : '———'; -}; - -/** - * @desc parse asset - * @param {Object} assetData - * @return The parsed asset. - */ -export const parseAsset = ({ asset_code: address, ...asset } = {}) => { - const metadata = getTokenMetadata(asset.mainnet_address || address); - const name = parseAssetName(metadata, asset.name); - const symbol = parseAssetSymbol(metadata, asset.symbol); - - const parsedAsset = { - ...asset, - ...metadata, - address, - isNativeAsset: isNativeAsset(address, asset.chain_id || ChainId.mainnet), - name, - symbol, - uniqueId: getUniqueId(address, asset.chain_id), - }; - - return parsedAsset; -}; - -export const parseAssetsNative = (assets, nativeCurrency) => assets.map(asset => parseAssetNative(asset, nativeCurrency)); - -export const parseAssetNative = (asset, nativeCurrency) => { - const assetNativePrice = asset?.price; - if (isNil(assetNativePrice)) { - return asset; - } - - const priceUnit = assetNativePrice?.value ?? 0; - const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); - return { - ...asset, - native: { - balance: nativeDisplay, - change: isLowerCaseMatch(asset.symbol, nativeCurrency) - ? null - : assetNativePrice.relative_change_24h - ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) - : '', - price: { - amount: priceUnit, - display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), - }, - }, - }; -}; diff --git a/src/parsers/accounts.ts b/src/parsers/accounts.ts new file mode 100644 index 00000000000..279e7eaa89a --- /dev/null +++ b/src/parsers/accounts.ts @@ -0,0 +1,32 @@ +import isNil from 'lodash/isNil'; +import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; +import { isLowerCaseMatch } from '@/utils'; +import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; + +export const parseAssetsNative = (assets: ParsedAddressAsset[], nativeCurrency: NativeCurrencyKey) => + assets.map(asset => parseAssetNative(asset, nativeCurrency)); + +export const parseAssetNative = (asset: ParsedAddressAsset, nativeCurrency: NativeCurrencyKey) => { + const assetNativePrice = asset?.price; + if (isNil(assetNativePrice)) { + return asset; + } + + const priceUnit = assetNativePrice?.value ?? 0; + const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); + return { + ...asset, + native: { + balance: nativeDisplay, + change: isLowerCaseMatch(asset.symbol, nativeCurrency) + ? undefined + : assetNativePrice.relative_change_24h + ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) + : '', + price: { + amount: priceUnit?.toString(), + display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), + }, + }, + }; +}; diff --git a/src/parsers/gas.ts b/src/parsers/gas.ts index 01058c09b71..ce48764e84b 100644 --- a/src/parsers/gas.ts +++ b/src/parsers/gas.ts @@ -33,7 +33,6 @@ import { multiply, toFixedDecimals, } from '@/helpers/utilities'; -import { Network } from '@/networks/types'; type BigNumberish = number | string | BigNumber; @@ -98,8 +97,7 @@ const parseGasDataConfirmationTime = ( }; export const parseRainbowMeteorologyData = ( - rainbowMeterologyData: RainbowMeteorologyData, - network: Network + rainbowMeterologyData: RainbowMeteorologyData ): { gasFeeParamsBySpeed: GasFeeParamsBySpeed; baseFeePerGas: GasFeeParam; diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 1f520d23bde..705b2cde5c0 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,4 +1,4 @@ -export { parseAssetName, parseAssetSymbol, parseAsset, parseAssetNative, parseAssetsNative } from './accounts'; +export { parseAssetNative, parseAssetsNative } from './accounts'; export { parseL2GasPrices, parseGasFeesBySpeed, diff --git a/src/parsers/requests.js b/src/parsers/requests.js index ae7da2d87b6..3bf47986390 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -77,7 +77,7 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = ethereumUtils.getNativeAssetForNetwork({ chainId }); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/parsers/transactions.ts b/src/parsers/transactions.ts index 495ff7ba0c4..b303e748dfa 100644 --- a/src/parsers/transactions.ts +++ b/src/parsers/transactions.ts @@ -20,7 +20,7 @@ import { TransactionType, TransactionWithChangesType, } from '@/resources/transactions/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const LAST_TXN_HASH_BUFFER = 20; diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 208f337024f..5143fd2e922 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -1,6 +1,5 @@ -import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { Network } from '@/helpers'; +import { NewTransaction, ParsedAddressAsset, TransactionGasParamAmounts } from '@/entities'; +import { getProvider } from '@/handlers/web3'; import { add, addBuffer, greaterThan, lessThan, multiply, subtract } from '@/helpers/utilities'; import { RainbowError } from '@/logger'; import store from '@/redux/store'; @@ -13,6 +12,7 @@ import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rai import { Address } from 'viem'; import { ActionProps } from '../references'; import { executeCrosschainSwap } from './crosschainSwap'; +import { ChainId } from '@/networks/types'; // This action is used to bridge the claimed funds to another chain export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) { @@ -51,7 +51,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // 2 - We use the default gas limit (already inflated) from the quote to calculate the aproximate gas fee const initalGasLimit = bridgeQuote.defaultGasLimit as string; - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism( // @ts-ignore @@ -153,17 +153,21 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps throw new Error('[CLAIM-BRIDGE]: executeCrosschainSwap returned undefined'); } - const typedAssetToBuy = { + const typedAssetToBuy: ParsedAddressAsset = { ...parameters.assetToBuy, network: getNetworkFromChainId(parameters.assetToBuy.chainId), + chainId: parameters.assetToBuy.chainId, colors: undefined, networks: undefined, + native: undefined, }; const typedAssetToSell = { ...parameters.assetToSell, network: getNetworkFromChainId(parameters.assetToSell.chainId), + chainId: parameters.assetToSell.chainId, colors: undefined, networks: undefined, + native: undefined, }; // 5 - if the swap was successful we add the transaction to the store @@ -197,7 +201,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps addNewTransaction({ address: bridgeQuote.from as Address, - network: getNetworkFromChainId(parameters.chainId), + chainId: parameters.chainId, transaction, }); diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index 92aea428ffc..ddbd4789be3 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -1,10 +1,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { CrosschainQuote, fillCrosschainQuote } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { REFERRER, gasUnits, ReferrerType } from '@/references'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { addNewTransaction } from '@/state/pendingTransactions'; @@ -39,7 +39,7 @@ export const estimateCrosschainSwapGasLimit = async ({ quote: CrosschainQuote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -201,8 +201,10 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToSell, network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), + chainId: parameters.assetToSell.chainId, colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, + native: undefined, }, value: quote.sellAmount.toString(), }, @@ -213,8 +215,10 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToBuy, network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), + chainId: parameters.assetToBuy.chainId, colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, + native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -223,7 +227,6 @@ export const crosschainSwap = async ({ hash: swap.hash as TxHash, // TODO: MARK - Replace this once we migrate network => chainId network, - // chainId: parameters.chainId, nonce: swap.nonce, status: 'pending', type: 'swap', @@ -233,8 +236,7 @@ export const crosschainSwap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - // chainId: parameters.chainId as ChainId, - network, + chainId, transaction, }); diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts index b5a9a05a48b..6fc53450458 100644 --- a/src/raps/actions/ens.ts +++ b/src/raps/actions/ens.ts @@ -6,7 +6,6 @@ import { analytics } from '@/analytics'; import { ENSRegistrationRecords, NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { estimateENSTransactionGasLimit, formatRecordsForTransaction } from '@/handlers/ens'; import { toHex } from '@/handlers/web3'; -import { NetworkTypes } from '@/helpers'; import { ENSRegistrationTransactionType, getENSExecutionDetails, REGISTRATION_MODES } from '@/helpers/ens'; import * as i18n from '@/languages'; import { saveCommitRegistrationParameters, updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; @@ -14,7 +13,7 @@ import store from '@/redux/store'; import { logger, RainbowError } from '@/logger'; import { parseGasParamAmounts } from '@/parsers'; import { addNewTransaction } from '@/state/pendingTransactions'; -import { Network } from '@/networks/types'; +import { ChainId, Network } from '@/networks/types'; import { createRegisterENSRap, createRenewENSRap, @@ -25,7 +24,6 @@ import { } from '../registerENS'; import { Logger } from '@ethersproject/logger'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { ChainId } from '@/__swaps__/types/chains'; export interface ENSRapActionResponse { baseNonce?: number | null; @@ -481,7 +479,7 @@ const ensAction = async ( }, to: tx?.to, value: toHex(tx.value), - network: NetworkTypes.mainnet, + network: Network.mainnet, status: 'pending', }; @@ -490,7 +488,7 @@ const ensAction = async ( addNewTransaction({ address: ownerAddress, transaction: newTransaction, - network: Network.mainnet, + chainId: ChainId.mainnet, }); return tx?.nonce; }; diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index 7a060b2e173..2b1eddb690c 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -15,11 +15,11 @@ import { unwrapNativeAsset, wrapNativeAsset, } from '@rainbow-me/swaps'; -import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { Address } from 'viem'; import { metadataPOSTClient } from '@/graphql'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { add } from '@/helpers/utilities'; @@ -62,7 +62,7 @@ export const estimateSwapGasLimit = async ({ quote: Quote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -145,8 +145,7 @@ export const estimateUnlockAndSwapFromMetadata = async ({ chainId, }); - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const swapTransaction = await populateSwap({ provider, quote, @@ -303,9 +302,6 @@ export const swap = async ({ if (!swap || !swap?.hash) throw new RainbowError('swap: error executeSwap'); - // TODO: MARK - Replace this once we migrate network => chainId - const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); - const nativePriceForAssetToBuy = (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice ? { value: (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice, @@ -342,6 +338,7 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, + native: undefined, }, value: quote.sellAmount.toString(), }, @@ -354,6 +351,7 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, + native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -386,8 +384,7 @@ export const swap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - // chainId: parameters.chainId as ChainId, - network, + chainId: parameters.chainId, transaction, }); diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index 08592cc3597..b4377935b99 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -2,10 +2,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { MaxUint256 } from '@ethersproject/constants'; import { Contract, PopulatedTransaction } from '@ethersproject/contracts'; import { parseUnits } from '@ethersproject/units'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { Address, erc20Abi, erc721Abi } from 'viem'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { TransactionGasParams, TransactionLegacyGasParams } from '@/__swaps__/types/gas'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; @@ -35,8 +35,7 @@ export const getAssetRawAllowance = async ({ chainId: ChainId; }) => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(assetAddress, erc20Abi, provider); const allowance = await tokenContract.allowance(owner, spender); return allowance.toString(); @@ -87,8 +86,7 @@ export const estimateApprove = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const gasLimit = await tokenContract.estimateGas.approve(spender, MaxUint256, { from: owner, @@ -114,8 +112,7 @@ export const populateApprove = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const approveTransaction = await tokenContract.populateTransaction.approve(spender, MaxUint256, { from: owner, @@ -141,8 +138,7 @@ export const estimateERC721Approval = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); const gasLimit = await tokenContract.estimateGas.setApprovalForAll(spender, false, { from: owner, @@ -168,8 +164,7 @@ export const populateRevokeApproval = async ({ type: 'erc20' | 'nft'; }): Promise => { if (!tokenAddress || !spenderAddress || !chainId) return {}; - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); if (type === 'erc20') { const amountToApprove = parseUnits('0', 'ether'); @@ -290,12 +285,9 @@ export const unlock = async ({ ...gasParams, } satisfies NewTransaction; - // TODO: MARK - Replace this once we migrate network => chainId - const network = ethereumUtils.getNetworkFromChainId(approval.chainId); - addNewTransaction({ address: parameters.fromAddress as Address, - network, + chainId: approval.chainId, transaction, }); diff --git a/src/raps/references.ts b/src/raps/references.ts index d8f68cf8bb9..6c6bf22dba7 100644 --- a/src/raps/references.ts +++ b/src/raps/references.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum SwapModalField { input = 'inputAmount', diff --git a/src/raps/unlockAndSwap.ts b/src/raps/unlockAndSwap.ts index 614e66b992c..cdf9349adcb 100644 --- a/src/raps/unlockAndSwap.ts +++ b/src/raps/unlockAndSwap.ts @@ -7,7 +7,7 @@ import { } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; import { isLowerCaseMatch } from '@/utils'; diff --git a/src/raps/utils.ts b/src/raps/utils.ts index b91b60cb99b..cacc8795bb2 100644 --- a/src/raps/utils.ts +++ b/src/raps/utils.ts @@ -5,11 +5,10 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { ALLOWS_PERMIT, CrosschainQuote, Quote, getQuoteExecutionDetails, getRainbowRouterContractAddress } from '@rainbow-me/swaps'; import { mainnet } from 'viem/chains'; import { Chain, erc20Abi } from 'viem'; -import { Network } from '@/helpers'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { ethereumUtils, gasUtils } from '@/utils'; import { add, greaterThan, multiply } from '@/helpers/utilities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { gasUnits } from '@/references'; import { toHexNoLeadingZeros } from '@/handlers/web3'; diff --git a/src/redux/contacts.ts b/src/redux/contacts.ts index bfa15e0e912..7de2fe6610f 100644 --- a/src/redux/contacts.ts +++ b/src/redux/contacts.ts @@ -1,6 +1,5 @@ import { Dispatch } from 'redux'; import { getContacts, saveContacts } from '@/handlers/localstorage/contacts'; -import { Network } from '@/helpers/networkTypes'; import { omitFlatten } from '@/helpers/utilities'; import { AppGetState } from '@/redux/store'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -33,11 +32,6 @@ export interface Contact { */ ens: string; - /** - * The network. - */ - network: Network; - /** * The contact's nickname. */ @@ -85,8 +79,7 @@ export const contactsLoadState = () => async (dispatch: Dispatch - (dispatch: Dispatch, getState: AppGetState) => { + (address: string, nickname: string, color: number, ens: string) => (dispatch: Dispatch, getState: AppGetState) => { const loweredAddress = address.toLowerCase(); const { contacts } = getState().contacts; const updatedContacts = { @@ -95,7 +88,6 @@ export const contactsAddOrUpdate = address: loweredAddress, color, ens, - network, nickname, }, }; diff --git a/src/redux/ensRegistration.ts b/src/redux/ensRegistration.ts index 69d95299692..44c6d1041d4 100644 --- a/src/redux/ensRegistration.ts +++ b/src/redux/ensRegistration.ts @@ -3,9 +3,9 @@ import { Dispatch } from 'react'; import { AppDispatch, AppGetState } from './store'; import { ENSRegistrations, ENSRegistrationState, Records, RegistrationParameters, TransactionRegistrationParameters } from '@/entities'; import { getLocalENSRegistrations, saveLocalENSRegistrations } from '@/handlers/localstorage/accountLocal'; -import { NetworkTypes } from '@/helpers'; import { ENS_RECORDS, REGISTRATION_MODES } from '@/helpers/ens'; import { omitFlatten } from '@/helpers/utilities'; +import { Network } from '@/networks/types'; const ENS_REGISTRATION_SET_CHANGED_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_CHANGED_RECORDS'; const ENS_REGISTRATION_SET_INITIAL_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_INITIAL_RECORDS'; @@ -345,7 +345,7 @@ export const saveCommitRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -382,7 +382,7 @@ export const updateTransactionRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -408,7 +408,7 @@ export const removeRegistrationByName = (name: string) => async (dispatch: AppDi }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, diff --git a/src/redux/explorer.ts b/src/redux/explorer.ts index 20c9f151f31..39465af9ae5 100644 --- a/src/redux/explorer.ts +++ b/src/redux/explorer.ts @@ -4,8 +4,8 @@ import { ThunkDispatch } from 'redux-thunk'; import { io, Socket } from 'socket.io-client'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AppGetState, AppState } from './store'; -import { getProviderForNetwork, isHardHat } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; +import { getProvider, isHardHat } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; // -- Constants --------------------------------------- // const EXPLORER_UPDATE_SOCKETS = 'explorer/EXPLORER_UPDATE_SOCKETS'; @@ -110,7 +110,7 @@ export const explorerClearState = () => (dispatch: ThunkDispatch (dispatch: ThunkDispatch, getState: AppGetState) => { - const { network, accountAddress } = getState().settings; + const { accountAddress, chainId } = getState().settings; const { addressSocket } = getState().explorer; // if there is another socket unsubscribe first @@ -118,9 +118,9 @@ export const explorerInit = () => (dispatch: ThunkDispatch void) => await mutex.runExclusive(callback); -const getGasPricePollingInterval = (network: Network): number => { - return getNetworkObj(network).gas.pollingIntervalInMs; +const getGasPricePollingInterval = (chainId: ChainId): number => { + return getNetworkObject({ chainId }).gas.pollingIntervalInMs; }; -const getDefaultGasLimit = (network: Network, defaultGasLimit: number): number => { - switch (network) { - case Network.arbitrum: +const getDefaultGasLimit = (chainId: ChainId, defaultGasLimit: number): number => { + switch (chainId) { + case ChainId.arbitrum: return ethUnits.arbitrum_basic_tx; - case Network.polygon: - case Network.bsc: - case Network.optimism: - case Network.mainnet: - case Network.zora: + case ChainId.polygon: + case ChainId.bsc: + case ChainId.optimism: + case ChainId.mainnet: + case ChainId.zora: default: return defaultGasLimit; } @@ -73,7 +73,7 @@ export interface GasState { gasFeeParamsBySpeed: GasFeeParamsBySpeed | LegacyGasFeeParamsBySpeed; selectedGasFee: SelectedGasFee | LegacySelectedGasFee; gasFeesBySpeed: GasFeesBySpeed | LegacyGasFeesBySpeed; - txNetwork: Network | null; + chainId: ChainId; currentBlockParams: CurrentBlockParams; blocksToConfirmation: BlocksToConfirmation; customGasFeeModifiedByUser: boolean; @@ -121,22 +121,22 @@ const getUpdatedGasFeeParams = ( gasLimit: string, nativeCurrency: NativeCurrencyKey, selectedGasFeeOption: string, - txNetwork: Network, + chainId: ChainId, l1GasFeeOptimism: BigNumber | null = null ) => { let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (txNetwork) { - case Network.polygon: + switch (chainId) { + case ChainId.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case Network.bsc: + case ChainId.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; - case Network.avalanche: + case ChainId.avalanche: nativeTokenPriceUnit = ethereumUtils.getAvaxPriceUnit(); break; - case Network.degen: + case ChainId.degen: nativeTokenPriceUnit = ethereumUtils.getDegenPriceUnit(); break; default: @@ -144,7 +144,7 @@ const getUpdatedGasFeeParams = ( break; } - const isLegacyGasNetwork = getNetworkObj(txNetwork).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasFeesBySpeed = isLegacyGasNetwork ? parseLegacyGasFeesBySpeed( @@ -192,7 +192,7 @@ export const updateGasFeeForSpeed = export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispatch: AppDispatch, getState: AppGetState) => { const { - txNetwork, + chainId, defaultGasLimit, gasFeesBySpeed, gasFeeParamsBySpeed, @@ -204,15 +204,15 @@ export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispa } = getState().gas; const { nativeCurrency } = getState().settings; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (txNetwork) { - case Network.polygon: + switch (chainId) { + case ChainId.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case Network.bsc: + case ChainId.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; default: @@ -257,7 +257,7 @@ export const getPolygonGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(Network.polygon)) as { + } = (await rainbowMeteorologyGetData(ChainId.polygon)) as { data: RainbowMeteorologyLegacyData; }; const polygonGasPriceBumpFactor = 1.05; @@ -287,7 +287,7 @@ export const getBscGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(Network.bsc)) as { + } = (await rainbowMeteorologyGetData(ChainId.bsc)) as { data: RainbowMeteorologyLegacyData; }; @@ -312,7 +312,7 @@ export const getBscGasPrices = async () => { } }; export const getArbitrumGasPrices = async () => { - const provider = getProviderForNetwork(Network.arbitrum); + const provider = getProvider({ chainId: ChainId.arbitrum }); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -330,7 +330,7 @@ export const getArbitrumGasPrices = async () => { }; export const getOptimismGasPrices = async () => { - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -347,7 +347,7 @@ export const getOptimismGasPrices = async () => { }; export const getBaseGasPrices = async () => { - const provider = getProviderForNetwork(Network.base); + const provider = getProvider({ chainId: ChainId.base }); const baseGasPrice = await provider.getGasPrice(); const BasePriceBumpFactor = 1.05; @@ -366,7 +366,7 @@ export const getBaseGasPrices = async () => { }; export const getAvalancheGasPrices = async () => { - const provider = getProviderForNetwork(Network.avalanche); + const provider = getProvider({ chainId: ChainId.avalanche }); const baseGasPrice = await provider.getGasPrice(); const AvalanchePriceBumpFactor = 1.05; @@ -385,7 +385,7 @@ export const getAvalancheGasPrices = async () => { }; export const getDegenGasPrices = async () => { - const provider = getProviderForNetwork(Network.degen); + const provider = getProvider({ chainId: ChainId.degen }); const baseGasPrice = await provider.getGasPrice(); const DegenPriceBumpFactor = 1.05; @@ -404,7 +404,7 @@ export const getDegenGasPrices = async () => { }; export const getBlastGasPrices = async () => { - const provider = getProviderForNetwork(Network.blast); + const provider = getProvider({ chainId: ChainId.blast }); const baseGasPrice = await provider.getGasPrice(); const BlastPriceBumpFactor = 1.05; @@ -423,7 +423,7 @@ export const getBlastGasPrices = async () => { }; export const getZoraGasPrices = async () => { - const provider = getProviderForNetwork(Network.zora); + const provider = getProvider({ chainId: ChainId.zora }); const baseGasPrice = await provider.getGasPrice(); const ZoraPriceBumpFactor = 1.05; @@ -441,12 +441,12 @@ export const getZoraGasPrices = async () => { return priceData; }; -export const getEIP1559GasParams = async (network: Network) => { - const { data } = (await rainbowMeteorologyGetData(network)) as { +export const getEIP1559GasParams = async (chainId: ChainId) => { + const { data } = (await rainbowMeteorologyGetData(chainId)) as { data: RainbowMeteorologyData; }; const { gasFeeParamsBySpeed, baseFeePerGas, baseFeeTrend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - parseRainbowMeteorologyData(data, network); + parseRainbowMeteorologyData(data); return { baseFeePerGas, blocksToConfirmation, @@ -458,44 +458,45 @@ export const getEIP1559GasParams = async (network: Network) => { }; export const gasPricesStartPolling = - (network = Network.mainnet, flashbots = false) => + (chainId = ChainId.mainnet, flashbots = false) => async (dispatch: AppDispatch, getState: AppGetState) => { dispatch(gasPricesStopPolling()); // this should be chain agnostic - const getGasPrices = (network: Network) => + const getGasPrices = () => withRunExclusive( () => new Promise(async (fetchResolve, fetchReject) => { try { - dispatch({ - payload: network, - type: GAS_UPDATE_TRANSACTION_NETWORK, - }); const { gasFeeParamsBySpeed: existingGasFees, customGasFeeModifiedByUser, defaultGasLimit, gasLimit, selectedGasFee, - txNetwork, + chainId, selectedGasFee: lastSelectedGasFee, gasFeesBySpeed: lastGasFeesBySpeed, currentBlockParams, l1GasFeeOptimism, } = getState().gas; + dispatch({ + payload: chainId, + type: GAS_UPDATE_TRANSACTION_NETWORK, + }); + const { nativeCurrency } = getState().settings; - const networkObj = getNetworkObj(network); + const networkObject = getNetworkObject({ chainId }); let dataIsReady = true; - if (networkObj.gas.gasType === 'legacy') { + if (networkObject.gas.gasType === 'legacy') { // OP chains have an additional fee we need to load - if (getNetworkObj(network).gas?.OptimismTxFee) { + if (networkObject.gas?.OptimismTxFee) { dataIsReady = l1GasFeeOptimism !== null; } - const adjustedGasFees = await networkObj.gas.getGasPrices(); + const adjustedGasFees = await networkObject.gas.getGasPrices(); if (!adjustedGasFees) return; @@ -503,7 +504,7 @@ export const gasPricesStartPolling = if (!gasFeeParamsBySpeed) return; const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed: updatedGasFeesBySpeed } = dataIsReady ? getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -511,7 +512,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ) : { @@ -529,7 +530,7 @@ export const gasPricesStartPolling = } else { try { const { gasFeeParamsBySpeed, baseFeePerGas, trend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - await getEIP1559GasParams(network); + await getEIP1559GasParams(chainId); if (flashbots) { [SLOW, NORMAL, FAST, URGENT].forEach(speed => { @@ -553,7 +554,7 @@ export const gasPricesStartPolling = // Set a really gas estimate to guarantee that we're gonna be over // the basefee at the time we fork mainnet during our hardhat tests let baseFee = baseFeePerGas; - if (network === Network.mainnet && IS_TESTING === 'true') { + if (chainId === ChainId.mainnet && IS_TESTING === 'true') { const providerUrl = ( web3Provider || ({} as { @@ -576,7 +577,7 @@ export const gasPricesStartPolling = gasFeeParamsBySpeed[CUSTOM] = gasFeeParamsBySpeed[URGENT]; } const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBaseFee, @@ -584,7 +585,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ); @@ -608,27 +609,27 @@ export const gasPricesStartPolling = } fetchResolve(true); } catch (e) { - logger.error(new RainbowError(`[redux/gas]: Gas Estimates Failed for ${network}: ${e}`)); + logger.error(new RainbowError(`[redux/gas]: Gas Estimates Failed for ${chainId}: ${e}`)); fetchReject(e); } }) ); - const watchGasPrices = async (network: Network, pollingInterval: number) => { + const watchGasPrices = async (chainId: ChainId, pollingInterval: number) => { try { - await getGasPrices(network); + await getGasPrices(); // eslint-disable-next-line no-empty } catch (e) { } finally { gasPricesHandle && clearTimeout(gasPricesHandle); gasPricesHandle = setTimeout(() => { - watchGasPrices(network, pollingInterval); + watchGasPrices(chainId, pollingInterval); }, pollingInterval); } }; - const pollingInterval = getGasPricePollingInterval(network); - watchGasPrices(network, pollingInterval); + const pollingInterval = getGasPricePollingInterval(chainId); + watchGasPrices(chainId, pollingInterval); }; export const gasUpdateGasFeeOption = (newGasPriceOption: string) => (dispatch: AppDispatch, getState: AppGetState) => @@ -660,10 +661,10 @@ export const gasUpdateTxFee = (updatedGasLimit?: number, overrideGasOption?: string, l1GasFeeOptimism: BigNumber | null = null) => (dispatch: AppDispatch, getState: AppGetState) => withRunExclusive(async () => { - const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, txNetwork, currentBlockParams } = getState().gas; + const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, chainId, currentBlockParams } = getState().gas; const { nativeCurrency } = getState().settings; - if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObj(txNetwork).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { + if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObject({ chainId }).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { // if fee prices not ready, we need to store the gas limit for future calculations // the rest is as the initial state value if (updatedGasLimit) { @@ -674,7 +675,7 @@ export const gasUpdateTxFee = } } else { const _selectedGasFeeOption = overrideGasOption || selectedGasFee.option || NORMAL; - const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -682,7 +683,7 @@ export const gasUpdateTxFee = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ); dispatch({ @@ -715,7 +716,7 @@ const INITIAL_STATE: GasState = { gasLimit: null, l1GasFeeOptimism: null, selectedGasFee: {} as SelectedGasFee, - txNetwork: null, + chainId: ChainId.mainnet, secondsPerNewBlock: 15, }; @@ -770,7 +771,7 @@ export default (state = INITIAL_STATE, action: { type: string; payload: any }) = case GAS_UPDATE_TRANSACTION_NETWORK: return { ...state, - txNetwork: action.payload, + chainId: action.payload, }; case GAS_PRICES_RESET: return INITIAL_STATE; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index aa5196b9cd4..a15206fbace 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -6,7 +6,7 @@ import { getLocalRequests, removeLocalRequest, saveLocalRequests } from '@/handl import { omitFlatten } from '@/helpers/utilities'; import { getRequestDisplayDetails } from '@/parsers'; import { logger } from '@/logger'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; // -- Constants --------------------------------------- // diff --git a/src/redux/settings.ts b/src/redux/settings.ts index 728caf45bcd..844d28b1348 100644 --- a/src/redux/settings.ts +++ b/src/redux/settings.ts @@ -10,24 +10,23 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { getAppIcon, + getChainId, getFlashbotsEnabled, getLanguage, getNativeCurrency, - getNetwork, getTestnetsEnabled, saveAppIcon, + saveChainId, saveFlashbotsEnabled, saveLanguage, saveNativeCurrency, - saveNetwork, saveTestnetsEnabled, } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; import { explorerClearState, explorerInit } from '@/redux/explorer'; import { AppState } from '@/redux/store'; -import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; +import { Network, ChainId } from '@/networks/types'; // -- Constants ------------------------------------------------------------- // const SETTINGS_UPDATE_SETTINGS_ADDRESS = 'settings/SETTINGS_UPDATE_SETTINGS_ADDRESS'; @@ -105,7 +104,6 @@ interface SettingsStateUpdateNetworkSuccessAction { type: typeof SETTINGS_UPDATE_NETWORK_SUCCESS; payload: { chainId: SettingsState['chainId']; - network: SettingsState['network']; }; } @@ -151,11 +149,10 @@ export const settingsLoadState = export const settingsLoadNetwork = () => async (dispatch: Dispatch) => { try { - const network = await getNetwork(); - const chainId = ethereumUtils.getChainIdFromNetwork(network); - await web3SetHttpProvider(network); + const chainId = await getChainId(); + await web3SetHttpProvider(chainId); dispatch({ - payload: { chainId, network }, + payload: { chainId }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); } catch (error) { @@ -237,15 +234,14 @@ export const settingsUpdateAccountAddress = }); }; -export const settingsUpdateNetwork = (network: Network) => async (dispatch: Dispatch) => { - const chainId = ethereumUtils.getChainIdFromNetwork(network); - await web3SetHttpProvider(network); +export const settingsUpdateNetwork = (chainId: ChainId) => async (dispatch: Dispatch) => { + await web3SetHttpProvider(chainId); try { dispatch({ - payload: { chainId, network }, + payload: { chainId }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); - saveNetwork(network); + saveChainId(chainId); } catch (error) { logger.error(new RainbowError(`[redux/settings]: Error updating network settings: ${error}`)); } @@ -315,7 +311,6 @@ export default (state = INITIAL_STATE, action: SettingsStateUpdateAction) => { return { ...state, chainId: action.payload.chainId, - network: action.payload.network, }; case SETTINGS_UPDATE_LANGUAGE_SUCCESS: return { diff --git a/src/redux/showcaseTokens.ts b/src/redux/showcaseTokens.ts index d0d1d934ab5..23252088f9f 100644 --- a/src/redux/showcaseTokens.ts +++ b/src/redux/showcaseTokens.ts @@ -4,8 +4,8 @@ import { Dispatch } from 'redux'; import { getPreference } from '../model/preferences'; import { AppGetState } from './store'; import { getShowcaseTokens, getWebDataEnabled, saveShowcaseTokens, saveWebDataEnabled } from '@/handlers/localstorage/accountLocal'; -import networkTypes from '@/helpers/networkTypes'; import WalletTypes from '@/helpers/walletTypes'; +import { Network } from '@/networks/types'; // -- Constants --------------------------------------- // @@ -204,7 +204,7 @@ export const removeShowcaseToken = (tokenId: string) => (dispatch: Dispatch + (enabled: boolean, address: string, network = Network.mainnet) => async (dispatch: Dispatch) => { dispatch({ payload: enabled, diff --git a/src/redux/swap.ts b/src/redux/swap.ts index 10bf5a81080..0ca1da8addd 100644 --- a/src/redux/swap.ts +++ b/src/redux/swap.ts @@ -109,7 +109,7 @@ export const updateSwapInputCurrency = dispatch({ payload: newInputCurrency, type: SWAP_UPDATE_INPUT_CURRENCY }); if ( type === ExchangeModalTypes.swap && - newInputCurrency?.network !== outputCurrency?.network && + newInputCurrency?.chainId !== outputCurrency?.chainId && newInputCurrency && !ignoreTypeCheck ) { @@ -131,7 +131,7 @@ export const updateSwapOutputCurrency = } else { if ( type === ExchangeModalTypes.swap && - newOutputCurrency?.network !== inputCurrency?.network && + newOutputCurrency?.chainId !== inputCurrency?.chainId && newOutputCurrency && !ignoreTypeCheck ) { diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index da0ea48400c..f75a286e044 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -25,13 +25,15 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { convertHexToString, delay, omitBy, pickBy } from '@/helpers/utilities'; import WalletConnectApprovalSheetType from '@/helpers/walletConnectApprovalSheetTypes'; import Routes from '@/navigation/routesNames'; -import { ethereumUtils, watchingAlert } from '@/utils'; +import { watchingAlert } from '@/utils'; import { getFCMToken } from '@/notifications/tokens'; import { logger, RainbowError } from '@/logger'; import { IS_DEV, IS_IOS, IS_TEST } from '@/env'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { Verify } from '@walletconnect/types'; import { RequestSource, handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; +import { ChainId } from '@/networks/types'; +import { Address } from 'viem'; // -- Variables --------------------------------------- // let showRedirectSheetThreshold = 300; @@ -132,8 +134,8 @@ export type WalletconnectResultType = 'timedOut' | 'sign' | 'transaction' | 'sig export interface WalletconnectApprovalSheetRouteParams { callback: ( approved: boolean, - chainId: number, - accountAddress: string, + chainId: ChainId, + accountAddress: Address, peerId: WalletconnectRequestData['peerId'], dappScheme: WalletconnectRequestData['dappScheme'], dappName: WalletconnectRequestData['dappName'], @@ -470,11 +472,11 @@ const listenOnNewMessages = const requestId = payload.id; if (payload.method === 'wallet_addEthereumChain' || payload.method === `wallet_switchEthereumChain`) { const { chainId } = payload.params[0]; - const currentNetwork = ethereumUtils.getNetworkFromChainId( - // @ts-expect-error "_chainId" is private. - Number(walletConnector._chainId) + // @ts-expect-error "_chainId" is private. + const currentChainId = Number(walletConnector._chainId); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => + network.id.toString() ); - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { dispatch(walletConnectSetPendingRedirect()); @@ -511,7 +513,7 @@ const listenOnNewMessages = }); } }, - currentNetwork, + currentChainId, meta: { chainIds: [Number(numericChainId)], dappName, diff --git a/src/references/chain-assets.json b/src/references/chain-assets.json index 766268a1054..3f05d7c252a 100644 --- a/src/references/chain-assets.json +++ b/src/references/chain-assets.json @@ -1,7 +1,8 @@ { - "goerli": [ + "5": [ { "asset": { + "chainId": 5, "asset_code": "eth", "mainnet_address": "eth", "colors": { @@ -29,9 +30,10 @@ "quantity": "0" } ], - "mainnet": [ + "1": [ { "asset": { + "chainId": 1, "asset_code": "eth", "colors": { "fallback": "#E8EAF5", diff --git a/src/references/gasUnits.ts b/src/references/gasUnits.ts index d92d4d42df2..02e494dcdf6 100644 --- a/src/references/gasUnits.ts +++ b/src/references/gasUnits.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const gasUnits = { basic_approval: '55000', diff --git a/src/references/index.ts b/src/references/index.ts index a692c06120e..5d4e5caec68 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -1,7 +1,6 @@ import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay, Network } from '@/networks/types'; import { Asset } from '@/entities'; -import { Network } from '@/helpers/networkTypes'; import { AddressZero } from '@ethersproject/constants'; import type { Address } from 'viem'; diff --git a/src/references/rainbow-token-list/index.ts b/src/references/rainbow-token-list/index.ts index 29a59976443..688e07eef13 100644 --- a/src/references/rainbow-token-list/index.ts +++ b/src/references/rainbow-token-list/index.ts @@ -6,8 +6,7 @@ import RAINBOW_TOKEN_LIST_DATA from './rainbow-token-list.json'; import { RainbowToken } from '@/entities'; import { STORAGE_IDS } from '@/model/mmkv'; import { logger, RainbowError } from '@/logger'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { Network, ChainId } from '@/networks/types'; export const rainbowListStorage = new MMKV({ id: STORAGE_IDS.RAINBOW_TOKEN_LIST, diff --git a/src/references/testnet-assets-by-chain.ts b/src/references/testnet-assets-by-chain.ts index eb56ab9f147..8bfb3c1d61f 100644 --- a/src/references/testnet-assets-by-chain.ts +++ b/src/references/testnet-assets-by-chain.ts @@ -1,6 +1,5 @@ import { UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainName } from '@/__swaps__/types/chains'; -import { Network } from '@/helpers'; +import { ChainId, ChainName } from '@/networks/types'; type ChainAssets = { [uniqueId: UniqueId]: { @@ -10,8 +9,8 @@ type ChainAssets = { }; // NOTE: Don't import `ETH_ADDRESS` as it's resolving to undefined... -export const chainAssets: Partial> = { - [Network.goerli]: { +export const chainAssets: Partial> = { + [ChainId.goerli]: { eth_5: { asset: { asset_code: 'eth', @@ -38,7 +37,7 @@ export const chainAssets: Partial> = { quantity: '0', }, }, - [Network.mainnet]: { + [ChainId.mainnet]: { eth_1: { asset: { asset_code: 'eth', diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts index 5f2ea008d0e..1e0a285e3d5 100644 --- a/src/resources/assets/UserAssetsQuery.ts +++ b/src/resources/assets/UserAssetsQuery.ts @@ -2,15 +2,15 @@ import isEmpty from 'lodash/isEmpty'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { NativeCurrencyKey } from '@/entities'; import { saveAccountEmptyState } from '@/handlers/localstorage/accountLocal'; -import { Network } from '@/helpers/networkTypes'; import { greaterThan } from '@/helpers/utilities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { createQueryKey, queryClient, QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { useQuery } from '@tanstack/react-query'; import { filterPositionsData, parseAddressAsset } from './assets'; import { fetchHardhatBalances } from './hardhatAssets'; import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types'; +import { Network } from '@/networks/types'; // /////////////////////////////////////////////// // Query Types @@ -62,7 +62,9 @@ async function userAssetsQueryFunction({ } try { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( + network => network.id + ); const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds(address, currency, chainIds); let parsedSuccessResults = results; diff --git a/src/resources/assets/assetSelectors.ts b/src/resources/assets/assetSelectors.ts index c5fb9bf467b..41d6d2ba9e5 100644 --- a/src/resources/assets/assetSelectors.ts +++ b/src/resources/assets/assetSelectors.ts @@ -1,4 +1,4 @@ -import { ParsedAddressAsset } from '@/entities'; +import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { parseAssetsNative } from '@/parsers'; import isEmpty from 'lodash/isEmpty'; import isNil from 'lodash/isNil'; @@ -12,13 +12,13 @@ export function selectUserAssetWithUniqueId(uniqueId: string) { }; } -export function selectSortedUserAssets(nativeCurrency: string) { +export function selectSortedUserAssets(nativeCurrency: NativeCurrencyKey) { return (accountAssets: RainbowAddressAssets) => { return sortAssetsByNativeAmount(accountAssets, nativeCurrency); }; } -const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: string): ParsedAddressAsset[] => { +const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: NativeCurrencyKey): ParsedAddressAsset[] => { let assetsNativePrices = Object.values(accountAssets); if (!isEmpty(assetsNativePrices)) { diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index 48cf161d614..37cee5c850b 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -3,7 +3,6 @@ import isEmpty from 'lodash/isEmpty'; import { MMKV } from 'react-native-mmkv'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers/networkTypes'; import { convertRawAmountToBalance } from '@/helpers/utilities'; import { BooleanMap } from '@/hooks/useCoinListEditOptions'; import { queryClient } from '@/react-query'; @@ -14,11 +13,10 @@ import { RainbowPositions } from '@/resources/defi/types'; import { ethereumUtils } from '@/utils'; import { AddysAddressAsset, AddysAsset, ParsedAsset, RainbowAddressAssets } from './types'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/networks/types'; const storage = new MMKV(); -const MAINNET_CHAIN_ID = ethereumUtils.getChainIdFromNetwork(Network.mainnet); - export const filterPositionsData = ( address: string, currency: NativeCurrencyKey, @@ -43,7 +41,7 @@ export const filterPositionsData = ( export function parseAsset({ address, asset }: { address: string; asset: AddysAsset }): ParsedAsset { const network = asset?.network; const chainId = ethereumUtils.getChainIdFromNetwork(network); - const mainnetAddress = asset?.networks?.[MAINNET_CHAIN_ID]?.address; + const mainnetAddress = asset?.networks?.[ChainId.mainnet]?.address; const uniqueId = getUniqueId(address, chainId); const parsedAsset = { @@ -74,6 +72,7 @@ export function parseAddressAsset({ assetData }: { assetData: AddysAddressAsset const asset = assetData?.asset; const quantity = assetData?.quantity; const address = assetData?.asset?.asset_code; + const parsedAsset = parseAsset({ address, asset, diff --git a/src/resources/assets/externalAssetsQuery.ts b/src/resources/assets/externalAssetsQuery.ts index 0092abdeebc..de29cf9d322 100644 --- a/src/resources/assets/externalAssetsQuery.ts +++ b/src/resources/assets/externalAssetsQuery.ts @@ -4,8 +4,9 @@ import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunct import { convertAmountAndPriceToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; import { NativeCurrencyKey } from '@/entities'; import { Token } from '@/graphql/__generated__/metadata'; -import { ethereumUtils } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { isNativeAsset } from '@/__swaps__/utils/chains'; +import { AddressOrEth } from '@/__swaps__/types/assets'; export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute @@ -19,7 +20,9 @@ export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute // Types type ExternalToken = Pick; export type FormattedExternalAsset = ExternalToken & { + address: string; icon_url?: string; + isNativeAsset: boolean; native: { change: string; price: { @@ -43,9 +46,16 @@ export const externalTokenQueryKey = ({ address, chainId, currency }: ExternalTo type externalTokenQueryKey = ReturnType; // Helpers -const formatExternalAsset = (asset: ExternalToken, nativeCurrency: NativeCurrencyKey): FormattedExternalAsset => { +const formatExternalAsset = ( + address: string, + chainId: ChainId, + asset: ExternalToken, + nativeCurrency: NativeCurrencyKey +): FormattedExternalAsset => { return { ...asset, + address, + isNativeAsset: isNativeAsset(address as AddressOrEth, chainId), native: { change: asset?.price?.relativeChange24h ? convertAmountToPercentageDisplay(`${asset?.price?.relativeChange24h}`) : '', price: convertAmountAndPriceToNativeDisplay(1, asset?.price?.value || 0, nativeCurrency), @@ -62,7 +72,7 @@ export async function fetchExternalToken({ address, chainId, currency }: Externa currency, }); if (response.token) { - return formatExternalAsset(response.token, currency); + return formatExternalAsset(address, chainId, response.token, currency); } else { return null; } diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/hardhatAssets.ts index 63ba4a2b21f..df898634440 100644 --- a/src/resources/assets/hardhatAssets.ts +++ b/src/resources/assets/hardhatAssets.ts @@ -1,23 +1,23 @@ import { Contract } from '@ethersproject/contracts'; import { keyBy, mapValues } from 'lodash'; -import { Network } from '@/helpers/networkTypes'; import { web3Provider } from '@/handlers/web3'; // TODO JIN -import { getNetworkObj } from '@/networks'; import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS, SUPPORTED_CHAIN_IDS } from '@/references'; import { parseAddressAsset } from './assets'; import { RainbowAddressAssets } from './types'; import { logger, RainbowError } from '@/logger'; import { AddressOrEth, UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { AddressZero } from '@ethersproject/constants'; import chainAssetsByChainId from '@/references/testnet-assets-by-chain'; +import { getNetworkObject } from '@/networks'; +import { ChainId, ChainName, Network } from '@/networks/types'; const fetchHardhatBalancesWithBalanceChecker = async ( tokens: string[], address: string, - network: Network = Network.mainnet + chainId: ChainId = ChainId.mainnet ): Promise<{ [tokenAddress: string]: string } | null> => { - const balanceCheckerContract = new Contract(getNetworkObj(network).balanceCheckerAddress, balanceCheckerContractAbi, web3Provider); + const networkObject = getNetworkObject({ chainId }); + const balanceCheckerContract = new Contract(networkObject.balanceCheckerAddress, balanceCheckerContractAbi, web3Provider); try { const values = await balanceCheckerContract.balances([address], tokens); const balances: { @@ -42,13 +42,16 @@ const fetchHardhatBalancesWithBalanceChecker = async ( * @param network - The network to fetch the balances for. * @returns The balances of the hardhat assets for the given account address and network. */ -export const fetchHardhatBalances = async (accountAddress: string, network: Network = Network.mainnet): Promise => { - const chainAssetsMap = keyBy(chainAssets[network as keyof typeof chainAssets], ({ asset }) => `${asset.asset_code}_${asset.chainId}`); +export const fetchHardhatBalances = async (accountAddress: string, chainId: ChainId = ChainId.mainnet): Promise => { + const chainAssetsMap = keyBy( + chainAssets[`${chainId}` as keyof typeof chainAssets], + ({ asset }) => `${asset.asset_code}_${asset.chainId}` + ); const tokenAddresses = Object.values(chainAssetsMap).map(({ asset: { asset_code } }) => asset_code === ETH_ADDRESS ? AddressZero : asset_code.toLowerCase() ); - const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); if (!balances) return {}; const updatedAssets = mapValues(chainAssetsMap, chainAsset => { @@ -67,7 +70,7 @@ export const fetchHardhatBalances = async (accountAddress: string, network: Netw export const fetchHardhatBalancesByChainId = async ( accountAddress: string, - network: Network = Network.mainnet + chainId: ChainId = ChainId.mainnet ): Promise<{ assets: { [uniqueId: UniqueId]: { @@ -77,12 +80,13 @@ export const fetchHardhatBalancesByChainId = async ( }; chainIdsInResponse: ChainId[]; }> => { - const chainAssetsMap = chainAssetsByChainId[network as keyof typeof chainAssets] || {}; + const chainAssetsMap = chainAssetsByChainId[`${chainId}` as keyof typeof chainAssets] || {}; + const tokenAddresses = Object.values(chainAssetsMap).map(({ asset }) => asset.asset_code === ETH_ADDRESS ? AddressZero : asset.asset_code.toLowerCase() ); - const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); if (!balances) return { assets: {}, diff --git a/src/resources/assets/types.ts b/src/resources/assets/types.ts index 39a5f41ea6d..a056e695b91 100644 --- a/src/resources/assets/types.ts +++ b/src/resources/assets/types.ts @@ -1,6 +1,6 @@ -import { Network } from '@/helpers/networkTypes'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { Network } from '@/networks/types'; export type AddysAccountAssetsResponse = { meta: AddysAccountAssetsMeta; @@ -54,7 +54,7 @@ export interface ParsedAsset { address: string; color?: string; colors?: TokenColors; - chainId?: number; + chainId: number; chainName?: string; decimals: number; icon_url?: string; diff --git a/src/resources/assets/useUserAsset.ts b/src/resources/assets/useUserAsset.ts index b94d22c15ec..50f9b072410 100644 --- a/src/resources/assets/useUserAsset.ts +++ b/src/resources/assets/useUserAsset.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { getNetworkObject } from '@/networks'; diff --git a/src/resources/defi/PositionsQuery.ts b/src/resources/defi/PositionsQuery.ts index 8579523d939..124948a1c93 100644 --- a/src/resources/defi/PositionsQuery.ts +++ b/src/resources/defi/PositionsQuery.ts @@ -3,14 +3,14 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { NativeCurrencyKey } from '@/entities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { AddysPositionsResponse, PositionsArgs } from './types'; import { parsePositions } from './utils'; export const buildPositionsUrl = (address: string) => { - const networkString = RainbowNetworks.filter(network => network.enabled) + const networkString = RainbowNetworkObjects.filter(network => network.enabled) .map(network => network.id) .join(','); return `https://addys.p.rainbow.me/v3/${networkString}/${address}/positions`; diff --git a/src/resources/ens/ensAddressQuery.ts b/src/resources/ens/ensAddressQuery.ts index f84bb000bb8..a4110e5c5d1 100644 --- a/src/resources/ens/ensAddressQuery.ts +++ b/src/resources/ens/ensAddressQuery.ts @@ -1,7 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryFunctionArgs } from '@/react-query'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; // Set a default stale time of 10 seconds so we don't over-fetch // (query will serve cached data & invalidate after 10s). @@ -23,7 +24,7 @@ const ensAddressQueryKey = ({ name }: ENSAddressArgs) => createQueryKey('ensAddr // Query Function async function ensAddressQueryFunction({ queryKey: [{ name }] }: QueryFunctionArgs) { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const address = await provider.resolveName(name); return address; } diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 3bb6096f8aa..6a3b918f42a 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -1,8 +1,7 @@ import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { NativeCurrencyKeys, RainbowToken } from '@/entities'; -import { Network } from '@/networks/types'; import { createQueryKey, queryClient } from '@/react-query'; import { DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { promiseUtils } from '@/utils'; diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index 5e57fda8bf8..1730cb71592 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,15 +1,14 @@ import { QueryFunction, useQuery } from '@tanstack/react-query'; import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; -import { NFT } from '@/resources/nfts/types'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; import { useSelector } from 'react-redux'; import { AppState } from '@/redux/store'; -import { Network } from '@/helpers'; import { UniqueAsset } from '@/entities'; import { arcClient } from '@/graphql'; import { createSelector } from 'reselect'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; +import { ChainId } from '@/networks/types'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour @@ -21,12 +20,12 @@ export const nftsQueryKey = ({ address, sortBy }: { address: string; sortBy: Nft export const nftListingQueryKey = ({ contractAddress, tokenId, - network, + chainId, }: { contractAddress: string; tokenId: string; - network: Omit; -}) => createQueryKey('nftListing', { contractAddress, tokenId, network }); + chainId: Omit; +}) => createQueryKey('nftListing', { contractAddress, tokenId, chainId }); const walletsSelector = (state: AppState) => state.wallets?.wallets; @@ -105,17 +104,17 @@ export function useLegacyNFTs({ export function useNFTListing({ contractAddress, tokenId, - network, + chainId, }: { contractAddress: string; tokenId: string; - network: Omit; + chainId: Omit; }) { return useQuery( - nftListingQueryKey({ contractAddress, tokenId, network }), - async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, network)) ?? null, + nftListingQueryKey({ contractAddress, tokenId, chainId }), + async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, chainId)) ?? null, { - enabled: !!network && !!contractAddress && !!tokenId, + enabled: !!chainId && !!contractAddress && !!tokenId, staleTime: 0, cacheTime: 0, } diff --git a/src/resources/nfts/simplehash/index.ts b/src/resources/nfts/simplehash/index.ts index 8e6e9f24271..5d716846c47 100644 --- a/src/resources/nfts/simplehash/index.ts +++ b/src/resources/nfts/simplehash/index.ts @@ -1,11 +1,11 @@ import { NFT_API_KEY, NFT_API_URL } from 'react-native-dotenv'; import { RainbowFetchClient } from '@/rainbow-fetch'; -import { Network } from '@/helpers'; import { SimpleHashListing, SimpleHashNFT, SimpleHashMarketplaceId } from '@/resources/nfts/simplehash/types'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { UniqueAsset } from '@/entities'; import { RainbowError, logger } from '@/logger'; import { getGnosisNetworkObject } from '@/networks/gnosis'; +import { ChainId } from '@/networks/types'; export const START_CURSOR = 'start'; @@ -18,16 +18,16 @@ const createCursorSuffix = (cursor: string) => (cursor === START_CURSOR ? '' : ` export async function fetchSimpleHashNFT( contractAddress: string, tokenId: string, - network: Omit = Network.mainnet + chainId: Omit = ChainId.mainnet ): Promise { - const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; + const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; - if (!chain) { - logger.warn(`[simplehash]: no SimpleHash chain for network: ${network}`); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); return; } - const response = await nftApi.get(`/nfts/${chain}/${contractAddress}/${tokenId}`, { + const response = await nftApi.get(`/nfts/${simplehashNetwork}/${contractAddress}/${tokenId}`, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', @@ -40,15 +40,15 @@ export async function fetchSimpleHashNFT( export async function fetchSimpleHashNFTListing( contractAddress: string, tokenId: string, - network: Omit = Network.mainnet + chainId: Omit = ChainId.mainnet ): Promise { // array of all eth listings on OpenSea for this token let listings: SimpleHashListing[] = []; let cursor = START_CURSOR; - const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; + const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; - if (!chain) { - logger.warn(`[simplehash]: no SimpleHash chain for network: ${network}`); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); return; } @@ -57,7 +57,7 @@ export async function fetchSimpleHashNFTListing( // eslint-disable-next-line no-await-in-loop const response = await nftApi.get( // OpenSea ETH offers only for now - `/nfts/listings/${chain}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, + `/nfts/listings/${simplehashNetwork}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, { headers: { 'Accept': 'application/json', @@ -84,16 +84,16 @@ export async function fetchSimpleHashNFTListing( * @param nft */ export async function refreshNFTContractMetadata(nft: UniqueAsset) { - const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; + const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; - if (!chain) { - logger.warn(`[simplehash]: no SimpleHash chain for network: ${nft.network}`); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); return; } try { await nftApi.post( - `/nfts/refresh/${chain}/${nft.asset_contract.address}`, + `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}`, {}, { headers: { @@ -111,7 +111,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { // If the collection has > 20k NFTs, the above request will fail. // In that case, we need to refresh the given NFT individually. await nftApi.post( - `/nfts/refresh/${chain}/${nft.asset_contract.address}/${nft.id}`, + `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}/${nft.id}`, {}, { headers: { @@ -136,10 +136,10 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { * @param nft */ export async function reportNFT(nft: UniqueAsset) { - const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; + const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; - if (!chain) { - logger.warn(`[simplehash]: no SimpleHash chain for network: ${nft.network}`); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); return; } @@ -148,7 +148,7 @@ export async function reportNFT(nft: UniqueAsset) { '/nfts/report/spam', { contract_address: nft.asset_contract.address, - chain_id: chain, + chain_id: simplehashNetwork, token_id: nft.id, event_type: 'mark_as_spam', }, diff --git a/src/resources/nfts/simplehash/types.ts b/src/resources/nfts/simplehash/types.ts index a26cddcf86b..7cc2c802cbe 100644 --- a/src/resources/nfts/simplehash/types.ts +++ b/src/resources/nfts/simplehash/types.ts @@ -1,4 +1,4 @@ -import { Network } from '@/helpers'; +import { Network } from '@/networks/types'; /** * @see https://docs.simplehash.com/reference/sale-model diff --git a/src/resources/nfts/simplehash/utils.ts b/src/resources/nfts/simplehash/utils.ts index 68929492ca2..8cf2c3e8ac6 100644 --- a/src/resources/nfts/simplehash/utils.ts +++ b/src/resources/nfts/simplehash/utils.ts @@ -20,7 +20,7 @@ import { deviceUtils } from '@/utils'; import { TokenStandard } from '@/handlers/web3'; import { handleNFTImages } from '@/utils/handleNFTImages'; import { SimpleHashNft } from '@/graphql/__generated__/arc'; -import { Network } from '@/helpers'; +import { Network, chainNameToIdMapping } from '@/networks/types'; const ENS_COLLECTION_NAME = 'ENS'; const SVG_MIME_TYPE = 'image/svg+xml'; @@ -78,6 +78,7 @@ export function simpleHashNFTToUniqueAsset(nft: SimpleHashNft, address: string): slug: marketplace?.marketplace_collection_id ?? '', twitter_username: collection.twitter_username, }, + chainId: chainNameToIdMapping[nft.chain as keyof typeof chainNameToIdMapping], description: nft.description, external_link: nft.external_url, familyImage: collection.image_url, diff --git a/src/resources/nfts/types.ts b/src/resources/nfts/types.ts index aafbe3f300a..5f92ca09560 100644 --- a/src/resources/nfts/types.ts +++ b/src/resources/nfts/types.ts @@ -1,5 +1,5 @@ -import { Network } from '@/helpers/networkTypes'; import { Asset, AssetContract, AssetType } from '@/entities'; +import { Network } from '@/networks/types'; import { UniqueTokenType } from '@/utils/uniqueTokens'; export enum NFTMarketplaceId { diff --git a/src/resources/nfts/utils.ts b/src/resources/nfts/utils.ts index 23b0d94af41..587bd19d742 100644 --- a/src/resources/nfts/utils.ts +++ b/src/resources/nfts/utils.ts @@ -3,9 +3,9 @@ import { gretch } from 'gretchen'; import { paths } from '@reservoir0x/reservoir-sdk'; import { RainbowError, logger } from '@/logger'; import { handleSignificantDecimals } from '@/helpers/utilities'; -import { Network } from '@/helpers'; import { IS_PROD } from '@/env'; import { RESERVOIR_API_KEY_DEV, RESERVOIR_API_KEY_PROD } from 'react-native-dotenv'; +import { Network } from '@/networks/types'; const SUPPORTED_NETWORKS = [Network.mainnet, Network.polygon, Network.bsc, Network.arbitrum, Network.optimism, Network.base, Network.zora]; diff --git a/src/resources/reservoir/mints.ts b/src/resources/reservoir/mints.ts index 0c667846440..1da3370c832 100644 --- a/src/resources/reservoir/mints.ts +++ b/src/resources/reservoir/mints.ts @@ -1,13 +1,12 @@ import { EthereumAddress } from '@/entities'; import { arcClient } from '@/graphql'; -import { getNetworkObj } from '@/networks'; import { Navigation } from '@/navigation'; -import { Network } from '@/networks/types'; import Routes from '@/navigation/routesNames'; -import { logger, RainbowError } from '@/logger'; +import { logger } from '@/logger'; import { WrappedAlert as Alert } from '@/helpers/alert'; import * as lang from '@/languages'; import { BigNumberish } from '@ethersproject/bignumber'; +import { ChainId } from '@/networks/types'; const showAlert = () => { Alert.alert( @@ -17,14 +16,14 @@ const showAlert = () => { { cancelable: false } ); }; -export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, network: Network) => { + +export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, chainId: ChainId) => { logger.debug('[mints]: Navigating to Mint Collection', { contractAddress, - network, + chainId, }); try { - const chainId = getNetworkObj(network).id; const res = await arcClient.getReservoirCollection({ contractAddress, chainId, @@ -35,13 +34,13 @@ export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint, }); } else { - logger.warn('[mints]: No collection found', { contractAddress, network }); + logger.warn('[mints]: No collection found', { contractAddress, chainId }); showAlert(); } } catch (e) { logger.warn(`[mints]: navigateToMintCollection error`, { contractAddress, - network, + chainId, error: e, }); showAlert(); diff --git a/src/resources/reservoir/utils.ts b/src/resources/reservoir/utils.ts index ec4dcf2140c..4e027b18c67 100644 --- a/src/resources/reservoir/utils.ts +++ b/src/resources/reservoir/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const RAINBOW_FEE_ADDRESS_MAINNET = '0x69d6d375de8c7ade7e44446df97f49e661fdad7d'; const RAINBOW_FEE_ADDRESS_POLYGON = '0xfb9af3db5e19c4165f413f53fe3bbe6226834548'; diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts index 34208c52bc0..2d01ef35f24 100644 --- a/src/resources/transactions/consolidatedTransactions.ts +++ b/src/resources/transactions/consolidatedTransactions.ts @@ -5,7 +5,7 @@ import { TransactionApiResponse, TransactionsReceivedMessage } from './types'; import { RainbowError, logger } from '@/logger'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { parseTransaction } from '@/parsers/transactions'; import { ethereumUtils } from '@/utils'; @@ -125,7 +125,7 @@ export function useConsolidatedTransactions( { address, currency }: Pick, config: InfiniteQueryConfig = {} ) { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); return useInfiniteQuery( consolidatedTransactionsQueryKey({ diff --git a/src/resources/transactions/transaction.ts b/src/resources/transactions/transaction.ts index dd10966baea..ded3b12f7a9 100644 --- a/src/resources/transactions/transaction.ts +++ b/src/resources/transactions/transaction.ts @@ -3,13 +3,13 @@ import { createQueryKey, queryClient, QueryFunctionArgs, QueryFunctionResult } f import { useQuery } from '@tanstack/react-query'; import { consolidatedTransactionsQueryFunction, consolidatedTransactionsQueryKey } from './consolidatedTransactions'; import { useAccountSettings } from '@/hooks'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { parseTransaction } from '@/parsers/transactions'; -import { Network } from '@/networks/types'; import { RainbowError, logger } from '@/logger'; import { TransactionApiResponse } from './types'; +import { ChainId } from '@/networks/types'; export type ConsolidatedTransactionsResult = QueryFunctionResult; export type PaginatedTransactions = { pages: ConsolidatedTransactionsResult[] }; @@ -18,25 +18,24 @@ export type TransactionArgs = { hash: string; address: string; currency: NativeCurrencyKey; - network: Network; + chainId: ChainId; }; type TransactionQueryKey = ReturnType; export type BackendTransactionArgs = { hash: string; - network: Network; + chainId: ChainId; enabled: boolean; }; -export const transactionQueryKey = ({ hash, address, currency, network }: TransactionArgs) => - createQueryKey('transactions', { address, currency, network, hash }, { persisterVersion: 1 }); +export const transactionQueryKey = ({ hash, address, currency, chainId }: TransactionArgs) => + createQueryKey('transactions', { address, currency, chainId, hash }, { persisterVersion: 1 }); export const fetchTransaction = async ({ - queryKey: [{ address, currency, network, hash }], + queryKey: [{ address, currency, chainId, hash }], }: QueryFunctionArgs): Promise => { try { - const chainId = getNetworkObj(network).id; const url = `https://addys.p.rainbow.me/v3/${chainId}/${address}/transactions/${hash}`; const response = await rainbowFetch<{ payload: { transaction: TransactionApiResponse } }>(url, { method: 'get', @@ -70,19 +69,19 @@ export const fetchTransaction = async ({ export const transactionFetchQuery = async ({ address, currency, - network, + chainId, hash, }: { address: string; currency: NativeCurrencyKey; - network: Network; + chainId: ChainId; hash: string; -}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, network, hash }), fetchTransaction); +}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, chainId, hash }), fetchTransaction); -export function useBackendTransaction({ hash, network }: BackendTransactionArgs) { +export function useBackendTransaction({ hash, chainId }: BackendTransactionArgs) { const { accountAddress, nativeCurrency } = useAccountSettings(); - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, @@ -94,11 +93,11 @@ export function useBackendTransaction({ hash, network }: BackendTransactionArgs) hash: hash, address: accountAddress, currency: nativeCurrency, - network: network, + chainId: chainId, }; return useQuery(transactionQueryKey(params), fetchTransaction, { - enabled: !!hash && !!accountAddress && !!network, + enabled: !!hash && !!accountAddress && !!chainId, initialData: () => { const queryData = queryClient.getQueryData(paginatedTransactionsKey); const pages = queryData?.pages || []; @@ -114,15 +113,15 @@ export function useBackendTransaction({ hash, network }: BackendTransactionArgs) }); } -export const useTransaction = ({ network, hash }: { network: Network; hash: string }) => { +export const useTransaction = ({ chainId, hash }: { chainId: ChainId; hash: string }) => { const { data: backendTransaction, isLoading: backendTransactionIsLoading, isFetched: backendTransactionIsFetched, } = useBackendTransaction({ hash, - network, - enabled: !!hash && !!network, + chainId, + enabled: !!hash && !!chainId, }); return { diff --git a/src/screens/AddCash/components/ProviderCard.tsx b/src/screens/AddCash/components/ProviderCard.tsx index cbe8ffcb9a1..9dae223ca5f 100644 --- a/src/screens/AddCash/components/ProviderCard.tsx +++ b/src/screens/AddCash/components/ProviderCard.tsx @@ -5,20 +5,18 @@ import chroma from 'chroma-js'; import { IS_IOS } from '@/env'; import { Box, Text, Inline, Bleed, useBackgroundColor } from '@/design-system'; -import { Network } from '@/helpers/networkTypes'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { Ramp as RampLogo } from '@/components/icons/svg/Ramp'; -import { Ratio as RatioLogo } from '@/components/icons/svg/Ratio'; import { Coinbase as CoinbaseLogo } from '@/components/icons/svg/Coinbase'; import { Moonpay as MoonpayLogo } from '@/components/icons/svg/Moonpay'; import { FiatProviderName } from '@/entities/f2c'; -import { convertAPINetworkToInternalNetwork } from '@/screens/AddCash/utils'; +import { convertAPINetworkToInternalChainIds } from '@/screens/AddCash/utils'; import { ProviderConfig, CalloutType, PaymentMethod } from '@/screens/AddCash/types'; import * as i18n from '@/languages'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ethereumUtils } from '@/utils'; +import { ChainId } from '@/networks/types'; type PaymentMethodConfig = { name: string; @@ -81,26 +79,22 @@ function getPaymentMethodConfigs(paymentMethods: { type: PaymentMethod }[]) { return methods; } -function NetworkIcons({ networks }: { networks: Network[] }) { +function NetworkIcons({ chainIds }: { chainIds?: ChainId[] }) { return ( - {networks.map((network, index) => { + {chainIds?.map((chainId, index) => { return ( 0 ? -6 : 0 }} style={{ position: 'relative', - zIndex: networks.length - index, + zIndex: chainIds.length - index, borderRadius: 30, }} > - {network !== Network.mainnet ? ( - - ) : ( - - )} + {chainId !== ChainId.mainnet ? : } ); })} @@ -233,7 +227,7 @@ export function ProviderCard({ config }: { config: ProviderConfig }) { ); diff --git a/src/screens/AddCash/utils.ts b/src/screens/AddCash/utils.ts index cedf027759e..aa4508030c3 100644 --- a/src/screens/AddCash/utils.ts +++ b/src/screens/AddCash/utils.ts @@ -1,14 +1,14 @@ -import { Network } from '@/helpers/networkTypes'; +import { ChainId } from '@/networks/types'; import { Network as APINetwork } from '@/screens/AddCash/types'; -export function convertAPINetworkToInternalNetwork(network: APINetwork): Network | undefined { +export function convertAPINetworkToInternalChainIds(network: APINetwork): ChainId | undefined { const networkMap = { - [APINetwork.Ethereum]: Network.mainnet, - [APINetwork.Arbitrum]: Network.arbitrum, - [APINetwork.Optimism]: Network.optimism, - [APINetwork.Polygon]: Network.polygon, - [APINetwork.Base]: Network.base, - [APINetwork.BSC]: Network.bsc, + [APINetwork.Ethereum]: ChainId.mainnet, + [APINetwork.Arbitrum]: ChainId.arbitrum, + [APINetwork.Optimism]: ChainId.optimism, + [APINetwork.Polygon]: ChainId.polygon, + [APINetwork.Base]: ChainId.base, + [APINetwork.BSC]: ChainId.bsc, }; // @ts-ignore diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx index 25abf58d032..9d141f2b049 100644 --- a/src/screens/CurrencySelectModal.tsx +++ b/src/screens/CurrencySelectModal.tsx @@ -15,7 +15,7 @@ import { Modal } from '../components/modal'; import { STORAGE_IDS } from '../model/mmkv'; import { analytics } from '@/analytics'; import { addHexPrefix, isL2Chain } from '@/handlers/web3'; -import { CurrencySelectionTypes, Network, TokenSectionTypes } from '@/helpers'; +import { CurrencySelectionTypes, TokenSectionTypes } from '@/helpers'; import { useAccountSettings, useInteraction, @@ -40,7 +40,7 @@ import DiscoverSearchInput from '@/components/discover/DiscoverSearchInput'; import { externalTokenQueryKey, fetchExternalToken } from '@/resources/assets/externalAssetsQuery'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { queryClient } from '@/react-query/queryClient'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; export interface EnrichedExchangeAsset extends SwappableAsset { ens: boolean; @@ -151,15 +151,11 @@ export default function CurrencySelectModal() { (newAsset: any, selectAsset: any, type: any) => { const otherAsset = type === 'input' ? outputCurrency : inputCurrency; const hasShownWarning = getHasShownWarning(); - if ( - otherAsset && - ethereumUtils.getChainIdFromNetwork(newAsset?.network) !== ethereumUtils.getChainIdFromNetwork(otherAsset?.network) && - !hasShownWarning - ) { + if (otherAsset && newAsset?.chainId !== otherAsset?.chainId && !hasShownWarning) { Keyboard.dismiss(); InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { - network: newAsset?.network, + chainId: newAsset?.chainId, onClose: () => { setHasShownWarning(); selectAsset(); @@ -213,6 +209,7 @@ export default function CurrencySelectModal() { name: 'Unswappable', symbol: 'UNSWAP', network: Network.mainnet, + chainId: ChainId.mainnet, id: 'foobar', uniqueId: '0x123', }); @@ -293,14 +290,14 @@ export default function CurrencySelectModal() { screen: Routes.MAIN_EXCHANGE_SCREEN, }); setSearchQuery(''); - setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); + setCurrentChainId(item.chainId); }, android ? 500 : 0 ); } else { navigate(Routes.MAIN_EXCHANGE_SCREEN); setSearchQuery(''); - setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); + setCurrentChainId(item.chainId); } if (searchQueryForSearch) { analytics.track('Selected a search result in Swap', { @@ -326,8 +323,7 @@ export default function CurrencySelectModal() { InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { assetName: item?.symbol, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - networkName: currentL2Name, + chainId: currentChainId, onClose: linkToHop, type: 'obtainL2Assets', }); @@ -430,11 +426,10 @@ export default function CurrencySelectModal() { const handleBackButton = useCallback(() => { setSearchQuery(''); InteractionManager.runAfterInteractions(() => { - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputCurrency?.network); - setCurrentChainId(inputChainId); + setCurrentChainId(inputCurrency?.chainId); }); setIsTransitioning(true); // continue to display list while transitiong back - }, [inputCurrency?.network]); + }, [inputCurrency?.chainId]); useEffect(() => { // check if list has items before attempting to scroll diff --git a/src/screens/ENSConfirmRegisterSheet.tsx b/src/screens/ENSConfirmRegisterSheet.tsx index beb2f56167c..82daf89ba8e 100644 --- a/src/screens/ENSConfirmRegisterSheet.tsx +++ b/src/screens/ENSConfirmRegisterSheet.tsx @@ -40,7 +40,7 @@ import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDomina import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { ActionTypes } from '@/hooks/useENSRegistrationActionHandler'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const ENSConfirmRegisterSheetHeight = 600; export const ENSConfirmRenewSheetHeight = 560; diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 304fcfe7764..86441c24004 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -27,7 +27,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { Box, Row, Rows } from '@/design-system'; import { GasFee, LegacyGasFee, LegacyGasFeeParams, SwappableAsset } from '@/entities'; -import { ExchangeModalTypes, isKeyboardOpen, Network } from '@/helpers'; +import { ExchangeModalTypes, isKeyboardOpen } from '@/helpers'; import { KeyboardType } from '@/helpers/keyboardTypes'; import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; import { delay, greaterThan } from '@/helpers/utilities'; @@ -70,7 +70,7 @@ import { ReviewPromptAction } from '@/storage/schema'; import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; import { getNextNonce } from '@/state/nonces'; import { getChainName } from '@/__swaps__/utils/chains'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { AddressOrEth, ParsedAsset } from '@/__swaps__/types/assets'; import { TokenColors } from '@/graphql/__generated__/metadata'; import { estimateSwapGasLimit } from '@/raps/actions'; @@ -82,7 +82,7 @@ export const DEFAULT_SLIPPAGE_BIPS = { [ChainId.polygon]: 200, [ChainId.base]: 200, [ChainId.bsc]: 200, - [Network.optimism]: 200, + [ChainId.optimism]: 200, [ChainId.arbitrum]: 200, [ChainId.goerli]: 100, [ChainId.gnosis]: 200, @@ -145,15 +145,14 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty updateDefaultGasLimit, updateGasFeeOption, updateTxFee, - txNetwork, - + chainId, isGasReady, } = useGas(); const { accountAddress, flashbotsEnabled, nativeCurrency } = useAccountSettings(); const [isAuthorizing, setIsAuthorizing] = useState(false); const prevGasFeesParamsBySpeed = usePrevious(gasFeeParamsBySpeed); - const prevChainId = usePrevious(ethereumUtils.getChainIdFromNetwork(txNetwork)); + const prevChainId = usePrevious(chainId); const keyboardListenerSubscription = useRef(); @@ -222,7 +221,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty if (currentChainId !== prevChainId) { speedUrgentSelected.current = false; } - }, [currentChainId, prevChainId, txNetwork]); + }, [currentChainId, prevChainId]); const defaultGasLimit = useMemo(() => { return ethereumUtils.getBasicSwapGasLimit(Number(currentChainId)); @@ -347,14 +346,14 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty ) { updateGasLimit(); } - }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, txNetwork, updateGasLimit]); + }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, updateGasLimit]); // Listen to gas prices, Uniswap reserves updates useEffect(() => { updateDefaultGasLimit(defaultGasLimit); InteractionManager.runAfterInteractions(() => { // Start polling in the current network - startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId), flashbots); + startPollingGasFees(currentChainId, flashbots); }); return () => { stopPollingGasFees(); @@ -432,7 +431,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty } logger.debug(`[ExchangeModal]: getting nonce for account ${accountAddress}`); - const currentNonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + const currentNonce = await getNextNonce({ address: accountAddress, chainId: currentChainId }); logger.debug(`[ExchangeModal]: nonce for account ${accountAddress} is ${currentNonce}`); const { independentField, independentValue, slippageInBips, source } = store.getState().swap; @@ -649,7 +648,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty const confirmButtonProps = useMemoOne( () => ({ - currentNetwork: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, disabled: !Number(inputAmount) || (!loading && !tradeDetails), inputAmount, isAuthorizing, @@ -691,7 +690,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setParams({ focused: false }); navigate(Routes.SWAP_SETTINGS_SHEET, { asset: outputCurrency, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, restoreFocusOnSwapModal: () => { android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); setParams({ focused: true }); @@ -781,8 +780,8 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty lastFocusedInput?.blur(); navigate(Routes.EXPLAIN_SHEET, { inputToken: inputCurrency?.symbol, - fromNetwork: ethereumUtils.getNetworkFromChainId(inputChainId), - toNetwork: ethereumUtils.getNetworkFromChainId(outputChainId), + fromChainId: inputChainId, + toChainId: outputChainId, isCrosschainSwap, isBridgeSwap, onClose: () => { diff --git a/src/screens/ExplainSheet.js b/src/screens/ExplainSheet.js index d5ce85ef357..f92f02ea5a9 100644 --- a/src/screens/ExplainSheet.js +++ b/src/screens/ExplainSheet.js @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; @@ -10,21 +11,19 @@ import { Emoji, GradientText, Text } from '../components/text'; import { useNavigation } from '../navigation/Navigation'; import { DoubleChevron } from '@/components/icons'; import { Box } from '@/design-system'; -import networkTypes from '@/helpers/networkTypes'; import { useDimensions } from '@/hooks'; import styled from '@/styled-thing'; import { fonts, fontWithWidth, padding, position } from '@/styles'; -import { ethereumUtils, gasUtils, getTokenMetadata } from '@/utils'; +import { ethereumUtils, gasUtils } from '@/utils'; import { buildRainbowLearnUrl } from '@/utils/buildRainbowUrl'; import { cloudPlatformAccountName } from '@/utils/platform'; import { useTheme } from '@/theme'; import { isL2Chain } from '@/handlers/web3'; import { IS_ANDROID } from '@/env'; import * as i18n from '@/languages'; -import { getNetworkObj } from '@/networks'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const { GAS_TRENDS } = gasUtils; export const ExplainSheetHeight = android ? 454 : 434; @@ -78,8 +77,8 @@ const FLOOR_PRICE_EXPLAINER = lang.t('explain.floor_price.text'); const gasExplainer = network => lang.t('explain.gas.text', { networkName: network }); -const availableNetworksExplainer = (tokenSymbol, networks) => { - const readableNetworks = networks?.map(network => getNetworkObj(network).name)?.join(', '); +const availableNetworksExplainer = (tokenSymbol, chainIds) => { + const readableNetworks = chainIds?.map(chainId => chainIdToNameMapping[chainId])?.join(', '); return lang.t('explain.available_networks.text', { tokenSymbol: tokenSymbol, @@ -158,8 +157,11 @@ const ENS_CONFIGURATION_EXPLAINER = export const explainers = (params, theme) => { const colors = theme?.colors; - const fromNetworkObject = getNetworkObj(params?.fromNetwork); - const toNetworkObject = getNetworkObj(params?.toNetwork); + const chainId = params?.chainId; + const network = ethereumUtils.getNetworkFromChainId(chainId); + const networkName = chainIdToNameMapping[chainId]; + const fromChainId = params?.fromChainId; + const toChainId = params?.toChainId; return { op_rewards_airdrop_timing: { emoji: '📦', @@ -210,7 +212,7 @@ export const explainers = (params, theme) => { title: params?.inputToken ? lang.t(`explain.output_disabled.${params?.isCrosschainSwap ? 'title_crosschain' : 'title'}`, { inputToken: params?.inputToken, - fromNetwork: fromNetworkObject.name, + fromNetwork: chainIdToNameMapping[fromChainId], }) : lang.t('explain.output_disabled.title_empty'), @@ -218,18 +220,18 @@ export const explainers = (params, theme) => { ? lang.t(`explain.output_disabled.${params?.isBridgeSwap ? 'text_bridge' : 'text_crosschain'}`, { inputToken: params?.inputToken, outputToken: params?.outputToken, - fromNetwork: fromNetworkObject.name, - toNetwork: toNetworkObject.name, + fromNetwork: chainIdToNameMapping[fromChainId], + toNetwork: chainIdToNameMapping[toChainId], }) : lang.t('explain.output_disabled.text', { - fromNetwork: fromNetworkObject?.name, + fromNetwork: chainIdToNameMapping[fromChainId]?.name, inputToken: params?.inputToken, outputToken: params?.outputToken, }), - logo: !isL2Chain({ chainId: fromNetworkObject.id }) ? ( + logo: !isL2Chain({ chainId: fromChainId }) ? ( ) : ( - + ), }, floor_price: { @@ -244,15 +246,15 @@ export const explainers = (params, theme) => { size={40} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.network)} + chainId={chainId} colors={params?.nativeAsset?.colors} theme={theme} /> ), extraHeight: 2, - text: gasExplainer(getNetworkObj(params?.network).name), + text: gasExplainer(chainIdToNameMapping[chainId]), title: lang.t('explain.gas.title', { - networkName: getNetworkObj(params?.network).name, + networkName: chainIdToNameMapping[chainId], }), }, ens_primary_name: { @@ -533,22 +535,17 @@ export const explainers = (params, theme) => { }, swapResetInputs: { button: { - label: `Continue with ${getNetworkObj(params?.network)?.name}`, - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.06), - textColor: colors?.networkColors[params?.network] && colors?.networkColors?.[params?.network], + label: `Continue with ${networkName}`, + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.06), + textColor: colors?.networkColors[chainId] && colors?.networkColors?.[network], }, emoji: '🔐', extraHeight: -90, text: SWAP_RESET_EXPLAINER, - title: `Switching to ${getNetworkObj(params?.network)?.name}`, + title: `Switching to ${networkName}`, logo: - params?.network !== 'mainnet' ? ( - + chainId !== ChainId.mainnet ? ( + ) : ( ), @@ -596,7 +593,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -652,7 +649,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -661,7 +658,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} + chainId={params?.outputCurrency?.chainId} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -678,7 +675,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -687,7 +684,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} + chainId={params?.outputCurrency?.chainId} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -697,24 +694,24 @@ export const explainers = (params, theme) => { availableNetworks: { buttonText: `Go to Hop`, extraHeight: -90, - text: availableNetworksExplainer(params?.tokenSymbol, params?.networks), + text: availableNetworksExplainer(params?.tokenSymbol, params?.chainIds), title: - params?.networks?.length > 1 + params?.chainIds?.length > 1 ? lang.t('explain.available_networks.title_plural', { - length: params?.networks?.length, + length: params?.chainIds?.length, }) : lang.t('explain.available_networks.title_singular', { - network: params?.networks?.[0], + network: params?.chainIds?.[0], }), logo: ( - {params?.networks?.map((network, index) => { + {params?.chainIds?.map((chainId, index) => { return ( 0 ? -12 : params?.networks?.length % 2 === 0 ? -2 : -30, + custom: index > 0 ? -12 : params?.chainIds?.length % 2 === 0 ? -2 : -30, }} style={{ borderColor: colors.transparent, @@ -723,10 +720,10 @@ export const explainers = (params, theme) => { zIndex: index, }} width={{ custom: 40 }} - zIndex={params?.networks?.length - index} + zIndex={params?.chainIds?.length - index} > - {network !== 'mainnet' ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( @@ -779,7 +776,7 @@ export const explainers = (params, theme) => { {lang.t('explain.obtain_l2_asset.fragment3')} ), - logo: , + logo: , title: lang.t('explain.obtain_l2_asset.title', { networkName: params?.networkName, }), @@ -867,19 +864,12 @@ export const explainers = (params, theme) => { }, swap_refuel_add: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[params?.network], - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), + textColor: colors?.networkColors[chainId], + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_deduct: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[params?.network], - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), + textColor: colors?.networkColors[chainId], + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_notice: { extraHeight: 50, logo: ( - + diff --git a/src/screens/MintsSheet/card/Card.tsx b/src/screens/MintsSheet/card/Card.tsx index 1870ce8454e..d9e30975c0a 100644 --- a/src/screens/MintsSheet/card/Card.tsx +++ b/src/screens/MintsSheet/card/Card.tsx @@ -3,8 +3,7 @@ import React, { useEffect, useState } from 'react'; import { getTimeElapsedFromDate } from '../utils'; import { Bleed, Box, Cover, Inline, Inset, Stack, Text, useForegroundColor } from '@/design-system'; import { abbreviateNumber, convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; -import { getNetworkObj } from '@/networks'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { getNetworkObject } from '@/networks'; import { ButtonPressAnimation } from '@/components/animations'; import { Placeholder, RecentMintCell } from './RecentMintCell'; import { View } from 'react-native'; @@ -14,7 +13,7 @@ import * as i18n from '@/languages'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const NUM_NFTS = 3; @@ -29,11 +28,9 @@ export function Card({ collection }: { collection: MintableCollection }) { const separatorTertiary = useForegroundColor('separatorTertiary'); const price = convertRawAmountToRoundedDecimal(collection.mintStatus.price, 18, 6); - const currencySymbol = getNetworkObj(getNetworkFromChainId(collection.chainId)).nativeCurrency.symbol; + const currencySymbol = getNetworkObject({ chainId: collection.chainId }).nativeCurrency.symbol; const isFree = !price; - const network = getNetworkFromChainId(collection.chainId); - // update elapsed time every minute if it's less than an hour useEffect(() => { if (timeElapsed && timeElapsed[timeElapsed.length - 1] === 'm') { @@ -106,7 +103,7 @@ export function Card({ collection }: { collection: MintableCollection }) { chainId: collection.chainId, priceInEth: price, }); - navigateToMintCollection(collection.contract, collection.mintStatus.price, network); + navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); }} style={{ borderRadius: 99, diff --git a/src/screens/NFTOffersSheet/OfferRow.tsx b/src/screens/NFTOffersSheet/OfferRow.tsx index 2fb228536a2..93b64b83c18 100644 --- a/src/screens/NFTOffersSheet/OfferRow.tsx +++ b/src/screens/NFTOffersSheet/OfferRow.tsx @@ -18,6 +18,7 @@ import { Network } from '@/networks/types'; import { useAccountSettings } from '@/hooks'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; const NFT_SIZE = 50; const MARKETPLACE_ORB_SIZE = 18; @@ -100,7 +101,7 @@ export const OfferRow = ({ offer }: { offer: NftOffer }) => { const bgColor = useBackgroundColor('surfaceSecondaryElevated'); const chainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address, + address: offer.paymentToken.address as AddressOrEth, chainId, currency: nativeCurrency, }); diff --git a/src/screens/NFTSingleOfferSheet/index.tsx b/src/screens/NFTSingleOfferSheet/index.tsx index 7d682f2f143..16869b59a77 100644 --- a/src/screens/NFTSingleOfferSheet/index.tsx +++ b/src/screens/NFTSingleOfferSheet/index.tsx @@ -39,7 +39,7 @@ import { createWalletClient, http } from 'viem'; import { RainbowError, logger } from '@/logger'; import { useTheme } from '@/theme'; -import { Network } from '@/helpers'; +import { Network, ChainId } from '@/networks/types'; import { getNetworkObject } from '@/networks'; import { CardSize } from '@/components/unique-token/CardSize'; import { queryClient } from '@/react-query'; @@ -205,7 +205,7 @@ export function NFTSingleOfferSheet() { let reservoirEstimate = 0; const txs: Transaction[] = []; const fallbackEstimate = - offer.network === Network.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; + offerChainId === ChainId.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; steps.forEach(step => step.items?.forEach(item => { if (item?.data?.to && item?.data?.from && item?.data?.data) { @@ -247,13 +247,13 @@ export function NFTSingleOfferSheet() { // estimate gas useEffect(() => { if (!isReadOnlyWallet && !isExpired) { - startPollingGasFees(offer?.network as Network); + startPollingGasFees(offerChainId); estimateGas(); } return () => { stopPollingGasFees(); }; - }, [estimateGas, isExpired, isReadOnlyWallet, offer?.network, startPollingGasFees, stopPollingGasFees, updateTxFee]); + }, [estimateGas, isExpired, isReadOnlyWallet, offer.network, offerChainId, startPollingGasFees, stopPollingGasFees, updateTxFee]); const acceptOffer = useCallback(async () => { logger.debug(`[NFTSingleOfferSheet]: Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); @@ -287,7 +287,7 @@ export function NFTSingleOfferSheet() { chain: networkObj, transport: http(networkObj.rpc()), }); - const nonce = await getNextNonce({ address: accountAddress, network: networkObj.value }); + const nonce = await getNextNonce({ address: accountAddress, chainId: networkObj.id }); try { let errorMessage = ''; let didComplete = false; @@ -333,6 +333,7 @@ export function NFTSingleOfferSheet() { nonce: item?.txHashes?.length > 1 ? nonce + 1 : nonce, asset: { ...offer.paymentToken, + chainId: offerChainId, network: offer.network as Network, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, @@ -347,6 +348,7 @@ export function NFTSingleOfferSheet() { asset: { ...offer.paymentToken, network: offer.network as Network, + chainId: offerChainId, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, value: offer.grossAmount.raw, @@ -371,7 +373,7 @@ export function NFTSingleOfferSheet() { addNewTransaction({ transaction: tx, address: accountAddress, - network: offer.network as Network, + chainId: offerChainId, }); txsRef.current.push(tx.hash); } diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 4fc67c9baaf..9d1613e22b5 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -57,11 +57,11 @@ import { position } from '@/styles'; import { useTheme } from '@/theme'; import { ethereumUtils, getUniqueTokenType, promiseUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; -import { getNetworkObj } from '@/networks'; import { IS_ANDROID } from '@/env'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { performanceTracking, TimeToSignOperation, Screens } from '@/state/performance/performance'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const Container = styled(Centered).attrs({ direction: 'column', @@ -97,12 +97,12 @@ const checkboxOffset = 44; export function getDefaultCheckboxes({ isENS, ensProfile, - network, + chainId, toAddress, }: { isENS: boolean; ensProfile: ENSProfile; - network: string; + chainId: ChainId; toAddress: string; }): Checkbox[] { if (isENS) { @@ -132,7 +132,7 @@ export function getDefaultCheckboxes({ checked: false, id: 'has-wallet-that-supports', label: lang.t('wallet.transaction.checkboxes.has_a_wallet_that_supports', { - networkName: capitalize(network), + networkName: capitalize(chainIdToNameMapping[chainId]), }), }, ]; @@ -195,7 +195,7 @@ export const SendConfirmationSheet = () => { }, []); const { - params: { amountDetails, asset, callback, ensProfile, isL2, isNft, network, to, toAddress }, + params: { amountDetails, asset, callback, ensProfile, isL2, isNft, chainId, to, toAddress }, // eslint-disable-next-line @typescript-eslint/no-explicit-any } = useRoute(); @@ -229,7 +229,7 @@ export const SendConfirmationSheet = () => { transactions.forEach(tx => { if (tx.to?.toLowerCase() === toAddress?.toLowerCase() && tx.from?.toLowerCase() === accountAddress?.toLowerCase()) { sends += 1; - if (tx.network === network) { + if (tx.chainId === chainId) { sendsCurrentNetwork += 1; } } @@ -241,7 +241,7 @@ export const SendConfirmationSheet = () => { } } } - }, [accountAddress, isSendingToUserAccount, network, toAddress, transactions]); + }, [accountAddress, isSendingToUserAccount, chainId, toAddress, transactions]); const contact = useMemo(() => { return contacts?.[toAddress?.toLowerCase()]; @@ -250,7 +250,7 @@ export const SendConfirmationSheet = () => { const uniqueTokenType = getUniqueTokenType(asset); const isENS = uniqueTokenType === 'ENS' && profilesEnabled; - const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, network, toAddress })); + const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, chainId, toAddress })); useEffect(() => { if (isENS) { @@ -500,7 +500,7 @@ export const SendConfirmationSheet = () => { badgeYPosition={0} borderRadius={10} imageUrl={imageUrl} - network={asset.network} + chainId={asset?.chainId} showLargeShadow size={50} /> @@ -508,7 +508,7 @@ export const SendConfirmationSheet = () => { { address: toAddress, name: avatarName || address(to, 4, 8), }} - network={network} + chainId={chainId} scaleTo={0.75} > { {/* @ts-expect-error JavaScript component */} { onPress={handleL2DisclaimerPress} prominent customText={i18n.t(i18n.l.expanded_state.asset.l2_disclaimer_send, { - network: getNetworkObj(asset.network).name, + network: chainIdToNameMapping[asset.chainId], })} symbol={asset.symbol} /> diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index fc6acb6f425..1d7438f1519 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -25,7 +25,6 @@ import { resolveNameOrAddress, web3Provider, } from '@/handlers/web3'; -import Network from '@/helpers/networkTypes'; import { checkIsValidAddressOrDomain, checkIsValidAddressOrDomainFormat, isENSAddressFormat } from '@/helpers/validators'; import { prefetchENSAvatar, @@ -66,7 +65,7 @@ import { getNextNonce } from '@/state/nonces'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { REGISTRATION_STEPS } from '@/helpers/ens'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; @@ -120,7 +119,7 @@ export default function SendSheet(props) { const { contacts, onRemoveContact, filteredContacts } = useContacts(); const { userAccounts, watchedAccounts } = useUserAccounts(); const { sendableUniqueTokens } = useSendableUniqueTokens(); - const { accountAddress, nativeCurrency, network } = useAccountSettings(); + const { accountAddress, nativeCurrency, chainId } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const { action: transferENS } = useENSRegistrationActionHandler({ @@ -267,10 +266,10 @@ export default function SendSheet(props) { // belongs to if (prevChainId !== currentChainId) { InteractionManager.runAfterInteractions(() => { - startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId)); + startPollingGasFees(currentChainId); }); } - }, [startPollingGasFees, selected.network, prevChainId, currentChainId]); + }, [startPollingGasFees, selected.chainId, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -282,11 +281,10 @@ export default function SendSheet(props) { }, [stopPollingGasFees]); useEffect(() => { - const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); - const networkChainId = ethereumUtils.getChainIdFromNetwork(network); + const assetChainId = selected.chainId; if (assetChainId && (assetChainId !== currentChainId || !currentChainId || prevChainId !== currentChainId)) { let provider = web3Provider; - if (networkChainId === ChainId.goerli) { + if (chainId === ChainId.goerli) { setCurrentChainId(ChainId.goerli); provider = getProvider({ chainId: ChainId.goerli }); setCurrentProvider(provider); @@ -296,7 +294,7 @@ export default function SendSheet(props) { setCurrentProvider(provider); } } - }, [currentChainId, isNft, network, prevChainId, selected?.network, sendUpdateSelected]); + }, [currentChainId, isNft, chainId, prevChainId, selected?.chainId, sendUpdateSelected]); const onChangeNativeAmount = useCallback( newNativeAmount => { @@ -424,7 +422,7 @@ export default function SendSheet(props) { }, true, currentProvider, - currentChainIdNetwork + currentChainId ); if (!lessThan(updatedGasLimit, gasLimit)) { @@ -466,7 +464,7 @@ export default function SendSheet(props) { from: accountAddress, gasLimit: gasLimitToUse, network: currentChainIdNetwork, - nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, network: currentChainIdNetwork })), + nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, chainId: currentChainId })), to: toAddress, ...gasParams, }; @@ -515,7 +513,7 @@ export default function SendSheet(props) { txDetails.status = 'pending'; addNewTransaction({ address: accountAddress, - network: currentChainIdNetwork, + chainId: currentChainId, transaction: txDetails, }); } @@ -674,7 +672,7 @@ export default function SendSheet(props) { const checkboxes = getDefaultCheckboxes({ ensProfile, isENS: true, - network, + chainId, toAddress: recipient, }); navigate(Routes.SEND_CONFIRMATION_SHEET, { @@ -686,7 +684,7 @@ export default function SendSheet(props) { isENS, isL2, isNft, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, profilesEnabled, to: recipient, toAddress, @@ -701,7 +699,7 @@ export default function SendSheet(props) { isNft, nativeCurrencyInputRef, navigate, - network, + chainId, profilesEnabled, recipient, selected, @@ -751,11 +749,11 @@ export default function SendSheet(props) { const [ensSuggestions, setEnsSuggestions] = useState([]); const [loadingEnsSuggestions, setLoadingEnsSuggestions] = useState(false); useEffect(() => { - if (network === Network.mainnet && !recipientOverride && recipient?.length) { + if (chainId === ChainId.mainnet && !recipientOverride && recipient?.length) { setLoadingEnsSuggestions(true); debouncedFetchSuggestions(recipient, setEnsSuggestions, setLoadingEnsSuggestions, profilesEnabled); } - }, [network, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); + }, [chainId, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); useEffect(() => { checkAddress(debouncedInput); @@ -764,7 +762,7 @@ export default function SendSheet(props) { useEffect(() => { if (!currentProvider?._network?.chainId) return; - const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); + const assetChainId = selected.chainId; const currentProviderChainId = currentProvider._network.chainId; if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { @@ -777,7 +775,7 @@ export default function SendSheet(props) { }, false, currentProvider, - ethereumUtils.getNetworkFromChainId(currentChainId) + currentChainId ) .then(async gasLimit => { if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { @@ -801,7 +799,7 @@ export default function SendSheet(props) { toAddress, updateTxFee, updateTxFeeForOptimism, - network, + chainId, isNft, currentChainId, ]); @@ -854,7 +852,6 @@ export default function SendSheet(props) { [name, emoji]); diff --git a/src/screens/SettingsSheet/components/DevSection.tsx b/src/screens/SettingsSheet/components/DevSection.tsx index 44daf6f517f..8821b07b2b8 100644 --- a/src/screens/SettingsSheet/components/DevSection.tsx +++ b/src/screens/SettingsSheet/components/DevSection.tsx @@ -1,12 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import lang from 'i18n-js'; import React, { useCallback, useContext, useState } from 'react'; -import { - // @ts-ignore - HARDHAT_URL_ANDROID, - // @ts-ignore - HARDHAT_URL_IOS, -} from 'react-native-dotenv'; // @ts-ignore import Restart from 'react-native-restart'; import { useDispatch } from 'react-redux'; @@ -16,10 +10,8 @@ import MenuContainer from './MenuContainer'; import MenuItem from './MenuItem'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { deleteAllBackups } from '@/handlers/cloudBackup'; -import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowContext } from '@/helpers/RainbowContext'; import isTestFlight from '@/helpers/isTestFlight'; -import networkTypes from '@/helpers/networkTypes'; import { useWallets } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { wipeKeychain } from '@/model/keychain'; @@ -48,11 +40,13 @@ import { getFCMToken } from '@/notifications/tokens'; import { removeGlobalNotificationSettings } from '@/notifications/settings/settings'; import { nonceStore } from '@/state/nonces'; import { pendingTransactionsStore } from '@/state/pendingTransactions'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const DevSection = () => { const { navigate } = useNavigation(); const { config, setConfig } = useContext(RainbowContext) as any; const { wallets } = useWallets(); + const setConnectedToHardhat = useConnectedToHardhatStore.getState().setConnectedToHardhat; const { walletNotificationSettings } = useAllNotificationSettingsFromStorage(); const dispatch = useDispatch(); @@ -75,15 +69,16 @@ const DevSection = () => { const connectToHardhat = useCallback(async () => { try { - const ready = await web3SetHttpProvider((ios && HARDHAT_URL_IOS) || (android && HARDHAT_URL_ANDROID) || 'http://127.0.0.1:8545'); - logger.debug(`[DevSection] connected to hardhat: ${ready}`); + const connectToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; + setConnectedToHardhat(!connectToHardhat); + logger.debug(`[DevSection] connected to hardhat`); } catch (e) { - await web3SetHttpProvider(networkTypes.mainnet); + setConnectedToHardhat(false); logger.error(new RainbowError(`[DevSection] error connecting to hardhat: ${e}`)); } navigate(Routes.PROFILE_SCREEN); dispatch(explorerInit()); - }, [dispatch, navigate]); + }, [dispatch, navigate, setConnectedToHardhat]); const checkAlert = useCallback(async () => { try { @@ -317,7 +312,15 @@ const DevSection = () => { onPress={connectToHardhat} size={52} testID="hardhat-section" - titleComponent={} + titleComponent={ + + } /> } diff --git a/src/screens/SettingsSheet/components/NetworkSection.tsx b/src/screens/SettingsSheet/components/NetworkSection.tsx index 7e8e32ffa21..b8edc2256cc 100644 --- a/src/screens/SettingsSheet/components/NetworkSection.tsx +++ b/src/screens/SettingsSheet/components/NetworkSection.tsx @@ -9,44 +9,44 @@ import { analytics } from '@/analytics'; import { Separator, Stack } from '@/design-system'; import { useAccountSettings, useInitializeAccountData, useLoadAccountData, useResetAccountState } from '@/hooks'; import { settingsUpdateNetwork } from '@/redux/settings'; -import { Network } from '@/helpers'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; +import { ChainId } from '@/networks/types'; -const networks = values(RainbowNetworks).filter(({ networkType }) => networkType !== 'layer2'); +const networkObjects = values(RainbowNetworkObjects).filter(({ networkType }) => networkType !== 'layer2'); interface NetworkSectionProps { inDevSection?: boolean; } const NetworkSection = ({ inDevSection }: NetworkSectionProps) => { - const { network, testnetsEnabled } = useAccountSettings(); + const { chainId, testnetsEnabled } = useAccountSettings(); const resetAccountState = useResetAccountState(); const loadAccountData = useLoadAccountData(); const initializeAccountData = useInitializeAccountData(); const dispatch = useDispatch(); const onNetworkChange = useCallback( - async (network: Network) => { + async (chainId: ChainId) => { await resetAccountState(); - await dispatch(settingsUpdateNetwork(network)); + await dispatch(settingsUpdateNetwork(chainId)); InteractionManager.runAfterInteractions(async () => { await loadAccountData(); initializeAccountData(); - analytics.track('Changed network', { network }); + analytics.track('Changed network', { chainId }); }); }, [dispatch, initializeAccountData, loadAccountData, resetAccountState] ); const renderNetworkList = useCallback(() => { - return networks.map(({ name, value, networkType }) => ( + return networkObjects.map(({ name, id, networkType }) => ( onNetworkChange(value)} - rightComponent={value === network && } + key={id} + onPress={() => onNetworkChange(id)} + rightComponent={id === chainId && } size={52} - testID={`${value}-network`} + testID={`${id}-network`} titleComponent={ { } /> )); - }, [inDevSection, network, onNetworkChange, testnetsEnabled]); + }, [inDevSection, chainId, onNetworkChange, testnetsEnabled]); return inDevSection ? ( }>{renderNetworkList()} diff --git a/src/screens/SettingsSheet/components/NotificationsSection.tsx b/src/screens/SettingsSheet/components/NotificationsSection.tsx index 6cbc3dee350..1fb1099dd9a 100644 --- a/src/screens/SettingsSheet/components/NotificationsSection.tsx +++ b/src/screens/SettingsSheet/components/NotificationsSection.tsx @@ -17,7 +17,7 @@ import { abbreviations, deviceUtils } from '@/utils'; import { Box } from '@/design-system'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { RainbowAccount } from '@/model/wallet'; -import { isTestnetNetwork } from '@/handlers/web3'; +import { isTestnetChain } from '@/handlers/web3'; import { useFocusEffect } from '@react-navigation/native'; import { SettingsLoadingIndicator } from '@/screens/SettingsSheet/components/SettingsLoadingIndicator'; import { showNotificationSubscriptionErrorAlert, showOfflineAlert } from '@/screens/SettingsSheet/components/notificationAlerts'; @@ -157,8 +157,8 @@ const WalletRow = ({ ens, groupOff, isTestnet, loading, notificationSettings, wa const NotificationsSection = () => { const { justBecameActive } = useAppState(); const { navigate } = useNavigation(); - const { network } = useAccountSettings(); - const isTestnet = isTestnetNetwork(network); + const { chainId } = useAccountSettings(); + const isTestnet = isTestnetChain({ chainId }); const { wallets, walletNames } = useWallets(); const { isConnected } = useNetInfo(); const { points_enabled, points_notifications_toggle } = useRemoteConfig(); diff --git a/src/screens/SettingsSheet/components/SettingsSection.tsx b/src/screens/SettingsSheet/components/SettingsSection.tsx index 27c29e18593..9fae44a89eb 100644 --- a/src/screens/SettingsSheet/components/SettingsSection.tsx +++ b/src/screens/SettingsSheet/components/SettingsSection.tsx @@ -203,27 +203,6 @@ const SettingsSection = ({ testID="currency-section" titleComponent={} /> - {/* {(testnetsEnabled || IS_DEV) && ( - - } - onPress={onPressNetwork} - rightComponent={ - - {getNetworkObj(network).name} - - } - size={60} - testID="network-section" - titleComponent={ - - } - /> - )} */} { InteractionManager.runAfterInteractions(() => { if (currentChainId) { if (!isMessageRequest) { - const network = ethereumUtils.getNetworkFromChainId(currentChainId); - startPollingGasFees(network); + startPollingGasFees(currentChainId); fetchMethodName(transactionDetails?.payload?.params[0].data); } else { setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); @@ -368,14 +367,9 @@ export const SignTransactionSheet = () => { useEffect(() => { (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); + const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId: currentChainId, address: accountInfo.address }); if (asset && provider) { - const balance = await getOnchainAssetBalance( - asset, - accountInfo.address, - ethereumUtils.getNetworkFromChainId(currentChainId), - provider - ); + const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentChainId, provider); if (balance) { const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; setNativeAsset(assetWithOnchainBalance); @@ -390,7 +384,7 @@ export const SignTransactionSheet = () => { (async () => { if (!isMessageRequest && !nonceForDisplay) { try { - const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + const nonce = await getNextNonce({ address: currentAddress, chainId: currentChainId }); if (nonce || nonce === 0) { const nonceAsString = nonce.toString(); setNonceForDisplay(nonceAsString); @@ -644,7 +638,7 @@ export const SignTransactionSheet = () => { const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + const nonce = await getNextNonce({ address: accountInfo.address, chainId: currentChainId }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -740,7 +734,7 @@ export const SignTransactionSheet = () => { if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + chainId: currentChainId, address: accountAddress, }); txSavedInCurrentWallet = true; @@ -772,7 +766,7 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + chainId: currentChainId, address: txDetails?.from as string, }); }); @@ -928,7 +922,7 @@ export const SignTransactionSheet = () => { { /> ) : ( { }; interface SimulationCardProps { - currentNetwork: Network; + chainId: ChainId; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1103,7 +1097,7 @@ interface SimulationCardProps { } const SimulationCard = ({ - currentNetwork, + chainId, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1316,7 +1310,7 @@ const SimulationCard = ({ {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { symbol: walletBalance?.symbol, - network: getNetworkObj(currentNetwork).name, + network: chainIdToNameMapping[chainId], })} ) : ( @@ -1354,7 +1348,7 @@ const SimulationCard = ({ }; interface DetailsCardProps { - currentNetwork: Network; + chainId: ChainId; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1366,7 +1360,7 @@ interface DetailsCardProps { } const DetailsCard = ({ - currentNetwork, + chainId, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1424,15 +1418,15 @@ const DetailsCard = ({ - {} + {} {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - ethereumUtils.openAddressInBlockExplorer( - meta?.to?.address || toAddress || meta?.transferTo?.address || '', - ethereumUtils.getChainIdFromNetwork(currentNetwork) - ) + ethereumUtils.openAddressInBlockExplorer({ + address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', + chainId, + }) } value={ meta?.to?.name || @@ -1560,7 +1554,7 @@ const SimulatedEventRow = ({ const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const { data: externalAsset } = useExternalToken({ - address: asset?.assetCode || '', + address: (asset?.assetCode || '') as AddressOrEth, chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), currency: nativeCurrency, }); @@ -1636,12 +1630,12 @@ const SimulatedEventRow = ({ }; const DetailRow = ({ - currentNetwork, + chainId, detailType, onPress, value, }: { - currentNetwork?: Network; + chainId?: ChainId; detailType: DetailType; onPress?: () => void; value: string; @@ -1662,9 +1656,7 @@ const DetailRow = ({ {detailType === 'sourceCodeVerification' && ( )} - {detailType === 'chain' && currentNetwork && ( - - )} + {detailType === 'chain' && chainId && } {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( {value} diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index 521fee3648f..0226d4e9323 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -1,5 +1,4 @@ import { useRoute } from '@react-navigation/native'; -import { captureException } from '@sentry/react-native'; import { BigNumber } from 'bignumber.js'; import lang from 'i18n-js'; import { isEmpty } from 'lodash'; @@ -17,7 +16,7 @@ import { Emoji, Text } from '../components/text'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { removeRegistrationByName, saveCommitRegistrationParameters } from '@/redux/ensRegistration'; import { GasFeeTypes } from '@/entities'; -import { getFlashbotsProvider, getProviderForNetwork, isL2Chain, toHex } from '@/handlers/web3'; +import { getFlashbotsProvider, getProvider, isL2Chain, toHex } from '@/handlers/web3'; import { greaterThan } from '@/helpers/utilities'; import { useAccountSettings, useDimensions, useGas, useWallets } from '@/hooks'; import { sendTransaction } from '@/model/wallet'; @@ -27,9 +26,9 @@ import { updateGasFeeForSpeed } from '@/redux/gas'; import { ethUnits } from '@/references'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { ethereumUtils, gasUtils, safeAreaInsetValues } from '@/utils'; +import { gasUtils, safeAreaInsetValues } from '@/utils'; import { logger, RainbowError } from '@/logger'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import * as i18n from '@/languages'; import { updateTransaction } from '@/state/pendingTransactions'; @@ -101,7 +100,7 @@ const calcGasParamRetryValue = prevWeiValue => { export default function SpeedUpAndCancelSheet() { const { navigate, goBack } = useNavigation(); - const { accountAddress, network } = useAccountSettings(); + const { accountAddress, chainId } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const dispatch = useDispatch(); const { height: deviceHeight } = useDimensions(); @@ -118,7 +117,7 @@ export default function SpeedUpAndCancelSheet() { const [minMaxPriorityFeePerGas, setMinMaxPriorityFeePerGas] = useState(calcGasParamRetryValue(tx.maxPriorityFeePerGas)); const [minMaxFeePerGas, setMinMaxFeePerGas] = useState(calcGasParamRetryValue(tx.maxFeePerGas)); const fetchedTx = useRef(false); - const [currentNetwork, setCurrentNetwork] = useState(null); + const [currentChainId, setCurrentChainId] = useState(null); const [currentProvider, setCurrentProvider] = useState(null); const [data, setData] = useState(null); const [gasLimit, setGasLimit] = useState(null); @@ -172,7 +171,11 @@ export default function SpeedUpAndCancelSheet() { updatedTx.hash = res.result?.hash; updatedTx.status = 'pending'; updatedTx.type = 'cancel'; - updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); + updateTransaction({ + address: accountAddress, + transaction: updatedTx, + chainId: currentChainId, + }); } catch (e) { logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting cancel tx: ${e}`)); } finally { @@ -185,7 +188,7 @@ export default function SpeedUpAndCancelSheet() { }, [ accountAddress, cancelCommitTransactionHash, - currentNetwork, + currentChainId, currentProvider, getNewTransactionGasParams, goBack, @@ -242,7 +245,11 @@ export default function SpeedUpAndCancelSheet() { updatedTx.status = 'pending'; updatedTx.type = 'speed_up'; - updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); + updateTransaction({ + address: accountAddress, + transaction: updatedTx, + chainId: currentChainId, + }); } catch (e) { logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting speed up tx: ${e}`)); } finally { @@ -254,7 +261,7 @@ export default function SpeedUpAndCancelSheet() { } }, [ accountAddress, - currentNetwork, + currentChainId, currentProvider, data, gasLimit, @@ -278,21 +285,21 @@ export default function SpeedUpAndCancelSheet() { // Set the network useEffect(() => { - setCurrentNetwork(tx?.network || network); - }, [network, tx.network]); + setCurrentChainId(tx?.chainId || chainId); + }, [chainId, tx.chainId]); // Set the provider useEffect(() => { - if (currentNetwork) { - startPollingGasFees(currentNetwork, tx.flashbots); + if (currentChainId) { + startPollingGasFees(currentChainId, tx.flashbots); const updateProvider = async () => { let provider; - if (getNetworkObj(tx?.network).features.flashbots && tx.flashbots) { - logger.debug(`[SpeedUpAndCancelSheet]: using flashbots provider for network ${currentNetwork}`); + if (getNetworkObject({ chainId: tx?.chainId })?.features?.flashbots && tx.flashbots) { + logger.debug(`[SpeedUpAndCancelSheet]: using flashbots provider for chainId ${tx?.chainId}`); provider = await getFlashbotsProvider(); } else { - logger.debug(`[SpeedUpAndCancelSheet]: using provider for network ${currentNetwork}`); - provider = getProviderForNetwork(currentNetwork); + logger.debug(`[SpeedUpAndCancelSheet]: using provider for network ${tx?.chainId}`); + provider = getProvider({ chainId: currentChainId }); } setCurrentProvider(provider); }; @@ -303,7 +310,7 @@ export default function SpeedUpAndCancelSheet() { stopPollingGasFees(); }; } - }, [currentNetwork, startPollingGasFees, stopPollingGasFees, tx.flashbots, tx?.network]); + }, [currentChainId, startPollingGasFees, stopPollingGasFees, tx?.chainId, tx.flashbots]); // Update gas limit useEffect(() => { @@ -313,11 +320,11 @@ export default function SpeedUpAndCancelSheet() { updateGasFeeOption(gasUtils.URGENT); speedUrgentSelected.current = true; } - }, [currentNetwork, gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); + }, [gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); useEffect(() => { const init = async () => { - if (currentNetwork && currentProvider && !fetchedTx.current) { + if (currentChainId && currentProvider && !fetchedTx.current) { try { fetchedTx.current = true; const hexGasLimit = toHex(tx?.gasLimit?.toString() || '0x'); @@ -361,7 +368,7 @@ export default function SpeedUpAndCancelSheet() { }; init(); - }, [currentNetwork, currentProvider, goBack, isL2, network, tx, tx.gasLimit, tx.hash, type, updateGasFeeOption]); + }, [currentChainId, currentProvider, goBack, isL2, tx, tx?.gasLimit, tx.hash, type, updateGasFeeOption]); useEffect(() => { if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current) { @@ -475,8 +482,7 @@ export default function SpeedUpAndCancelSheet() { ({ color: colors.alpha(colors.blueGreyDark, 0.3), @@ -69,8 +67,8 @@ const NetworkPill = ({ chainIds }) => { const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); const networkMenuItems = useMemo(() => { - RainbowNetworks.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ - actionKey: network.value, + RainbowNetworkObjects.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ + actionKey: network.id, actionTitle: network.name, icon: { iconType: 'ASSET', @@ -128,7 +126,7 @@ const NetworkPill = ({ chainIds }) => { ) : ( - {availableNetworkChainIds[0] !== Network.mainnet ? ( + {availableNetworkChainIds[0] !== ChainId.mainnet ? ( ) : ( @@ -136,7 +134,7 @@ const NetworkPill = ({ chainIds }) => { - {getNetworkObj(availableNetworkChainIds[0]).name} + {chainIdToNameMapping[availableNetworkChainIds[0]]} @@ -151,7 +149,7 @@ export default function WalletConnectApprovalSheet() { const { colors, isDarkMode } = useTheme(); const { goBack } = useNavigation(); const { params } = useRoute(); - const { network, accountAddress } = useAccountSettings(); + const { chainId: settingsChainId, accountAddress } = useAccountSettings(); const { navigate } = useNavigation(); const { selectedWallet, walletNames, wallets } = useWallets(); const handled = useRef(false); @@ -182,8 +180,8 @@ export default function WalletConnectApprovalSheet() { const failureExplainSheetVariant = params?.failureExplainSheetVariant; const chainIds = meta?.chainIds; // WC v2 supports multi-chain const chainId = meta?.proposedChainId || chainIds?.[0] || 1; // WC v1 only supports 1 - const currentNetwork = params?.currentNetwork; - const [approvalNetwork, setApprovalNetwork] = useState(currentNetwork || network); + const currentChainId = params?.currentChainId; + const [approvalChainId, setApprovalChainId] = useState(currentChainId || settingsChainId); const isWalletConnectV2 = meta.isWalletConnectV2; const { dappName, dappUrl, dappScheme, imageUrl, peerId } = meta; @@ -225,18 +223,18 @@ export default function WalletConnectApprovalSheet() { * v2. */ const approvalNetworkInfo = useMemo(() => { - const networkObj = getNetworkObj(approvalNetwork); + const networkObj = getNetworkObject({ chainId: approvalChainId }); return { chainId: networkObj.id, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, name: networkObj.name, value: networkObj.value, }; - }, [approvalNetwork, isDarkMode]); + }, [approvalChainId, isDarkMode]); const handleOnPressNetworksMenuItem = useCallback( - ({ nativeEvent }) => setApprovalNetwork(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), - [setApprovalNetwork] + ({ nativeEvent }) => setApprovalChainId(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), + [setApprovalChainId] ); const handleSuccess = useCallback( @@ -253,8 +251,7 @@ export default function WalletConnectApprovalSheet() { useEffect(() => { if (chainId && type === WalletConnectApprovalSheetType.connect) { - const network = ethereumUtils.getNetworkFromChainId(Number(chainId)); - setApprovalNetwork(network); + setApprovalChainId(chainId); } }, [chainId, type]); @@ -284,7 +281,7 @@ export default function WalletConnectApprovalSheet() { }, [handleSuccess, goBack]); const onPressAndroid = useCallback(() => { - androidShowNetworksActionSheet(({ network }) => setApprovalNetwork(network)); + androidShowNetworksActionSheet(({ chainId }) => setApprovalChainId(chainId)); }, []); const handlePressChangeWallet = useCallback(() => { @@ -358,19 +355,11 @@ export default function WalletConnectApprovalSheet() { }} > - + {`${ - type === WalletConnectApprovalSheetType.connect - ? approvalNetworkInfo.name - : ethereumUtils.getNetworkNameFromChainId(Number(chainId)) + type === WalletConnectApprovalSheetType.connect ? approvalNetworkInfo.name : chainIdToNameMapping[chainId] } ${type === WalletConnectApprovalSheetType.connect && menuItems.length > 1 ? '􀁰' : ''}`} @@ -379,8 +368,8 @@ export default function WalletConnectApprovalSheet() { } }, [ NetworkSwitcherParent, + approvalNetworkInfo.chainId, approvalNetworkInfo.name, - approvalNetworkInfo.value, chainId, chainIds, handleOnPressNetworksMenuItem, @@ -411,7 +400,7 @@ export default function WalletConnectApprovalSheet() { {type === WalletConnectApprovalSheetType.connect ? lang.t(lang.l.walletconnect.wants_to_connect) : lang.t(lang.l.walletconnect.wants_to_connect_to_network, { - network: ethereumUtils.getNetworkNameFromChainId(Number(chainId)), + network: chainIdToNameMapping[chainId], })} diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 02ea352ec5c..d78336c324e 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -1,22 +1,17 @@ -import { InteractionManager, View } from 'react-native'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { View } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import { AssetList } from '../../components/asset-list'; import { Page } from '../../components/layout'; -import { Network } from '@/helpers'; import { useRemoveFirst } from '@/navigation/useRemoveFirst'; -import { settingsUpdateNetwork } from '@/redux/settings'; import { navbarHeight } from '@/components/navbar/Navbar'; import { Box } from '@/design-system'; import { useAccountAccentColor, useAccountSettings, - useInitializeAccountData, useInitializeWallet, - useLoadAccountData, useLoadAccountLateData, useLoadGlobalLateData, - useResetAccountState, useWalletSectionsData, } from '@/hooks'; import Routes from '@rainbow-me/routes'; @@ -51,28 +46,8 @@ const WalletScreen: React.FC = ({ navigation, route }) => { const loadAccountLateData = useLoadAccountLateData(); const loadGlobalLateData = useLoadGlobalLateData(); - const dispatch = useDispatch(); - const resetAccountState = useResetAccountState(); - const loadAccountData = useLoadAccountData(); - const initializeAccountData = useInitializeAccountData(); const insets = useSafeAreaInsets(); - const revertToMainnet = useCallback(async () => { - await resetAccountState(); - await dispatch(settingsUpdateNetwork(Network.mainnet)); - InteractionManager.runAfterInteractions(async () => { - await loadAccountData(); - initializeAccountData(); - }); - }, [dispatch, initializeAccountData, loadAccountData, resetAccountState]); - - useEffect(() => { - const supportedNetworks = [Network.mainnet]; - if (!supportedNetworks.includes(currentNetwork)) { - revertToMainnet(); - } - }, [currentNetwork, revertToMainnet]); - const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady); const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData(); diff --git a/src/screens/discover/components/DiscoverHome.tsx b/src/screens/discover/components/DiscoverHome.tsx index 67a6e8ce011..866c7688096 100644 --- a/src/screens/discover/components/DiscoverHome.tsx +++ b/src/screens/discover/components/DiscoverHome.tsx @@ -7,7 +7,7 @@ import useExperimentalFlag, { NFT_OFFERS, FEATURED_RESULTS, } from '@rainbow-me/config/experimentalHooks'; -import { isTestnetNetwork } from '@/handlers/web3'; +import { isTestnetChain } from '@/handlers/web3'; import { Inline, Inset, Stack, Box } from '@/design-system'; import { useAccountSettings, useWallets } from '@/hooks'; import { ENSCreateProfileCard } from '@/components/cards/ENSCreateProfileCard'; @@ -33,7 +33,7 @@ export const HORIZONTAL_PADDING = 20; export default function DiscoverHome() { const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results } = useRemoteConfig(); - const { network } = useAccountSettings(); + const { chainId } = useAccountSettings(); const profilesEnabledLocalFlag = useExperimentalFlag(PROFILES); const profilesEnabledRemoteFlag = profiles_enabled; const hardwareWalletsEnabled = useExperimentalFlag(HARDWARE_WALLETS); @@ -42,7 +42,7 @@ export default function DiscoverHome() { const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; const opRewardsLocalFlag = useExperimentalFlag(OP_REWARDS); const opRewardsRemoteFlag = op_rewards_enabled; - const testNetwork = isTestnetNetwork(network); + const testNetwork = isTestnetChain({ chainId }); const { navigate } = useNavigation(); const isProfilesEnabled = profilesEnabledLocalFlag && profilesEnabledRemoteFlag; diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 74233112e6b..52741d9fc4e 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -19,11 +19,10 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { ethereumUtils } from '@/utils'; -import { Network } from '@/helpers'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; export const SearchContainer = styled(Row)({ height: '100%', @@ -134,7 +133,7 @@ export default function DiscoverSearch() { network === Network.optimism; } const contractAddress = query.split('/')[1]; - navigateToMintCollection(contractAddress, network); + navigateToMintCollection(contractAddress, ethereumUtils.getChainIdFromNetwork(network)); } }; checkAndHandleMint(searchQuery); diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index 596e7c6f150..399b8ca3e56 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -56,7 +56,7 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const NFT_IMAGE_HEIGHT = 250; // inset * 2 -> 28 *2 @@ -200,7 +200,7 @@ const MintSheet = () => { // check address balance useEffect(() => { const checkInsufficientEth = async () => { - const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress))?.balance?.amount ?? 0; + const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }))?.balance?.amount ?? 0; const totalMintPrice = multiply(price.amount, quantity); if (greaterThanOrEqualTo(totalMintPrice, nativeBalance)) { @@ -237,8 +237,7 @@ const MintSheet = () => { // start poll gas price useEffect(() => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - startPollingGasFees(network); + startPollingGasFees(chainId); return () => { stopPollingGasFees(); @@ -365,9 +364,8 @@ const MintSheet = () => { }); const feeAddress = getRainbowFeeAddress(chainId); - const nonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(chainId) }); + const nonce = await getNextNonce({ address: accountAddress, chainId }); try { - const currentNetwork = ethereumUtils.getNetworkFromChainId(chainId); await getClient()?.actions.mintToken({ items: [ { @@ -388,10 +386,11 @@ const MintSheet = () => { step.items?.forEach(item => { if (item.txHashes?.[0]?.txHash && txRef.current !== item.txHashes[0].txHash && item.status === 'incomplete') { const asset = { + chainId, type: 'nft', icon_url: imageUrl, address: mintCollection.id || '', - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), name: mintCollection.name || '', decimals: 18, symbol: 'NFT', @@ -401,7 +400,7 @@ const MintSheet = () => { const paymentAsset = { type: 'nft', address: ETH_ADDRESS, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), name: mintCollection.publicMintInfo?.price?.currency?.name || 'Ethereum', decimals: mintCollection.publicMintInfo?.price?.currency?.decimals || 18, symbol: ETH_SYMBOL, @@ -414,7 +413,7 @@ const MintSheet = () => { to: item.data?.to, from: item.data?.from, hash: item.txHashes[0].txHash, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), nonce, changes: [ { @@ -437,7 +436,7 @@ const MintSheet = () => { addNewTransaction({ transaction: tx, address: accountAddress, - network: currentNetwork, + chainId, }); analyticsV2.track(event.mintsMintedNFT, { collectionName: mintCollection.name || '', @@ -683,7 +682,9 @@ const MintSheet = () => { symbol="􀉆" label={i18n.t(i18n.l.minting.contract)} value={ - ethereumUtils.openAddressInBlockExplorer(mintCollection.id!, chainId)}> + ethereumUtils.openAddressInBlockExplorer({ address: mintCollection.id!, chainId })} + > {contractAddressDisplay} diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index a1e710bfe53..7fa9780b228 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -5,7 +5,7 @@ import { Bleed, Box, Text, TextShadow, globalColors, useBackgroundColor, useColo import * as i18n from '@/languages'; import { ListHeader, ListPanel, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; import { ChainImage } from '@/components/coin-icon/ChainImage'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { useAccountAccentColor, useAccountProfile, useAccountSettings } from '@/hooks'; import { safeAreaInsetValues } from '@/utils'; @@ -17,7 +17,6 @@ import { PointsErrorType } from '@/graphql/__generated__/metadata'; import { useMutation } from '@tanstack/react-query'; import { invalidatePointsQuery, usePoints } from '@/resources/points'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; -import { Network } from '@/helpers'; import { ButtonPressAnimation } from '@/components/animations'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; @@ -29,7 +28,7 @@ import { walletExecuteRap } from '@/raps/execute'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { loadWallet } from '@/model/wallet'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { getGasSettingsBySpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -211,7 +210,7 @@ const ClaimingRewards = ({ }>({ mutationFn: async () => { // Fetch the native asset from the origin chain - const opEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.optimism); + const opEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.optimism }); const opEth = { ...opEth_, chainName: chainNameFromChainId(ChainId.optimism), @@ -220,9 +219,9 @@ const ClaimingRewards = ({ // Fetch the native asset from the destination chain let destinationEth_; if (chainId === ChainId.base) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.base); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.base }); } else if (chainId === ChainId.zora) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.zora); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.zora }); } else { destinationEth_ = opEth; } @@ -261,7 +260,7 @@ const ClaimingRewards = ({ gasParams, } satisfies RapSwapActionParameters<'claimBridge'>; - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 357630f1aa3..915e6c22a8f 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -9,7 +9,6 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, isENSNFTRecord } from '@/utils'; import { address as formatAddress } from '@/utils/abbreviations'; -import { Network } from '@/networks/types'; import { ContactAvatar, showDeleteContactActionSheet } from '@/components/contacts'; import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; import MaskedView from '@react-native-masked-view/masked-view'; @@ -20,7 +19,7 @@ import { useTheme } from '@/theme'; import LinearGradient from 'react-native-linear-gradient'; import { ButtonPressAnimation } from '@/components/animations'; import { noop } from 'lodash'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -128,7 +127,7 @@ export const LeaderboardRow = memo(function LeaderboardRow({ setClipboard(address); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); + ethereumUtils.openAddressInBlockExplorer({ address: address, chainId: ChainId.mainnet }); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/screens/points/content/PointsContent.tsx b/src/screens/points/content/PointsContent.tsx index e6e6012c8ed..c90c006f7af 100644 --- a/src/screens/points/content/PointsContent.tsx +++ b/src/screens/points/content/PointsContent.tsx @@ -62,7 +62,7 @@ import { format, intervalToDuration, isToday } from 'date-fns'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ETH_REWARDS, useExperimentalFlag } from '@/config'; import { RewardsActionButton } from '../components/RewardsActionButton'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const InfoCards = ({ points }: { points: GetPointsDataForWalletQuery | undefined }) => { const labelSecondary = useForegroundColor('labelSecondary'); diff --git a/src/screens/points/contexts/PointsProfileContext.tsx b/src/screens/points/contexts/PointsProfileContext.tsx index 93d8bd841d0..bed940f82a9 100644 --- a/src/screens/points/contexts/PointsProfileContext.tsx +++ b/src/screens/points/contexts/PointsProfileContext.tsx @@ -13,10 +13,10 @@ import { loadWallet, signPersonalMessage } from '@/model/wallet'; import { RainbowError, logger } from '@/logger'; import { queryClient } from '@/react-query'; import { useNavigation } from '@/navigation'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { Network } from '@/networks/types'; +import { getProvider } from '@/handlers/web3'; import { analyticsV2 } from '@/analytics'; import { delay } from '@/utils/delay'; +import { ChainId } from '@/networks/types'; type PointsProfileContext = { step: RainbowPointsFlowSteps; @@ -134,7 +134,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); throw new RainbowError('Points: Error getting onboard challenge'); } - const provider = getProviderForNetwork(Network.mainnet); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ address: accountAddress, provider }); if (!wallet) { Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); diff --git a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx index 223fa5d7d06..9040b9a8e1b 100644 --- a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx @@ -101,7 +101,7 @@ export const TransactionDetailsHashAndActionsSection: React.FC = ({ trans weight="heavy" onPress={onViewOnBlockExplorerPress} label={i18n.t(i18n.l.wallet.action.view_on, { - blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer({ chainId })), })} lightShadows /> diff --git a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx index d0790ebe0e6..5672b52ae02 100644 --- a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx @@ -6,8 +6,6 @@ import { Box, Stack, globalColors } from '@/design-system'; import { TransactionDetailsDivider } from '@/screens/transaction-details/components/TransactionDetailsDivider'; import * as i18n from '@/languages'; -import { Network } from '@/networks/types'; - import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; import { useAccountSettings } from '@/hooks'; @@ -17,7 +15,7 @@ import ImgixImage from '@/components/images/ImgixImage'; import { View } from 'react-native'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type Props = { transaction: RainbowTransaction; diff --git a/src/screens/transaction-details/components/TransactionMasthead.tsx b/src/screens/transaction-details/components/TransactionMasthead.tsx index 7461caddc5d..92c16db5100 100644 --- a/src/screens/transaction-details/components/TransactionMasthead.tsx +++ b/src/screens/transaction-details/components/TransactionMasthead.tsx @@ -34,7 +34,7 @@ import ImageAvatar from '@/components/contacts/ImageAvatar'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import * as lang from '@/languages'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const TransactionMastheadHeight = android ? 153 : 135; @@ -128,7 +128,7 @@ function CurrencyTile({ } }); } - }, []); + }, [accountName, address, addressContact?.nickname]); useEffect(() => { if (!addressAccount?.image && (fetchedEnsName || addressContact?.ens)) { diff --git a/src/state/appSessions/index.test.ts b/src/state/appSessions/index.test.ts index 6ff71512df9..02e8cf06905 100644 --- a/src/state/appSessions/index.test.ts +++ b/src/state/appSessions/index.test.ts @@ -1,4 +1,4 @@ -import { Network } from '@/networks/types'; +import { ChainId } from '@/networks/types'; import { useAppSessionsStore } from '.'; const UNISWAP_HOST = 'uniswap.org'; @@ -15,13 +15,13 @@ test('should be able to add session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.mainnet, + chainId: ChainId.mainnet, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet }, + sessions: { [ADDRESS_1]: ChainId.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -33,13 +33,13 @@ test('should be able to add session to an existent host', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -51,19 +51,19 @@ test('should be able to add session to a new host', async () => { url: OPENSEA_URL, host: OPENSEA_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, [OPENSEA_HOST]: { url: OPENSEA_URL, host: OPENSEA_HOST, - sessions: { [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -76,7 +76,7 @@ test('should be able to remove app session for a host', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -89,7 +89,7 @@ test('should be able to remove a session for a host and address', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet }, + sessions: { [ADDRESS_1]: ChainId.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -101,7 +101,7 @@ test('should be able to update active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); updateActiveSession({ host: UNISWAP_HOST, address: ADDRESS_1 }); expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress).toStrictEqual(ADDRESS_1); @@ -110,9 +110,9 @@ test('should be able to update active session', async () => { test('should be able to update active session network', async () => { const { updateActiveSessionNetwork } = useAppSessionsStore.getState(); - updateActiveSessionNetwork({ host: UNISWAP_HOST, network: Network.base }); + updateActiveSessionNetwork({ host: UNISWAP_HOST, chainId: ChainId.base }); const activeSessionAddress = useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress; - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(Network.base); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(ChainId.base); }); test('should be able to update session network', async () => { @@ -121,9 +121,9 @@ test('should be able to update session network', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.zora, + chainId: ChainId.zora, }); - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(Network.zora); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(ChainId.zora); }); test('should be able to clear all sessions', async () => { @@ -139,14 +139,14 @@ test('should be able to check if host has an active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.mainnet, + chainId: ChainId.mainnet, }); const activeSession = getActiveSession({ host: UNISWAP_HOST }); expect(activeSession).toStrictEqual({ activeSessionAddress: ADDRESS_1, host: UNISWAP_HOST, sessions: { - '0x123': Network.mainnet, + '0x123': ChainId.mainnet, }, url: UNISWAP_URL, }); @@ -157,13 +157,13 @@ test('should be able to update session chain id', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_1, }, }); diff --git a/src/state/appSessions/index.ts b/src/state/appSessions/index.ts index 46ae89ee9b3..5a2ae7dd35c 100644 --- a/src/state/appSessions/index.ts +++ b/src/state/appSessions/index.ts @@ -1,25 +1,32 @@ import { Address } from 'viem'; -import { Network } from '@/networks/types'; +import { Network, ChainId, networkToIdMapping } from '@/networks/types'; import { createRainbowStore } from '../internal/createRainbowStore'; -export interface AppSession { +export interface AppSessionV0 { activeSessionAddress: Address; host: string; sessions: Record; url: string; } -export interface AppSessionsStore { +export interface AppSession { + activeSessionAddress: Address; + host: string; + sessions: Record; + url: string; +} + +export interface AppSessionsStore { appSessions: Record; getActiveSession: ({ host }: { host: string }) => AppSession; removeAddressSessions: ({ address }: { address: Address }) => void; - addSession: ({ host, address, network, url }: { host: string; address: Address; network: Network; url: string }) => void; - removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; network: Network } | null; + addSession: ({ host, address, chainId, url }: { host: string; address: Address; chainId: ChainId; url: string }) => void; + removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; chainId: ChainId } | null; removeAppSession: ({ host }: { host: string }) => void; updateActiveSession: ({ host, address }: { host: string; address: Address }) => void; - updateActiveSessionNetwork: ({ host, network }: { host: string; network: Network }) => void; - updateSessionNetwork: ({ address, host, network }: { address: Address; host: string; network: Network }) => void; + updateActiveSessionNetwork: ({ host, chainId }: { host: string; chainId: ChainId }) => void; + updateSessionNetwork: ({ address, host, chainId }: { address: Address; host: string; chainId: ChainId }) => void; clearSessions: () => void; } @@ -47,18 +54,18 @@ export const useAppSessionsStore = createRainbowStore { + addSession: ({ host, address, chainId, url }) => { const appSessions = get().appSessions; const existingSession = appSessions[host]; if (!existingSession || !existingSession.sessions) { appSessions[host] = { host, - sessions: { [address]: network }, + sessions: { [address]: chainId }, activeSessionAddress: address, url, }; } else { - appSessions[host].sessions[address] = network; + appSessions[host].sessions[address] = chainId; appSessions[host].activeSessionAddress = address; } set({ @@ -93,7 +100,7 @@ export const useAppSessionsStore = createRainbowStore { + updateActiveSessionNetwork: ({ host, chainId }) => { const appSessions = get().appSessions; const appSession = appSessions[host] || {}; set({ @@ -130,13 +137,13 @@ export const useAppSessionsStore = createRainbowStore { + updateSessionNetwork: ({ host, address, chainId }) => { const appSessions = get().appSessions; const appSession = appSessions[host]; if (!appSession) return; @@ -147,7 +154,7 @@ export const useAppSessionsStore = createRainbowStore { + if (version === 0) { + const oldState = persistedState as AppSessionsStore; + const appSessions: AppSessionsStore['appSessions'] = {}; + for (const [host, session] of Object.entries(oldState.appSessions)) { + const sessions = session.sessions; + const newSessions = Object.keys(sessions).reduce( + (acc, addr) => { + const address = addr as Address; + const network = sessions[address]; + acc[address] = networkToIdMapping[network]; + return acc as Record; + }, + {} as Record + ); + appSessions[host] = { + activeSessionAddress: session.activeSessionAddress, + host: session.host, + sessions: newSessions, + url: session.url, + }; + } + return { + ...oldState, + appSessions, + }; + } + return persistedState as AppSessionsStore; + }, } ); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 87dbe97b7c0..512dfd56eed 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,5 +1,4 @@ import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; import { getIsHardhatConnected } from '@/handlers/web3'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; @@ -7,6 +6,7 @@ import store from '@/redux/store'; import { ETH_ADDRESS, SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; import { swapsStore } from '@/state/swaps/swapsStore'; +import { ChainId } from '@/networks/types'; const SEARCH_CACHE_MAX_ENTRIES = 50; diff --git a/src/state/connectedToHardhat/index.ts b/src/state/connectedToHardhat/index.ts new file mode 100644 index 00000000000..8d6b2c5a4ae --- /dev/null +++ b/src/state/connectedToHardhat/index.ts @@ -0,0 +1,28 @@ +import create from 'zustand'; +import { createRainbowStore } from '../internal/createRainbowStore'; + +export interface ConnectedToHardhatState { + connectedToHardhat: boolean; + setConnectedToHardhat: (connectedToHardhat: boolean) => void; + + connectedToHardhatOp: boolean; + setConnectedToHardhatOp: (connectedToHardhatOp: boolean) => void; +} + +export const useConnectedToHardhatStore = createRainbowStore( + set => ({ + connectedToHardhat: false, + setConnectedToHardhat: connectedToHardhat => { + set({ connectedToHardhat }); + }, + + connectedToHardhatOp: false, + setConnectedToHardhatOp: connectedToHardhatOp => { + set({ connectedToHardhatOp }); + }, + }), + { + storageKey: 'connectedToHardhat', + version: 0, + } +); diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts index 01dbcd64012..df1a4d11df0 100644 --- a/src/state/internal/createRainbowStore.ts +++ b/src/state/internal/createRainbowStore.ts @@ -38,6 +38,11 @@ interface RainbowPersistConfig { * @default 0 */ version?: number; + /** + * A function to perform persisted state migration. + * This function will be called when persisted state versions mismatch with the one specified here. + */ + migrate?: (persistedState: unknown, version: number) => S | Promise; } /** @@ -46,7 +51,7 @@ interface RainbowPersistConfig { * @returns An object containing the persist storage and version. */ function createPersistStorage(config: RainbowPersistConfig) { - const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0 } = config; + const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0, migrate } = config; const persistStorage: PersistOptions>['storage'] = { getItem: (name: string) => { @@ -151,6 +156,7 @@ export function createRainbowStore( partialize: persistConfig.partialize || (state => state), storage: persistStorage, version, + migrate: persistConfig.migrate, }) ) ); diff --git a/src/state/nonces/index.ts b/src/state/nonces/index.ts index 8383d5ada1e..d9b41e79781 100644 --- a/src/state/nonces/index.ts +++ b/src/state/nonces/index.ts @@ -1,7 +1,7 @@ import create from 'zustand'; import { createStore } from '../internal/createStore'; -import { Network } from '@/networks/types'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { Network, ChainId, networkToIdMapping } from '@/networks/types'; +import { getProvider } from '@/handlers/web3'; type NonceData = { currentNonce?: number; @@ -10,41 +10,49 @@ type NonceData = { type GetNonceArgs = { address: string; - network: Network; + chainId: ChainId; }; type UpdateNonceArgs = NonceData & GetNonceArgs; -export async function getNextNonce({ address, network }: { address: string; network: Network }) { +export async function getNextNonce({ address, chainId }: { address: string; chainId: ChainId }) { const { getNonce } = nonceStore.getState(); - const localNonceData = getNonce({ address, network }); + const localNonceData = getNonce({ address, chainId }); const localNonce = localNonceData?.currentNonce || 0; - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); const txCountIncludingPending = await provider.getTransactionCount(address, 'pending'); if (!localNonce && !txCountIncludingPending) return 0; const ret = Math.max(localNonce + 1, txCountIncludingPending); return ret; } -export interface CurrentNonceState { - nonces: Record>; - setNonce: ({ address, currentNonce, latestConfirmedNonce, network }: UpdateNonceArgs) => void; - getNonce: ({ address, network }: GetNonceArgs) => NonceData | null; +type NoncesV0 = { + [network in Network]: NonceData; +}; + +type Nonces = { + [chainId in ChainId]: NonceData; +}; + +export interface CurrentNonceState { + nonces: Record; + setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }: UpdateNonceArgs) => void; + getNonce: ({ address, chainId }: GetNonceArgs) => NonceData | null; clearNonces: () => void; } -export const nonceStore = createStore( +export const nonceStore = createStore>( (set, get) => ({ nonces: {}, - setNonce: ({ address, currentNonce, latestConfirmedNonce, network }) => { + setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }) => { const { nonces: oldNonces } = get(); - const addressAndChainIdNonces = oldNonces?.[address]?.[network] || {}; + const addressAndChainIdNonces = oldNonces?.[address]?.[chainId] || {}; set({ nonces: { ...oldNonces, [address]: { ...oldNonces[address], - [network]: { + [chainId]: { currentNonce: currentNonce ?? addressAndChainIdNonces?.currentNonce, latestConfirmedNonce: latestConfirmedNonce ?? addressAndChainIdNonces?.latestConfirmedNonce, }, @@ -52,9 +60,9 @@ export const nonceStore = createStore( }, }); }, - getNonce: ({ address, network }) => { + getNonce: ({ address, chainId }) => { const { nonces } = get(); - return nonces[address]?.[network] ?? null; + return nonces[address]?.[chainId] ?? null; }, clearNonces: () => { set({ nonces: {} }); @@ -63,7 +71,26 @@ export const nonceStore = createStore( { persist: { name: 'nonces', - version: 0, + version: 1, + migrate: (persistedState: unknown, version: number) => { + if (version === 0) { + const oldState = persistedState as CurrentNonceState; + const newNonces: CurrentNonceState['nonces'] = {}; + for (const [address, networkNonces] of Object.entries(oldState.nonces)) { + for (const [network, nonceData] of Object.entries(networkNonces)) { + if (!newNonces[address]) { + newNonces[address] = {} as Record; + } + newNonces[address][networkToIdMapping[network as Network]] = nonceData; + } + } + return { + ...oldState, + nonces: newNonces, + }; + } + return persistedState as CurrentNonceState; + }, }, } ); diff --git a/src/state/pendingTransactions/index.ts b/src/state/pendingTransactions/index.ts index fd5ac70063b..3eb8e2db2e5 100644 --- a/src/state/pendingTransactions/index.ts +++ b/src/state/pendingTransactions/index.ts @@ -2,8 +2,8 @@ import { RainbowTransaction, NewTransaction } from '@/entities/transactions'; import { createStore } from '../internal/createStore'; import create from 'zustand'; import { parseNewTransaction } from '@/parsers/transactions'; -import { Network } from '@/networks/types'; import { nonceStore } from '../nonces'; +import { ChainId } from '@/networks/types'; export interface PendingTransactionsState { pendingTransactions: Record; @@ -35,7 +35,7 @@ export const pendingTransactionsStore = createStore( ...currentPendingTransactions, [address]: [ ...addressPendingTransactions.filter(tx => { - if (tx.network === pendingTransaction.network) { + if (tx.chainId === pendingTransaction.chainId) { return tx.nonce !== pendingTransaction.nonce; } return true; @@ -70,11 +70,11 @@ export const usePendingTransactionsStore = create(pendingTransactionsStore); export const addNewTransaction = ({ address, - network, + chainId, transaction, }: { address: string; - network: Network; + chainId: ChainId; transaction: NewTransaction; }) => { const { addPendingTransaction } = pendingTransactionsStore.getState(); @@ -83,18 +83,18 @@ export const addNewTransaction = ({ addPendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - network, + chainId, currentNonce: transaction.nonce, }); }; export const updateTransaction = ({ address, - network, + chainId, transaction, }: { address: string; - network: Network; + chainId: ChainId; transaction: NewTransaction; }) => { const { updatePendingTransaction } = pendingTransactionsStore.getState(); @@ -103,7 +103,7 @@ export const updateTransaction = ({ updatePendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - network, + chainId, currentNonce: transaction.nonce, }); }; diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index 8f3c4917fea..df49c0f32a2 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -2,7 +2,7 @@ import { MIN_FLASHBOTS_PRIORITY_FEE } from '@/__swaps__/screens/Swap/constants'; import { getCustomGasSettings, setCustomMaxPriorityFee } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { getSelectedGasSpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { RecentSwap } from '@/__swaps__/types/swap'; import { getCachedGasSuggestions } from '@/__swaps__/utils/meteorology'; diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 8d547f2f8eb..e97b968911d 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -1,13 +1,12 @@ -import { memo } from 'react'; import { Address } from 'viem'; import { useAccountSettings } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; import { getIsHardhatConnected } from '@/handlers/web3'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; +import { ChainId } from '@/networks/types'; export const UserAssetsSync = function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); diff --git a/src/styles/colors.ts b/src/styles/colors.ts index 89edb3c587b..8985cda6999 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { globalColors } from '@/design-system'; import currentColors from '../theme/currentColors'; import { memoFn } from '../utils/memoFn'; +import { ChainId } from '@/networks/types'; export type Colors = ReturnType; @@ -186,18 +187,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; let networkColors = { - arbitrum: '#2D374B', - base: '#0052FF', - goerli: '#f6c343', - gnosis: '#479E9C', - mainnet: '#25292E', - optimism: '#FF4040', - polygon: '#8247E5', - bsc: '#F0B90B', - zora: '#2B5DF0', - avalanche: '#E84142', - degen: '#A36EFD', - blast: '#25292E', + [ChainId.arbitrum]: '#2D374B', + [ChainId.base]: '#0052FF', + [ChainId.goerli]: '#f6c343', + [ChainId.gnosis]: '#479E9C', + [ChainId.mainnet]: '#25292E', + [ChainId.optimism]: '#FF4040', + [ChainId.polygon]: '#8247E5', + [ChainId.bsc]: '#F0B90B', + [ChainId.zora]: '#2B5DF0', + [ChainId.avalanche]: '#E84142', + [ChainId.degen]: '#A36EFD', + [ChainId.blast]: '#25292E', }; let gradients = { @@ -328,18 +329,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; networkColors = { - arbitrum: '#ADBFE3', - base: '#3979FF', - goerli: '#f6c343', - gnosis: '#479E9C', - mainnet: '#E0E8FF', - optimism: '#FF6A6A', - polygon: '#A275EE', - bsc: '#F0B90B', - zora: '#6183F0', - avalanche: '#FF5D5E', - degen: '#A36EFD', - blast: '#FCFC03', + [ChainId.arbitrum]: '#ADBFE3', + [ChainId.base]: '#3979FF', + [ChainId.goerli]: '#f6c343', + [ChainId.gnosis]: '#479E9C', + [ChainId.mainnet]: '#E0E8FF', + [ChainId.optimism]: '#FF6A6A', + [ChainId.polygon]: '#A275EE', + [ChainId.bsc]: '#F0B90B', + [ChainId.zora]: '#6183F0', + [ChainId.avalanche]: '#FF5D5E', + [ChainId.degen]: '#A36EFD', + [ChainId.blast]: '#FCFC03', }; } diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index 46f3a0433db..9d96405b93f 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -24,8 +24,7 @@ import { SelectedGasFee, } from '@/entities'; import { getOnchainAssetBalance } from '@/handlers/assets'; -import { getIsHardhatConnected, getProviderForNetwork, isTestnetNetwork, toHex } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; +import { getIsHardhatConnected, getProvider, isTestnetChain, toHex } from '@/handlers/web3'; import { convertRawAmountToDecimalFormat, fromWei, greaterThan, isZero, subtract, add } from '@/helpers/utilities'; import { Navigation } from '@/navigation'; import { parseAssetNative } from '@/parsers'; @@ -43,32 +42,39 @@ import { import Routes from '@/navigation/routesNames'; import { logger, RainbowError } from '@/logger'; import { IS_IOS } from '@/env'; -import { RainbowNetworks, getNetworkObj, getNetworkObject } from '@/networks'; +import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; import { externalTokenQueryKey, FormattedExternalAsset, fetchExternalToken, useExternalToken, } from '@/resources/assets/externalAssetsQuery'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; +import { AddressOrEth } from '@/__swaps__/types/assets'; -const getNetworkNativeAsset = (chainId: ChainId): ParsedAddressAsset | undefined => { +const getNetworkNativeAsset = ({ chainId }: { chainId: ChainId }) => { const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId); return getAccountAsset(nativeAssetUniqueId); }; -export const getNativeAssetForNetwork = async (chainId: ChainId, address?: EthereumAddress): Promise => { - const network = getNetworkFromChainId(chainId); - const networkNativeAsset = getNetworkNativeAsset(chainId); +export const getNativeAssetForNetwork = async ({ + chainId, + address, +}: { + chainId: ChainId; + address?: EthereumAddress; +}): Promise => { + const networkNativeAsset = getNetworkNativeAsset({ chainId }); const { accountAddress, nativeCurrency } = store.getState().settings; const differentWallet = address?.toLowerCase() !== accountAddress?.toLowerCase(); let nativeAsset = differentWallet ? undefined : networkNativeAsset; // If the asset is on a different wallet, or not available in this wallet if (differentWallet || !nativeAsset) { - const mainnetAddress = getNetworkObject({ chainId })?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; - const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; + const networkObject = getNetworkObject({ chainId }); + const mainnetAddress = networkObject?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; + const nativeAssetAddress = networkObject.nativeCurrency.address as AddressOrEth; const externalAsset = await queryClient.fetchQuery( externalTokenQueryKey({ address: nativeAssetAddress, chainId, currency: nativeCurrency }), @@ -81,20 +87,20 @@ export const getNativeAssetForNetwork = async (chainId: ChainId, address?: Ether // @ts-ignore nativeAsset = { ...externalAsset, - network, - uniqueId: getUniqueId(getNetworkObject({ chainId }).nativeCurrency.address, chainId), - address: getNetworkObject({ chainId }).nativeCurrency.address, - decimals: getNetworkObject({ chainId }).nativeCurrency.decimals, - symbol: getNetworkObject({ chainId }).nativeCurrency.symbol, + network: networkObject.value, + uniqueId: getUniqueId(networkObject.nativeCurrency.address, chainId), + address: networkObject.nativeCurrency.address, + decimals: networkObject.nativeCurrency.decimals, + symbol: networkObject.nativeCurrency.symbol, }; } - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); if (nativeAsset) { nativeAsset.mainnet_address = mainnetAddress; - nativeAsset.address = getNetworkObject({ chainId }).nativeCurrency.address; + nativeAsset.address = networkObject.nativeCurrency.address; - const balance = await getOnchainAssetBalance(nativeAsset, address, network, provider); + const balance = await getOnchainAssetBalance(nativeAsset, address, chainId, provider); if (balance) { const assetWithBalance = { @@ -168,11 +174,11 @@ const getAssetPrice = (address: EthereumAddress = ETH_ADDRESS): number => { }; export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { - let address = getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS; + let address = (getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS) as AddressOrEth; let internalChainId = ChainId.mainnet; const { nativeCurrency } = store.getState().settings; if (chainId === ChainId.avalanche || chainId === ChainId.degen) { - address = getNetworkObject({ chainId }).nativeCurrency?.address; + address = getNetworkObject({ chainId }).nativeCurrency?.address as AddressOrEth; internalChainId = chainId; } const { data: nativeAsset } = useExternalToken({ @@ -185,14 +191,14 @@ export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { }; // anotha 1 -const getPriceOfNativeAssetForNetwork = (network: Network) => { - if (network === Network.polygon) { +const getPriceOfNativeAssetForNetwork = ({ chainId }: { chainId: ChainId }) => { + if (chainId === ChainId.polygon) { return getMaticPriceUnit(); - } else if (network === Network.bsc) { + } else if (chainId === ChainId.bsc) { return getBnbPriceUnit(); - } else if (network === Network.avalanche) { + } else if (chainId === ChainId.avalanche) { return getAvaxPriceUnit(); - } else if (network === Network.degen) { + } else if (chainId === ChainId.degen) { return getDegenPriceUnit(); } return getEthPriceUnit(); @@ -274,7 +280,7 @@ const getDataString = (func: string, arrVals: string[]) => { * @param {Number} chainId */ export const getNetworkFromChainId = (chainId: ChainId): Network => { - return RainbowNetworks.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; + return RainbowNetworkObjects.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; }; /** @@ -282,7 +288,7 @@ export const getNetworkFromChainId = (chainId: ChainId): Network => { * @param {Number} chainId */ const getNetworkNameFromChainId = (chainId: ChainId): string => { - return RainbowNetworks.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; + return RainbowNetworkObjects.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; }; /** @@ -290,20 +296,20 @@ const getNetworkNameFromChainId = (chainId: ChainId): string => { * @param {String} network */ const getChainIdFromNetwork = (network?: Network): ChainId => { - return network ? getNetworkObj(network).id : ChainId.mainnet; + return RainbowNetworkObjects.find(networkObject => networkObject.value === network)?.id || ChainId.mainnet; }; /** * @desc get etherscan host from network string * @param {String} network */ -function getEtherscanHostForNetwork(chainId: ChainId): string { +function getEtherscanHostForNetwork({ chainId }: { chainId: ChainId }): string { const base_host = 'etherscan.io'; const networkObject = getNetworkObject({ chainId }); const blockExplorer = networkObject.blockExplorers?.default?.url; const network = networkObject.network as Network; - if (network && isTestnetNetwork(network)) { + if (network && isTestnetChain({ chainId })) { return `${network}.${base_host}`; } else { return blockExplorer || base_host; @@ -375,11 +381,11 @@ export const getFirstTransactionTimestamp = async (address: EthereumAddress): Pr return timestamp ? timestamp * 1000 : undefined; }; -function getBlockExplorer(chainId: ChainId) { +function getBlockExplorer({ chainId }: { chainId: ChainId }) { return getNetworkObject({ chainId }).blockExplorers?.default.name || 'etherscan'; } -function openAddressInBlockExplorer(address: EthereumAddress, chainId: ChainId) { +function openAddressInBlockExplorer({ address, chainId }: { address: EthereumAddress; chainId: ChainId }) { const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/address/${address}`); } @@ -425,7 +431,7 @@ async function parseEthereumUrl(data: string) { if (!functionName) { // Send native asset const chainId = getChainIdFromNetwork(network); - asset = getNetworkNativeAsset(chainId); + asset = getNetworkNativeAsset({ chainId }); // @ts-ignore if (!asset || asset?.balance.amount === 0) { @@ -469,17 +475,17 @@ export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) = export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => `${address}_${chainId}`; -export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: EthereumAddress; chainId: ChainId } => { +export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: AddressOrEth; chainId: ChainId } => { const parts = uniqueId.split('_'); // If the unique ID does not contain '_', it's a mainnet address if (parts.length === 1) { - return { address: parts[0], chainId: ChainId.mainnet }; + return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; } // If the unique ID contains '_', the last part is the network and the rest is the address const network = parts[1] as Network; // Assuming the last part is a valid Network enum value - const address = parts[0]; + const address = parts[0] as AddressOrEth; const chainId = getChainIdFromNetwork(network); return { address, chainId }; @@ -530,13 +536,13 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const getBasicSwapGasLimit = (chainId: ChainId) => { switch (chainId) { - case getChainIdFromNetwork(Network.arbitrum): + case ChainId.arbitrum: return ethUnits.basic_swap_arbitrum; - case getChainIdFromNetwork(Network.polygon): + case ChainId.polygon: return ethUnits.basic_swap_polygon; - case getChainIdFromNetwork(Network.bsc): + case ChainId.bsc: return ethUnits.basic_swap_bsc; - case getChainIdFromNetwork(Network.optimism): + case ChainId.optimism: return ethUnits.basic_swap_optimism; default: return ethUnits.basic_swap; diff --git a/src/utils/getTokenMetadata.ts b/src/utils/getTokenMetadata.ts deleted file mode 100644 index fe376441c80..00000000000 --- a/src/utils/getTokenMetadata.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TokenMetadata } from '@/entities/tokens'; -import { omitFlatten } from '@/helpers/utilities'; -import { rainbowTokenList } from '@/references'; - -export default function getTokenMetadata(tokenAddress: string | undefined): Omit | undefined { - return undefined; -} diff --git a/src/utils/getUrlForTrustIconFallback.ts b/src/utils/getUrlForTrustIconFallback.ts index c248639dfc9..49030949001 100644 --- a/src/utils/getUrlForTrustIconFallback.ts +++ b/src/utils/getUrlForTrustIconFallback.ts @@ -1,18 +1,19 @@ +import { ChainId } from '@/networks/types'; import { EthereumAddress } from '@/entities'; -import { Network } from '@/networks/types'; +import ethereumUtils from './ethereumUtils'; -export default function getUrlForTrustIconFallback(address: EthereumAddress, network: Network): string | null { +export default function getUrlForTrustIconFallback(address: EthereumAddress, chainId: ChainId): string | null { if (!address) return null; let networkPath = 'ethereum'; - switch (network) { - case Network.mainnet: + switch (chainId) { + case ChainId.mainnet: networkPath = 'ethereum'; break; - case Network.bsc: + case ChainId.bsc: networkPath = 'smartchain'; break; default: - networkPath = network; + networkPath = ethereumUtils.getNetworkFromChainId(chainId); } return `https://rainbowme-res.cloudinary.com/image/upload/assets/${networkPath}/${address}.png`; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 7e70e27cf5d..aad92e7e346 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,7 +12,6 @@ export { default as ethereumUtils } from './ethereumUtils'; export { default as formatURLForDisplay } from './formatURLForDisplay'; export { default as gasUtils } from './gas'; export { default as getDominantColorFromImage } from './getDominantColorFromImage'; -export { default as getTokenMetadata } from './getTokenMetadata'; export { getUniqueTokenFormat, getUniqueTokenType } from './uniqueTokens'; export { default as getUrlForTrustIconFallback } from './getUrlForTrustIconFallback'; export { default as haptics } from './haptics'; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 8a6f193a480..52f5a2a334f 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -15,13 +15,15 @@ import { SEND_TRANSACTION } from './signingMethods'; import { handleSessionRequestResponse } from '@/walletConnect'; import ethereumUtils from './ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { maybeSignUri } from '@/handlers/imgix'; import { getActiveRoute } from '@/navigation/Navigation'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; +import { Address } from 'viem'; +import { ChainId } from '@/networks/types'; export type RequestSource = 'walletconnect' | 'browser'; @@ -35,9 +37,13 @@ export interface DappConnectionData { address?: string; } -export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): Promise<{ chainId: number; address: string } | Error> => { +export const handleDappBrowserConnectionPrompt = ( + dappData: DappConnectionData +): Promise<{ chainId: ChainId; address: Address } | Error> => { return new Promise((resolve, reject) => { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( + network => network.id + ); const receivedTimestamp = Date.now(); const routeParams: WalletconnectApprovalSheetRouteParams = { receivedTimestamp, diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 8507c2e048e..4eee7e3a9c6 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -37,16 +37,17 @@ import * as explain from '@/screens/Explain'; import { Box } from '@/design-system'; import { AuthRequestAuthenticateSignature, AuthRequestResponseErrorReason, RPCMethod, RPCPayload } from '@/walletConnect/types'; import { AuthRequest } from '@/walletConnect/sheets/AuthRequest'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { RainbowNetworks } from '@/networks'; +import { getProvider } from '@/handlers/web3'; +import { RainbowNetworkObjects } from '@/networks'; import { uniq } from 'lodash'; import { fetchDappMetadata } from '@/resources/metadata/dapp'; import { DAppStatus } from '@/graphql/__generated__/metadata'; import { handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; import { PerformanceTracking } from '@/performance/tracking'; +import { ChainId } from '@/networks/types'; -const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworks.filter(({ features }) => features.walletconnect).map(({ id }) => id); +const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworkObjects.filter(({ features }) => features.walletconnect).map(({ id }) => id); const SUPPORTED_SESSION_EVENTS = ['chainChanged', 'accountsChanged']; @@ -270,7 +271,7 @@ export function isSupportedMethod(method: RPCMethod) { } export function isSupportedChain(chainId: number) { - return !!RainbowNetworks.find(({ id, features }) => id === chainId && features.walletconnect); + return !!RainbowNetworkObjects.find(({ id, features }) => id === chainId && features.walletconnect); } /** @@ -878,7 +879,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { * encapsulate reused code. */ const loadWalletAndSignMessage = async () => { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.arbitrum }); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, provider }); if (!wallet) { diff --git a/tsconfig.json b/tsconfig.json index 2f2c192422a..9d8fe1cca5a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,6 @@ "@rainbow-me/model/*": ["src/model/*"], "@rainbow-me/navigation": ["src/navigation"], "@rainbow-me/navigation/*": ["src/navigation/*"], - "@rainbow-me/networkTypes": ["./src/helpers/networkTypes"], "@rainbow-me/parsers": ["src/parsers"], "@rainbow-me/raps": ["src/raps"], "@rainbow-me/react-query": ["./src/react-query"], From 1552bf9eed12c1e15d47fc3b6291f1d5f391939b Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 28 Aug 2024 20:24:34 -0400 Subject: [PATCH 67/78] pull address_to from change instead of top-level address_to (#6052) --- src/parsers/transactions.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/parsers/transactions.ts b/src/parsers/transactions.ts index b303e748dfa..712b8de45b7 100644 --- a/src/parsers/transactions.ts +++ b/src/parsers/transactions.ts @@ -113,10 +113,16 @@ export const parseTransaction = async ( iconUrl: meta.contract_icon_url, }; + // NOTE: For send transactions, the to address should be pulled from the outgoing change directly, not the txn.address_to + let to = txn.address_to; + if (meta.type === 'send') { + to = txn.changes.find(change => change?.direction === 'out')?.address_to ?? txn.address_to; + } + return { chainId, from: txn.address_from, - to: txn.address_to, + to, title: `${type}.${status}`, description, hash, From 156c0ec59fd21291db3b1609178a60143c6bda70 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 28 Aug 2024 21:03:55 -0400 Subject: [PATCH 68/78] bump fastlane (#6062) --- ios/Gemfile.lock | 79 ++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index ca3dbe8fdae..1af4554f95c 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -1,37 +1,40 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml activesupport (7.0.5.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.882.0) - aws-sdk-core (3.190.3) + aws-partitions (1.969.0) + aws-sdk-core (3.202.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.76.0) - aws-sdk-core (~> 3, >= 3.188.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.159.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) claide (1.1.0) cocoapods (1.14.3) addressable (~> 2.8) @@ -84,7 +87,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.109.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -106,22 +109,22 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -142,10 +145,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -154,7 +157,7 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) @@ -175,12 +178,12 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) + google-cloud-errors (1.4.0) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -196,41 +199,44 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.7) domain_name (~> 0.5) httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) - jwt (2.7.1) - mini_magick (4.12.0) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) mini_mime (1.1.5) minitest (5.18.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.1) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - optparse (0.4.0) + nkf (0.2.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (4.0.7) - rake (13.1.0) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.3.6) + strscan rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.18.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -238,6 +244,7 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -253,13 +260,13 @@ GEM uber (0.1.0) unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.25.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.2, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) From c6edc67ef07b18356af902daba17f8e18050acb5 Mon Sep 17 00:00:00 2001 From: Christopher Howard Date: Thu, 29 Aug 2024 15:36:07 -0400 Subject: [PATCH 69/78] Implement perceived finality (#6037) * feat: perceived finality initial * fix: update chain id import --- src/hooks/useWatchPendingTxs.ts | 40 ++++++ src/resources/assets/UserAssetsQuery.ts | 47 +++++-- src/state/staleBalances/index.test.ts | 165 ++++++++++++++++++++++++ src/state/staleBalances/index.ts | 99 ++++++++++++++ 4 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 src/state/staleBalances/index.test.ts create mode 100644 src/state/staleBalances/index.ts diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 8e22b715a99..72ec35f0a93 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -16,6 +16,7 @@ import { Address } from 'viem'; import { nftsQueryKey } from '@/resources/nfts'; import { getNftSortForAddress } from './useNFTsSortBy'; import { ChainId } from '@/networks/types'; +import { staleBalancesStore } from '@/state/staleBalances'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ @@ -164,6 +165,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); const watchPendingTransactions = useCallback(async () => { + const connectedToHardhat = getIsHardhatConnected(); if (!pendingTransactions?.length) return; const updatedPendingTransactions = await Promise.all( pendingTransactions.map((tx: RainbowTransaction) => processPendingTransaction(tx)) @@ -190,6 +192,20 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const chainIds = RainbowNetworkObjects.filter(networkObject => networkObject.enabled && networkObject.networkType !== 'testnet').map( networkObject => networkObject.id ); + minedTransactions.forEach(tx => { + if (tx.changes?.length) { + tx.changes?.forEach(change => { + processStaleAsset({ asset: change?.asset, address, transactionHash: tx?.hash }); + }); + } else if (tx.asset) { + processStaleAsset({ address, asset: tx.asset, transactionHash: tx?.hash }); + } + }); + + queryClient.refetchQueries({ + queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }), + }); + await queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ address, @@ -217,3 +233,27 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => return { watchPendingTransactions }; }; + +function processStaleAsset({ + asset, + address, + transactionHash, +}: { + asset: RainbowTransaction['asset']; + address: string; + transactionHash: string; +}) { + const { addStaleBalance } = staleBalancesStore.getState(); + const chainId = asset?.chainId; + if (asset && typeof chainId === 'number') { + const changedAssetAddress = asset?.address as Address; + addStaleBalance({ + address, + chainId, + info: { + address: changedAssetAddress, + transactionHash, + }, + }); + } +} diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts index 1e0a285e3d5..47db136d5e2 100644 --- a/src/resources/assets/UserAssetsQuery.ts +++ b/src/resources/assets/UserAssetsQuery.ts @@ -11,6 +11,7 @@ import { filterPositionsData, parseAddressAsset } from './assets'; import { fetchHardhatBalances } from './hardhatAssets'; import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types'; import { Network } from '@/networks/types'; +import { staleBalancesStore } from '@/state/staleBalances'; // /////////////////////////////////////////////// // Query Types @@ -32,15 +33,26 @@ type UserAssetsQueryKey = ReturnType; // /////////////////////////////////////////////// // Query Function -const fetchUserAssetsForChainIds = async (address: string, currency: NativeCurrencyKey, chainIds: number[]) => { +const fetchUserAssetsForChainIds = async ({ + address, + currency, + chainIds, + staleBalanceParam, +}: { + address: string; + currency: NativeCurrencyKey; + chainIds: number[]; + staleBalanceParam?: string; +}) => { const chainIdsString = chainIds.join(','); - const url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets`; + let url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets?currency=${currency.toLowerCase()}`; + + if (staleBalanceParam) { + url += url + staleBalanceParam; + } const response = await rainbowFetch(url, { method: 'get', - params: { - currency: currency.toLowerCase(), - }, headers: { Authorization: `Bearer ${ADDYS_API_KEY}`, }, @@ -66,7 +78,10 @@ async function userAssetsQueryFunction({ network => network.id ); - const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds(address, currency, chainIds); + staleBalancesStore.getState().clearExpiredData(address); + const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address); + + const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); let parsedSuccessResults = results; // grab cached data for chain IDs with errors @@ -102,7 +117,7 @@ const retryErroredChainIds = async ( connectedToHardhat: boolean, erroredChainIds: number[] ) => { - const { meta, results } = await fetchAndParseUserAssetsForChainIds(address, currency, erroredChainIds); + const { meta, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds: erroredChainIds }); let parsedSuccessResults = results; const successChainIds = meta?.chain_ids; @@ -142,12 +157,18 @@ interface AssetsAndMetadata { results: RainbowAddressAssets; } -const fetchAndParseUserAssetsForChainIds = async ( - address: string, - currency: NativeCurrencyKey, - chainIds: number[] -): Promise => { - const data = await fetchUserAssetsForChainIds(address, currency, chainIds); +const fetchAndParseUserAssetsForChainIds = async ({ + address, + currency, + chainIds, + staleBalanceParam, +}: { + address: string; + currency: NativeCurrencyKey; + chainIds: number[]; + staleBalanceParam?: string; +}): Promise => { + const data = await fetchUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); let parsedSuccessResults = parseUserAssetsByChain(data); // filter out positions data diff --git a/src/state/staleBalances/index.test.ts b/src/state/staleBalances/index.test.ts new file mode 100644 index 00000000000..b07e73acb7b --- /dev/null +++ b/src/state/staleBalances/index.test.ts @@ -0,0 +1,165 @@ +import { Address } from 'viem'; + +import { staleBalancesStore } from '.'; +import { DAI_ADDRESS, OP_ADDRESS } from '@/references'; +import { ETH_ADDRESS } from '@rainbow-me/swaps'; +import { ChainId } from '@/networks/types'; + +const TEST_ADDRESS_1 = '0xFOO'; +const TEST_ADDRESS_2 = '0xBAR'; +const THEN = Date.now() - 700000; +const WHEN = Date.now() + 60000; + +test('should be able to add asset information to the staleBalances object', async () => { + const { addStaleBalance, staleBalances } = staleBalancesStore.getState(); + expect(staleBalances).toStrictEqual({}); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [DAI_ADDRESS]: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should generate accurate stale balance query params and clear expired data - case #1', async () => { + const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + clearExpiredData(TEST_ADDRESS_1); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); + +test('should be able to remove expired stale balance and preserve unexpired data', async () => { + const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS as Address, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + clearExpiredData(TEST_ADDRESS_1); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should preserve data from other addresses when clearing expired data', async () => { + const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_2, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS as Address, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + clearExpiredData(TEST_ADDRESS_1); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + [TEST_ADDRESS_2]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should generate accurate stale balance query params and clear expired data - case #2', async () => { + const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + clearExpiredData(TEST_ADDRESS_2); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_2); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); + +test('should generate accurate stale balance query params and clear expired data - case #3', async () => { + const { addStaleBalance, getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.optimism, + info: { + address: OP_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + + clearExpiredData(TEST_ADDRESS_1); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}&token=${ChainId.optimism}.${OP_ADDRESS}`); + + clearExpiredData(TEST_ADDRESS_2); + const queryParam2 = getStaleBalancesQueryParam(TEST_ADDRESS_2); + expect(queryParam2).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); diff --git a/src/state/staleBalances/index.ts b/src/state/staleBalances/index.ts new file mode 100644 index 00000000000..8a9928aaacb --- /dev/null +++ b/src/state/staleBalances/index.ts @@ -0,0 +1,99 @@ +import { createRainbowStore } from '../internal/createRainbowStore'; + +const TIME_TO_WATCH = 600000; + +interface StaleBalanceInfo { + address: string; + expirationTime?: number; + transactionHash: string; +} + +interface StaleBalances { + [key: string]: StaleBalanceInfo; +} +interface StaleBalancesByChainId { + [key: number]: StaleBalances; +} + +export interface StaleBalancesState { + addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => void; + clearExpiredData: (address: string) => void; + getStaleBalancesQueryParam: (address: string) => string; + staleBalances: Record; +} + +export const staleBalancesStore = createRainbowStore( + (set, get) => ({ + addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => { + set(state => { + const { staleBalances } = state; + const staleBalancesForUser = staleBalances[address] || {}; + const staleBalancesForChain = staleBalancesForUser[chainId] || {}; + const newStaleBalancesForChain = { + ...staleBalancesForChain, + [info.address]: { + ...info, + expirationTime: info.expirationTime || Date.now() + TIME_TO_WATCH, + }, + }; + const newStaleBalancesForUser = { + ...staleBalancesForUser, + [chainId]: newStaleBalancesForChain, + }; + return { + staleBalances: { + ...staleBalances, + [address]: newStaleBalancesForUser, + }, + }; + }); + }, + clearExpiredData: (address: string) => { + set(state => { + const { staleBalances } = state; + const staleBalancesForUser = staleBalances[address] || {}; + const newStaleBalancesForUser: StaleBalancesByChainId = { + ...staleBalancesForUser, + }; + for (const c of Object.keys(staleBalancesForUser)) { + const chainId = parseInt(c, 10); + const newStaleBalancesForChain = { + ...(staleBalancesForUser[chainId] || {}), + }; + for (const staleBalance of Object.values(newStaleBalancesForChain)) { + if (typeof staleBalance.expirationTime === 'number' && staleBalance.expirationTime <= Date.now()) { + delete newStaleBalancesForChain[staleBalance.address]; + } + } + newStaleBalancesForUser[chainId] = newStaleBalancesForChain; + } + return { + staleBalances: { + ...staleBalances, + [address]: newStaleBalancesForUser, + }, + }; + }); + }, + getStaleBalancesQueryParam: (address: string) => { + let queryStringFragment = ''; + const { staleBalances } = get(); + const staleBalancesForUser = staleBalances[address]; + for (const c of Object.keys(staleBalancesForUser)) { + const chainId = parseInt(c, 10); + const staleBalancesForChain = staleBalancesForUser[chainId]; + for (const staleBalance of Object.values(staleBalancesForChain)) { + if (typeof staleBalance.expirationTime === 'number') { + queryStringFragment += `&token=${chainId}.${staleBalance.address}`; + } + } + } + return queryStringFragment; + }, + staleBalances: {}, + }), + { + storageKey: 'staleBalances', + version: 0, + } +); From 349bd225abe357795f15b59a1c526edc144c4f46 Mon Sep 17 00:00:00 2001 From: Christopher Howard Date: Thu, 29 Aug 2024 15:36:51 -0400 Subject: [PATCH 70/78] fix: remove p-queue (#6063) * fix: remove p-queue * fix: update yarn lock file --- package.json | 1 - .../firstTransactionTimestampQuery.ts | 8 ++++---- yarn.lock | 20 +------------------ 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 533313474f1..52c229cff23 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,6 @@ "moti": "0.28", "multiformats": "9.6.2", "nanoid": "3.2.0", - "p-queue": "7.2.0", "p-wait-for": "4.1.0", "pako": "2.0.4", "parse-ms": "2.1.0", diff --git a/src/resources/transactions/firstTransactionTimestampQuery.ts b/src/resources/transactions/firstTransactionTimestampQuery.ts index 033c295dad8..c1bbbc42888 100644 --- a/src/resources/transactions/firstTransactionTimestampQuery.ts +++ b/src/resources/transactions/firstTransactionTimestampQuery.ts @@ -1,5 +1,4 @@ import { useQuery } from '@tanstack/react-query'; -import PQueue from 'p-queue/dist'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { getFirstTransactionTimestamp } from '@/utils/ethereumUtils'; @@ -23,8 +22,6 @@ export type FirstTransactionTimestampQueryKey = ReturnType) { @@ -35,7 +32,10 @@ export async function firstTransactionTimestampQueryFunction({ address = (await fetchENSAddress({ name: addressOrName })) ?? ''; } - const timestamp = address ? await queue.add(async () => getFirstTransactionTimestamp(address)) : null; + let timestamp; + if (address) { + timestamp = await getFirstTransactionTimestamp(address); + } return timestamp ?? null; } diff --git a/yarn.lock b/yarn.lock index 346f7b3376b..9d7df69de4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7976,7 +7976,6 @@ __metadata: multiformats: "npm:9.6.2" nanoid: "npm:3.2.0" node-vibrant: "npm:3.2.1-alpha.1" - p-queue: "npm:7.2.0" p-wait-for: "npm:4.1.0" pako: "npm:2.0.4" parse-ms: "npm:2.1.0" @@ -13163,13 +13162,6 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.7": - version: 4.0.7 - resolution: "eventemitter3@npm:4.0.7" - checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b - languageName: node - linkType: hard - "events@npm:3.3.0, events@npm:^3.0.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -19590,17 +19582,7 @@ __metadata: languageName: node linkType: hard -"p-queue@npm:7.2.0": - version: 7.2.0 - resolution: "p-queue@npm:7.2.0" - dependencies: - eventemitter3: "npm:^4.0.7" - p-timeout: "npm:^5.0.2" - checksum: 10c0/0dad31488d6afe5c27a84ed00a703eef1ed4387338e0debe8155d36172808c6ae0451be5d88a12aa41f1deb4d3583ecd19e5f6ded5f06c937b01ff828d18c6cb - languageName: node - linkType: hard - -"p-timeout@npm:^5.0.0, p-timeout@npm:^5.0.2": +"p-timeout@npm:^5.0.0": version: 5.1.0 resolution: "p-timeout@npm:5.1.0" checksum: 10c0/1b026cf9d5878c64bec4341ca9cda8ec6b8b3aea8a57885ca0fe2b35753a20d767fb6f9d3aa41e1252f42bc95432c05ea33b6b18f271fb10bfb0789591850a41 From ab48c685d2919f9d77f60fc96e1d21039fe44105 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:00:54 -0400 Subject: [PATCH 71/78] More WC changes (#6064) * more wc refactor * separate push notifications from wc listeners --- src/App.tsx | 5 +-- src/walletConnect/index.tsx | 65 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 02c836a846e..541791f9e3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; -import { initListeners as initWalletConnectListeners } from '@/walletConnect'; +import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; @@ -144,12 +144,13 @@ function App({ walletReady }: AppProps) { } identifyFlow(); eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + initWalletConnectListeners(); const p1 = analyticsV2.initializeRudderstack(); const p2 = setupDeeplinking(); const p3 = saveFCMToken(); Promise.all([p1, p2, p3]).then(() => { - initWalletConnectListeners(); + initWalletConnectPushNotifications(); PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); analyticsV2.track(analyticsV2.event.applicationDidMount); }); diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 4eee7e3a9c6..66128be8dd5 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -9,7 +9,7 @@ import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; import WalletConnectCore, { Core } from '@walletconnect/core'; -import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; +import Client, { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -94,7 +94,6 @@ export function maybeGoBackAndClearHasPendingRedirect({ delay = 0 }: { delay?: n /** * MAY BE UNDEFINED if WC v2 hasn't been instantiated yet */ -let syncWeb3WalletClient: Awaited> | undefined; let lastConnector: string | undefined = undefined; @@ -102,46 +101,41 @@ let walletConnectCore: WalletConnectCore | undefined; let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; -let initPromise: ReturnType<(typeof Web3Wallet)['init']> | null = null; +let initPromise: Promise | undefined = undefined; + +let syncWeb3WalletClient: Client | undefined = undefined; export const initializeWCv2 = async () => { - walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); - - web3WalletClient = Web3Wallet.init({ - core: walletConnectCore, - metadata: { - name: '🌈 Rainbow', - description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', - url: 'https://rainbow.me', - icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], - redirect: { - native: 'rainbow://wc', - universal: 'https://rnbwapp.com/wc', + if (!walletConnectCore) { + walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); + } + + if (!web3WalletClient) { + // eslint-disable-next-line require-atomic-updates + web3WalletClient = Web3Wallet.init({ + core: walletConnectCore, + metadata: { + name: '🌈 Rainbow', + description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', + url: 'https://rainbow.me', + icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], + redirect: { + native: 'rainbow://wc', + universal: 'https://rnbwapp.com/wc', + }, }, - }, - }); + }); + } + return web3WalletClient; }; -// this function ensures we only initialize the client once export async function getWeb3WalletClient() { - if (!syncWeb3WalletClient) { - if (!initPromise) { - if (web3WalletClient) { - initPromise = web3WalletClient.then(client => { - syncWeb3WalletClient = client; - return client; - }); - } else { - await initializeWCv2(); - return getWeb3WalletClient(); - } - } - // Wait for the initialization promise to resolve - return initPromise; - } else { - return syncWeb3WalletClient; + if (!initPromise) { + initPromise = initializeWCv2(); } + + return initPromise; } /** @@ -385,11 +379,14 @@ export async function initListeners() { events.emit('walletConnectV2SessionDeleted'); }, 500); }); +} +export async function initWalletConnectPushNotifications() { try { const token = await getFCMToken(); if (token) { + const client = await getWeb3WalletClient(); const client_id = await client.core.crypto.getClientId(); // initial subscription From 35d3e524073ea635ef66d8ff74ce8536f284d28a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 30 Aug 2024 15:01:04 -0400 Subject: [PATCH 72/78] Mobile Wallet Protocol / Txn cleanup (#6061) * lots of sign transaction sheet cleanup including original ticket fix * fix toHex ref * mwp * create mwp secure store and wrap app in provider * add android intents and clena up app.tsx * idk * changes * handshake request * basic impl * update some comments * some progress today at least * move business logic off App.tsx * move backup check to wallet screen * latest changes * use suspense and lazy to fix oop issue * expose config * recursively handle incoming actions * only process one incoming message at once * UNDO THIS PROBABLY * progress on personal sign * personal sign working * send transactions working * cleanup * fix lint and other calls to getRequestDisplayDetails * Update src/handlers/deeplinks.ts * fml * add switch eth chain * lint * fix lint * code review changes * cleanup App.tsx * fixes * code review changes * code review --- globals.d.ts | 1 + ios/Podfile.lock | 12 + package.json | 1 + src/App.tsx | 205 +- src/components/AppStateChangeHandler.tsx | 38 + src/components/DeeplinkHandler.tsx | 65 + src/components/FadeGradient.tsx | 47 + src/components/FadedScrollCard.tsx | 281 +++ .../MobileWalletProtocolListener.tsx | 51 + .../Transactions/TransactionDetailsCard.tsx | 127 ++ .../Transactions/TransactionDetailsRow.tsx | 90 + .../Transactions/TransactionIcons.tsx | 196 ++ .../Transactions/TransactionMessageCard.tsx | 113 + .../TransactionSimulatedEventRow.tsx | 109 + .../TransactionSimulationCard.tsx | 314 +++ src/components/Transactions/constants.ts | 121 + src/components/Transactions/types.ts | 18 + src/handlers/assets.ts | 2 +- src/handlers/deeplinks.ts | 19 +- src/helpers/accountInfo.ts | 7 +- src/helpers/dappNameHandler.ts | 3 + src/helpers/findWalletWithAccount.ts | 5 +- src/hooks/useApplicationSetup.ts | 56 + src/hooks/useCalculateGasLimit.ts | 74 + src/hooks/useConfirmTransaction.ts | 27 + src/hooks/useHasEnoughBalance.ts | 47 + src/hooks/useNonceForDisplay.ts | 32 + src/hooks/useProviderSetup.ts | 45 + src/hooks/useSubmitTransaction.ts | 56 + src/hooks/useTransactionSetup.ts | 66 + src/languages/en_US.json | 1 + src/navigation/Routes.android.tsx | 29 +- src/navigation/Routes.ios.tsx | 29 +- src/navigation/config.tsx | 3 +- src/notifications/tokens.ts | 2 +- src/parsers/requests.js | 6 +- src/redux/requests.ts | 6 +- src/redux/walletconnect.ts | 4 +- .../transactions/transactionSimulation.ts | 146 ++ src/screens/SignTransactionSheet.tsx | 1979 ++--------------- src/screens/WalletConnectApprovalSheet.js | 12 +- src/screens/WalletScreen/index.tsx | 6 + src/state/performance/operations.ts | 1 + src/storage/index.ts | 51 +- src/utils/formatDate.ts | 28 + src/utils/requestNavigationHandlers.ts | 277 ++- src/walletConnect/index.tsx | 2 +- tsconfig.json | 1 + yarn.lock | 34 + 49 files changed, 2886 insertions(+), 1959 deletions(-) create mode 100644 src/components/AppStateChangeHandler.tsx create mode 100644 src/components/DeeplinkHandler.tsx create mode 100644 src/components/FadeGradient.tsx create mode 100644 src/components/FadedScrollCard.tsx create mode 100644 src/components/MobileWalletProtocolListener.tsx create mode 100644 src/components/Transactions/TransactionDetailsCard.tsx create mode 100644 src/components/Transactions/TransactionDetailsRow.tsx create mode 100644 src/components/Transactions/TransactionIcons.tsx create mode 100644 src/components/Transactions/TransactionMessageCard.tsx create mode 100644 src/components/Transactions/TransactionSimulatedEventRow.tsx create mode 100644 src/components/Transactions/TransactionSimulationCard.tsx create mode 100644 src/components/Transactions/constants.ts create mode 100644 src/components/Transactions/types.ts create mode 100644 src/hooks/useApplicationSetup.ts create mode 100644 src/hooks/useCalculateGasLimit.ts create mode 100644 src/hooks/useConfirmTransaction.ts create mode 100644 src/hooks/useHasEnoughBalance.ts create mode 100644 src/hooks/useNonceForDisplay.ts create mode 100644 src/hooks/useProviderSetup.ts create mode 100644 src/hooks/useSubmitTransaction.ts create mode 100644 src/hooks/useTransactionSetup.ts create mode 100644 src/resources/transactions/transactionSimulation.ts create mode 100644 src/utils/formatDate.ts diff --git a/globals.d.ts b/globals.d.ts index 29d1107dba2..068c5a86e74 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -109,4 +109,5 @@ declare module 'react-native-dotenv' { export const REACT_NATIVE_RUDDERSTACK_WRITE_KEY: string; export const RUDDERSTACK_DATA_PLANE_URL: string; export const SILENCE_EMOJI_WARNINGS: boolean; + export const MWP_ENCRYPTION_KEY: string; } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1c400b0f1c1..f28ca0bae70 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,6 +4,9 @@ PODS: - BVLinearGradient (2.8.3): - React-Core - CocoaAsyncSocket (7.6.5) + - CoinbaseWalletSDK/Client (1.1.0) + - CoinbaseWalletSDK/Host (1.1.0): + - CoinbaseWalletSDK/Client - DoubleConversion (1.1.6) - FasterImage (1.6.2): - FasterImage/Nuke (= 1.6.2) @@ -171,6 +174,9 @@ PODS: - MMKV (1.3.9): - MMKVCore (~> 1.3.9) - MMKVCore (1.3.9) + - mobile-wallet-protocol-host (0.1.7): + - CoinbaseWalletSDK/Host + - React-Core - MultiplatformBleAdapter (0.1.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -1826,6 +1832,7 @@ DEPENDENCIES: - GoogleUtilities - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libwebp + - "mobile-wallet-protocol-host (from `../node_modules/@coinbase/mobile-wallet-protocol-host`)" - nanopb - PanModal (from `https://github.com/rainbow-me/PanModal`, commit `ab97d74279ba28c2891b47a5dc767ed4dd7cf994`) - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`) @@ -1956,6 +1963,7 @@ SPEC REPOS: https://github.com/CocoaPods/Specs.git: - Branch - CocoaAsyncSocket + - CoinbaseWalletSDK - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -2007,6 +2015,8 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 + mobile-wallet-protocol-host: + :path: "../node_modules/@coinbase/mobile-wallet-protocol-host" PanModal: :commit: ab97d74279ba28c2891b47a5dc767ed4dd7cf994 :git: https://github.com/rainbow-me/PanModal @@ -2257,6 +2267,7 @@ SPEC CHECKSUMS: Branch: d99436c6f3d5b2529ba948d273e47e732830f207 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 + CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 @@ -2282,6 +2293,7 @@ SPEC CHECKSUMS: MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843 MMKV: 817ba1eea17421547e01e087285606eb270a8dcb MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 + mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d nanopb: 438bc412db1928dac798aa6fd75726007be04262 PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3 diff --git a/package.json b/package.json index 52c229cff23..37c442c9cde 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@bradgarropy/use-countdown": "1.4.1", "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", + "@coinbase/mobile-wallet-protocol-host": "0.1.7", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", "@ensdomains/eth-ens-namehash": "2.0.15", diff --git a/src/App.tsx b/src/App.tsx index 541791f9e3a..c2ea134d17b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,40 +1,31 @@ -import './languages'; +import '@/languages'; import * as Sentry from '@sentry/react-native'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; -import branch from 'react-native-branch'; - +import React, { useCallback, useEffect, useState } from 'react'; +import { AppRegistry, Dimensions, LogBox, StyleSheet, View } from 'react-native'; +import { MobileWalletProtocolProvider } from '@coinbase/mobile-wallet-protocol-host'; +import { DeeplinkHandler } from '@/components/DeeplinkHandler'; +import { AppStateChangeHandler } from '@/components/AppStateChangeHandler'; +import { useApplicationSetup } from '@/hooks/useApplicationSetup'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; import { connect, Provider as ReduxProvider } from 'react-redux'; import { RecoilRoot } from 'recoil'; -import PortalConsumer from './components/PortalConsumer'; -import ErrorBoundary from './components/error-boundary/ErrorBoundary'; -import { OfflineToast } from './components/toasts'; -import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug'; -import monitorNetwork from './debugging/network'; -import { Playground } from './design-system/playground/Playground'; -import handleDeeplink from './handlers/deeplinks'; -import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; -import RainbowContextWrapper from './helpers/RainbowContext'; -import isTestFlight from './helpers/isTestFlight'; +import PortalConsumer from '@/components/PortalConsumer'; +import ErrorBoundary from '@/components/error-boundary/ErrorBoundary'; +import { OfflineToast } from '@/components/toasts'; +import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from '@/config/debug'; +import monitorNetwork from '@/debugging/network'; +import { Playground } from '@/design-system/playground/Playground'; +import RainbowContextWrapper from '@/helpers/RainbowContext'; import * as keychain from '@/model/keychain'; -import { loadAddress } from './model/wallet'; -import { Navigation } from './navigation'; -import RoutesComponent from './navigation/Routes'; -import { PerformanceContextMap } from './performance/PerformanceContextMap'; -import { PerformanceTracking } from './performance/tracking'; -import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetrics'; -import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query'; -import store from './redux/store'; -import { walletConnectLoadState } from './redux/walletconnect'; -import { MainThemeProvider } from './theme/ThemeContext'; -import { branchListener } from './utils/branch'; -import { addressKey } from './utils/keychainConstants'; +import { Navigation } from '@/navigation'; +import { PersistQueryClientProvider, persistOptions, queryClient } from '@/react-query'; +import store, { AppDispatch, type AppState } from '@/redux/store'; +import { MainThemeProvider } from '@/theme/ThemeContext'; +import { addressKey } from '@/utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; -import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute'; -import Routes from '@/navigation/routesNames'; +import { InitialRouteContext } from '@/navigation/initialRoute'; import { Portal } from '@/react-native-cool-modals/Portal'; import { NotificationsHandler } from '@/notifications/NotificationsHandler'; import { analyticsV2 } from '@/analytics'; @@ -42,18 +33,15 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; -import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; -import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; -import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { initializeRemoteConfig } from '@/model/remoteConfig'; import { NavigationContainerRef } from '@react-navigation/native'; -import { RootStackParamList } from './navigation/types'; +import { RootStackParamList } from '@/navigation/types'; import { Address } from 'viem'; -import { IS_DEV } from './env'; -import { checkIdentifierOnLaunch } from './model/backup'; -import { prefetchDefaultFavorites } from './resources/favorites'; +import { IS_DEV } from '@/env'; +import { prefetchDefaultFavorites } from '@/resources/favorites'; +import Routes from '@/navigation/Routes'; if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -62,140 +50,49 @@ if (IS_DEV) { enableScreens(); -const containerStyle = { flex: 1 }; +const sx = StyleSheet.create({ + container: { + flex: 1, + }, +}); interface AppProps { walletReady: boolean; } function App({ walletReady }: AppProps) { - const [appState, setAppState] = useState(AppState.currentState); - const [initialRoute, setInitialRoute] = useState(null); - const eventSubscription = useRef | null>(null); - const branchListenerRef = useRef | null>(null); - const navigatorRef = useRef | null>(null); - - const setupDeeplinking = useCallback(async () => { - const initialUrl = await Linking.getInitialURL(); - - branchListenerRef.current = await branchListener(url => { - logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); - try { - handleDeeplink(url, initialRoute); - } catch (error) { - if (error instanceof Error) { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: error.message, - url, - }); - } else { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: 'Unknown error', - url, - }); - } - } - }); - - if (initialUrl) { - logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); - branch.openURL(initialUrl); - } - }, [initialRoute]); - - const identifyFlow = useCallback(async () => { - const address = await loadAddress(); - if (address) { - setTimeout(() => { - InteractionManager.runAfterInteractions(() => { - handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); - }); - }, 10_000); - - InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); - } - - setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - }, []); - - const handleAppStateChange = useCallback( - (nextAppState: AppStateStatus) => { - if (appState === 'background' && nextAppState === 'active') { - store.dispatch(walletConnectLoadState()); - } - setAppState(nextAppState); - analyticsV2.track(analyticsV2.event.appStateChange, { - category: 'app state', - label: nextAppState, - }); - }, - [appState] - ); + const { initialRoute } = useApplicationSetup(); const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { - navigatorRef.current = ref; Navigation.setTopLevelNavigator(ref); }, []); - useEffect(() => { - if (!__DEV__ && isTestFlight) { - logger.debug(`[App]: Test flight usage - ${isTestFlight}`); - } - identifyFlow(); - eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); - initWalletConnectListeners(); - - const p1 = analyticsV2.initializeRudderstack(); - const p2 = setupDeeplinking(); - const p3 = saveFCMToken(); - Promise.all([p1, p2, p3]).then(() => { - initWalletConnectPushNotifications(); - PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); - analyticsV2.track(analyticsV2.event.applicationDidMount); - }); - - return () => { - eventSubscription.current?.remove(); - branchListenerRef.current?.(); - }; - }, []); - - useEffect(() => { - if (walletReady) { - logger.debug(`[App]: ✅ Wallet ready!`); - runWalletBackupStatusChecks(); - } - }, [walletReady]); - return ( - + {initialRoute && ( - + )} + + ); } -export type AppStore = typeof store; -export type RootState = ReturnType; -export type AppDispatch = AppStore['dispatch']; - -const AppWithRedux = connect(state => ({ +const AppWithRedux = connect(state => ({ walletReady: state.appState.walletReady, }))(App); function Root() { - const [initializing, setInitializing] = React.useState(true); + const [initializing, setInitializing] = useState(true); - React.useEffect(() => { + useEffect(() => { async function initializeApplication() { await initializeRemoteConfig(); await migrate(); @@ -301,19 +198,21 @@ function Root() { prefetchDefaultFavorites(); }} > - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/components/AppStateChangeHandler.tsx b/src/components/AppStateChangeHandler.tsx new file mode 100644 index 00000000000..ee8b29d2ebb --- /dev/null +++ b/src/components/AppStateChangeHandler.tsx @@ -0,0 +1,38 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { AppState, AppStateStatus, Linking } from 'react-native'; +import { analyticsV2 } from '@/analytics'; +import store from '@/redux/store'; +import { walletConnectLoadState } from '@/redux/walletconnect'; + +type AppStateChangeHandlerProps = { + walletReady: boolean; +}; + +export function AppStateChangeHandler({ walletReady }: AppStateChangeHandlerProps) { + const [appState, setAppState] = useState(AppState.currentState); + const eventSubscription = useRef | null>(null); + + const handleAppStateChange = useCallback( + (nextAppState: AppStateStatus) => { + if (appState === 'background' && nextAppState === 'active') { + store.dispatch(walletConnectLoadState()); + } + setAppState(nextAppState); + analyticsV2.track(analyticsV2.event.appStateChange, { + category: 'app state', + label: nextAppState, + }); + }, + [appState] + ); + + useEffect(() => { + if (!walletReady) return; + + eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + + return () => eventSubscription.current?.remove(); + }, [handleAppStateChange]); + + return null; +} diff --git a/src/components/DeeplinkHandler.tsx b/src/components/DeeplinkHandler.tsx new file mode 100644 index 00000000000..e34f7ef4e8b --- /dev/null +++ b/src/components/DeeplinkHandler.tsx @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { Linking } from 'react-native'; +import branch from 'react-native-branch'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import handleDeeplink from '@/handlers/deeplinks'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { logger, RainbowError } from '@/logger'; +import { branchListener } from '@/utils/branch'; + +type DeeplinkHandlerProps = { + initialRoute: InitialRoute; + walletReady: boolean; +}; + +export function DeeplinkHandler({ initialRoute, walletReady }: DeeplinkHandlerProps) { + const branchListenerRef = useRef | null>(null); + const { handleRequestUrl, sendFailureToClient } = useMobileWalletProtocolHost(); + + const setupDeeplinking = useCallback(async () => { + const initialUrl = await Linking.getInitialURL(); + + branchListenerRef.current = await branchListener(async url => { + logger.debug(`[App]: Branch listener called`, {}, logger.DebugContext.deeplinks); + + try { + handleDeeplink({ + url, + initialRoute, + handleRequestUrl, + sendFailureToClient, + }); + } catch (error) { + if (error instanceof Error) { + logger.error(new RainbowError('Error opening deeplink'), { + message: error.message, + url, + }); + } else { + logger.error(new RainbowError('Error opening deeplink'), { + message: 'Unknown error', + url, + }); + } + } + }); + + if (initialUrl) { + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); + branch.openURL(initialUrl); + } + }, [handleRequestUrl, initialRoute, sendFailureToClient]); + + useEffect(() => { + if (!walletReady) return; + + setupDeeplinking(); + return () => { + if (branchListenerRef.current) { + branchListenerRef.current(); + } + }; + }, [setupDeeplinking, walletReady]); + + return null; +} diff --git a/src/components/FadeGradient.tsx b/src/components/FadeGradient.tsx new file mode 100644 index 00000000000..88fdade67a7 --- /dev/null +++ b/src/components/FadeGradient.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated from 'react-native-reanimated'; + +import { Box, globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +type FadeGradientProps = { side: 'top' | 'bottom'; style?: StyleProp>> }; + +export const FadeGradient = ({ side, style }: FadeGradientProps) => { + const { colors, isDarkMode } = useTheme(); + + const isTop = side === 'top'; + const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; + const transparentColor = colors.alpha(solidColor, 0); + + return ( + + + + ); +}; diff --git a/src/components/FadedScrollCard.tsx b/src/components/FadedScrollCard.tsx new file mode 100644 index 00000000000..bf8a1ed9c39 --- /dev/null +++ b/src/components/FadedScrollCard.tsx @@ -0,0 +1,281 @@ +import React, { useCallback, useState } from 'react'; +import { TouchableWithoutFeedback } from 'react-native'; +import Animated, { + Easing, + SharedValue, + interpolate, + interpolateColor, + measure, + runOnJS, + runOnUI, + useAnimatedReaction, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +import { globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +import { useDimensions } from '@/hooks'; +import { FadeGradient } from '@/components/FadeGradient'; + +const COLLAPSED_CARD_HEIGHT = 56; +const MAX_CARD_HEIGHT = 176; + +const CARD_BORDER_WIDTH = 1.5; + +const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +type FadedScrollCardProps = { + cardHeight: SharedValue; + children: React.ReactNode; + contentHeight: SharedValue; + expandedCardBottomInset?: number; + expandedCardTopInset?: number; + initialScrollEnabled?: boolean; + isExpanded: boolean; + onPressCollapsedCard?: () => void; + skipCollapsedState?: boolean; +}; + +export const FadedScrollCard = ({ + cardHeight, + children, + contentHeight, + expandedCardBottomInset = 120, + expandedCardTopInset = 120, + initialScrollEnabled, + isExpanded, + onPressCollapsedCard, + skipCollapsedState, +}: FadedScrollCardProps) => { + const { height: deviceHeight, width: deviceWidth } = useDimensions(); + const { isDarkMode } = useTheme(); + + const cardRef = useAnimatedRef(); + + const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); + const [isFullyExpanded, setIsFullyExpanded] = useState(false); + + const yPosition = useSharedValue(0); + + const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); + + const containerStyle = useAnimatedStyle(() => { + return { + height: + cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState + ? interpolate( + cardHeight.value, + [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], + [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], + 'clamp' + ) + : undefined, + zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), + }; + }); + + const backdropStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const cardStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); + + const outputRange = [0, 0]; + + const yPos = -yPosition.value; + const offset = + deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight - (yPosition.value + expandedCardHeight); + + if (yPos + expandedCardTopInset + offset >= deviceHeight - expandedCardBottomInset) { + outputRange.push(0); + } else { + outputRange.push(deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight); + } + + return { + borderColor: interpolateColor( + cardHeight.value, + [0, MAX_CARD_HEIGHT, expandedCardHeight], + isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] + ), + height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, + position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', + transform: [ + { + translateY: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, expandedCardHeight], outputRange), + }, + ], + }; + }); + + const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { + return { + transform: skipCollapsedState + ? undefined + : [ + { + translateY: interpolate( + cardHeight.value, + [ + 0, + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT + ? MAX_CARD_HEIGHT + : contentHeight.value + CARD_BORDER_WIDTH * 2, + maxExpandedHeight, + ], + [-2, -2, 0, 0] + ), + }, + ], + }; + }); + + const shadowStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const handleContentSizeChange = useCallback( + (width: number, height: number) => { + contentHeight.value = Math.round(height); + }, + [contentHeight] + ); + + const handleOnLayout = useCallback(() => { + runOnUI(() => { + if (cardHeight.value === MAX_CARD_HEIGHT) { + const measurement = measure(cardRef); + if (measurement === null) { + return; + } + if (yPosition.value !== measurement.pageY) { + yPosition.value = measurement.pageY; + } + } + })(); + }, [cardHeight, cardRef, yPosition]); + + useAnimatedReaction( + () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), + ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { + if ( + isFullyExpanded !== previous?.isFullyExpanded || + isExpanded !== previous?.isExpanded || + contentHeight !== previous?.contentHeight + ) { + if (isFullyExpanded) { + const expandedCardHeight = + contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; + if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { + cardHeight.value = withTiming(expandedCardHeight, timingConfig); + } else { + runOnJS(setIsFullyExpanded)(false); + } + } else if (isExpanded) { + cardHeight.value = withTiming( + contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, + timingConfig + ); + } else { + cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); + } + + const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); + runOnJS(setScrollEnabled)(enableScroll); + } + } + ); + + return ( + + { + if (isFullyExpanded) { + setIsFullyExpanded(false); + } + }} + pointerEvents={isFullyExpanded ? 'auto' : 'none'} + style={[ + { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + height: deviceHeight * 3, + left: -deviceWidth * 0.5, + position: 'absolute', + top: -deviceHeight, + width: deviceWidth * 2, + zIndex: -1, + }, + backdropStyle, + ]} + /> + + + + { + if (!isFullyExpanded) { + setIsFullyExpanded(true); + } else setIsFullyExpanded(false); + } + } + > + {children} + + + + + + + + ); +}; diff --git a/src/components/MobileWalletProtocolListener.tsx b/src/components/MobileWalletProtocolListener.tsx new file mode 100644 index 00000000000..1c5dd960d4e --- /dev/null +++ b/src/components/MobileWalletProtocolListener.tsx @@ -0,0 +1,51 @@ +import { useEffect, useRef } from 'react'; +import { addDiagnosticLogListener, getAndroidIntentUrl, useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { handleMobileWalletProtocolRequest } from '@/utils/requestNavigationHandlers'; +import { logger, RainbowError } from '@/logger'; +import { IS_ANDROID, IS_DEV } from '@/env'; + +export const MobileWalletProtocolListener = () => { + const { message, handleRequestUrl, sendFailureToClient, ...mwpProps } = useMobileWalletProtocolHost(); + const lastMessageUuidRef = useRef(null); + + useEffect(() => { + if (message && lastMessageUuidRef.current !== message.uuid) { + lastMessageUuidRef.current = message.uuid; + try { + handleMobileWalletProtocolRequest({ request: message, ...mwpProps }); + } catch (error) { + logger.error(new RainbowError('Error handling Mobile Wallet Protocol request'), { + error, + }); + } + } + }, [message, mwpProps]); + + useEffect(() => { + if (IS_DEV) { + const removeListener = addDiagnosticLogListener(event => { + logger.debug(`[MobileWalletProtocolListener]: Diagnostic log event: ${JSON.stringify(event, null, 2)}`); + }); + + return () => removeListener(); + } + }, []); + + useEffect(() => { + if (IS_ANDROID) { + (async function handleAndroidIntent() { + const intentUrl = await getAndroidIntentUrl(); + if (intentUrl) { + const response = await handleRequestUrl(intentUrl); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + } + })(); + } + }, [handleRequestUrl, sendFailureToClient]); + + return null; +}; diff --git a/src/components/Transactions/TransactionDetailsCard.tsx b/src/components/Transactions/TransactionDetailsCard.tsx new file mode 100644 index 00000000000..307af8def77 --- /dev/null +++ b/src/components/Transactions/TransactionDetailsCard.tsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import * as i18n from '@/languages'; +import Animated, { interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { abbreviations, ethereumUtils } from '@/utils'; +import { TransactionSimulationMeta } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/networks/types'; + +import { getNetworkObject } from '@/networks'; +import { TransactionDetailsRow } from '@/components/Transactions/TransactionDetailsRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { IconContainer } from '@/components/Transactions/TransactionIcons'; +import { formatDate } from '@/utils/formatDate'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; + +interface TransactionDetailsCardProps { + chainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + meta: TransactionSimulationMeta | undefined; + methodName: string; + noChanges: boolean; + nonce: string | undefined; + toAddress: string; +} + +export const TransactionDetailsCard = ({ + chainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + meta, + methodName, + noChanges, + nonce, + toAddress, +}: TransactionDetailsCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const [isExpanded, setIsExpanded] = useState(false); + + const currentNetwork = getNetworkObject({ chainId }); + + const listStyle = useAnimatedStyle(() => ({ + opacity: interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; + + const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); + const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; + const showTransferToRow = !!meta?.transferTo?.address; + // Hide DetailsCard if balance is insufficient once loaded + if (!isLoading && isBalanceEnough === false) { + return <>; + } + return ( + setIsExpanded(true)} + > + + + + + + 􁙠 + + + + {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} + + + + + + {} + {!!(meta?.to?.address || toAddress || showTransferToRow) && ( + + ethereumUtils.openAddressInBlockExplorer({ + address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', + chainId, + }) + } + value={ + meta?.to?.name || + abbreviations.address(meta?.to?.address || toAddress, 4, 6) || + meta?.to?.address || + toAddress || + meta?.transferTo?.address || + '' + } + /> + )} + {showFunctionRow && } + {!!meta?.to?.sourceCodeStatus && } + {!!meta?.to?.created && } + {nonce && } + + + + + ); +}; diff --git a/src/components/Transactions/TransactionDetailsRow.tsx b/src/components/Transactions/TransactionDetailsRow.tsx new file mode 100644 index 00000000000..e32b6cdcd7a --- /dev/null +++ b/src/components/Transactions/TransactionDetailsRow.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { ChainImage } from '@/components/coin-icon/ChainImage'; +import { Box, Inline, Text } from '@/design-system'; + +import { DetailIcon, DetailBadge, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { SMALL_CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { DetailType, DetailInfo } from '@/components/Transactions/types'; +import { ChainId } from '@/networks/types'; + +interface TransactionDetailsRowProps { + chainId?: ChainId; + detailType: DetailType; + onPress?: () => void; + value: string; +} + +export const TransactionDetailsRow = ({ chainId, detailType, onPress, value }: TransactionDetailsRowProps) => { + const detailInfo: DetailInfo = infoForDetailType[detailType]; + + return ( + + + + + + {detailInfo.label} + + + + {detailType === 'function' && } + {detailType === 'sourceCodeVerification' && ( + + )} + {detailType === 'chain' && chainId && } + {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( + + {value} + + )} + {(detailType === 'contract' || detailType === 'to') && ( + + + + + 􀂄 + + + + + )} + + + + ); +}; + +const infoForDetailType: { [key: string]: DetailInfo } = { + chain: { + icon: '􀤆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), + }, + contract: { + icon: '􀉆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), + }, + to: { + icon: '􀉩', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), + }, + function: { + icon: '􀡅', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), + }, + sourceCodeVerification: { + icon: '􀕹', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), + }, + dateCreated: { + icon: '􀉉', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), + }, + nonce: { + icon: '􀆃', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), + }, +}; diff --git a/src/components/Transactions/TransactionIcons.tsx b/src/components/Transactions/TransactionIcons.tsx new file mode 100644 index 00000000000..53ef21fcd6e --- /dev/null +++ b/src/components/Transactions/TransactionIcons.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { AnimatePresence, MotiView } from 'moti'; + +import { Bleed, Box, Text, globalColors, useForegroundColor } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; +import { infoForEventType, motiTimingConfig } from '@/components/Transactions/constants'; + +import { useTheme } from '@/theme'; +import { DetailInfo, EventInfo, EventType } from '@/components/Transactions/types'; + +export const EventIcon = ({ eventType }: { eventType: EventType }) => { + const eventInfo: EventInfo = infoForEventType[eventType]; + + const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; + const isWarningIcon = + eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; + + return ( + + {!hideInnerFill && ( + + )} + + {eventInfo.icon} + + + ); +}; + +export const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { + return ( + + + {detailInfo.icon} + + + ); +}; + +export const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { + const { colors, isDarkMode } = useTheme(); + const separatorTertiary = useForegroundColor('separatorTertiary'); + + const infoForBadgeType: { + [key: string]: { + backgroundColor: string; + borderColor: string; + label?: string; + text: TextColor; + textOpacity?: number; + }; + } = { + function: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + text: 'labelQuaternary', + }, + unknown: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + label: 'Unknown', + text: 'labelQuaternary', + }, + unverified: { + backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, + borderColor: colors.alpha(colors.red, 0.02), + label: 'Unverified', + text: 'red', + textOpacity: 0.76, + }, + verified: { + backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, + borderColor: colors.alpha(colors.green, 0.02), + label: 'Verified', + text: 'green', + textOpacity: 0.76, + }, + }; + + return ( + + + + {infoForBadgeType[type].label || value} + + + + ); +}; + +export const VerifiedBadge = () => { + return ( + + + + + 􀇻 + + + + ); +}; + +export const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { + return ( + + {visible && ( + + + + + + 􀁣 + + + + + )} + + ); +}; + +export const IconContainer = ({ + children, + hitSlop, + opacity, + size = 20, +}: { + children: React.ReactNode; + hitSlop?: number; + opacity?: number; + size?: number; +}) => { + // Prevent wide icons from being clipped + const extraHorizontalSpace = 4; + + return ( + + + {children} + + + ); +}; diff --git a/src/components/Transactions/TransactionMessageCard.tsx b/src/components/Transactions/TransactionMessageCard.tsx new file mode 100644 index 00000000000..3e946bf6d6d --- /dev/null +++ b/src/components/Transactions/TransactionMessageCard.tsx @@ -0,0 +1,113 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; +import { useSharedValue } from 'react-native-reanimated'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; + +import { useClipboard } from '@/hooks'; +import { logger } from '@/logger'; +import { isSignTypedData } from '@/utils/signingMethods'; + +import { RPCMethod } from '@/walletConnect/types'; +import { sanitizeTypedData } from '@/utils/signingUtils'; +import { + estimateMessageHeight, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { AnimatedCheckmark, IconContainer } from '@/components/Transactions/TransactionIcons'; + +type TransactionMessageCardProps = { + expandedCardBottomInset: number; + message: string; + method: RPCMethod; +}; + +export const TransactionMessageCard = ({ expandedCardBottomInset, message, method }: TransactionMessageCardProps) => { + const { setClipboard } = useClipboard(); + const [didCopy, setDidCopy] = useState(false); + + let displayMessage = message; + if (isSignTypedData(method)) { + try { + const parsedMessage = JSON.parse(message); + const sanitizedMessage = sanitizeTypedData(parsedMessage); + displayMessage = sanitizedMessage; + } catch (error) { + logger.warn(`[TransactionMessageCard]: Error parsing signed typed data for ${method}`, { + error, + }); + } + + displayMessage = JSON.stringify(displayMessage, null, 4); + } + + const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); + + const cardHeight = useSharedValue( + estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 + ); + const contentHeight = useSharedValue(estimatedMessageHeight); + + const handleCopyPress = useCallback( + (message: string) => { + if (didCopy) return; + setClipboard(message); + setDidCopy(true); + const copyTimer = setTimeout(() => { + setDidCopy(false); + }, 2000); + return () => clearTimeout(copyTimer); + }, + [didCopy, setClipboard] + ); + + return ( + MAX_CARD_HEIGHT} + isExpanded + skipCollapsedState + > + + + + + + 􀙤 + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} + + + + handleCopyPress(message)}> + + + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} + + + + + + + + + {displayMessage} + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulatedEventRow.tsx b/src/components/Transactions/TransactionSimulatedEventRow.tsx new file mode 100644 index 00000000000..ce4d26052eb --- /dev/null +++ b/src/components/Transactions/TransactionSimulatedEventRow.tsx @@ -0,0 +1,109 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import { Image, PixelRatio } from 'react-native'; + +import { Bleed, Box, Inline, Text } from '@/design-system'; + +import { useTheme } from '@/theme'; +import { TransactionAssetType, TransactionSimulationAsset } from '@/graphql/__generated__/metadataPOST'; +import { Network } from '@/networks/types'; +import { convertAmountToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; + +import { useAccountSettings } from '@/hooks'; + +import { maybeSignUri } from '@/handlers/imgix'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; +import { EventInfo, EventType } from '@/components/Transactions/types'; +import { infoForEventType, CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { EventIcon } from '@/components/Transactions/TransactionIcons'; +import { ethereumUtils } from '@/utils'; + +type TransactionSimulatedEventRowProps = { + amount: string | 'unlimited'; + asset: TransactionSimulationAsset | undefined; + eventType: EventType; + price?: number | undefined; +}; + +export const TransactionSimulatedEventRow = ({ amount, asset, eventType, price }: TransactionSimulatedEventRowProps) => { + const theme = useTheme(); + const { nativeCurrency } = useAccountSettings(); + + const chainId = ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet); + + const { data: externalAsset } = useExternalToken({ + address: asset?.assetCode || '', + chainId, + currency: nativeCurrency, + }); + + const eventInfo: EventInfo = infoForEventType[eventType]; + + const formattedAmount = useMemo(() => { + if (!asset) return; + + const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; + const assetDisplayName = + asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; + const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; + + const displayAmount = + asset?.decimals === 0 + ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` + : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; + + const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; + + return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; + }, [amount, asset, eventInfo?.amountPrefix]); + + const url = maybeSignUri(asset?.iconURL, { + fm: 'png', + w: 16 * PixelRatio.get(), + }); + + const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; + + const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); + + return ( + + + + + + + {eventInfo.label} + + {showUSD && ( + + {formattedPrice} + + )} + + + + + {asset?.type !== TransactionAssetType.Nft ? ( + + ) : ( + + )} + + + {formattedAmount} + + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulationCard.tsx b/src/components/Transactions/TransactionSimulationCard.tsx new file mode 100644 index 00000000000..e491aeaf6a7 --- /dev/null +++ b/src/components/Transactions/TransactionSimulationCard.tsx @@ -0,0 +1,314 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import Animated, { + interpolate, + useAnimatedReaction, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { TransactionErrorType, TransactionSimulationResult, TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; + +import { getNetworkObject } from '@/networks'; +import { isEmpty } from 'lodash'; +import { TransactionSimulatedEventRow } from '@/components/Transactions/TransactionSimulatedEventRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { EventIcon, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, + rotationConfig, + timingConfig, +} from '@/components/Transactions/constants'; +import { ChainId } from '@/networks/types'; + +interface TransactionSimulationCardProps { + chainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + txSimulationApiError: unknown; + isPersonalSignRequest: boolean; + noChanges: boolean; + simulation: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; + walletBalance: { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; + }; +} + +export const TransactionSimulationCard = ({ + chainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + txSimulationApiError, + isPersonalSignRequest, + noChanges, + simulation, + simulationError, + simulationScanResult, + walletBalance, +}: TransactionSimulationCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const spinnerRotation = useSharedValue(0); + + const listStyle = useAnimatedStyle(() => ({ + opacity: noChanges + ? withTiming(1, timingConfig) + : interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const spinnerStyle = useAnimatedStyle(() => { + return { + transform: [{ rotate: `${spinnerRotation.value}deg` }], + }; + }); + + useAnimatedReaction( + () => ({ isLoading, isPersonalSignRequest }), + ({ isLoading, isPersonalSignRequest }, previous = { isLoading: false, isPersonalSignRequest: false }) => { + if (isLoading && !previous?.isLoading) { + spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); + } else if ( + (!isLoading && previous?.isLoading) || + (isPersonalSignRequest && !previous?.isPersonalSignRequest && previous?.isLoading) + ) { + spinnerRotation.value = withTiming(360, timingConfig); + } + }, + [isLoading, isPersonalSignRequest] + ); + const renderSimulationEventRows = useMemo(() => { + if (isBalanceEnough === false) return null; + + return ( + <> + {simulation?.approvals?.map(change => { + return ( + + ); + })} + {simulation?.out?.map(change => { + return ( + + ); + })} + {simulation?.in?.map(change => { + return ( + + ); + })} + + ); + }, [isBalanceEnough, simulation]); + + const titleColor: TextColor = useMemo(() => { + if (isLoading) { + return 'label'; + } + if (isBalanceEnough === false) { + return 'blue'; + } + if (noChanges || isPersonalSignRequest || txSimulationApiError) { + return 'labelQuaternary'; + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return 'orange'; + } + if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { + return 'red'; + } + return 'label'; + }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, isPersonalSignRequest, txSimulationApiError]); + + const titleText = useMemo(() => { + if (isLoading) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); + } + if (isBalanceEnough === false) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); + } + if (txSimulationApiError || isPersonalSignRequest) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); + } + if (simulationScanResult === TransactionScanResultType.Malicious) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); + } + if (noChanges) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); + } + if (simulationError) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); + } + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); + }, [ + isBalanceEnough, + isLoading, + noChanges, + simulationError, + simulationScanResult, + isPersonalSignRequest, + txSimulationApiError, + walletBalance?.symbol, + ]); + + const isExpanded = useMemo(() => { + if (isLoading || isPersonalSignRequest) { + return false; + } + const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; + return shouldExpandOnLoad; + }, [isBalanceEnough, isLoading, isPersonalSignRequest, noChanges, simulation, simulationError]); + + return ( + + + + + {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( + + ) : ( + + {!isLoading && noChanges && !isPersonalSignRequest ? ( + + {/* The extra space avoids icon clipping */} + {'􀻾 '} + + ) : ( + + + 􀬨 + + + )} + + )} + + {titleText} + + + {/* TODO: Unhide once we add explainer sheets */} + {/* + + + + + 􀁜 + + + + + */} + + + + {isBalanceEnough === false ? ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { + symbol: walletBalance?.symbol, + network: getNetworkObject({ chainId }).name, + })} + + ) : ( + <> + {isPersonalSignRequest && ( + + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} + + + )} + {txSimulationApiError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.tx_api_error)} + + )} + {simulationError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} + + )} + {simulationScanResult === TransactionScanResultType.Warning && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} + + )} + {simulationScanResult === TransactionScanResultType.Malicious && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} + + )} + + )} + {renderSimulationEventRows} + + + + + ); +}; diff --git a/src/components/Transactions/constants.ts b/src/components/Transactions/constants.ts new file mode 100644 index 00000000000..79e6e0d8df5 --- /dev/null +++ b/src/components/Transactions/constants.ts @@ -0,0 +1,121 @@ +import * as i18n from '@/languages'; +import { Screens } from '@/state/performance/operations'; +import { safeAreaInsetValues } from '@/utils'; +import { TransitionConfig } from 'moti'; +import { Easing } from 'react-native-reanimated'; +import { EventInfo } from '@/components/Transactions/types'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; + +export const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; +export const GAS_BUTTON_SPACE = + 30 + // GasSpeedButton height + 24; // Between GasSpeedButton and bottom of sheet + +export const EXPANDED_CARD_BOTTOM_INSET = + SCREEN_BOTTOM_INSET + + 24 + // Between bottom of sheet and bottom of Cancel/Confirm + 52 + // Cancel/Confirm height + 24 + // Between Cancel/Confirm and wallet avatar row + 44 + // Wallet avatar row height + 24; // Between wallet avatar row and bottom of expandable area + +export const COLLAPSED_CARD_HEIGHT = 56; +export const MAX_CARD_HEIGHT = 176; + +export const CARD_ROW_HEIGHT = 12; +export const SMALL_CARD_ROW_HEIGHT = 10; +export const CARD_BORDER_WIDTH = 1.5; + +export const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; + +export const rotationConfig = { + duration: 2100, + easing: Easing.linear, +}; + +export const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +export const motiTimingConfig: TransitionConfig = { + duration: 225, + easing: Easing.bezier(0.2, 0, 0, 1), + type: 'timing', +}; + +export const SCREEN_FOR_REQUEST_SOURCE = { + [RequestSource.BROWSER]: Screens.DAPP_BROWSER, + [RequestSource.WALLETCONNECT]: Screens.WALLETCONNECT, + [RequestSource.MOBILE_WALLET_PROTOCOL]: Screens.MOBILE_WALLET_PROTOCOL, +}; + +export const CHARACTERS_PER_LINE = 40; +export const LINE_HEIGHT = 11; +export const LINE_GAP = 9; + +export const estimateMessageHeight = (message: string) => { + const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); + const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; + + return messageHeight; +}; + +export const infoForEventType: { [key: string]: EventInfo } = { + send: { + amountPrefix: '- ', + icon: '􀁷', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), + textColor: 'red', + }, + receive: { + amountPrefix: '+ ', + icon: '􀁹', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), + textColor: 'green', + }, + approve: { + amountPrefix: '', + icon: '􀎤', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), + textColor: 'label', + }, + revoke: { + amountPrefix: '', + icon: '􀎠', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), + textColor: 'label', + }, + failed: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), + textColor: 'red', + }, + insufficientBalance: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'blue', + label: '', + textColor: 'blue', + }, + MALICIOUS: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: '', + textColor: 'red', + }, + WARNING: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'orange', + label: '', + textColor: 'orange', + }, +}; diff --git a/src/components/Transactions/types.ts b/src/components/Transactions/types.ts new file mode 100644 index 00000000000..ce9eaa8b3c3 --- /dev/null +++ b/src/components/Transactions/types.ts @@ -0,0 +1,18 @@ +import { TextColor } from '@/design-system/color/palettes'; + +export type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; + +export type EventInfo = { + amountPrefix: string; + icon: string; + iconColor: TextColor; + label: string; + textColor: TextColor; +}; + +export type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; + +export type DetailInfo = { + icon: string; + label: string; +}; diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 164ae1c436a..2df864afce7 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -9,7 +9,7 @@ export function isNativeAsset(address: string, chainId: ChainId) { return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } -export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: any, provider: any) { +export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: ChainId, provider: any) { // Check if it's the native chain asset if (isNativeAsset(address, chainId)) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index c8914e691bd..33316a8c0a0 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -19,6 +19,13 @@ import { FiatProviderName } from '@/entities/f2c'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { queryClient } from '@/react-query'; import { pointsReferralCodeQueryKey } from '@/resources/points'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { InitialRoute } from '@/navigation/initialRoute'; + +interface DeeplinkHandlerProps extends Pick, 'handleRequestUrl' | 'sendFailureToClient'> { + url: string; + initialRoute: InitialRoute; +} /* * You can test these deeplinks with the following command: @@ -26,7 +33,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; * `xcrun simctl openurl booted "https://link.rainbow.me/0x123"` */ -export default async function handleDeeplink(url: string, initialRoute: any = null) { +export default async function handleDeeplink({ url, initialRoute, handleRequestUrl, sendFailureToClient }: DeeplinkHandlerProps) { if (!url) { logger.warn(`[handleDeeplink]: No url provided`); return; @@ -200,6 +207,16 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu break; } + case 'wsegue': { + const response = await handleRequestUrl(url); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + break; + } + default: { logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; diff --git a/src/helpers/accountInfo.ts b/src/helpers/accountInfo.ts index d694523cb71..be67dab7d58 100644 --- a/src/helpers/accountInfo.ts +++ b/src/helpers/accountInfo.ts @@ -1,3 +1,4 @@ +import { RainbowAccount } from '@/model/wallet'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '../helpers/emojiHandler'; import { address } from '../utils/abbreviations'; import { addressHashedEmoji, isValidImagePath } from '../utils/profileUtils'; @@ -17,8 +18,10 @@ export function getAccountProfileInfo(selectedWallet: any, walletNames: any, acc const accountENS = walletNames?.[accountAddress]; - const selectedAccount = selectedWallet.addresses?.find((account: any) => account.address === accountAddress); - + const lowerCaseAccountAddress = accountAddress.toLowerCase(); + const selectedAccount = selectedWallet.addresses?.find( + (account: RainbowAccount) => account.address?.toLowerCase() === lowerCaseAccountAddress + ); if (!selectedAccount) { return {}; } diff --git a/src/helpers/dappNameHandler.ts b/src/helpers/dappNameHandler.ts index b679aebd122..a3c06dd4c66 100644 --- a/src/helpers/dappNameHandler.ts +++ b/src/helpers/dappNameHandler.ts @@ -5,6 +5,9 @@ export const getDappHostname = memoFn(url => { // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. const urlObject = new URL(url); let hostname; + if (!urlObject.hostname) { + return null; + } const subdomains = urlObject.hostname.split('.'); if (subdomains.length === 2) { hostname = urlObject.hostname; diff --git a/src/helpers/findWalletWithAccount.ts b/src/helpers/findWalletWithAccount.ts index 109371d2b82..21de1be494e 100644 --- a/src/helpers/findWalletWithAccount.ts +++ b/src/helpers/findWalletWithAccount.ts @@ -1,11 +1,12 @@ -import { RainbowWallet } from '@/model/wallet'; +import { RainbowAccount, RainbowWallet } from '@/model/wallet'; export function findWalletWithAccount(wallets: { [key: string]: RainbowWallet }, accountAddress: string): RainbowWallet | undefined { const sortedKeys = Object.keys(wallets).sort(); let walletWithAccount: RainbowWallet | undefined; + const lowerCaseAccountAddress = accountAddress.toLowerCase(); sortedKeys.forEach(key => { const wallet = wallets[key]; - const found = wallet.addresses?.find((account: any) => account.address === accountAddress); + const found = wallet.addresses?.find((account: RainbowAccount) => account.address?.toLowerCase() === lowerCaseAccountAddress); if (found) { walletWithAccount = wallet; } diff --git a/src/hooks/useApplicationSetup.ts b/src/hooks/useApplicationSetup.ts new file mode 100644 index 00000000000..0ce196b74b8 --- /dev/null +++ b/src/hooks/useApplicationSetup.ts @@ -0,0 +1,56 @@ +import { useCallback, useEffect, useState } from 'react'; +import { logger, RainbowError } from '@/logger'; +import { InteractionManager } from 'react-native'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { ReviewPromptAction } from '@/storage/schema'; +import { loadAddress } from '@/model/wallet'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { PerformanceContextMap } from '@/performance/PerformanceContextMap'; +import Routes from '@/navigation/routesNames'; +import { checkIdentifierOnLaunch } from '@/model/backup'; +import { analyticsV2 } from '@/analytics'; +import { saveFCMToken } from '@/notifications/tokens'; +import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; +import isTestFlight from '@/helpers/isTestFlight'; +import { PerformanceTracking } from '@/performance/tracking'; +import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; + +export function useApplicationSetup() { + const [initialRoute, setInitialRoute] = useState(null); + + const identifyFlow = useCallback(async () => { + const address = await loadAddress(); + if (address) { + setTimeout(() => { + InteractionManager.runAfterInteractions(() => { + handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); + }); + }, 10_000); + + InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); + } + + setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + }, []); + + useEffect(() => { + if (!IS_DEV && isTestFlight) { + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); + } + identifyFlow(); + initWalletConnectListeners(); + + Promise.all([analyticsV2.initializeRudderstack(), saveFCMToken()]) + .catch(error => { + logger.error(new RainbowError('Failed to initialize rudderstack or save FCM token', error)); + }) + .finally(() => { + initWalletConnectPushNotifications(); + PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); + analyticsV2.track(analyticsV2.event.applicationDidMount); + }); + }, [identifyFlow]); + + return { initialRoute }; +} diff --git a/src/hooks/useCalculateGasLimit.ts b/src/hooks/useCalculateGasLimit.ts new file mode 100644 index 00000000000..1ae0800caae --- /dev/null +++ b/src/hooks/useCalculateGasLimit.ts @@ -0,0 +1,74 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { estimateGas, web3Provider, toHex } from '@/handlers/web3'; +import { convertHexToString, omitFlatten } from '@/helpers/utilities'; +import { logger, RainbowError } from '@/logger'; +import { getNetworkObject } from '@/networks'; +import { ethereumUtils } from '@/utils'; +import { hexToNumber, isHex } from 'viem'; +import { isEmpty } from 'lodash'; +import { InteractionManager } from 'react-native'; +import { GasFeeParamsBySpeed } from '@/entities'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { useGas } from '@/hooks'; +import { ChainId } from '@/networks/types'; + +type CalculateGasLimitProps = { + isMessageRequest: boolean; + gasFeeParamsBySpeed: GasFeeParamsBySpeed; + provider: StaticJsonRpcProvider | null; + req: any; + updateTxFee: ReturnType['updateTxFee']; + chainId: ChainId; +}; + +export const useCalculateGasLimit = ({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + chainId, +}: CalculateGasLimitProps) => { + const calculatingGasLimit = useRef(false); + + const calculateGasLimit = useCallback(async () => { + calculatingGasLimit.current = true; + const txPayload = req; + if (isHex(txPayload?.type)) { + txPayload.type = hexToNumber(txPayload?.type); + } + let gas = txPayload.gasLimit || txPayload.gas; + + try { + logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); + const rawGasLimit = await estimateGas(cleanTxPayload, provider); + logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + if (rawGasLimit) { + gas = toHex(rawGasLimit); + } + } catch (error) { + logger.error(new RainbowError('WC: error estimating gas'), { error }); + } finally { + logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + + const networkObject = getNetworkObject({ chainId }); + if (chainId && networkObject.gas.OptimismTxFee) { + const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider || web3Provider); + updateTxFee(gas, null, l1GasFeeOptimism); + } else { + updateTxFee(gas, null); + } + } + }, [chainId, req, updateTxFee, provider]); + + useEffect(() => { + if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { + InteractionManager.runAfterInteractions(() => { + calculateGasLimit(); + }); + } + }, [calculateGasLimit, gasFeeParamsBySpeed, isMessageRequest, provider]); + + return { calculateGasLimit }; +}; diff --git a/src/hooks/useConfirmTransaction.ts b/src/hooks/useConfirmTransaction.ts new file mode 100644 index 00000000000..6600cead84b --- /dev/null +++ b/src/hooks/useConfirmTransaction.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; + +type UseConfirmTransactionProps = { + isMessageRequest: boolean; + isBalanceEnough: boolean | undefined; + isValidGas: boolean; + handleSignMessage: () => void; + handleConfirmTransaction: () => void; +}; + +export const useConfirmTransaction = ({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, +}: UseConfirmTransactionProps) => { + const onConfirm = useCallback(async () => { + if (isMessageRequest) { + return handleSignMessage(); + } + if (!isBalanceEnough || !isValidGas) return; + return handleConfirmTransaction(); + }, [isMessageRequest, isBalanceEnough, isValidGas, handleConfirmTransaction, handleSignMessage]); + + return { onConfirm }; +}; diff --git a/src/hooks/useHasEnoughBalance.ts b/src/hooks/useHasEnoughBalance.ts new file mode 100644 index 00000000000..4217091802c --- /dev/null +++ b/src/hooks/useHasEnoughBalance.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { fromWei, greaterThanOrEqualTo } from '@/helpers/utilities'; +import BigNumber from 'bignumber.js'; +import { SelectedGasFee } from '@/entities'; +import { ChainId } from '@/networks/types'; + +type WalletBalance = { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; +}; + +type BalanceCheckParams = { + isMessageRequest: boolean; + walletBalance: WalletBalance; + chainId: ChainId; + selectedGasFee: SelectedGasFee; + req: any; +}; + +export const useHasEnoughBalance = ({ isMessageRequest, walletBalance, chainId, selectedGasFee, req }: BalanceCheckParams) => { + const [isBalanceEnough, setIsBalanceEnough] = useState(); + + useEffect(() => { + if (isMessageRequest) { + setIsBalanceEnough(true); + return; + } + + const { gasFee } = selectedGasFee; + if (!walletBalance.isLoaded || !chainId || !gasFee?.estimatedFee) { + return; + } + + const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const balanceAmount = walletBalance.amount; + const value = req?.value ?? 0; + + const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); + const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + + setIsBalanceEnough(isEnough); + }, [isMessageRequest, chainId, selectedGasFee, walletBalance, req]); + + return { isBalanceEnough }; +}; diff --git a/src/hooks/useNonceForDisplay.ts b/src/hooks/useNonceForDisplay.ts new file mode 100644 index 00000000000..ca7b302bedb --- /dev/null +++ b/src/hooks/useNonceForDisplay.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { getNextNonce } from '@/state/nonces'; +import { ChainId } from '@/networks/types'; +import { logger, RainbowError } from '@/logger'; + +type UseNonceParams = { + isMessageRequest: boolean; + address: string; + chainId: ChainId; +}; + +export const useNonceForDisplay = ({ isMessageRequest, address, chainId }: UseNonceParams) => { + const [nonceForDisplay, setNonceForDisplay] = useState(); + + useEffect(() => { + if (!isMessageRequest && !nonceForDisplay) { + (async () => { + try { + const nonce = await getNextNonce({ address, chainId }); + if (nonce || nonce === 0) { + const nonceAsString = nonce.toString(); + setNonceForDisplay(nonceAsString); + } + } catch (error) { + logger.error(new RainbowError(`[useNonceForDisplay]: Failed to get nonce for display: ${error}`)); + } + })(); + } + }, [address, chainId, isMessageRequest, nonceForDisplay]); + + return { nonceForDisplay }; +}; diff --git a/src/hooks/useProviderSetup.ts b/src/hooks/useProviderSetup.ts new file mode 100644 index 00000000000..bd1daa5b854 --- /dev/null +++ b/src/hooks/useProviderSetup.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { ethereumUtils } from '@/utils'; +import { getOnchainAssetBalance } from '@/handlers/assets'; +import { ParsedAddressAsset } from '@/entities'; +import { ChainId } from '@/networks/types'; + +export const useProviderSetup = (chainId: ChainId, address: string) => { + const [provider, setProvider] = useState(null); + const [nativeAsset, setNativeAsset] = useState(null); + + useEffect(() => { + const initProvider = async () => { + let p; + if (chainId === ChainId.mainnet) { + p = await getFlashbotsProvider(); + } else { + p = getProvider({ chainId }); + } + setProvider(p); + }; + initProvider(); + }, [chainId]); + + useEffect(() => { + const fetchNativeAsset = async () => { + if (provider) { + const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address }); + if (asset) { + const balance = await getOnchainAssetBalance(asset, address, chainId, provider); + if (balance) { + const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; + setNativeAsset(assetWithOnchainBalance); + } else { + setNativeAsset(asset); + } + } + } + }; + fetchNativeAsset(); + }, [address, chainId, provider]); + + return { provider, nativeAsset }; +}; diff --git a/src/hooks/useSubmitTransaction.ts b/src/hooks/useSubmitTransaction.ts new file mode 100644 index 00000000000..8d6f861ae25 --- /dev/null +++ b/src/hooks/useSubmitTransaction.ts @@ -0,0 +1,56 @@ +import { useCallback, useState } from 'react'; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { SCREEN_FOR_REQUEST_SOURCE } from '@/components/Transactions/constants'; +import { logger, RainbowError } from '@/logger'; + +export const useTransactionSubmission = ({ + isBalanceEnough, + accountInfo, + onConfirm, + source, +}: { + isBalanceEnough: boolean | undefined; + accountInfo: { isHardwareWallet: boolean }; + onConfirm: () => Promise; + source: RequestSource; +}) => { + const [isAuthorizing, setIsAuthorizing] = useState(false); + const { navigate } = useNavigation(); + + const onPressSend = useCallback(async () => { + if (isAuthorizing) return; + try { + setIsAuthorizing(true); + await onConfirm(); + } catch (error) { + logger.error(new RainbowError(`[useTransactionSubmission]: Error while sending transaction: ${error}`)); + } finally { + setIsAuthorizing(false); + } + }, [isAuthorizing, onConfirm]); + + const submitFn = useCallback( + () => + performanceTracking.getState().executeFn({ + fn: async () => { + if (!isBalanceEnough) { + navigate(Routes.ADD_CASH_SHEET); + return; + } + if (accountInfo.isHardwareWallet) { + navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); + } else { + await onPressSend(); + } + }, + operation: TimeToSignOperation.CallToAction, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + })(), + [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] + ); + + return { submitFn, isAuthorizing }; +}; diff --git a/src/hooks/useTransactionSetup.ts b/src/hooks/useTransactionSetup.ts new file mode 100644 index 00000000000..910a53e9da9 --- /dev/null +++ b/src/hooks/useTransactionSetup.ts @@ -0,0 +1,66 @@ +import * as i18n from '@/languages'; +import { useCallback, useEffect, useState } from 'react'; +import { InteractionManager } from 'react-native'; +import useGas from '@/hooks/useGas'; +import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; +import { analytics } from '@/analytics'; +import { event } from '@/analytics/event'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { ChainId } from '@/networks/types'; + +type TransactionSetupParams = { + chainId: ChainId; + startPollingGasFees: ReturnType['startPollingGasFees']; + stopPollingGasFees: ReturnType['stopPollingGasFees']; + isMessageRequest: boolean; + transactionDetails: any; + source: RequestSource; +}; + +export const useTransactionSetup = ({ + chainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, +}: TransactionSetupParams) => { + const [methodName, setMethodName] = useState(null); + + const fetchMethodName = useCallback( + async (data: string) => { + const methodSignaturePrefix = data.substr(0, 10); + try { + const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, chainId); + if (name) { + setMethodName(name); + } + } catch (e) { + setMethodName(data); + } + }, + [chainId] + ); + + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (chainId) { + if (!isMessageRequest) { + startPollingGasFees(chainId); + fetchMethodName(transactionDetails?.payload?.params?.[0].data); + } else { + setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); + } + analytics.track(event.txRequestShownSheet, { source }); + } + }); + + return () => { + if (!isMessageRequest) { + stopPollingGasFees(); + } + }; + }, [isMessageRequest, chainId, transactionDetails?.payload?.params, source, fetchMethodName, startPollingGasFees, stopPollingGasFees]); + + return { methodName }; +}; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 3b0836c40a4..725c6a9a2eb 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2836,6 +2836,7 @@ "unavailable_personal_sign": "Simulation for personal signs is not yet supported", "unavailable_zora_network": "Simulation on Zora is not yet supported", "failed_to_simulate": "The simulation failed, which suggests your transaction is likely to fail. This may be an issue with the app you’re using.", + "tx_api_error": "We are unable to determine whether or not your transaction will succeed or fail. Proceed with caution.", "warning": "No known malicious behavior was detected, but this transaction has characteristics that may pose a risk to your wallet.", "malicious": "Signing this transaction could result in losing access to everything in your wallet." }, diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 39d424b8c06..007652f2302 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -90,6 +90,7 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const OuterStack = createStackNavigator(); @@ -269,25 +270,13 @@ function AuthNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index c468af96ecb..4f1a8e96407 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -104,6 +104,7 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const NativeStack = createNativeStackNavigator(); @@ -292,25 +293,13 @@ function NativeStackNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 182550aa6c8..19663d09165 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -27,6 +27,7 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; import { ChainId, chainIdToNameMapping } from '@/networks/types'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -276,7 +277,7 @@ export const signTransactionSheetConfig = { options: ({ route }: { route: SignTransactionSheetRouteProp }) => ({ ...buildCoolModalConfig({ ...route.params, - backgroundOpacity: route?.params?.source === 'walletconnect' ? 1 : 0.7, + backgroundOpacity: route?.params?.source === RequestSource.WALLETCONNECT ? 1 : 0.7, cornerRadius: 0, springDamping: 1, topOffset: 0, diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index 8c88b9ee1bf..4bc8b78a88d 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -2,7 +2,7 @@ import messaging from '@react-native-firebase/messaging'; import { getLocal, saveLocal } from '@/handlers/localstorage/common'; import { getPermissionStatus } from '@/notifications/permissions'; -import { logger, RainbowError } from '@/logger'; +import { logger } from '@/logger'; export const registerTokenRefreshListener = () => messaging().onTokenRefresh(fcmToken => { diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 3bf47986390..9b8747334d8 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -9,7 +9,7 @@ import { isSignTypedData, SIGN, PERSONAL_SIGN, SEND_TRANSACTION, SIGN_TRANSACTIO import { isAddress } from '@ethersproject/address'; import { toUtf8String } from '@ethersproject/strings'; -export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { +export const getRequestDisplayDetails = async (payload, nativeCurrency, chainId) => { const timestampInMs = Date.now(); if (payload.method === SEND_TRANSACTION || payload.method === SIGN_TRANSACTION) { const transaction = Object.assign(payload?.params?.[0] ?? null); @@ -75,9 +75,9 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ timestampInMs, }); -const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { +const getTransactionDisplayDetails = async (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork({ chainId }); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId }); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index a15206fbace..7733af73291 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -71,7 +71,7 @@ export interface WalletconnectRequestData extends RequestData { /** * Display details loaded for a request. */ -interface RequestDisplayDetails { +export interface RequestDisplayDetails { /** * Data loaded for the request, depending on the type of request. */ @@ -154,7 +154,7 @@ export const addRequestToApprove = icons?: string[]; } ) => - (dispatch: Dispatch, getState: AppGetState) => { + async (dispatch: Dispatch, getState: AppGetState) => { const { requests } = getState().requests; const { walletConnectors } = getState().walletconnect; const { accountAddress, network, nativeCurrency } = getState().settings; @@ -163,7 +163,7 @@ export const addRequestToApprove = const chainId = walletConnector._chainId; // @ts-expect-error "_accounts" is private. const address = walletConnector._accounts[0]; - const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, chainId); + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, chainId); const oneHourAgoTs = Date.now() - EXPIRATION_THRESHOLD_IN_MS; // @ts-expect-error This fails to compile as `displayDetails` does not // always return an object with `timestampInMs`. Still, the error thrown diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index f75a286e044..c4658b7e090 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -561,7 +561,9 @@ const listenOnNewMessages = return; } const { requests: pendingRequests } = getState().requests; - const request = !pendingRequests[requestId] ? dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) : null; + const request = !pendingRequests[requestId] + ? await dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) + : null; if (request) { handleWalletConnectRequest(request); InteractionManager.runAfterInteractions(() => { diff --git a/src/resources/transactions/transactionSimulation.ts b/src/resources/transactions/transactionSimulation.ts new file mode 100644 index 00000000000..c8e0a556ccd --- /dev/null +++ b/src/resources/transactions/transactionSimulation.ts @@ -0,0 +1,146 @@ +import { createQueryKey, QueryConfig, QueryFunctionArgs } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { RainbowError, logger } from '@/logger'; +import { metadataPOSTClient } from '@/graphql'; +import { TransactionErrorType, TransactionScanResultType, TransactionSimulationResult } from '@/graphql/__generated__/metadataPOST'; +import { isNil } from 'lodash'; +import { RequestData } from '@/redux/requests'; +import { ChainId } from '@/networks/types'; + +type SimulationArgs = { + address: string; + chainId: ChainId; + isMessageRequest: boolean; + nativeCurrency: string; + req: any; // Replace 'any' with the correct type for 'req' + requestMessage: string; + simulationUnavailable: boolean; + transactionDetails: RequestData; +}; + +type SimulationResult = { + simulationData: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; +}; + +const simulationQueryKey = ({ + address, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, +}: SimulationArgs) => + createQueryKey( + 'txSimulation', + { + address, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, + }, + { persisterVersion: 1 } + ); + +const fetchSimulation = async ({ + queryKey: [{ address, chainId, isMessageRequest, nativeCurrency, req, requestMessage, simulationUnavailable, transactionDetails }], +}: QueryFunctionArgs): Promise => { + try { + let simulationData; + + if (isMessageRequest) { + simulationData = await metadataPOSTClient.simulateMessage({ + address, + chainId, + message: { + method: transactionDetails?.payload?.method, + params: [requestMessage], + }, + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateMessage?.error?.type, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { + return { + simulationData: simulationData.simulateMessage?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } + } else { + simulationData = await metadataPOSTClient.simulateTransactions({ + chainId, + currency: nativeCurrency?.toLowerCase(), + transactions: [ + { + from: req?.from, + to: req?.to, + data: req?.data || '0x', + value: req?.value || '0x0', + }, + ], + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions?.[0]?.scanning?.result, + }; + } else if (simulationData?.simulateTransactions?.[0]?.error) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateTransactions?.[0]?.error?.type, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } else if (simulationData.simulateTransactions?.[0]?.simulation) { + return { + simulationData: simulationData.simulateTransactions[0]?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } + } + + return { + simulationData: undefined, + simulationError: undefined, + simulationScanResult: undefined, + }; + } catch (error) { + logger.error(new RainbowError('Error while simulating'), { error }); + throw error; + } +}; + +export const useSimulation = ( + args: SimulationArgs, + config: QueryConfig> = {} +) => { + return useQuery(simulationQueryKey(args), fetchSimulation, { + enabled: !!args.address && !!args.chainId, + retry: 3, + refetchOnWindowFocus: false, + staleTime: Infinity, + ...config, + }); +}; diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 7f48aa81aaa..11be9876981 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -1,68 +1,31 @@ -/* eslint-disable no-nested-ternary */ -import BigNumber from 'bignumber.js'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { AnimatePresence, MotiView, TransitionConfig } from 'moti'; +import React, { useCallback, useMemo } from 'react'; +import { AnimatePresence, MotiView } from 'moti'; import * as i18n from '@/languages'; -import { Image, InteractionManager, PixelRatio, ScrollView, StyleProp, TouchableWithoutFeedback, ViewStyle } from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import Animated, { - Easing, - SharedValue, - interpolate, - interpolateColor, - measure, - runOnJS, - runOnUI, - useAnimatedReaction, - useAnimatedRef, - useAnimatedStyle, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; +import { Image, InteractionManager, PixelRatio, ScrollView } from 'react-native'; +import Animated from 'react-native-reanimated'; import { Transaction } from '@ethersproject/transactions'; -import { ButtonPressAnimation } from '@/components/animations'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { SheetActionButton } from '@/components/sheet'; import { Bleed, Box, Columns, Inline, Inset, Stack, Text, globalColors, useBackgroundColor, useForegroundColor } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; -import { NewTransaction, ParsedAddressAsset } from '@/entities'; +import { NewTransaction } from '@/entities'; import { useNavigation } from '@/navigation'; import { useTheme } from '@/theme'; -import { abbreviations, deviceUtils, ethereumUtils, safeAreaInsetValues } from '@/utils'; +import { deviceUtils } from '@/utils'; import { PanGestureHandler } from 'react-native-gesture-handler'; import { RouteProp, useRoute } from '@react-navigation/native'; -import { metadataPOSTClient } from '@/graphql'; -import { - TransactionAssetType, - TransactionErrorType, - TransactionSimulationAsset, - TransactionSimulationMeta, - TransactionSimulationResult, - TransactionScanResultType, -} from '@/graphql/__generated__/metadataPOST'; -import { Network, ChainId, chainIdToNameMapping } from '@/networks/types'; -import { - convertAmountToNativeDisplay, - convertHexToString, - convertRawAmountToBalance, - delay, - fromWei, - greaterThan, - greaterThanOrEqualTo, - omitFlatten, -} from '@/helpers/utilities'; +import { TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/networks/types'; +import { convertHexToString, delay, greaterThan, omitFlatten } from '@/helpers/utilities'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { getAccountProfileInfo } from '@/helpers/accountInfo'; -import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWallet, useWallets } from '@/hooks'; +import { useAccountSettings, useGas, useSwitchWallet, useWallets } from '@/hooks'; import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; -import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { estimateGasWithPadding, getProvider, toHex } from '@/handlers/web3'; import { GasSpeedButton } from '@/components/gas'; import { getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; @@ -72,96 +35,60 @@ import { SIGN_TYPED_DATA, SIGN_TYPED_DATA_V4, isMessageDisplayType, - isPersonalSign as checkIsPersonalSign, - isSignTypedData, + isPersonalSign, } from '@/utils/signingMethods'; -import { isEmpty, isNil } from 'lodash'; -import Routes from '@/navigation/routesNames'; +import { isNil } from 'lodash'; import { parseGasParamsForTransaction } from '@/parsers/gas'; import { loadWallet, sendTransaction, signPersonalMessage, signTransaction, signTypedDataMessage } from '@/model/wallet'; import { analyticsV2 as analytics } from '@/analytics'; import { maybeSignUri } from '@/handlers/imgix'; -import { RPCMethod } from '@/walletConnect/types'; import { isAddress } from '@ethersproject/address'; -import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; -import { sanitizeTypedData } from '@/utils/signingUtils'; import { hexToNumber, isHex } from 'viem'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNextNonce } from '@/state/nonces'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { RequestData } from '@/redux/requests'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; -import { getOnchainAssetBalance } from '@/handlers/assets'; -import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { AddressOrEth } from '@/__swaps__/types/assets'; - -const COLLAPSED_CARD_HEIGHT = 56; -const MAX_CARD_HEIGHT = 176; - -const CARD_ROW_HEIGHT = 12; -const SMALL_CARD_ROW_HEIGHT = 10; -const CARD_BORDER_WIDTH = 1.5; - -const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; -const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; - -const GAS_BUTTON_SPACE = - 30 + // GasSpeedButton height - 24; // Between GasSpeedButton and bottom of sheet - -const EXPANDED_CARD_BOTTOM_INSET = - SCREEN_BOTTOM_INSET + - 24 + // Between bottom of sheet and bottom of Cancel/Confirm - 52 + // Cancel/Confirm height - 24 + // Between Cancel/Confirm and wallet avatar row - 44 + // Wallet avatar row height - 24; // Between wallet avatar row and bottom of expandable area - -const rotationConfig = { - duration: 2100, - easing: Easing.linear, -}; - -const timingConfig = { - duration: 300, - easing: Easing.bezier(0.2, 0, 0, 1), -}; - -const motiTimingConfig: TransitionConfig = { - duration: 225, - easing: Easing.bezier(0.2, 0, 0, 1), - type: 'timing', -}; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import { useSimulation } from '@/resources/transactions/transactionSimulation'; +import { TransactionSimulationCard } from '@/components/Transactions/TransactionSimulationCard'; +import { TransactionDetailsCard } from '@/components/Transactions/TransactionDetailsCard'; +import { TransactionMessageCard } from '@/components/Transactions/TransactionMessageCard'; +import { VerifiedBadge } from '@/components/Transactions/TransactionIcons'; +import { + SCREEN_FOR_REQUEST_SOURCE, + EXPANDED_CARD_BOTTOM_INSET, + GAS_BUTTON_SPACE, + motiTimingConfig, + SCREEN_BOTTOM_INSET, + infoForEventType, +} from '@/components/Transactions/constants'; +import { useCalculateGasLimit } from '@/hooks/useCalculateGasLimit'; +import { useTransactionSetup } from '@/hooks/useTransactionSetup'; +import { useHasEnoughBalance } from '@/hooks/useHasEnoughBalance'; +import { useNonceForDisplay } from '@/hooks/useNonceForDisplay'; +import { useProviderSetup } from '@/hooks/useProviderSetup'; +import { useTransactionSubmission } from '@/hooks/useSubmitTransaction'; +import { useConfirmTransaction } from '@/hooks/useConfirmTransaction'; type SignTransactionSheetParams = { transactionDetails: RequestData; onSuccess: (hash: string) => void; onCancel: (error?: Error) => void; onCloseScreen: (canceled: boolean) => void; - network: Network; chainId: ChainId; address: string; source: RequestSource; }; -const SCREEN_FOR_REQUEST_SOURCE = { - browser: Screens.DAPP_BROWSER, - walletconnect: Screens.WALLETCONNECT, -}; - export type SignTransactionSheetRouteProp = RouteProp<{ SignTransactionSheet: SignTransactionSheetParams }, 'SignTransactionSheet'>; export const SignTransactionSheet = () => { - const { goBack, navigate } = useNavigation(); + const { goBack } = useNavigation(); const { colors, isDarkMode } = useTheme(); const { accountAddress, nativeCurrency } = useAccountSettings(); - const [simulationData, setSimulationData] = useState(); - const [simulationError, setSimulationError] = useState(undefined); - const [simulationScanResult, setSimulationScanResult] = useState(undefined); const { params: routeParams } = useRoute(); const { wallets, walletNames } = useWallets(); @@ -171,28 +98,24 @@ export const SignTransactionSheet = () => { onSuccess: onSuccessCallback, onCancel: onCancelCallback, onCloseScreen: onCloseScreenCallback, - chainId: currentChainId, - address: currentAddress, + chainId, + address: specifiedAddress, // for request type specific handling source, } = routeParams; - const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + console.log({ specifiedAddress }); + + const addressToUse = specifiedAddress ?? accountAddress; - const isPersonalSign = checkIsPersonalSign(transactionDetails.payload.method); + const { provider, nativeAsset } = useProviderSetup(chainId, addressToUse); + + const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + const isPersonalSignRequest = isPersonalSign(transactionDetails.payload.method); const label = useForegroundColor('label'); const surfacePrimary = useBackgroundColor('surfacePrimary'); - const [provider, setProvider] = useState(null); - const [isAuthorizing, setIsAuthorizing] = useState(false); - const [isLoading, setIsLoading] = useState(!isPersonalSign); - const [methodName, setMethodName] = useState(null); - const calculatingGasLimit = useRef(false); - const [isBalanceEnough, setIsBalanceEnough] = useState(); - const [nonceForDisplay, setNonceForDisplay] = useState(); - - const [nativeAsset, setNativeAsset] = useState(null); const formattedDappUrl = useMemo(() => { try { const { hostname } = new URL(transactionDetails?.dappUrl); @@ -202,106 +125,16 @@ export const SignTransactionSheet = () => { } }, [transactionDetails]); - const { - gasLimit, - isValidGas, - startPollingGasFees, - stopPollingGasFees, - isSufficientGas, - updateTxFee, - selectedGasFee, - gasFeeParamsBySpeed, - } = useGas(); - - const simulationUnavailable = isPersonalSign; - - const itemCount = (simulationData?.in?.length || 0) + (simulationData?.out?.length || 0) + (simulationData?.approvals?.length || 0); - - const noChanges = !!(simulationData && itemCount === 0) && simulationScanResult === TransactionScanResultType.Ok; - const req = transactionDetails?.payload?.params?.[0]; const request = useMemo(() => { return isMessageRequest - ? { message: transactionDetails?.displayDetails?.request } + ? { message: transactionDetails?.displayDetails?.request || '' } : { ...transactionDetails?.displayDetails?.request, - nativeAsset: nativeAsset, + nativeAsset, }; }, [isMessageRequest, transactionDetails?.displayDetails?.request, nativeAsset]); - const calculateGasLimit = useCallback(async () => { - calculatingGasLimit.current = true; - const txPayload = req; - if (isHex(txPayload?.type)) { - txPayload.type = hexToNumber(txPayload?.type); - } - // use the default - let gas = txPayload.gasLimit || txPayload.gas; - - const provider = getProvider({ chainId: currentChainId }); - try { - // attempt to re-run estimation - logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); - // safety precaution: we want to ensure these properties are not used for gas estimation - const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); - const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); - if (rawGasLimit) { - gas = toHex(rawGasLimit); - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); - } finally { - logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); - const networkObject = getNetworkObject({ chainId: currentChainId }); - if (networkObject && networkObject.gas.OptimismTxFee) { - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); - updateTxFee(gas, null, l1GasFeeOptimism); - } else { - updateTxFee(gas, null); - } - } - }, [currentChainId, req, updateTxFee]); - - const fetchMethodName = useCallback( - async (data: string) => { - const methodSignaturePrefix = data.substr(0, 10); - try { - const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); - if (name) { - setMethodName(name); - } - } catch (e) { - setMethodName(data); - } - }, - [currentChainId] - ); - - // start polling for gas and get fn name - useEffect(() => { - InteractionManager.runAfterInteractions(() => { - if (currentChainId) { - if (!isMessageRequest) { - startPollingGasFees(currentChainId); - fetchMethodName(transactionDetails?.payload?.params[0].data); - } else { - setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); - } - analytics.track(event.txRequestShownSheet), { source }; - } - }); - }, [isMessageRequest, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source, currentChainId]); - - // get gas limit - useEffect(() => { - if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { - InteractionManager.runAfterInteractions(() => { - calculateGasLimit(); - }); - } - }, [calculateGasLimit, gasLimit, gasFeeParamsBySpeed, isMessageRequest, provider, updateTxFee]); - const walletBalance = useMemo(() => { return { amount: nativeAsset?.balance?.amount || 0, @@ -311,168 +144,77 @@ export const SignTransactionSheet = () => { }; }, [nativeAsset?.balance?.amount, nativeAsset?.balance?.display, nativeAsset?.symbol]); - // check native balance is sufficient - useEffect(() => { - if (isMessageRequest) { - setIsBalanceEnough(true); - return; - } + const { gasLimit, isValidGas, startPollingGasFees, stopPollingGasFees, updateTxFee, selectedGasFee, gasFeeParamsBySpeed } = useGas(); - const { gasFee } = selectedGasFee; - if (!walletBalance?.isLoaded || !currentChainId || !gasFee?.estimatedFee) { - return; - } + const { methodName } = useTransactionSetup({ + chainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, + }); - // Get the TX fee Amount - const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const { isBalanceEnough } = useHasEnoughBalance({ + isMessageRequest, + walletBalance, + chainId, + selectedGasFee, + req, + }); + + useCalculateGasLimit({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + chainId, + }); - // Get the ETH balance - const balanceAmount = walletBalance?.amount ?? 0; + const { nonceForDisplay } = useNonceForDisplay({ + isMessageRequest, + address: addressToUse, + chainId, + }); - // Get the TX value - const txPayload = req; - const value = txPayload?.value ?? 0; + const { + data: simulationResult, + isLoading: txSimulationLoading, + error: txSimulationApiError, + } = useSimulation( + { + address: addressToUse, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage: request.message, + simulationUnavailable: isPersonalSignRequest, + transactionDetails, + }, + { + enabled: !isPersonalSignRequest, + } + ); - // Check that there's enough ETH to pay for everything! - const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); - const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + const itemCount = + (simulationResult?.simulationData?.in?.length || 0) + + (simulationResult?.simulationData?.out?.length || 0) + + (simulationResult?.simulationData?.approvals?.length || 0); - setIsBalanceEnough(isEnough); - }, [isMessageRequest, isSufficientGas, selectedGasFee, walletBalance, req, currentChainId]); + const noChanges = + !!(simulationResult?.simulationData && itemCount === 0) && simulationResult?.simulationScanResult === TransactionScanResultType.Ok; const accountInfo = useMemo(() => { - const selectedWallet = wallets ? findWalletWithAccount(wallets, currentAddress) : undefined; - const profileInfo = getAccountProfileInfo(selectedWallet, walletNames, currentAddress); + const selectedWallet = wallets ? findWalletWithAccount(wallets, addressToUse) : undefined; + const profileInfo = getAccountProfileInfo(selectedWallet, walletNames, addressToUse); return { ...profileInfo, - address: currentAddress, + address: addressToUse, isHardwareWallet: !!selectedWallet?.deviceId, }; - }, [wallets, currentAddress, walletNames]); - - useEffect(() => { - const initProvider = async () => { - let p; - // check on this o.O - if (currentChainId === ChainId.mainnet) { - p = await getFlashbotsProvider(); - } else { - p = getProvider({ chainId: currentChainId }); - } - - setProvider(p); - }; - initProvider(); - }, [currentChainId, setProvider]); - - useEffect(() => { - (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId: currentChainId, address: accountInfo.address }); - if (asset && provider) { - const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentChainId, provider); - if (balance) { - const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; - setNativeAsset(assetWithOnchainBalance); - } else { - setNativeAsset(asset); - } - } - })(); - }, [accountInfo.address, currentChainId, provider]); - - useEffect(() => { - (async () => { - if (!isMessageRequest && !nonceForDisplay) { - try { - const nonce = await getNextNonce({ address: currentAddress, chainId: currentChainId }); - if (nonce || nonce === 0) { - const nonceAsString = nonce.toString(); - setNonceForDisplay(nonceAsString); - } - } catch (error) { - console.error('Failed to get nonce for display:', error); - } - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accountInfo.address, currentChainId, getNextNonce, isMessageRequest]); - - useEffect(() => { - const timeout = setTimeout(async () => { - try { - let simulationData; - if (isMessageRequest) { - // Message Signing - simulationData = await metadataPOSTClient.simulateMessage({ - address: accountAddress, - chainId: currentChainId, - message: { - method: transactionDetails?.payload?.method, - params: [request.message], - }, - domain: transactionDetails?.dappUrl, - }); - // Handle message simulation response - if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { - setSimulationError(simulationData?.simulateMessage?.error?.type); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - setSimulationData(undefined); - } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { - setSimulationData(simulationData.simulateMessage?.simulation); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } - } else { - // TX Signing - simulationData = await metadataPOSTClient.simulateTransactions({ - chainId: currentChainId, - currency: nativeCurrency?.toLowerCase(), - transactions: [ - { - from: req?.from, - to: req?.to, - data: req?.data, - value: req?.value || '0x0', - }, - ], - domain: transactionDetails?.dappUrl, - }); - // Handle TX simulation response - if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateTransactions?.[0]?.scanning?.result); - } else if (simulationData?.simulateTransactions?.[0]?.error) { - setSimulationError(simulationData?.simulateTransactions?.[0]?.error?.type); - setSimulationData(undefined); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } else if (simulationData.simulateTransactions?.[0]?.simulation) { - setSimulationData(simulationData.simulateTransactions[0]?.simulation); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); - } finally { - setIsLoading(false); - } - }, 750); - - return () => { - clearTimeout(timeout); - }; - }, [ - accountAddress, - currentChainId, - isMessageRequest, - isPersonalSign, - nativeCurrency, - req, - request.message, - simulationUnavailable, - transactionDetails, - ]); + }, [wallets, addressToUse, walletNames]); const closeScreen = useCallback( (canceled: boolean) => @@ -520,86 +262,12 @@ export const SignTransactionSheet = () => { [accountInfo.isHardwareWallet, closeScreen, onCancelCallback, source, transactionDetails?.payload?.method] ); - const handleSignMessage = useCallback(async () => { - const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); - let response = null; - - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { - return; - } - - const existingWallet = await performanceTracking.getState().executeFn({ - fn: loadWallet, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.KeychainRead, - })({ - address: accountInfo.address, - provider, - timeTracking: { - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.Authentication, - }, - }); - - if (!existingWallet) { - return; - } - switch (transactionDetails?.payload?.method) { - case PERSONAL_SIGN: - response = await performanceTracking.getState().executeFn({ - fn: signPersonalMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - case SIGN_TYPED_DATA_V4: - case SIGN_TYPED_DATA: - response = await performanceTracking.getState().executeFn({ - fn: signTypedDataMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - default: - break; - } - - if (response?.result) { - analytics.track(event.txRequestApprove, { - source, - requestType: 'signature', - dappName: transactionDetails?.dappName, - dappUrl: transactionDetails?.dappUrl, - isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - }); - onSuccessCallback?.(response.result); - - closeScreen(false); - } else { - await onCancel(response?.error); - } - }, [ - transactionDetails?.payload?.params, - transactionDetails?.payload?.method, - transactionDetails?.dappName, - transactionDetails?.dappUrl, - currentChainId, - accountInfo.address, - accountInfo.isHardwareWallet, - source, - onSuccessCallback, - closeScreen, - onCancel, - ]); - const handleConfirmTransaction = useCallback(async () => { const sendInsteadOfSign = transactionDetails.payload.method === SEND_TRANSACTION; const txPayload = req; let { gas } = txPayload; const gasLimitFromPayload = txPayload?.gasLimit; - if (!currentChainId) return; + if (!chainId) return; try { logger.debug( '[SignTransactionSheet]: gas suggested by dapp', @@ -638,7 +306,7 @@ export const SignTransactionSheet = () => { const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, chainId: currentChainId }); + const nonce = await getNextNonce({ address: accountInfo.address, chainId }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -654,11 +322,11 @@ export const SignTransactionSheet = () => { let response = null; try { - if (!currentChainId) { + if (!chainId) { return; } - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { + const providerToUse = provider || getProvider({ chainId }); + if (!providerToUse) { return; } const existingWallet = await performanceTracking.getState().executeFn({ @@ -667,7 +335,7 @@ export const SignTransactionSheet = () => { operation: TimeToSignOperation.KeychainRead, })({ address: accountInfo.address, - provider, + provider: providerToUse, timeTracking: { screen: SCREEN_FOR_REQUEST_SOURCE[source], operation: TimeToSignOperation.Authentication, @@ -685,8 +353,8 @@ export const SignTransactionSheet = () => { screen: SCREEN_FOR_REQUEST_SOURCE[source], operation: TimeToSignOperation.BroadcastTransaction, })({ - existingWallet: existingWallet, - provider, + existingWallet, + provider: providerToUse, transaction: txPayloadUpdated, }); } else { @@ -696,7 +364,7 @@ export const SignTransactionSheet = () => { operation: TimeToSignOperation.SignTransaction, })({ existingWallet, - provider, + provider: providerToUse, transaction: txPayloadUpdated, }); } @@ -714,7 +382,7 @@ export const SignTransactionSheet = () => { if (sendInsteadOfSign && sendResult?.hash) { txDetails = { status: 'pending', - chainId: currentChainId, + chainId, asset: displayDetails?.request?.asset || nativeAsset, contract: { name: transactionDetails.dappName, @@ -724,18 +392,18 @@ export const SignTransactionSheet = () => { from: displayDetails?.request?.from, gasLimit, hash: sendResult.hash, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + network: getNetworkObject({ chainId }).value, nonce: sendResult.nonce, to: displayDetails?.request?.to, value: sendResult.value.toString(), type: 'contract_interaction', ...gasParams, }; - if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { + if (accountInfo.address?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - chainId: currentChainId, - address: accountAddress, + chainId, + address: accountInfo.address, }); txSavedInCurrentWallet = true; } @@ -746,7 +414,7 @@ export const SignTransactionSheet = () => { dappName: transactionDetails.dappName, dappUrl: transactionDetails.dappUrl, isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + network: getNetworkObject({ chainId }).value, }); if (!sendInsteadOfSign) { @@ -766,7 +434,7 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - chainId: currentChainId, + chainId, address: txDetails?.from as string, }); }); @@ -777,7 +445,7 @@ export const SignTransactionSheet = () => { dappUrl: transactionDetails?.dappUrl, formattedDappUrl, rpcMethod: req?.method, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + network: getNetworkObject({ chainId }).value, }); // If the user is using a hardware wallet, we don't want to close the sheet on an error if (!accountInfo.isHardwareWallet) { @@ -791,7 +459,7 @@ export const SignTransactionSheet = () => { transactionDetails.dappUrl, transactionDetails.imageUrl, req, - currentChainId, + chainId, selectedGasFee, gasLimit, accountInfo.address, @@ -800,57 +468,106 @@ export const SignTransactionSheet = () => { source, closeScreen, nativeAsset, - accountAddress, onSuccessCallback, switchToWalletWithAddress, formattedDappUrl, onCancel, ]); - const onConfirm = useCallback(async () => { - if (isMessageRequest) { - return handleSignMessage(); - } - if (!isBalanceEnough || !isValidGas) return; - return handleConfirmTransaction(); - }, [handleConfirmTransaction, handleSignMessage, isBalanceEnough, isMessageRequest, isValidGas]); + const handleSignMessage = useCallback(async () => { + const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); + let response = null; - const onPressSend = useCallback(async () => { - if (isAuthorizing) return; - setIsAuthorizing(true); - try { - await onConfirm(); - setIsAuthorizing(false); - } catch (error) { - setIsAuthorizing(false); + const providerToUse = provider || getProvider({ chainId }); + if (!providerToUse) { + return; } - }, [isAuthorizing, onConfirm]); - const submitFn = useCallback( - () => - performanceTracking.getState().executeFn({ - fn: async () => { - if (!isBalanceEnough) { - navigate(Routes.ADD_CASH_SHEET); - return; - } - if (accountInfo.isHardwareWallet) { - navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); - } else { - await onPressSend(); - } - }, - operation: TimeToSignOperation.CallToAction, + const existingWallet = await performanceTracking.getState().executeFn({ + fn: loadWallet, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.KeychainRead, + })({ + address: accountInfo.address, + provider: providerToUse, + timeTracking: { screen: SCREEN_FOR_REQUEST_SOURCE[source], - })(), - [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] - ); + operation: TimeToSignOperation.Authentication, + }, + }); + + if (!existingWallet) { + return; + } + switch (transactionDetails?.payload?.method) { + case PERSONAL_SIGN: + response = await performanceTracking.getState().executeFn({ + fn: signPersonalMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + case SIGN_TYPED_DATA_V4: + case SIGN_TYPED_DATA: + response = await performanceTracking.getState().executeFn({ + fn: signTypedDataMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + default: + break; + } + + if (response?.result) { + analytics.track(event.txRequestApprove, { + source, + requestType: 'signature', + dappName: transactionDetails?.dappName, + dappUrl: transactionDetails?.dappUrl, + isHardwareWallet: accountInfo.isHardwareWallet, + network: getNetworkObject({ chainId }).value, + }); + onSuccessCallback?.(response.result); + + closeScreen(false); + } else { + await onCancel(response?.error); + } + }, [ + transactionDetails?.payload?.params, + transactionDetails?.payload?.method, + transactionDetails?.dappName, + transactionDetails?.dappUrl, + chainId, + accountInfo.address, + accountInfo.isHardwareWallet, + source, + onSuccessCallback, + closeScreen, + onCancel, + ]); + + const { onConfirm } = useConfirmTransaction({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, + }); + + const { submitFn } = useTransactionSubmission({ + isBalanceEnough, + accountInfo, + onConfirm, + source, + }); const onPressCancel = useCallback(() => onCancel(), [onCancel]); const expandedCardBottomInset = EXPANDED_CARD_BOTTOM_INSET + (isMessageRequest ? 0 : GAS_BUTTON_SPACE); - const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!currentChainId && !!selectedGasFee?.gasFee?.estimatedFee); + const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!chainId && !!selectedGasFee?.gasFee?.estimatedFee); return ( @@ -899,8 +616,9 @@ export const SignTransactionSheet = () => { { > {transactionDetails.dappName} - {source === 'browser' && } + {source === RequestSource.BROWSER && } {isMessageRequest @@ -921,33 +639,36 @@ export const SignTransactionSheet = () => { - {isMessageRequest ? ( - ) : ( - { ) : ( - {!!currentChainId && walletBalance?.isLoaded && ( + {!!chainId && walletBalance?.isLoaded && ( - + {`${walletBalance?.display} ${i18n.t(i18n.l.walletconnect.simulation.profile_section.on_network, { - network: getNetworkObject({ chainId: currentChainId })?.name, + network: getNetworkObject({ chainId })?.name, })}`} @@ -1018,7 +739,7 @@ export const SignTransactionSheet = () => { /> { disabled={!canPressConfirm} size="big" weight="heavy" - // eslint-disable-next-line react/jsx-props-no-spreading - {...((simulationError || (simulationScanResult && simulationScanResult !== TransactionScanResultType.Ok)) && { - color: simulationScanResult === TransactionScanResultType.Warning ? 'orange' : colors.red, - })} + color={ + simulationResult?.simulationError || + (simulationResult?.simulationScanResult && simulationResult?.simulationScanResult !== TransactionScanResultType.Ok) + ? simulationResult?.simulationScanResult === TransactionScanResultType.Warning + ? 'orange' + : colors.red + : undefined + } /> @@ -1043,7 +768,7 @@ export const SignTransactionSheet = () => { )} - {source === 'browser' && ( + {source === RequestSource.BROWSER && ( { { ); }; - -interface SimulationCardProps { - chainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - isPersonalSign: boolean; - noChanges: boolean; - simulation: TransactionSimulationResult | undefined; - simulationError: TransactionErrorType | undefined; - simulationScanResult: TransactionScanResultType | undefined; - walletBalance: { - amount: string | number; - display: string; - isLoaded: boolean; - symbol: string; - }; -} - -const SimulationCard = ({ - chainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - isPersonalSign, - noChanges, - simulation, - simulationError, - simulationScanResult, - walletBalance, -}: SimulationCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const spinnerRotation = useSharedValue(0); - - const simulationUnavailable = isPersonalSign; - - const listStyle = useAnimatedStyle(() => ({ - opacity: noChanges - ? withTiming(1, timingConfig) - : interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const spinnerStyle = useAnimatedStyle(() => { - return { - transform: [{ rotate: `${spinnerRotation.value}deg` }], - }; - }); - - useAnimatedReaction( - () => ({ isLoading, simulationUnavailable }), - ({ isLoading, simulationUnavailable }, previous = { isLoading: false, simulationUnavailable: false }) => { - if (isLoading && !previous?.isLoading) { - spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); - } else if ( - (!isLoading && previous?.isLoading) || - (simulationUnavailable && !previous?.simulationUnavailable && previous?.isLoading) - ) { - spinnerRotation.value = withTiming(360, timingConfig); - } - }, - [isLoading, simulationUnavailable] - ); - const renderSimulationEventRows = useMemo(() => { - if (isBalanceEnough === false) return null; - - return ( - <> - {simulation?.approvals?.map(change => { - return ( - - ); - })} - {simulation?.out?.map(change => { - return ( - - ); - })} - {simulation?.in?.map(change => { - return ( - - ); - })} - - ); - }, [isBalanceEnough, simulation]); - - const titleColor: TextColor = useMemo(() => { - if (isLoading) { - return 'label'; - } - if (isBalanceEnough === false) { - return 'blue'; - } - if (noChanges || simulationUnavailable) { - return 'labelQuaternary'; - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return 'orange'; - } - if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { - return 'red'; - } - return 'label'; - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable]); - - const titleText = useMemo(() => { - if (isLoading) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); - } - if (isBalanceEnough === false) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); - } - if (simulationUnavailable) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); - } - if (simulationScanResult === TransactionScanResultType.Malicious) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); - } - if (noChanges) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); - } - if (simulationError) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); - } - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable, walletBalance?.symbol]); - - const isExpanded = useMemo(() => { - if (isLoading || isPersonalSign) { - return false; - } - const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; - return shouldExpandOnLoad; - }, [isBalanceEnough, isLoading, isPersonalSign, noChanges, simulation, simulationError]); - - return ( - - - - - {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( - - ) : ( - - {!isLoading && noChanges && !simulationUnavailable ? ( - - {/* The extra space avoids icon clipping */} - {'􀻾 '} - - ) : ( - - - 􀬨 - - - )} - - )} - - {titleText} - - - {/* TODO: Unhide once we add explainer sheets */} - {/* - - - - - 􀁜 - - - - - */} - - - - {isBalanceEnough === false ? ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { - symbol: walletBalance?.symbol, - network: chainIdToNameMapping[chainId], - })} - - ) : ( - <> - {simulationUnavailable && isPersonalSign && ( - - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} - - - )} - {simulationError && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} - - )} - {simulationScanResult === TransactionScanResultType.Warning && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} - - )} - {simulationScanResult === TransactionScanResultType.Malicious && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} - - )} - - )} - {renderSimulationEventRows} - - - - - ); -}; - -interface DetailsCardProps { - chainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - meta: TransactionSimulationMeta | undefined; - methodName: string; - noChanges: boolean; - nonce: string | undefined; - toAddress: string; -} - -const DetailsCard = ({ - chainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - meta, - methodName, - noChanges, - nonce, - toAddress, -}: DetailsCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const [isExpanded, setIsExpanded] = useState(false); - - const listStyle = useAnimatedStyle(() => ({ - opacity: interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; - - const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); - const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; - const showTransferToRow = !!meta?.transferTo?.address; - // Hide DetailsCard if balance is insufficient once loaded - if (!isLoading && isBalanceEnough === false) { - return <>; - } - return ( - setIsExpanded(true)} - > - - - - - - 􁙠 - - - - {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} - - - - - - {} - {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - - ethereumUtils.openAddressInBlockExplorer({ - address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', - chainId, - }) - } - value={ - meta?.to?.name || - abbreviations.address(meta?.to?.address || toAddress, 4, 6) || - meta?.to?.address || - toAddress || - meta?.transferTo?.address || - '' - } - /> - )} - {showFunctionRow && } - {!!meta?.to?.sourceCodeStatus && } - {!!meta?.to?.created && } - {nonce && } - - - - - ); -}; - -const MessageCard = ({ - expandedCardBottomInset, - message, - method, -}: { - expandedCardBottomInset: number; - message: string; - method: RPCMethod; -}) => { - const { setClipboard } = useClipboard(); - const [didCopy, setDidCopy] = useState(false); - - let displayMessage = message; - if (isSignTypedData(method)) { - try { - const parsedMessage = JSON.parse(message); - const sanitizedMessage = sanitizeTypedData(parsedMessage); - displayMessage = sanitizedMessage; - // eslint-disable-next-line no-empty - } catch (e) { - logger.warn('[SignTransactionSheet]: Error while parsing message'); - } - - displayMessage = JSON.stringify(displayMessage, null, 4); - } - - const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); - - const cardHeight = useSharedValue( - estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 - ); - const contentHeight = useSharedValue(estimatedMessageHeight); - - const handleCopyPress = useCallback( - (message: string) => { - if (didCopy) return; - setClipboard(message); - setDidCopy(true); - const copyTimer = setTimeout(() => { - setDidCopy(false); - }, 2000); - return () => clearTimeout(copyTimer); - }, - [didCopy, setClipboard] - ); - - return ( - MAX_CARD_HEIGHT} - isExpanded - skipCollapsedState - > - - - - - - 􀙤 - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} - - - - handleCopyPress(message)}> - - - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} - - - - - - - - - {displayMessage} - - - - ); -}; - -const SimulatedEventRow = ({ - amount, - asset, - eventType, - price, -}: { - amount: string | 'unlimited'; - asset: TransactionSimulationAsset | undefined; - eventType: EventType; - price?: number | undefined; -}) => { - const theme = useTheme(); - const { nativeCurrency } = useAccountSettings(); - const { data: externalAsset } = useExternalToken({ - address: (asset?.assetCode || '') as AddressOrEth, - chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), - currency: nativeCurrency, - }); - - const eventInfo: EventInfo = infoForEventType[eventType]; - - const formattedAmount = useMemo(() => { - if (!asset) return; - - const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; - const assetDisplayName = - asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; - const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; - - const displayAmount = - asset?.decimals === 0 - ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` - : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; - - const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; - - return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; - }, [amount, asset, eventInfo?.amountPrefix]); - - const url = maybeSignUri(asset?.iconURL, { - fm: 'png', - w: 16 * PixelRatio.get(), - }); - - const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; - - const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); - - return ( - - - - - - - {eventInfo.label} - - {showUSD && ( - - {formattedPrice} - - )} - - - - - {asset?.type !== TransactionAssetType.Nft ? ( - - ) : ( - - )} - - - {formattedAmount} - - - - - ); -}; - -const DetailRow = ({ - chainId, - detailType, - onPress, - value, -}: { - chainId?: ChainId; - detailType: DetailType; - onPress?: () => void; - value: string; -}) => { - const detailInfo: DetailInfo = infoForDetailType[detailType]; - - return ( - - - - - - {detailInfo.label} - - - - {detailType === 'function' && } - {detailType === 'sourceCodeVerification' && ( - - )} - {detailType === 'chain' && chainId && } - {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( - - {value} - - )} - {(detailType === 'contract' || detailType === 'to') && ( - - - - - 􀂄 - - - - - )} - - - - ); -}; - -const EventIcon = ({ eventType }: { eventType: EventType }) => { - const eventInfo: EventInfo = infoForEventType[eventType]; - - const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; - const isWarningIcon = - eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; - - return ( - - {!hideInnerFill && ( - - )} - - {eventInfo.icon} - - - ); -}; - -const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { - return ( - - - {detailInfo.icon} - - - ); -}; - -const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { - const { colors, isDarkMode } = useTheme(); - const separatorTertiary = useForegroundColor('separatorTertiary'); - - const infoForBadgeType: { - [key: string]: { - backgroundColor: string; - borderColor: string; - label?: string; - text: TextColor; - textOpacity?: number; - }; - } = { - function: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - text: 'labelQuaternary', - }, - unknown: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - label: 'Unknown', - text: 'labelQuaternary', - }, - unverified: { - backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, - borderColor: colors.alpha(colors.red, 0.02), - label: 'Unverified', - text: 'red', - textOpacity: 0.76, - }, - verified: { - backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, - borderColor: colors.alpha(colors.green, 0.02), - label: 'Verified', - text: 'green', - textOpacity: 0.76, - }, - }; - - return ( - - - - {infoForBadgeType[type].label || value} - - - - ); -}; - -const VerifiedBadge = () => { - return ( - - - - - 􀇻 - - - - ); -}; - -const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { - return ( - - {visible && ( - - - - - - 􀁣 - - - - - )} - - ); -}; - -const FadedScrollCard = ({ - cardHeight, - children, - contentHeight, - expandedCardBottomInset = 120, - expandedCardTopInset = 120, - initialScrollEnabled, - isExpanded, - onPressCollapsedCard, - skipCollapsedState, -}: { - cardHeight: SharedValue; - children: React.ReactNode; - contentHeight: SharedValue; - expandedCardBottomInset?: number; - expandedCardTopInset?: number; - initialScrollEnabled?: boolean; - isExpanded: boolean; - onPressCollapsedCard?: () => void; - skipCollapsedState?: boolean; -}) => { - const { height: deviceHeight, width: deviceWidth } = useDimensions(); - const { isDarkMode } = useTheme(); - - const cardRef = useAnimatedRef(); - - const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); - const [isFullyExpanded, setIsFullyExpanded] = useState(false); - - const yPosition = useSharedValue(0); - - const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); - - const containerStyle = useAnimatedStyle(() => { - return { - height: - cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState - ? interpolate( - cardHeight.value, - [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], - [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], - 'clamp' - ) - : undefined, - zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), - }; - }); - - const backdropStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const cardStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); - return { - borderColor: interpolateColor( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] - ), - height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, - position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', - transform: [ - { - translateY: interpolate( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - [ - 0, - 0, - -yPosition.value + - expandedCardTopInset + - (deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight) - - (yPosition.value + expandedCardHeight >= deviceHeight - expandedCardBottomInset - ? 0 - : deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight), - ] - ), - }, - ], - }; - }); - - const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { - return { - transform: skipCollapsedState - ? undefined - : [ - { - translateY: interpolate( - cardHeight.value, - [ - 0, - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT - ? MAX_CARD_HEIGHT - : contentHeight.value + CARD_BORDER_WIDTH * 2, - maxExpandedHeight, - ], - [-2, -2, 0, 0] - ), - }, - ], - }; - }); - - const shadowStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const handleContentSizeChange = useCallback( - (width: number, height: number) => { - contentHeight.value = Math.round(height); - }, - [contentHeight] - ); - - const handleOnLayout = useCallback(() => { - runOnUI(() => { - if (cardHeight.value === MAX_CARD_HEIGHT) { - const measurement = measure(cardRef); - if (measurement === null) { - return; - } - if (yPosition.value !== measurement.pageY) { - yPosition.value = measurement.pageY; - } - } - })(); - }, [cardHeight, cardRef, yPosition]); - - useAnimatedReaction( - () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), - ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { - if ( - isFullyExpanded !== previous?.isFullyExpanded || - isExpanded !== previous?.isExpanded || - contentHeight !== previous?.contentHeight - ) { - if (isFullyExpanded) { - const expandedCardHeight = - contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; - if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { - cardHeight.value = withTiming(expandedCardHeight, timingConfig); - } else { - runOnJS(setIsFullyExpanded)(false); - } - } else if (isExpanded) { - cardHeight.value = withTiming( - contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, - timingConfig - ); - } else { - cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); - } - - const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); - runOnJS(setScrollEnabled)(enableScroll); - } - } - ); - - return ( - - { - if (isFullyExpanded) { - setIsFullyExpanded(false); - } - }} - pointerEvents={isFullyExpanded ? 'auto' : 'none'} - style={[ - { - backgroundColor: 'rgba(0, 0, 0, 0.6)', - height: deviceHeight * 3, - left: -deviceWidth * 0.5, - position: 'absolute', - top: -deviceHeight, - width: deviceWidth * 2, - zIndex: -1, - }, - backdropStyle, - ]} - /> - - - - { - if (!isFullyExpanded) { - setIsFullyExpanded(true); - } else setIsFullyExpanded(false); - } - } - > - {children} - - - - - - - - ); -}; - -const FadeGradient = ({ side, style }: { side: 'top' | 'bottom'; style?: StyleProp>> }) => { - const { colors, isDarkMode } = useTheme(); - - const isTop = side === 'top'; - const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; - const transparentColor = colors.alpha(solidColor, 0); - - return ( - - - - ); -}; - -const IconContainer = ({ - children, - hitSlop, - opacity, - size = 20, -}: { - children: React.ReactNode; - hitSlop?: number; - opacity?: number; - size?: number; -}) => { - // Prevent wide icons from being clipped - const extraHorizontalSpace = 4; - - return ( - - - {children} - - - ); -}; - -type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; - -type EventInfo = { - amountPrefix: string; - icon: string; - iconColor: TextColor; - label: string; - textColor: TextColor; -}; - -const infoForEventType: { [key: string]: EventInfo } = { - send: { - amountPrefix: '- ', - icon: '􀁷', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), - textColor: 'red', - }, - receive: { - amountPrefix: '+ ', - icon: '􀁹', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), - textColor: 'green', - }, - approve: { - amountPrefix: '', - icon: '􀎤', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), - textColor: 'label', - }, - revoke: { - amountPrefix: '', - icon: '􀎠', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), - textColor: 'label', - }, - failed: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), - textColor: 'red', - }, - insufficientBalance: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'blue', - label: '', - textColor: 'blue', - }, - MALICIOUS: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: '', - textColor: 'red', - }, - WARNING: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'orange', - label: '', - textColor: 'orange', - }, -}; - -type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; - -type DetailInfo = { - icon: string; - label: string; -}; - -const infoForDetailType: { [key: string]: DetailInfo } = { - chain: { - icon: '􀤆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), - }, - contract: { - icon: '􀉆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), - }, - to: { - icon: '􀉩', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), - }, - function: { - icon: '􀡅', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), - }, - sourceCodeVerification: { - icon: '􀕹', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), - }, - dateCreated: { - icon: '􀉉', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), - }, - nonce: { - icon: '􀆃', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), - }, -}; - -const CHARACTERS_PER_LINE = 40; -const LINE_HEIGHT = 11; -const LINE_GAP = 9; - -const estimateMessageHeight = (message: string) => { - const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); - const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; - - return messageHeight; -}; - -const formatDate = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - const diffTime = Math.abs(now.getTime() - date.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - const diffWeeks = Math.floor(diffDays / 7); - const diffMonths = Math.floor(diffDays / 30.44); - - if (diffDays === 0) { - return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); - } else if (diffDays === 1) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; - } else if (diffDays < 7) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; - } else if (diffWeeks === 1) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; - } else if (diffDays < 30.44) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; - } else if (diffMonths === 1) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; - } else if (diffDays < 365.25) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; - } else { - return date.toLocaleString('default', { month: 'short', year: 'numeric' }); - } -}; diff --git a/src/screens/WalletConnectApprovalSheet.js b/src/screens/WalletConnectApprovalSheet.js index 81fc33d918d..ffc166f11c6 100644 --- a/src/screens/WalletConnectApprovalSheet.js +++ b/src/screens/WalletConnectApprovalSheet.js @@ -406,11 +406,13 @@ export default function WalletConnectApprovalSheet() { - - {isScam && '􁅏 '} - {isVerified && '􀇻 '} - {formattedDappUrl} - + {formattedDappUrl && ( + + {isScam && '􁅏 '} + {isVerified && '􀇻 '} + {formattedDappUrl} + + )} diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index d78336c324e..e8b297d502e 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -28,6 +28,8 @@ import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; +import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; +import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; const WalletPage = styled(Page)({ ...position.sizeAsObject('100%'), @@ -81,6 +83,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { if (walletReady) { loadAccountLateData(); loadGlobalLateData(); + runWalletBackupStatusChecks(); } }, [loadAccountLateData, loadGlobalLateData, walletReady]); @@ -122,6 +125,9 @@ const WalletScreen: React.FC = ({ navigation, route }) => { + + {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} + ); diff --git a/src/state/performance/operations.ts b/src/state/performance/operations.ts index f30133ce5c6..a042200fc26 100644 --- a/src/state/performance/operations.ts +++ b/src/state/performance/operations.ts @@ -4,6 +4,7 @@ export enum Screens { SEND = 'Send', SEND_ENS = 'SendENS', WALLETCONNECT = 'WalletConnect', + MOBILE_WALLET_PROTOCOL = 'MobileWalletProtocol', } type RouteValues = (typeof Screens)[keyof typeof Screens]; diff --git a/src/storage/index.ts b/src/storage/index.ts index 824c7288ce7..14446bfbe4c 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -3,6 +3,7 @@ import { MMKV } from 'react-native-mmkv'; import { Account, Cards, Campaigns, Device, Review } from '@/storage/schema'; import { EthereumAddress, RainbowTransaction } from '@/entities'; import { Network } from '@/networks/types'; +import { SecureStorage } from '@coinbase/mobile-wallet-protocol-host'; /** * Generic storage class. DO NOT use this directly. Instead, use the exported @@ -12,8 +13,8 @@ export class Storage { protected sep = ':'; protected store: MMKV; - constructor({ id }: { id: string }) { - this.store = new MMKV({ id }); + constructor({ id, encryptionKey }: { id: string; encryptionKey?: string }) { + this.store = new MMKV({ id, encryptionKey }); } /** @@ -50,6 +51,13 @@ export class Storage { this.store.delete(scopes.join(this.sep)); } + /** + * Clear all values from storage + */ + clear() { + this.store.clearAll(); + } + /** * Remove many values from the same storage scope by keys * @@ -59,6 +67,21 @@ export class Storage { removeMany(scopes: [...Scopes], keys: Key[]) { keys.forEach(key => this.remove([...scopes, key])); } + + /** + * Encrypt the storage with a new key + * @param newEncryptionKey - The new encryption key + */ + encrypt(newEncryptionKey: string): void { + this.store.recrypt(newEncryptionKey); + } + + /** + * Remove encryption from the storage + */ + removeEncryption(): void { + this.store.recrypt(undefined); + } } /** @@ -88,3 +111,27 @@ export const cards = new Storage<[], Cards>({ id: 'cards' }); export const identifier = new Storage<[], { identifier: string }>({ id: 'identifier', }); + +/** + * Mobile Wallet Protocol storage + * + * @todo - fix any type here + */ +const mwpStorage = new Storage<[], { [key: string]: string }>({ id: 'mwp', encryptionKey: process.env.MWP_ENCRYPTION_KEY }); + +export const mwp: SecureStorage = { + get: async function (key: string): Promise { + const dataJson = mwpStorage.get([key]); + if (dataJson === undefined) { + return undefined; + } + return Promise.resolve(JSON.parse(dataJson) as T); + }, + set: async function (key: string, value: T): Promise { + const encoded = JSON.stringify(value); + mwpStorage.set([key], encoded); + }, + remove: async function (key: string): Promise { + mwpStorage.remove([key]); + }, +}; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts new file mode 100644 index 00000000000..8bb40dc1897 --- /dev/null +++ b/src/utils/formatDate.ts @@ -0,0 +1,28 @@ +import * as i18n from '@/languages'; + +export const formatDate = (dateString: string) => { + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - date.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30.44); + + if (diffDays === 0) { + return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); + } else if (diffDays === 1) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; + } else if (diffDays < 7) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; + } else if (diffWeeks === 1) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; + } else if (diffDays < 30.44) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; + } else if (diffMonths === 1) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; + } else if (diffDays < 365.25) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; + } else { + return date.toLocaleString('default', { month: 'short', year: 'numeric' }); + } +}; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 52f5a2a334f..e238b86c396 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -22,10 +22,256 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; +import { + EthereumAction, + isEthereumAction, + isHandshakeAction, + PersonalSignAction, + RequestMessage, + useMobileWalletProtocolHost, +} from '@coinbase/mobile-wallet-protocol-host'; +import { logger, RainbowError } from '@/logger'; +import { noop } from 'lodash'; +import { toUtf8String } from '@ethersproject/strings'; +import { BigNumber } from '@ethersproject/bignumber'; import { Address } from 'viem'; import { ChainId } from '@/networks/types'; -export type RequestSource = 'walletconnect' | 'browser'; +export enum RequestSource { + WALLETCONNECT = 'walletconnect', + BROWSER = 'browser', + MOBILE_WALLET_PROTOCOL = 'mobile-wallet-protocol', +} + +// Mobile Wallet Protocol + +interface HandleMobileWalletProtocolRequestProps + extends Omit, 'message' | 'handleRequestUrl' | 'sendFailureToClient'> { + request: RequestMessage; +} + +const constructEthereumActionPayload = (action: EthereumAction) => { + if (action.method === 'eth_sendTransaction') { + const { weiValue, fromAddress, toAddress, actionSource, gasPriceInWei, ...rest } = action.params; + return [ + { + ...rest, + from: fromAddress, + to: toAddress, + value: weiValue, + }, + ]; + } + + return Object.values(action.params); +}; + +const supportedMobileWalletProtocolActions: string[] = [ + 'eth_requestAccounts', + 'eth_sendTransaction', + 'eth_signTypedData_v4', + 'personal_sign', + 'wallet_switchEthereumChain', +]; + +export const handleMobileWalletProtocolRequest = async ({ + request, + fetchClientAppMetadata, + approveHandshake, + rejectHandshake, + approveAction, + rejectAction, + session, +}: HandleMobileWalletProtocolRequestProps): Promise => { + logger.debug(`Handling Mobile Wallet Protocol request: ${request.uuid}`); + + const { selected } = store.getState().wallets; + const { accountAddress } = store.getState().settings; + + let addressToUse = accountAddress; + let chainIdToUse = ChainId.mainnet; + + const isReadOnlyWallet = selected?.type === walletTypes.readOnly; + if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { + logger.debug('Rejecting request due to read-only wallet'); + watchingAlert(); + return Promise.reject(new Error('This wallet is read-only.')); + } + + const handleAction = async (currentIndex: number): Promise => { + const action = request.actions[currentIndex]; + logger.debug(`Handling action: ${action.kind}`); + + if (isHandshakeAction(action)) { + logger.debug(`Processing handshake action for ${action.appId}`); + const chainIds = Object.values(ChainId).filter(value => typeof value === 'number') as number[]; + const receivedTimestamp = Date.now(); + + const dappMetadata = await fetchClientAppMetadata(); + return new Promise((resolve, reject) => { + const routeParams: WalletconnectApprovalSheetRouteParams = { + receivedTimestamp, + meta: { + chainIds, + dappName: dappMetadata?.appName || dappMetadata?.appUrl || action.appName || action.appIconUrl || action.appId || '', + dappUrl: dappMetadata?.appUrl || action.appId || '', + imageUrl: maybeSignUri(dappMetadata?.iconUrl || action.appIconUrl), + isWalletConnectV2: false, + peerId: '', + dappScheme: action.callback, + proposedChainId: request.account?.networkId || chainIdToUse, + proposedAddress: request.account?.address || addressToUse, + }, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + timedOut: false, + callback: async (approved, chainId, address) => { + addressToUse = address; + chainIdToUse = chainId; + + if (approved) { + logger.debug(`Handshake approved for ${action.appId}`); + const success = await approveHandshake(dappMetadata); + resolve(success); + } else { + logger.debug(`Handshake rejected for ${action.appId}`); + await rejectHandshake('User rejected the handshake'); + reject('User rejected the handshake'); + } + }, + }; + + Navigation.handleAction( + Routes.WALLET_CONNECT_APPROVAL_SHEET, + routeParams, + getActiveRoute()?.name === Routes.WALLET_CONNECT_APPROVAL_SHEET + ); + }); + } else if (isEthereumAction(action)) { + logger.debug(`Processing ethereum action: ${action.method}`); + if (!supportedMobileWalletProtocolActions.includes(action.method)) { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type ${action.method}`)); + await rejectAction(action, { + message: 'Unsupported action type', + code: 4001, + }); + return false; + } + + if (action.method === 'wallet_switchEthereumChain') { + const chainId = BigNumber.from(action.params.chainId).toNumber(); + const isSupportedChain = Object.values(ChainId).includes(chainId); + if (!isSupportedChain) { + await rejectAction(action, { + message: 'Unsupported chain', + code: 4001, + }); + return false; + } + + await approveAction(action, { value: 'null' }); + return true; + } + + // NOTE: This is a workaround to approve the eth_requestAccounts action if the previous action was a handshake action. + const previousAction = request.actions[currentIndex - 1]; + if (previousAction && isHandshakeAction(previousAction)) { + logger.debug('Approving eth_requestAccounts'); + await approveAction(action, { + value: JSON.stringify({ + chain: request.account?.chain ?? 'eth', + networkId: chainIdToUse, + address: addressToUse, + }), + }); + return true; + } + + const nativeCurrency = store.getState().settings.nativeCurrency; + + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + if ((action as PersonalSignAction).params.message && (action as PersonalSignAction).params.message.type === 'Buffer') { + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + const messageFromBuffer = toUtf8String(Buffer.from((action as PersonalSignAction).params.message.data, 'hex')); + (action as PersonalSignAction).params.message = messageFromBuffer; + } + + const payload = { + method: action.method, + params: constructEthereumActionPayload(action), + }; + + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, request.account?.networkId ?? ChainId.mainnet); + const address = (action as PersonalSignAction).params.address || request.account?.address || accountAddress; + const requestWithDetails: RequestData = { + dappName: session?.dappName ?? session?.dappId ?? '', + dappUrl: session?.dappURL ?? '', + imageUrl: session?.dappImageURL ?? '', + address, + chainId: request.account?.networkId ?? ChainId.mainnet, + payload, + displayDetails, + }; + + return new Promise((resolve, reject) => { + const onSuccess = async (result: string) => { + logger.debug(`Ethereum action approved: [${action.method}]: ${result}`); + const success = await approveAction(action, { value: JSON.stringify(result) }); + resolve(success); + }; + + const onCancel = async (error?: Error) => { + if (error) { + logger.debug(`Ethereum action rejected: [${action.method}]: ${error.message}`); + await rejectAction(action, { + message: error.message, + code: 4001, + }); + reject(error.message); + } else { + logger.debug(`Ethereum action rejected: [${action.method}]: User rejected request`); + await rejectAction(action, { + message: 'User rejected request', + code: 4001, + }); + reject('User rejected request'); + } + }; + + Navigation.handleAction(Routes.CONFIRM_REQUEST, { + transactionDetails: requestWithDetails, + onSuccess, + onCancel, + onCloseScreen: noop, + chainId: request.account?.networkId ?? ChainId.mainnet, + address, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + }); + }); + } else { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type, ${action}`)); + return false; + } + }; + + const handleActions = async (actions: typeof request.actions, currentIndex: number = 0): Promise => { + if (currentIndex >= actions.length) { + logger.debug(`All actions completed successfully: ${actions.length}`); + return true; + } + + logger.debug(`Processing action ${currentIndex + 1} of ${actions.length}`); + const success = await handleAction(currentIndex); + if (success) { + return handleActions(actions, currentIndex + 1); + } else { + // stop processing if an action fails + return false; + } + }; + + // start processing actions starting at index 0 + return handleActions(request.actions); +}; // Dapp Browser @@ -58,7 +304,7 @@ export const handleDappBrowserConnectionPrompt = ( proposedChainId: dappData.chainId, proposedAddress: dappData.address, }, - source: 'browser', + source: RequestSource.BROWSER, timedOut: false, callback: async (approved, approvedChainId, accountAddress) => { if (approved) { @@ -83,16 +329,31 @@ export const handleDappBrowserConnectionPrompt = ( }); }; -export const handleDappBrowserRequest = async (request: Omit): Promise => { +const findWalletForAddress = async (address: string) => { + if (!address.trim()) { + return Promise.reject(new Error('Invalid address')); + } + const { wallets } = store.getState().wallets; - const selectedWallet = findWalletWithAccount(wallets!, request.address); - const isReadOnlyWallet = selectedWallet!.type === walletTypes.readOnly; + const selectedWallet = findWalletWithAccount(wallets!, address); + if (!selectedWallet) { + return Promise.reject(new Error('Wallet not found')); + } + + const isReadOnlyWallet = selectedWallet.type === walletTypes.readOnly; if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return Promise.reject(new Error('This wallet is read-only.')); } + + return selectedWallet; +}; + +export const handleDappBrowserRequest = async (request: Omit): Promise => { + await findWalletForAddress(request.address); + const nativeCurrency = store.getState().settings.nativeCurrency; - const displayDetails = getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); + const displayDetails = await getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); const requestWithDetails: RequestData = { ...request, @@ -124,7 +385,7 @@ export const handleDappBrowserRequest = async (request: Omit Date: Wed, 4 Sep 2024 12:36:41 -0400 Subject: [PATCH 73/78] fix bad merge from network -> chainId work (#6074) --- src/walletConnect/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 0faf3a4ec5e..5a45f567b37 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -718,12 +718,10 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se logger.debug(`[walletConnect]: handling request`, {}, logger.DebugContext.walletconnect); - const dappNetwork = ethereumUtils.getNetworkFromChainId(chainId); - const displayDetails = await getRequestDisplayDetails(event.params.request, nativeCurrency, dappNetwork); + const displayDetails = await getRequestDisplayDetails(event.params.request, nativeCurrency, chainId); const peerMeta = session.peer.metadata; const metadata = await fetchDappMetadata({ url: peerMeta.url, status: true }); - const dappName = metadata?.appName || peerMeta.name || lang.t(lang.l.walletconnect.unknown_url); const dappImage = metadata?.appLogo || peerMeta?.icons?.[0]; From 4ca3624fabdd7fa6108c04652c88e53c41631e98 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 4 Sep 2024 13:36:16 -0400 Subject: [PATCH 74/78] Hide collectibles based on feature flag (#6073) * hide collectibles section based on feature flag * change default remote config * re-enable for e2e * what happened to the linter --- .../RecyclerAssetList2/NFTEmptyState.tsx | 10 +++-- .../RecyclerAssetList2/NFTLoadingSkeleton.tsx | 8 ++++ .../WrappedCollectiblesHeader.tsx | 8 ++++ .../RecyclerAssetList2/WrappedNFT.tsx | 9 ++++ .../WrappedTokenFamilyHeader.tsx | 8 ++++ .../core/RawRecyclerList.tsx | 19 ++++++-- .../core/getLayoutProvider.tsx | 43 ++++++++++++++++++- .../profile-header/ProfileAvatarRow.tsx | 2 +- src/config/experimental.ts | 2 + src/config/experimentalHooks.ts | 1 - src/helpers/RainbowContext.tsx | 15 ++++++- src/hooks/useImportingWallet.ts | 2 +- src/hooks/useOnAvatarPress.ts | 2 +- src/model/remoteConfig.ts | 5 ++- src/screens/points/PointsScreen.tsx | 4 +- 15 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index 26f131b8638..cae66bb27ae 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -3,7 +3,7 @@ import Animated from 'react-native-reanimated'; import { Box, Stack, Text, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { TokenFamilyHeaderHeight } from './NFTLoadingSkeleton'; -import { MINTS, useExperimentalFlag } from '@/config'; +import { MINTS, NFTS_ENABLED, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; import { IS_TEST } from '@/env'; import { useMints } from '@/resources/mints'; @@ -44,7 +44,8 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp as={Animated.View} borderRadius={15} justifyContent="center" - paddingHorizontal="10px" + paddingVertical="12px" + paddingHorizontal="20px" style={[{ backgroundColor: isDarkMode ? SEPARATOR_COLOR : LIGHT_SEPARATOR_COLOR }]} > @@ -58,15 +59,18 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp }; export function NFTEmptyState() { - const { mints_enabled } = useRemoteConfig(); + const { mints_enabled, nfts_enabled } = useRemoteConfig(); const { accountAddress } = useAccountSettings(); const { data: { featuredMint }, } = useMints({ walletAddress: accountAddress }); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; + if (!nftsEnabled) return null; + return ( { }; const NFTLoadingSkeleton = ({ items = 5 }) => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + + if (!nftsEnabled) return null; + return ( {[...Array(items)].map((_, index) => ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index bb233041e82..de588f3e021 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -4,6 +4,9 @@ import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; import useNftSort from '@/hooks/useNFTsSortBy'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; const TokenFamilyHeaderHeight = 48; @@ -30,7 +33,12 @@ const getMenuItemIcon = (value: NftCollectionSortCriterion) => { }; const CollectiblesHeader = () => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const { nftSort, updateNFTSort } = useNftSort(); + + if (!nftsEnabled) return null; + return ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx index 17d7f6dd44e..9781c473237 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { TokenFamilyHeader } from '../../token-family'; import { useLatestCallback, useOpenFamilies } from '@/hooks'; import { ThemeContextProps } from '@/theme'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; type Props = { name: string; @@ -12,6 +15,9 @@ type Props = { }; export default React.memo(function WrappedTokenFamilyHeader({ name, total, image, theme, testID }: Props) { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + const { openFamilies, updateOpenFamilies } = useOpenFamilies(); const isFamilyOpen = openFamilies[name]; @@ -21,6 +27,8 @@ export default React.memo(function WrappedTokenFamilyHeader({ name, total, image }) ); + if (!nftsEnabled) return null; + return ( { return r1.uid !== r2.uid; @@ -54,6 +57,8 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ scrollIndicatorInsets?: object; type?: AssetListType; }) { + const remoteConfig = useRemoteConfig(); + const experimentalConfig = useContext(RainbowContext).config; const currentDataProvider = useMemoOne(() => dataProvider.cloneWithRows(briefSectionsData), [briefSectionsData]); const { isCoinListEdited, setIsCoinListEdited } = useCoinListEdited(); const y = useRecyclerAssetListPosition()!; @@ -65,8 +70,16 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ const cardIds = useMemo(() => getCardIdsForScreen(name as keyof typeof Routes), [getCardIdsForScreen, name]); const layoutProvider = useMemo( - () => getLayoutProvider(briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet), - [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet] + () => + getLayoutProvider({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + remoteConfig, + experimentalConfig, + }), + [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet, experimentalConfig] ); const { accountAddress } = useAccountSettings(); diff --git a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx index 13619bef984..ffe67eafd07 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx @@ -2,6 +2,11 @@ import { Dimension, Layout, LayoutManager, LayoutProvider } from 'recyclerlistvi import ViewDimensions from './ViewDimensions'; import { BaseCellType, CellType } from './ViewTypes'; import { deviceUtils } from '@/utils'; +import { getRemoteConfig, RainbowConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, REMOTE_CARDS, useExperimentalFlag } from '@/config'; +import { useContext } from 'react'; +import { RainbowContextType } from '@/helpers/RainbowContext'; +import { IS_TEST } from '@/env'; const getStyleOverridesForIndex = (indices: number[]) => (index: number) => { if (indices.includes(index)) { @@ -30,7 +35,24 @@ class BetterLayoutProvider extends LayoutProvider { } } -const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: boolean, cardIds: string[], isReadOnlyWallet: boolean) => { +const getLayoutProvider = ({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + experimentalConfig, + remoteConfig, +}: { + briefSectionsData: BaseCellType[]; + isCoinListEdited: boolean; + cardIds: string[]; + isReadOnlyWallet: boolean; + experimentalConfig: ReturnType>['config']; + remoteConfig: RainbowConfig; +}) => { + const remoteCardsEnabled = (remoteConfig.remote_cards_enabled || experimentalConfig[REMOTE_CARDS]) && !IS_TEST; + const nftsEnabled = (remoteConfig.nfts_enabled || experimentalConfig[NFTS_ENABLED]) && !IS_TEST; + const indicesToOverride = []; for (let i = 0; i < briefSectionsData.length; i++) { const val = briefSectionsData[i]; @@ -55,7 +77,24 @@ const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: dim.height = ViewDimensions[type].height; dim.width = ViewDimensions[type].width || dim.width; - if ((type === CellType.REMOTE_CARD_CAROUSEL && !cardIds.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) { + // NOTE: If NFTs are disabled, we don't want to render the NFTs section, so adjust the height to 0 + if ( + [ + CellType.NFTS_EMPTY, + CellType.NFTS_HEADER_SPACE_AFTER, + CellType.NFTS_HEADER_SPACE_BEFORE, + CellType.NFTS_HEADER, + CellType.NFTS_LOADING, + CellType.NFT, + CellType.FAMILY_HEADER, + ].includes(type) && + !nftsEnabled + ) { + dim.height = 0; + } + + // NOTE: If remote cards are disabled, we don't want to render the remote cards section, so adjust the height to 0 + if (type === CellType.REMOTE_CARD_CAROUSEL && (!remoteCardsEnabled || !cardIds.length || isReadOnlyWallet)) { dim.height = 0; } } diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx index 0e6e1fb85a0..19d0760d3c1 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx @@ -35,7 +35,7 @@ export function ProfileAvatarRow({ size = ProfileAvatarSize }: { size?: number } const ContextMenuButton = onAvatarPressProfile ? React.Fragment : ContextMenu; const handlePressMenuItem = useLatestCallback((e: any) => { - const index = avatarContextMenuConfig.menuItems?.findIndex(item => item.actionKey === e.nativeEvent.actionKey); + const index = avatarContextMenuConfig?.menuItems?.findIndex(item => item && item.actionKey === e.nativeEvent.actionKey); onSelectionCallback(index); }); diff --git a/src/config/experimental.ts b/src/config/experimental.ts index b380ec0b0ac..ba81ea0fc60 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -29,6 +29,7 @@ export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; export const FEATURED_RESULTS = 'Featured Results'; +export const NFTS_ENABLED = 'Nfts Enabled'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -66,6 +67,7 @@ export const defaultConfig: Record = { [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, [FEATURED_RESULTS]: { settings: true, value: false }, + [NFTS_ENABLED]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/config/experimentalHooks.ts b/src/config/experimentalHooks.ts index 4fd975e45b3..f99fa930c37 100644 --- a/src/config/experimentalHooks.ts +++ b/src/config/experimentalHooks.ts @@ -6,7 +6,6 @@ import isTestFlight from '@/helpers/isTestFlight'; const useExperimentalFlag = (name: any) => { if (IS_DEV || isTestFlight) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'config' does not exist on type '{}'. // eslint-disable-next-line react-hooks/rules-of-hooks return useContext(RainbowContext).config[name]; } else { diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index 44dca36510b..edb49f3a94e 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -4,7 +4,7 @@ import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; -import { defaultConfig } from '../config/experimental'; +import { defaultConfig } from '@/config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; @@ -16,7 +16,18 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -export const RainbowContext = createContext({}); +export type RainbowContextType = { + config: Record; + setConfig: (newConfig: Record) => void; + setGlobalState: (newState: any) => void; +}; + +export const RainbowContext = createContext({ + config: {}, + setConfig: () => {}, + setGlobalState: () => {}, +}); + const storageKey = 'config'; const storage = new MMKV({ diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index d2fce5085a5..719c7a65447 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -135,7 +135,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { } setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; - avatarUrl = avatarUrl || avatar?.imageUrl; + avatarUrl = avatarUrl || (avatar && avatar?.imageUrl); setBusy(false); startImportProfile(name, guardedForceColor, address, avatarUrl); analytics.track('Show wallet profile modal for ENS address', { diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index bda2d6f20f5..d4b3f3c9bac 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -238,7 +238,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { ].filter(x => x), }; - const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item.actionTitle).concat(ios ? ['Cancel'] : []); + const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item && item.actionTitle).concat(ios ? ['Cancel'] : []); const onAvatarPressProfile = useCallback(() => { navigate(Routes.PROFILE_SHEET, { diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index bf79c65490d..e2fa3709f8c 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -92,6 +92,7 @@ export interface RainbowConfig extends Record degen_mode: boolean; featured_results: boolean; + nfts_enabled: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -175,6 +176,7 @@ export const DEFAULT_CONFIG: RainbowConfig = { degen_mode: false, featured_results: false, + nfts_enabled: true, }; export async function fetchRemoteConfig(): Promise { @@ -230,7 +232,8 @@ export async function fetchRemoteConfig(): Promise { key === 'idfa_check_enabled' || key === 'rewards_enabled' || key === 'degen_mode' || - key === 'featured_results' + key === 'featured_results' || + key === 'nfts_enabled' ) { config[key] = entry.asBoolean(); } else { diff --git a/src/screens/points/PointsScreen.tsx b/src/screens/points/PointsScreen.tsx index 3d24b8ded00..61fe24d9f2d 100644 --- a/src/screens/points/PointsScreen.tsx +++ b/src/screens/points/PointsScreen.tsx @@ -75,7 +75,7 @@ export function PointsScreen() { navigate(Routes.CHANGE_WALLET_SHEET)} scaleTo={0.8} overflowMargin={50}> {accountImage ? ( @@ -83,7 +83,7 @@ export function PointsScreen() { )} - ) + ) : null } rightComponent={pointsNotificationsToggleEnabled ? : undefined} title={rewardsEnabled ? i18n.t(i18n.l.account.tab_rewards) : i18n.t(i18n.l.account.tab_points)} From f9b73b0a0a698a15ed38519a17f3d2851bf977cf Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 4 Sep 2024 14:47:18 -0400 Subject: [PATCH 75/78] ens send fix (#6075) * fix * add gas estimation checks --- src/screens/SendConfirmationSheet.tsx | 12 ++++++++++-- src/screens/SendSheet.js | 10 +++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 9d1613e22b5..4059d09a924 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -669,8 +669,16 @@ export const SendConfirmationSheet = () => { /> {isENS && ( - /* @ts-expect-error JavaScript component */ - + )} diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 1d7438f1519..e16bf35c613 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -765,7 +765,15 @@ export default function SendSheet(props) { const assetChainId = selected.chainId; const currentProviderChainId = currentProvider._network.chainId; - if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { + if ( + !!accountAddress && + amountDetails.assetAmount !== '' && + Object.entries(selected).length && + assetChainId === currentChainId && + currentProviderChainId === currentChainId && + isValidAddress && + !isEmpty(selected) + ) { estimateGasLimit( { address: accountAddress, From 34fe053e77820903ffe3e7b5acded8cbda3e0e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Wed, 4 Sep 2024 15:08:52 -0400 Subject: [PATCH 76/78] use correct chainId (#6076) --- src/redux/gas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/redux/gas.ts b/src/redux/gas.ts index 503017aad33..f6824d43c2d 100644 --- a/src/redux/gas.ts +++ b/src/redux/gas.ts @@ -474,7 +474,6 @@ export const gasPricesStartPolling = defaultGasLimit, gasLimit, selectedGasFee, - chainId, selectedGasFee: lastSelectedGasFee, gasFeesBySpeed: lastGasFeesBySpeed, currentBlockParams, From 705f4e3c477e7d8f6b5a81d883abe2ab726e121c Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 4 Sep 2024 15:53:21 -0400 Subject: [PATCH 77/78] change to id --- src/components/FeaturedResult/FeaturedResultCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FeaturedResult/FeaturedResultCard.tsx b/src/components/FeaturedResult/FeaturedResultCard.tsx index 594449258ea..3bf517c3e8a 100644 --- a/src/components/FeaturedResult/FeaturedResultCard.tsx +++ b/src/components/FeaturedResult/FeaturedResultCard.tsx @@ -25,7 +25,7 @@ export const FeaturedResultCard = ({ featuredResultId, onNavigate, Card, ...prop } await trackFeaturedResult({ - featuredResultCreativeId: featuredResult.advertiserId, + featuredResultCreativeId: featuredResult.id, placementId: featuredResult.placementSlug, impressionId: featuredResult.impressionId, type: TrackFeaturedResultType.Impression, From 281286843ab0f7768715aca291aa01638c68d9a4 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 4 Sep 2024 15:58:01 -0400 Subject: [PATCH 78/78] also fix clicks --- src/components/FeaturedResult/FeaturedResultCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FeaturedResult/FeaturedResultCard.tsx b/src/components/FeaturedResult/FeaturedResultCard.tsx index 3bf517c3e8a..319b89274f1 100644 --- a/src/components/FeaturedResult/FeaturedResultCard.tsx +++ b/src/components/FeaturedResult/FeaturedResultCard.tsx @@ -40,7 +40,7 @@ export const FeaturedResultCard = ({ featuredResultId, onNavigate, Card, ...prop const [cta] = featuredResult.ctas || []; await trackFeaturedResult({ - featuredResultCreativeId: featuredResult.advertiserId, + featuredResultCreativeId: featuredResult.id, placementId: featuredResult.placementSlug, impressionId: featuredResult.impressionId, type: TrackFeaturedResultType.Click,