diff --git a/scripts/networks.js b/scripts/networks.js index 24533afbdcd..d4fb99379e0 100644 --- a/scripts/networks.js +++ b/scripts/networks.js @@ -17,6 +17,7 @@ async function fetchData() { badgeURL } testnet + internal opStack defaultExplorer { url diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 4838c221cac..da79e3a04f4 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -24,7 +24,7 @@ import { parseSearchAsset } from '@/__swaps__/utils/assets'; import { AbsolutePortalRoot } from '@/components/AbsolutePortal'; import { useDelayedMount } from '@/hooks/useDelayedMount'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { useSwapsStore } from '@/state/swaps/swapsStore'; +import { swapsStore, useSwapsStore } from '@/state/swaps/swapsStore'; import { SwapWarning } from './components/SwapWarning'; import { clearCustomGasSettings } from './hooks/useCustomGas'; import { SwapProvider, useSwapContext } from './providers/swap-provider'; @@ -109,6 +109,7 @@ const useCleanupOnUnmount = () => { useEffect(() => { return () => { const highestValueEth = userAssetsStore.getState().getHighestValueEth(); + const preferredNetwork = swapsStore.getState().preferredNetwork; const parsedAsset = highestValueEth ? parseSearchAsset({ assetWithPrice: undefined, @@ -123,7 +124,7 @@ const useCleanupOnUnmount = () => { outputAsset: null, outputSearchQuery: '', quote: null, - selectedOutputChainId: parsedAsset?.chainId ?? ChainId.mainnet, + selectedOutputChainId: parsedAsset?.chainId ?? preferredNetwork ?? ChainId.mainnet, }); userAssetsStore.setState({ filter: 'all', inputSearchQuery: '' }); diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 643cfc7dab1..02532433b44 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -58,7 +58,8 @@ function EstimatedGasFee() { } function SelectedGas({ isPill }: { isPill?: boolean }) { - const chainId = swapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); + const preferredNetwork = swapsStore(s => s.preferredNetwork); + const chainId = swapsStore(s => s.inputAsset?.chainId || preferredNetwork || ChainId.mainnet); const selectedGasSpeed = useSelectedGasSpeed(chainId); return ( @@ -108,7 +109,8 @@ function keys(obj: Record | undefined) { const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; children: ReactNode }) => { const { SwapNavigation } = useSwapContext(); - const chainId = swapsStore(s => s.inputAsset?.chainId || ChainId.mainnet); + const preferredNetwork = swapsStore(s => s.preferredNetwork); + const chainId = swapsStore(s => s.inputAsset?.chainId || preferredNetwork || ChainId.mainnet); const metereologySuggestions = useMeteorologySuggestions({ chainId }); const customGasSettings = useCustomGasSettings(chainId); diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index a77dccd683d..9d47932ba9b 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -6,10 +6,11 @@ import { useMemo } from 'react'; import { formatUnits } from 'viem'; import { useAccountSettings } from '@/hooks'; -import { useSyncedSwapQuoteStore } from '../providers/SyncSwapStateAndSharedValues'; +import { calculateGasFeeWorklet, useSyncedSwapQuoteStore } from '../providers/SyncSwapStateAndSharedValues'; import { GasSettings } from './useCustomGas'; import { useSelectedGas } from './useSelectedGas'; import { useSwapEstimatedGasLimit } from './useSwapEstimatedGasLimit'; +import { useSwapsStore } from '@/state/swaps/swapsStore'; function safeBigInt(value: string) { try { @@ -19,24 +20,6 @@ function safeBigInt(value: string) { } } -const isFeeNaN = (value: string | undefined) => isNaN(Number(value)) || typeof value === 'undefined'; - -export function calculateGasFee(gasSettings: GasSettings, gasLimit: string) { - if (gasSettings.isEIP1559) { - if (isFeeNaN(gasSettings.maxBaseFee) || isFeeNaN(gasSettings.maxPriorityFee)) { - return null; - } - - return add(gasSettings.maxBaseFee, gasSettings.maxPriorityFee); - } - - if (isFeeNaN(gasSettings.gasPrice)) { - return null; - } - - return multiply(gasLimit, gasSettings.gasPrice); -} - export function useEstimatedGasFee({ chainId, gasLimit, @@ -52,13 +35,15 @@ export function useEstimatedGasFee({ return useMemo(() => { if (!gasLimit || !gasSettings || !nativeNetworkAsset?.price) return; - const fee = calculateGasFee(gasSettings, gasLimit); - if (!fee) return; + const gasFee = calculateGasFeeWorklet(gasSettings, gasLimit); + if (isNaN(Number(gasFee))) { + return; + } const networkAssetPrice = nativeNetworkAsset.price.value?.toString(); - if (!networkAssetPrice) return `${formatNumber(weiToGwei(fee))} Gwei`; + if (!networkAssetPrice) return `${formatNumber(weiToGwei(gasFee))} Gwei`; - const feeFormatted = formatUnits(safeBigInt(fee), nativeNetworkAsset.decimals).toString(); + const feeFormatted = formatUnits(safeBigInt(gasFee), nativeNetworkAsset.decimals).toString(); const feeInUserCurrency = multiply(networkAssetPrice, feeFormatted); return convertAmountToNativeDisplayWorklet(feeInUserCurrency, nativeCurrency, true); @@ -66,7 +51,8 @@ export function useEstimatedGasFee({ } export function useSwapEstimatedGasFee(overrideGasSettings?: GasSettings) { - const { assetToSell, quote, chainId = ChainId.mainnet } = useSyncedSwapQuoteStore(); + const preferredNetwork = useSwapsStore(s => s.preferredNetwork); + const { assetToSell, quote, chainId = preferredNetwork || ChainId.mainnet } = useSyncedSwapQuoteStore(); const gasSettings = useSelectedGas(chainId); const { data: estimatedGasLimit, isFetching } = useSwapEstimatedGasLimit({ chainId, assetToSell, quote }); diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index 3e3792d88b8..ffd07d2654c 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -6,9 +6,10 @@ import { ChainId } from '@/chains/types'; import { SharedValue, runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { ParsedAddressAsset } from '@/entities'; import { ethereumUtils } from '@/utils'; +import { swapsStore } from '@/state/swaps/swapsStore'; export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { - const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? ChainId.mainnet); + const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? swapsStore.getState().preferredNetwork ?? ChainId.mainnet); const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset({ chainId: chainId.value })); const getNativeAssetForNetwork = useCallback( diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 46dba532c92..99e1dfc56b8 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -26,6 +26,7 @@ import { GasSettings } from '../hooks/useCustomGas'; import { useSelectedGas } from '../hooks/useSelectedGas'; import { useSwapEstimatedGasLimit } from '../hooks/useSwapEstimatedGasLimit'; import { useSwapContext } from './swap-provider'; +import { useSwapsStore } from '@/state/swaps/swapsStore'; const BUFFER_RATIO = 0.5; @@ -89,18 +90,13 @@ export function calculateGasFeeWorklet(gasSettings: GasSettings, gasLimit: strin 'worklet'; if (gasSettings.isEIP1559) { - if (isFeeNaNWorklet(gasSettings.maxBaseFee) || isFeeNaNWorklet(gasSettings.maxPriorityFee)) { - return null; - } - - return sumWorklet(gasSettings.maxBaseFee || '0', gasSettings.maxPriorityFee || '0'); - } - - if (isFeeNaNWorklet(gasSettings.gasPrice)) { - return null; + const maxBaseFee = isFeeNaNWorklet(gasSettings.maxBaseFee) ? '0' : gasSettings.maxBaseFee; + const maxPriorityFee = isFeeNaNWorklet(gasSettings.maxPriorityFee) ? '0' : gasSettings.maxPriorityFee; + return mulWorklet(gasLimit, sumWorklet(maxBaseFee, maxPriorityFee)); } - return mulWorklet(gasLimit, gasSettings.gasPrice); + const gasPrice = isFeeNaNWorklet(gasSettings.gasPrice) ? '0' : gasSettings.gasPrice; + return mulWorklet(gasLimit, gasPrice); } export function formatUnitsWorklet(value: string, decimals: number) { @@ -138,8 +134,12 @@ const getHasEnoughFundsForGasWorklet = ({ export function SyncGasStateToSharedValues() { const { hasEnoughFundsForGas, internalSelectedInputAsset } = useSwapContext(); + const preferredNetwork = useSwapsStore(s => s.preferredNetwork); - const initialChainId = useMemo(() => internalSelectedInputAsset.value?.chainId || ChainId.mainnet, [internalSelectedInputAsset]); + const initialChainId = useMemo( + () => internalSelectedInputAsset.value?.chainId || preferredNetwork || ChainId.mainnet, + [internalSelectedInputAsset, preferredNetwork] + ); const { assetToSell, chainId = initialChainId, quote } = useSyncedSwapQuoteStore(); const gasSettings = useSelectedGas(chainId); @@ -198,7 +198,7 @@ export function SyncGasStateToSharedValues() { } const gasFee = calculateGasFeeWorklet(gasSettings, estimatedGasLimit); - if (gasFee === null || isNaN(Number(gasFee))) { + if (isNaN(Number(gasFee))) { return; } diff --git a/src/chains/types.ts b/src/chains/types.ts index 9592c5c199f..92425c2c14a 100644 --- a/src/chains/types.ts +++ b/src/chains/types.ts @@ -147,6 +147,7 @@ export interface BackendNetwork { badgeURL: string; }; testnet: boolean; + internal: boolean; opStack: boolean; defaultExplorer: { url: string; diff --git a/src/chains/utils/backendNetworks.ts b/src/chains/utils/backendNetworks.ts index 61715e2650b..0082b40abd0 100644 --- a/src/chains/utils/backendNetworks.ts +++ b/src/chains/utils/backendNetworks.ts @@ -1,7 +1,7 @@ import { Chain } from 'viem'; import { mainnet } from 'viem/chains'; -import { RPC_PROXY_API_KEY } from '@/env'; +import { IS_DEV, RPC_PROXY_API_KEY } from '@/env'; import { BackendNetwork } from '../types'; const proxyBackendNetworkRpcEndpoint = (endpoint: string) => { @@ -45,5 +45,5 @@ export function transformBackendNetworksToChains(networks?: BackendNetwork[]): C if (!networks) { return []; } - return networks.map(network => transformBackendNetworkToChain(network)); + return networks.filter(network => IS_DEV || !network.internal).map(network => transformBackendNetworkToChain(network)); } diff --git a/src/components/DappBrowser/BrowserTab.tsx b/src/components/DappBrowser/BrowserTab.tsx index 893b0c7f86b..c20d3d5cc17 100644 --- a/src/components/DappBrowser/BrowserTab.tsx +++ b/src/components/DappBrowser/BrowserTab.tsx @@ -48,7 +48,7 @@ import { USER_AGENT, USER_AGENT_APPLICATION_NAME, } from './constants'; -import { handleProviderRequestApp } from './handleProviderRequest'; +import { getDappHost, handleProviderRequestApp } from './handleProviderRequest'; import { useAnimatedTab } from './hooks/useAnimatedTab'; import { useTabScreenshotProvider } from './hooks/useTabScreenshotProvider'; import { freezeWebsite, getWebsiteMetadata, unfreezeWebsite } from './scripts'; @@ -254,15 +254,15 @@ const FreezableWebViewComponent = ({ [addRecent, animatedActiveTabIndex, backgroundColor, currentlyOpenTabIds, setLogo, setTitle, tabId, tabUrl] ); - const handleOnLoadStart = useCallback( - (event: { nativeEvent: { url: string | URL; title: string } }) => { + const handleOnLoad = useCallback( + (event: WebViewEvent) => { + if (event.nativeEvent.loading) return; const { origin } = new URL(event.nativeEvent.url); if (typeof webViewRef !== 'function' && webViewRef?.current) { if (!webViewRef?.current) { return; } - const messenger = appMessenger(webViewRef.current, tabId, origin); currentMessengerRef.current = messenger; } @@ -270,19 +270,6 @@ const FreezableWebViewComponent = ({ [webViewRef, tabId] ); - const handleOnLoad = useCallback((event: WebViewEvent) => { - if (event.nativeEvent.loading) return; - // placeholder - }, []); - - const handleOnLoadEnd = useCallback(() => { - return; - }, []); - - const handleOnError = useCallback(() => { - return; - }, []); - const handleShouldStartLoadWithRequest = useCallback( (request: { url: string }) => { if (request.url.startsWith('rainbow://wc') || request.url.startsWith('https://rnbwappdotcom.app.link/')) { @@ -310,13 +297,13 @@ const FreezableWebViewComponent = ({ const handleNavigationStateChange = useCallback( (navState: WebViewNavigation) => { - if (navState.url) { - runOnUI(updateTabUrlWorklet)(navState.url, tabId); + if (navState.navigationType !== 'other' || getDappHost(navState.url) === getDappHost(tabUrl)) { // ⚠️ TODO: Reintegrate canGoBack/canGoForward - we can just set it here now, reliably, because this // function no longer modifies the same URL state that's passed to the WebView's source prop. + runOnUI(updateTabUrlWorklet)(navState.url, tabId); } }, - [tabId, updateTabUrlWorklet] + [tabUrl, updateTabUrlWorklet, tabId] ); const handleOnOpenWindow = useCallback( @@ -364,10 +351,7 @@ const FreezableWebViewComponent = ({ { */ function getEtherscanHostForNetwork({ chainId }: { chainId: ChainId }): string { const base_host = 'etherscan.io'; - const blockExplorer = defaultChains[chainId].blockExplorers?.default?.url; + const blockExplorer = defaultChains[chainId]?.blockExplorers?.default?.url; const network = chainsName[chainId]; if (network && isTestnetChain({ chainId })) { @@ -354,7 +354,7 @@ export const getFirstTransactionTimestamp = async (address: EthereumAddress): Pr }; function getBlockExplorer({ chainId }: { chainId: ChainId }) { - return defaultChains[chainId].blockExplorers?.default.name || 'etherscan'; + return defaultChains[chainId]?.blockExplorers?.default.name || 'etherscan'; } function openAddressInBlockExplorer({ address, chainId }: { address: EthereumAddress; chainId: ChainId }) {