From 4557da5f86dd42ed254d1c256955bd26bdc0c255 Mon Sep 17 00:00:00 2001 From: peachbits Date: Wed, 10 Jan 2024 12:39:59 -0800 Subject: [PATCH 1/3] Move tag to end of error message --- src/components/services/AirshipInstance.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/services/AirshipInstance.tsx b/src/components/services/AirshipInstance.tsx index 1974eb3dc02..abb1ba18c61 100644 --- a/src/components/services/AirshipInstance.tsx +++ b/src/components/services/AirshipInstance.tsx @@ -19,8 +19,8 @@ export interface ShowErrorWarningOptions { */ export function showError(error: unknown, options: ShowErrorWarningOptions = {}): void { const { trackError = true, tag } = options - const tagMessage = tag == null ? '' : `Tag: ${tag}. ` - const translatedMessage = tagMessage + translateError(error) + const tagMessage = tag == null ? '' : ` Tag: ${tag}.` + const translatedMessage = translateError(error) + tagMessage if (trackError) { if (error instanceof Error) { // Log error with stack trace and a translated message to Bugsnag: From 65bb61163ca8a3b0392b5126974a713162aa1c13 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 9 Jan 2024 18:42:22 -0800 Subject: [PATCH 2/3] Force useAsyncEffect users to add tags --- CHANGELOG.md | 1 + src/components/Main.tsx | 12 +- src/components/charts/SwipeChart.tsx | 134 +++++------ src/components/modals/CategoryModal.tsx | 10 +- .../modals/PermissionsSettingModal.tsx | 16 +- src/components/modals/WalletListMenuModal.tsx | 112 ++++----- src/components/scenes/CoinRankingScene.tsx | 70 +++--- .../scenes/CreateWalletCompletionScene.tsx | 48 ++-- .../CryptoExchangeQuoteProcessingScene.tsx | 26 ++- src/components/scenes/EdgeLoginScene.tsx | 26 ++- .../scenes/Fio/FioCreateHandleScene.tsx | 36 +-- .../scenes/Fio/FioStakingChangeScene.tsx | 10 +- .../scenes/Fio/FioStakingOverviewScene.tsx | 10 +- src/components/scenes/GettingStartedScene.tsx | 12 +- .../scenes/Loans/LoanCreateScene.tsx | 44 ++-- .../scenes/Loans/LoanDashboardScene.tsx | 38 +-- .../scenes/Loans/LoanDetailsScene.tsx | 24 +- .../scenes/Loans/LoanManageScene.tsx | 170 +++++++------- src/components/scenes/LoginScene.tsx | 12 +- .../scenes/MigrateWalletCalculateFeeScene.tsx | 162 ++++++------- .../scenes/MigrateWalletCompletionScene.tsx | 220 +++++++++--------- src/components/scenes/SendScene2.tsx | 150 ++++++------ .../scenes/Staking/StakeOverviewScene.tsx | 36 +-- src/components/scenes/WalletListScene.tsx | 3 +- src/components/scenes/WcConnectScene.tsx | 12 +- src/components/scenes/WcConnectionsScene.tsx | 14 +- src/components/services/DeepLinkingManager.ts | 70 +++--- src/components/services/EdgeCoreManager.tsx | 12 +- .../services/WalletConnectService.tsx | 64 ++--- .../themed/EdgeProviderComponent.tsx | 24 +- src/components/ui4/scenes/HomeSceneUi4.tsx | 3 +- src/hooks/useAsyncEffect.ts | 8 +- src/hooks/useAsyncValue.ts | 34 +-- src/plugins/gui/scenes/AddressFormScene.tsx | 16 +- src/plugins/gui/scenes/SepaFormScene.tsx | 20 +- 35 files changed, 907 insertions(+), 752 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6234bd8d7ce..b0fc5e87157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - changed: Do not write tx.metadata in fiat sell transactions - changed: Use new EdgeTransaction.savedAction to show extended transaction info in tx list and tx details - changed: Use new EdgeTxAction data for Thorchain and Tron stake plugins +- changed: Make useAsyncEffect tags required - fixed: USP vs legacy landing experiment distribution - fixed: Paybis sell from Tron USDT - fixed: Remove `minWidth` style from stake option card diff --git a/src/components/Main.tsx b/src/components/Main.tsx index 2194cd4f3d2..9aa0222eea5 100644 --- a/src/components/Main.tsx +++ b/src/components/Main.tsx @@ -256,10 +256,14 @@ export const Main = () => { }) // Wait for the experiment config to initialize before rendering anything - useAsyncEffect(async () => { - if (isMaestro()) return - setLegacyLanding((await getExperimentConfigValue('legacyLanding')) === 'legacyLanding') - }, []) + useAsyncEffect( + async () => { + if (isMaestro()) return + setLegacyLanding((await getExperimentConfigValue('legacyLanding')) === 'legacyLanding') + }, + [], + 'setLegacyLanding' + ) return legacyLanding == null ? ( diff --git a/src/components/charts/SwipeChart.tsx b/src/components/charts/SwipeChart.tsx index 8d2e3a0a7bd..e9fe41563a8 100644 --- a/src/components/charts/SwipeChart.tsx +++ b/src/components/charts/SwipeChart.tsx @@ -171,76 +171,80 @@ const SwipeChartComponent = (params: Props) => { const maxPriceDataPoint = React.useMemo(() => chartData.find(point => point.y === maxPrice), [chartData, maxPrice]) // Fetch/cache chart data, set shared animation transition values - useAsyncEffect(async () => { - if (!isLoading) { - setIsLoading(true) - setChartData([]) - sMinMaxOpacity.value = 0 - - // Use cached data, if available - const cachedChartData = cachedTimespanChartData.get(selectedTimespan) - - const delayShowMinMaxLabels = () => { - // Delay the appearance of the min/max price labels while the chart - // price line finishes its entering animation - sMinMaxOpacity.value = withDelay(ANIMATION_DURATION.maxMinFadeInDelay, withTiming(1, { duration: ANIMATION_DURATION.maxMinFadeIn })) - } - - try { - if (cachedChartData != null) { - // The chart price line animation is slow when transitioning directly - // between datasets. - // Add a delay so the component can get re-mounted with fresh data - // instead. - setTimeout(() => { - setChartData(cachedChartData) - setIsLoading(false) - delayShowMinMaxLabels() - }, 10) - } else { - const unixNow = Math.trunc(new Date().getTime() / 1000) - const fromParam = unixNow - queryFromTimeOffset - const fetchPath = sprintf(DATASET_URL_4S, assetId, defaultFiat, fromParam, unixNow) - let fetchUrl = `${COINGECKO_URL}${fetchPath}` - do { - // Construct the dataset query - const response = await fetch(fetchUrl) - const result = await response.json() - const marketChartRange = asCoinGeckoMarketApi(result) - if ('status' in marketChartRange) { - if (marketChartRange.status.error_code === 429) { - // Rate limit error - if (!fetchUrl.includes('x_cg_pro_api_key')) { - fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}` - continue - } - } - throw new Error(String(marketChartRange)) - } else { - const rawChartData = marketChartRange.prices.map(rawDataPoint => { - return { - x: new Date(rawDataPoint[0]), - y: rawDataPoint[1] - } - }) - const reducedChartData = reduceChartData(rawChartData, selectedTimespan) + useAsyncEffect( + async () => { + if (!isLoading) { + setIsLoading(true) + setChartData([]) + sMinMaxOpacity.value = 0 + + // Use cached data, if available + const cachedChartData = cachedTimespanChartData.get(selectedTimespan) + + const delayShowMinMaxLabels = () => { + // Delay the appearance of the min/max price labels while the chart + // price line finishes its entering animation + sMinMaxOpacity.value = withDelay(ANIMATION_DURATION.maxMinFadeInDelay, withTiming(1, { duration: ANIMATION_DURATION.maxMinFadeIn })) + } - setChartData(reducedChartData) - cachedTimespanChartData.set(selectedTimespan, reducedChartData) - setCachedChartData(cachedTimespanChartData) + try { + if (cachedChartData != null) { + // The chart price line animation is slow when transitioning directly + // between datasets. + // Add a delay so the component can get re-mounted with fresh data + // instead. + setTimeout(() => { + setChartData(cachedChartData) setIsLoading(false) delayShowMinMaxLabels() - break - } - } while (true) + }, 10) + } else { + const unixNow = Math.trunc(new Date().getTime() / 1000) + const fromParam = unixNow - queryFromTimeOffset + const fetchPath = sprintf(DATASET_URL_4S, assetId, defaultFiat, fromParam, unixNow) + let fetchUrl = `${COINGECKO_URL}${fetchPath}` + do { + // Construct the dataset query + const response = await fetch(fetchUrl) + const result = await response.json() + const marketChartRange = asCoinGeckoMarketApi(result) + if ('status' in marketChartRange) { + if (marketChartRange.status.error_code === 429) { + // Rate limit error + if (!fetchUrl.includes('x_cg_pro_api_key')) { + fetchUrl = `${COINGECKO_URL_PRO}${fetchPath}&x_cg_pro_api_key=${ENV.COINGECKO_API_KEY}` + continue + } + } + throw new Error(String(marketChartRange)) + } else { + const rawChartData = marketChartRange.prices.map(rawDataPoint => { + return { + x: new Date(rawDataPoint[0]), + y: rawDataPoint[1] + } + }) + const reducedChartData = reduceChartData(rawChartData, selectedTimespan) + + setChartData(reducedChartData) + cachedTimespanChartData.set(selectedTimespan, reducedChartData) + setCachedChartData(cachedTimespanChartData) + setIsLoading(false) + delayShowMinMaxLabels() + break + } + } while (true) + } + } catch (e: any) { + showWarning(`Failed to retrieve market data for ${currencyCode}.`) + console.error(e) } - } catch (e: any) { - showWarning(`Failed to retrieve market data for ${currencyCode}.`) - console.error(e) } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTimespan]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [selectedTimespan], + 'swipeChart' + ) React.useEffect(() => { if (chartData.length > 0) { diff --git a/src/components/modals/CategoryModal.tsx b/src/components/modals/CategoryModal.tsx index 79c9c41a1c3..db5258a68e6 100644 --- a/src/components/modals/CategoryModal.tsx +++ b/src/components/modals/CategoryModal.tsx @@ -47,9 +47,13 @@ export function CategoryModal(props: Props) { const categories = useSelector(state => state.ui.subcategories) // Load the categories from disk: - useAsyncEffect(async () => { - await dispatch(getSubcategories()) - }, [dispatch]) + useAsyncEffect( + async () => { + await dispatch(getSubcategories()) + }, + [dispatch], + 'categoryModal' + ) const sortedCategories = React.useMemo(() => { // Transform the raw categories into row objects: diff --git a/src/components/modals/PermissionsSettingModal.tsx b/src/components/modals/PermissionsSettingModal.tsx index 986b55e0008..8d6fe4bf704 100644 --- a/src/components/modals/PermissionsSettingModal.tsx +++ b/src/components/modals/PermissionsSettingModal.tsx @@ -26,12 +26,16 @@ export function PermissionsSettingModal(props: { ? sprintf(lstrings.contacts_permission_modal_enable_settings_mandatory, name, permission) : sprintf(lstrings.contacts_permission_modal_enable_settings, name, permission) - useAsyncEffect(async () => { - if (!isAppForeground || !mandatory) return - const status = await check(permissionNames[permission]) - if (!checkIfDenied(status)) bridge.resolve(false) - return () => {} - }, [permission, isAppForeground]) + useAsyncEffect( + async () => { + if (!isAppForeground || !mandatory) return + const status = await check(permissionNames[permission]) + if (!checkIfDenied(status)) bridge.resolve(false) + return () => {} + }, + [permission, isAppForeground], + 'PermissionsSettingModal' + ) const handlePress = () => { openSettings().catch(showError) diff --git a/src/components/modals/WalletListMenuModal.tsx b/src/components/modals/WalletListMenuModal.tsx index 3365ba91a58..85717531dd6 100644 --- a/src/components/modals/WalletListMenuModal.tsx +++ b/src/components/modals/WalletListMenuModal.tsx @@ -140,72 +140,76 @@ export function WalletListMenuModal(props: Props) { dispatch(walletListMenuAction(navigation, walletId, option, tokenId)).catch(error => showError(error)) }) - useAsyncEffect(async () => { - if (wallet == null) { - setOptions([ - { label: lstrings.string_get_raw_keys, value: 'getRawKeys' }, - { label: lstrings.string_archive_wallet, value: 'rawDelete' } - ]) - return - } + useAsyncEffect( + async () => { + if (wallet == null) { + setOptions([ + { label: lstrings.string_get_raw_keys, value: 'getRawKeys' }, + { label: lstrings.string_archive_wallet, value: 'rawDelete' } + ]) + return + } - if (tokenId != null) { - setOptions([ - { - label: lstrings.string_resync, - value: 'resync' - }, - { - label: lstrings.fragment_wallets_export_transactions, - value: 'exportWalletTransactions' - }, - { - label: lstrings.fragment_wallets_delete_token, - value: 'delete' - } - ]) - return - } + if (tokenId != null) { + setOptions([ + { + label: lstrings.string_resync, + value: 'resync' + }, + { + label: lstrings.fragment_wallets_export_transactions, + value: 'exportWalletTransactions' + }, + { + label: lstrings.fragment_wallets_delete_token, + value: 'delete' + } + ]) + return + } - const result: Option[] = [] + const result: Option[] = [] - const { pluginId } = wallet.currencyInfo - if (pausedWallets != null && !isKeysOnlyPlugin(pluginId)) { - result.push({ - label: pausedWallets.has(walletId) ? lstrings.fragment_wallets_unpause_wallet : lstrings.fragment_wallets_pause_wallet, - value: 'togglePause' - }) - } + const { pluginId } = wallet.currencyInfo + if (pausedWallets != null && !isKeysOnlyPlugin(pluginId)) { + result.push({ + label: pausedWallets.has(walletId) ? lstrings.fragment_wallets_unpause_wallet : lstrings.fragment_wallets_pause_wallet, + value: 'togglePause' + }) + } - for (const option of WALLET_LIST_MENU) { - const { pluginIds, label, value } = option + for (const option of WALLET_LIST_MENU) { + const { pluginIds, label, value } = option - if (Array.isArray(pluginIds) && !pluginIds.includes(pluginId)) continue + if (Array.isArray(pluginIds) && !pluginIds.includes(pluginId)) continue - // Special case for `manageTokens`. Only allow pluginsIds that have metatokens - if (value === 'manageTokens') { - if (Object.keys(account.currencyConfig[pluginId].builtinTokens).length === 0) continue - } + // Special case for `manageTokens`. Only allow pluginsIds that have metatokens + if (value === 'manageTokens') { + if (Object.keys(account.currencyConfig[pluginId].builtinTokens).length === 0) continue + } - // Special case for light accounts. Don't allow `getSeed` or `getRawKeys` - if (account.username == null && (value === 'getSeed' || value === 'getRawKeys')) continue + // Special case for light accounts. Don't allow `getSeed` or `getRawKeys` + if (account.username == null && (value === 'getSeed' || value === 'getRawKeys')) continue - result.push({ label, value }) - } + result.push({ label, value }) + } - const splittable = await account.listSplittableWalletTypes(wallet.id) + const splittable = await account.listSplittableWalletTypes(wallet.id) - const currencyInfos = getCurrencyInfos(account) - for (const splitWalletType of splittable) { - const info = currencyInfos.find(({ walletType }) => walletType === splitWalletType) - if (info == null || getSpecialCurrencyInfo(info.pluginId).isSplittingDisabled) continue - result.push({ label: sprintf(lstrings.string_split_wallet, info.displayName), value: `split${info.pluginId}` }) - } + const currencyInfos = getCurrencyInfos(account) + for (const splitWalletType of splittable) { + const info = currencyInfos.find(({ walletType }) => walletType === splitWalletType) + if (info == null || getSpecialCurrencyInfo(info.pluginId).isSplittingDisabled) continue + result.push({ label: sprintf(lstrings.string_split_wallet, info.displayName), value: `split${info.pluginId}` }) + } - setOptions(result) + setOptions(result) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [], + 'WalletListMenuModal' + ) return ( diff --git a/src/components/scenes/CoinRankingScene.tsx b/src/components/scenes/CoinRankingScene.tsx index 09a54ca9b09..4a5e4ba20e5 100644 --- a/src/components/scenes/CoinRankingScene.tsx +++ b/src/components/scenes/CoinRankingScene.tsx @@ -140,43 +140,47 @@ const CoinRankingComponent = (props: Props) => { } }, []) - useAsyncEffect(async () => { - const queryLoop = async () => { - try { - let start = 1 - debugLog(LOG_COINRANK, `queryLoop ${defaultIsoFiat} dataSize=${dataSize} requestDataSize=${requestDataSize}`) - while (start < requestDataSize) { - const url = `v2/coinrank?fiatCode=${defaultIsoFiat}&start=${start}&length=${QUERY_PAGE_SIZE}` - const response = await fetchRates(url) - if (!response.ok) { - const text = await response.text() - console.warn(text) - break + useAsyncEffect( + async () => { + const queryLoop = async () => { + try { + let start = 1 + debugLog(LOG_COINRANK, `queryLoop ${defaultIsoFiat} dataSize=${dataSize} requestDataSize=${requestDataSize}`) + while (start < requestDataSize) { + const url = `v2/coinrank?fiatCode=${defaultIsoFiat}&start=${start}&length=${QUERY_PAGE_SIZE}` + const response = await fetchRates(url) + if (!response.ok) { + const text = await response.text() + console.warn(text) + break + } + const replyJson = await response.json() + const listings = asCoinranking(replyJson) + for (let i = 0; i < listings.data.length; i++) { + const rankIndex = start - 1 + i + const row = listings.data[i] + coinRankingDatas[rankIndex] = row + debugLog(LOG_COINRANK, `queryLoop: ${rankIndex.toString()} ${row.rank} ${row.currencyCode}`) + } + start += QUERY_PAGE_SIZE } - const replyJson = await response.json() - const listings = asCoinranking(replyJson) - for (let i = 0; i < listings.data.length; i++) { - const rankIndex = start - 1 + i - const row = listings.data[i] - coinRankingDatas[rankIndex] = row - debugLog(LOG_COINRANK, `queryLoop: ${rankIndex.toString()} ${row.rank} ${row.currencyCode}`) + setDataSize(coinRankingDatas.length) + if (lastUsedFiat !== defaultIsoFiat) { + setLastUsedFiat(defaultIsoFiat) } - start += QUERY_PAGE_SIZE + } catch (e: any) { + console.warn(e.message) } - setDataSize(coinRankingDatas.length) - if (lastUsedFiat !== defaultIsoFiat) { - setLastUsedFiat(defaultIsoFiat) - } - } catch (e: any) { - console.warn(e.message) + timeoutHandler.current = setTimeout(queryLoop, LISTINGS_REFRESH_INTERVAL) } - timeoutHandler.current = setTimeout(queryLoop, LISTINGS_REFRESH_INTERVAL) - } - if (timeoutHandler.current != null) { - clearTimeout(timeoutHandler.current) - } - queryLoop().catch(e => debugLog(LOG_COINRANK, e.message)) - }, [requestDataSize, defaultIsoFiat]) + if (timeoutHandler.current != null) { + clearTimeout(timeoutHandler.current) + } + queryLoop().catch(e => debugLog(LOG_COINRANK, e.message)) + }, + [requestDataSize, defaultIsoFiat], + 'CoinRankingComponent' + ) const listdata: number[] = React.useMemo(() => { debugLog(LOG_COINRANK, `Updating listdata dataSize=${dataSize} searchText=${searchText}`) diff --git a/src/components/scenes/CreateWalletCompletionScene.tsx b/src/components/scenes/CreateWalletCompletionScene.tsx index 2f47e7138d8..abf579cdc3f 100644 --- a/src/components/scenes/CreateWalletCompletionScene.tsx +++ b/src/components/scenes/CreateWalletCompletionScene.tsx @@ -85,31 +85,35 @@ const CreateWalletCompletionComponent = (props: Props) => { const flatListRef = React.useRef>(null) // Create the wallets and enable the tokens - useAsyncEffect(async () => { - const promises: Array<(() => Promise) | (() => Promise)> = [...walletPromises] - if (tokenKey != null) promises.push(tokenPromise) - - for (const [i, promise] of promises.entries()) { - try { - const wallet = await promise() - // We created a wallet so let's Update relevant pending tokens with the new walletId - if (wallet != null) { - newTokenItems - .filter(item => item.pluginId === wallet.currencyInfo.pluginId && item.createWalletIds[0] === PLACEHOLDER_WALLET_ID) - .forEach(item => (item.createWalletIds = [wallet.id])) + useAsyncEffect( + async () => { + const promises: Array<(() => Promise) | (() => Promise)> = [...walletPromises] + if (tokenKey != null) promises.push(tokenPromise) + + for (const [i, promise] of promises.entries()) { + try { + const wallet = await promise() + // We created a wallet so let's Update relevant pending tokens with the new walletId + if (wallet != null) { + newTokenItems + .filter(item => item.pluginId === wallet.currencyInfo.pluginId && item.createWalletIds[0] === PLACEHOLDER_WALLET_ID) + .forEach(item => (item.createWalletIds = [wallet.id])) + } + setItemStatus(currentState => ({ ...currentState, [filteredCreateItemsForDisplay[i].key]: 'complete' })) + } catch (e) { + showError(e) + setItemStatus(currentState => ({ ...currentState, [filteredCreateItemsForDisplay[i].key]: 'error' })) } - setItemStatus(currentState => ({ ...currentState, [filteredCreateItemsForDisplay[i].key]: 'complete' })) - } catch (e) { - showError(e) - setItemStatus(currentState => ({ ...currentState, [filteredCreateItemsForDisplay[i].key]: 'error' })) - } - flatListRef.current?.scrollToIndex({ animated: true, index: i, viewPosition: 0.5 }) - } - setDone(true) + flatListRef.current?.scrollToIndex({ animated: true, index: i, viewPosition: 0.5 }) + } + setDone(true) - return () => {} - }, []) + return () => {} + }, + [], + 'CreateWalletCompletionComponent' + ) const renderStatus = useHandler((item: WalletCreateItem) => { let icon = diff --git a/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx b/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx index 00eb706e6fe..01034492d08 100644 --- a/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx +++ b/src/components/scenes/CryptoExchangeQuoteProcessingScene.tsx @@ -42,18 +42,22 @@ export function CryptoExchangeQuoteProcessingScene(props: Props) { } }, []) - useAsyncEffect(async () => { - try { - const quotes = await fetchSwapQuotesPromise - if (mounted.current) onDone(quotes) - } catch (e: any) { - await onError(e) - } + useAsyncEffect( + async () => { + try { + const quotes = await fetchSwapQuotesPromise + if (mounted.current) onDone(quotes) + } catch (e: any) { + await onError(e) + } - return () => { - mounted.current = false - } - }, [onDone, onError]) + return () => { + mounted.current = false + } + }, + [onDone, onError], + 'CryptoExchangeQuoteProcessingScene' + ) return ( diff --git a/src/components/scenes/EdgeLoginScene.tsx b/src/components/scenes/EdgeLoginScene.tsx index c695cfc466e..d125c5095e9 100644 --- a/src/components/scenes/EdgeLoginScene.tsx +++ b/src/components/scenes/EdgeLoginScene.tsx @@ -35,18 +35,22 @@ export const EdgeLoginScene = (props: Props) => { ? sprintf(lstrings.edge_description_warning, lobby?.loginRequest?.displayName) : sprintf(lstrings.access_wallet_description, config.appName) - useAsyncEffect(async () => { - try { - setLobby(await account.fetchLobby(lobbyId)) - } catch (error: any) { - if (error.message.includes('Account does not')) { - await showOkModal(lstrings.edge_login_failed, lstrings.edge_login_fail_stale_qr) - } else { - showError(error) + useAsyncEffect( + async () => { + try { + setLobby(await account.fetchLobby(lobbyId)) + } catch (error: any) { + if (error.message.includes('Account does not')) { + await showOkModal(lstrings.edge_login_failed, lstrings.edge_login_fail_stale_qr) + } else { + showError(error) + } + navigation.pop() } - navigation.pop() - } - }, [lobbyId]) + }, + [lobbyId], + 'EdgeLoginScene' + ) const handleAccept = useHandler(async () => { if (lobby?.loginRequest == null) return diff --git a/src/components/scenes/Fio/FioCreateHandleScene.tsx b/src/components/scenes/Fio/FioCreateHandleScene.tsx index 1f9dd83c028..4071020d0e7 100644 --- a/src/components/scenes/Fio/FioCreateHandleScene.tsx +++ b/src/components/scenes/Fio/FioCreateHandleScene.tsx @@ -144,25 +144,29 @@ export const FioCreateHandleScene = (props: Props) => { }) // Create the new FIO wallet, default the handle to a cleaned version of the username - useAsyncEffect(async () => { - const domains = await fioPlugin.otherMethods.getDomains(freeRegRefCode) - if (domains.length === 1) { - if (!mounted.current) return - try { - setDomainStr(`${FIO_ADDRESS_DELIMITER}${asFreeFioDomain(domains[0]).domain}`) - } catch (e) { - setErrorText(lstrings.fio_register_handle_error) - return + useAsyncEffect( + async () => { + const domains = await fioPlugin.otherMethods.getDomains(freeRegRefCode) + if (domains.length === 1) { + if (!mounted.current) return + try { + setDomainStr(`${FIO_ADDRESS_DELIMITER}${asFreeFioDomain(domains[0]).domain}`) + } catch (e) { + setErrorText(lstrings.fio_register_handle_error) + return + } } - } - handleChangeFioHandle(account.username ?? '') + handleChangeFioHandle(account.username ?? '') - const wallet = await dispatch(createFioWallet()) + const wallet = await dispatch(createFioWallet()) - if (!mounted.current) return - setWallet(wallet) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + if (!mounted.current) return + setWallet(wallet) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [], + 'FioCreateHandleScene' + ) React.useEffect(() => { // Clear error, if there was one diff --git a/src/components/scenes/Fio/FioStakingChangeScene.tsx b/src/components/scenes/Fio/FioStakingChangeScene.tsx index fe973f778e5..1123596a788 100644 --- a/src/components/scenes/Fio/FioStakingChangeScene.tsx +++ b/src/components/scenes/Fio/FioStakingChangeScene.tsx @@ -196,9 +196,13 @@ export const FioStakingChangeScene = withWallet((props: Props) => { }) } - useAsyncEffect(async () => { - await dispatch(refreshAllFioAddresses()) - }, []) + useAsyncEffect( + async () => { + await dispatch(refreshAllFioAddresses()) + }, + [], + 'FioStakingChangeScene' + ) React.useEffect(() => { if (currencyPlugin != null && currencyPlugin.otherMethods != null && currencyPlugin.otherMethods.getStakeEstReturn != null) { diff --git a/src/components/scenes/Fio/FioStakingOverviewScene.tsx b/src/components/scenes/Fio/FioStakingOverviewScene.tsx index 65411fca0c0..cfbf00a0065 100644 --- a/src/components/scenes/Fio/FioStakingOverviewScene.tsx +++ b/src/components/scenes/Fio/FioStakingOverviewScene.tsx @@ -66,9 +66,13 @@ export const FioStakingOverviewSceneComponent = (props: Props) => { const stakingStatus = useWatch(currencyWallet, 'stakingStatus') const currencyCode = getCurrencyCode(currencyWallet, tokenId) - useAsyncEffect(async () => { - await refreshAllFioAddresses() - }, [refreshAllFioAddresses]) + useAsyncEffect( + async () => { + await refreshAllFioAddresses() + }, + [refreshAllFioAddresses], + 'FioStakingOverviewSceneComponent' + ) React.useEffect(() => { setLocks( diff --git a/src/components/scenes/GettingStartedScene.tsx b/src/components/scenes/GettingStartedScene.tsx index ad0a8f473bb..1c5662054d4 100644 --- a/src/components/scenes/GettingStartedScene.tsx +++ b/src/components/scenes/GettingStartedScene.tsx @@ -152,10 +152,14 @@ export const GettingStartedScene = (props: Props) => { ) // Initialize variant config values - useAsyncEffect(async () => { - setIsFinalSwipeEnabled((await getExperimentConfigValue('swipeLastUsp')) === 'true') - setCreateAccountType(await getExperimentConfigValue('createAccountType')) - }, []) + useAsyncEffect( + async () => { + setIsFinalSwipeEnabled((await getExperimentConfigValue('swipeLastUsp')) === 'true') + setCreateAccountType(await getExperimentConfigValue('createAccountType')) + }, + [], + 'GettingStartedScene' + ) // Redirect to login screen if device has memory of accounts // HACK: It's unknown how the localUsers dependency makes the routing work diff --git a/src/components/scenes/Loans/LoanCreateScene.tsx b/src/components/scenes/Loans/LoanCreateScene.tsx index 6364c53cf85..76276de9d5a 100644 --- a/src/components/scenes/Loans/LoanCreateScene.tsx +++ b/src/components/scenes/Loans/LoanCreateScene.tsx @@ -57,10 +57,14 @@ export const LoanCreateScene = (props: Props) => { const { currencyWallet: borrowEngineWallet } = borrowEngine // Force enable tokens required for loan - useAsyncEffect(async () => { - await enableToken('WBTC', borrowEngineWallet) - await enableToken('USDC', borrowEngineWallet) - }, []) + useAsyncEffect( + async () => { + await enableToken('WBTC', borrowEngineWallet) + await enableToken('USDC', borrowEngineWallet) + }, + [], + 'LoanCreateScene:1' + ) // #endregion Initialization @@ -139,10 +143,14 @@ export const LoanCreateScene = (props: Props) => { const [bankAccountsMap, setBankAccountsMap] = React.useState<{ [paymentMethodId: string]: PaymentMethod } | undefined>(undefined) - useAsyncEffect(async () => { - // TODO: Re-enable when new fiat ramp partner is avialable: - setBankAccountsMap(undefined) - }, [account]) + useAsyncEffect( + async () => { + // TODO: Re-enable when new fiat ramp partner is avialable: + setBankAccountsMap(undefined) + }, + [account], + 'LoanCreateScene:2' + ) const paymentMethod = destBankId == null || bankAccountsMap == null || Object.keys(bankAccountsMap).length === 0 ? undefined : bankAccountsMap[destBankId] // #endregion Destination Wallet/Bank Data @@ -158,15 +166,19 @@ export const LoanCreateScene = (props: Props) => { const [isLoading, setIsLoading] = React.useState(false) const debts = useWatch(borrowEngine, 'debts') const [apr, setApr] = React.useState(0) - useAsyncEffect(async () => { - if (destTokenId != null) { - const destDebt = debts.find(debt => debt.tokenId === destTokenId) - if (destDebt != null) { - const apr = await borrowEngine.getAprQuote(destTokenId) - setApr(apr) + useAsyncEffect( + async () => { + if (destTokenId != null) { + const destDebt = debts.find(debt => debt.tokenId === destTokenId) + if (destDebt != null) { + const apr = await borrowEngine.getAprQuote(destTokenId) + setApr(apr) + } } - } - }, [debts, destTokenId]) + }, + [debts, destTokenId], + 'LoanCreateScene:3' + ) // #endregion APR // #region Required Collateral, LTV diff --git a/src/components/scenes/Loans/LoanDashboardScene.tsx b/src/components/scenes/Loans/LoanDashboardScene.tsx index 006a401cafa..5df97588680 100644 --- a/src/components/scenes/Loans/LoanDashboardScene.tsx +++ b/src/components/scenes/Loans/LoanDashboardScene.tsx @@ -62,10 +62,14 @@ export const LoanDashboardScene = (props: Props) => { const currencyWallets = useWatch(account, 'currencyWallets') const [isWalletsLoaded, setIsWalletsLoaded] = React.useState(false) - useAsyncEffect(async () => { - await account.waitForAllWallets() - setIsWalletsLoaded(true) - }, [account]) + useAsyncEffect( + async () => { + await account.waitForAllWallets() + setIsWalletsLoaded(true) + }, + [account], + 'LoanDashboardScene:1' + ) const [isNewLoanLoading, setIsNewLoanLoading] = React.useState(false) @@ -76,16 +80,24 @@ export const LoanDashboardScene = (props: Props) => { // Effects // - useAsyncEffect(async () => { - if (await isShowLoanWelcomeModal(account.disklet)) await Airship.show<'ok' | undefined>(bridge => ) - }, []) + useAsyncEffect( + async () => { + if (await isShowLoanWelcomeModal(account.disklet)) await Airship.show<'ok' | undefined>(bridge => ) + }, + [], + 'LoanDashboardScene:2' + ) - useAsyncEffect(async () => { - // Only resync on scene mount every 5 minutes - if (Date.now() - lastResyncTimestamp > 5 * 60 * 1000) { - await dispatch(resyncLoanAccounts(account)) - } - }, [account, dispatch, lastResyncTimestamp]) + useAsyncEffect( + async () => { + // Only resync on scene mount every 5 minutes + if (Date.now() - lastResyncTimestamp > 5 * 60 * 1000) { + await dispatch(resyncLoanAccounts(account)) + } + }, + [account, dispatch, lastResyncTimestamp], + 'LoanDashboardScene:3' + ) // // Handlers diff --git a/src/components/scenes/Loans/LoanDetailsScene.tsx b/src/components/scenes/Loans/LoanDetailsScene.tsx index 1d6631f3a04..9573b9a296e 100644 --- a/src/components/scenes/Loans/LoanDetailsScene.tsx +++ b/src/components/scenes/Loans/LoanDetailsScene.tsx @@ -77,17 +77,21 @@ export const LoanDetailsSceneComponent = (props: Props) => { const [runningProgramMessage, setRunningProgramMessage] = React.useState(undefined) const isActionProgramRunning = runningProgramMessage != null - useAsyncEffect(async () => { - if (runningActionQueueItem != null) { - const displayInfo: ActionDisplayInfo = await getActionProgramDisplayInfo(account, runningActionQueueItem.program, runningActionQueueItem.state) - const activeStep = displayInfo.steps.find(step => step.status === 'active') - setRunningProgramMessage(activeStep != null ? activeStep.title : undefined) - } else { - setRunningProgramMessage(undefined) - } + useAsyncEffect( + async () => { + if (runningActionQueueItem != null) { + const displayInfo: ActionDisplayInfo = await getActionProgramDisplayInfo(account, runningActionQueueItem.program, runningActionQueueItem.state) + const activeStep = displayInfo.steps.find(step => step.status === 'active') + setRunningProgramMessage(activeStep != null ? activeStep.title : undefined) + } else { + setRunningProgramMessage(undefined) + } - return () => {} - }, [account, runningActionQueueItem]) + return () => {} + }, + [account, runningActionQueueItem], + 'LoanDetailsSceneComponent' + ) const summaryDetails = [ { label: lstrings.loan_collateral_value, value: displayFiatTotal(wallet, collateralTotal) }, diff --git a/src/components/scenes/Loans/LoanManageScene.tsx b/src/components/scenes/Loans/LoanManageScene.tsx index 27e841e803c..77b2dbc9971 100644 --- a/src/components/scenes/Loans/LoanManageScene.tsx +++ b/src/components/scenes/Loans/LoanManageScene.tsx @@ -203,87 +203,95 @@ export const LoanManageSceneComponent = (props: Props) => { // ----------------------------------------------------------------------------- // TODO: Full max button implementation and behavior - useAsyncEffect(async () => { - const currentLoanAssetNativeAmount = - manageActionData.actionSide === 'collaterals' - ? collaterals.find(collateral => collateral.tokenId === selectedAsset.tokenId)?.nativeAmount ?? '0' - : debts.find(debt => debt.tokenId === selectedAsset.tokenId)?.nativeAmount ?? '0' - - setPendingDebtOrCollateral({ - nativeAmount: max(add(currentLoanAssetNativeAmount, mul(actionAmountSign, actionNativeAmount)), '0'), - tokenId: selectedAsset.tokenId, - apr: 0 - }) + useAsyncEffect( + async () => { + const currentLoanAssetNativeAmount = + manageActionData.actionSide === 'collaterals' + ? collaterals.find(collateral => collateral.tokenId === selectedAsset.tokenId)?.nativeAmount ?? '0' + : debts.find(debt => debt.tokenId === selectedAsset.tokenId)?.nativeAmount ?? '0' + + setPendingDebtOrCollateral({ + nativeAmount: max(add(currentLoanAssetNativeAmount, mul(actionAmountSign, actionNativeAmount)), '0'), + tokenId: selectedAsset.tokenId, + apr: 0 + }) - return () => {} - }, [actionNativeAmount, selectedAsset.tokenId]) + return () => {} + }, + [actionNativeAmount, selectedAsset.tokenId], + 'LoanManageSceneComponent:1' + ) // Build Action Program - useAsyncEffect(async () => { - if (zeroString(actionNativeAmount) || isLtvExceeded) { - setActionProgram(undefined) - } else { - let actionOp: ActionOp - switch (loanManageType) { - case 'loan-manage-deposit': - actionOp = await makeAaveDepositAction({ - borrowPluginId, - depositTokenId: hardAllowedCollateralAssets[0].tokenId, - nativeAmount: actionNativeAmount, - borrowEngineWallet: borrowEngineWallet, - srcTokenId: selectedAsset.tokenId, - srcWallet: selectedAsset.wallet ?? borrowEngineWallet - }) - break - case 'loan-manage-borrow': - { - const destination: LoanAsset = { - wallet: borrowEngineWallet, - tokenId: selectedAsset.tokenId, - nativeAmount: actionNativeAmount, - ...(selectedAsset.paymentMethod != null ? { paymentMethodId: selectedAsset.paymentMethod.id } : {}), - ...(selectedAsset.tokenId != null ? { tokenId: selectedAsset.tokenId } : {}) - } - actionOp = await makeAaveBorrowAction({ - borrowEngineWallet, + useAsyncEffect( + async () => { + if (zeroString(actionNativeAmount) || isLtvExceeded) { + setActionProgram(undefined) + } else { + let actionOp: ActionOp + switch (loanManageType) { + case 'loan-manage-deposit': + actionOp = await makeAaveDepositAction({ borrowPluginId, - destination + depositTokenId: hardAllowedCollateralAssets[0].tokenId, + nativeAmount: actionNativeAmount, + borrowEngineWallet: borrowEngineWallet, + srcTokenId: selectedAsset.tokenId, + srcWallet: selectedAsset.wallet ?? borrowEngineWallet }) - } - break - case 'loan-manage-repay': - actionOp = { - type: 'seq', - actions: [ - { - type: 'loan-repay', - borrowPluginId, + break + case 'loan-manage-borrow': + { + const destination: LoanAsset = { + wallet: borrowEngineWallet, + tokenId: selectedAsset.tokenId, nativeAmount: actionNativeAmount, - walletId: borrowEngineWallet.id, - tokenId: hardAllowedDebtAssets[0].tokenId, - fromTokenId: selectedAsset.customAsset != null ? hardAllowedCollateralAssets[0].tokenId : null + ...(selectedAsset.paymentMethod != null ? { paymentMethodId: selectedAsset.paymentMethod.id } : {}), + ...(selectedAsset.tokenId != null ? { tokenId: selectedAsset.tokenId } : {}) } - ] - } - break - case 'loan-manage-withdraw': - actionOp = { - type: 'seq', - actions: [ - { - type: 'loan-withdraw', + actionOp = await makeAaveBorrowAction({ + borrowEngineWallet, borrowPluginId, - nativeAmount: actionNativeAmount, - walletId: borrowEngineWallet.id, - tokenId: selectedAsset.tokenId - } - ] - } - break + destination + }) + } + break + case 'loan-manage-repay': + actionOp = { + type: 'seq', + actions: [ + { + type: 'loan-repay', + borrowPluginId, + nativeAmount: actionNativeAmount, + walletId: borrowEngineWallet.id, + tokenId: hardAllowedDebtAssets[0].tokenId, + fromTokenId: selectedAsset.customAsset != null ? hardAllowedCollateralAssets[0].tokenId : null + } + ] + } + break + case 'loan-manage-withdraw': + actionOp = { + type: 'seq', + actions: [ + { + type: 'loan-withdraw', + borrowPluginId, + nativeAmount: actionNativeAmount, + walletId: borrowEngineWallet.id, + tokenId: selectedAsset.tokenId + } + ] + } + break + } + setActionProgram(await makeActionProgram(actionOp)) } - setActionProgram(await makeActionProgram(actionOp)) - } - }, [actionNativeAmount, loanManageType, borrowEngineWallet, borrowPluginId, isLtvExceeded, selectedAsset]) + }, + [actionNativeAmount, loanManageType, borrowEngineWallet, borrowPluginId, isLtvExceeded, selectedAsset], + 'LoanManageSceneComponent:2' + ) // Get Network Fees const [networkFeeMap = {}] = useAsyncValue(async () => { @@ -296,13 +304,17 @@ export const LoanManageSceneComponent = (props: Props) => { }, [account, actionProgram, clientId]) // APR - useAsyncEffect(async () => { - if (isShowAprChange) { - const apr = await borrowEngine.getAprQuote(selectedAsset.tokenId) - setNewApr(apr) - } - return () => {} - }, [borrowEngine, selectedAsset.tokenId, isShowAprChange]) + useAsyncEffect( + async () => { + if (isShowAprChange) { + const apr = await borrowEngine.getAprQuote(selectedAsset.tokenId) + setNewApr(apr) + } + return () => {} + }, + [borrowEngine, selectedAsset.tokenId, isShowAprChange], + 'LoanManageSceneComponent:3' + ) // #endregion Hooks diff --git a/src/components/scenes/LoginScene.tsx b/src/components/scenes/LoginScene.tsx index ba57638b980..106dfa5f791 100644 --- a/src/components/scenes/LoginScene.tsx +++ b/src/components/scenes/LoginScene.tsx @@ -173,10 +173,14 @@ export function LoginSceneComponent(props: Props) { }) // Wait for the experiment config to initialize before rendering anything - useAsyncEffect(async () => { - const experimentConfig = await getExperimentConfig() - setExperimentConfig(experimentConfig) - }, []) + useAsyncEffect( + async () => { + const experimentConfig = await getExperimentConfig() + setExperimentConfig(experimentConfig) + }, + [], + 'LoginSceneComponent' + ) const inMaestro = isMaestro() diff --git a/src/components/scenes/MigrateWalletCalculateFeeScene.tsx b/src/components/scenes/MigrateWalletCalculateFeeScene.tsx index 038d741bfa2..beb07883baa 100644 --- a/src/components/scenes/MigrateWalletCalculateFeeScene.tsx +++ b/src/components/scenes/MigrateWalletCalculateFeeScene.tsx @@ -119,98 +119,102 @@ const MigrateWalletCalculateFeeComponent = (props: Props) => { // Create getMaxSpendable/makeSpend promises for each selected asset. We'll group them by wallet first and then execute all of them while keeping // track of which makeSpends are successful so we can enable the slider. A single failure from any of a wallet's assets will cast them all as failures. - useAsyncEffect(async () => { - // This bundles the assets by similar walletId with the main asset (ie. ETH) at the end of each array so its makeSpend is called last - const bundledWalletAssets: MigrateWalletItem[][] = migrateWalletList.reduce((bundles: MigrateWalletItem[][], asset) => { - const { createWalletIds } = asset - const walletId = createWalletIds[0] - - // Find the bundle with the main currency at the end of it - const index = bundles.findIndex(bundle => walletId === bundle[0].createWalletIds[0]) - if (index === -1) { - bundles.push([asset]) // create bundle for this walletId - } else { - if (asset.tokenId != null) { - bundles[index].unshift(asset) // add token in front + useAsyncEffect( + async () => { + // This bundles the assets by similar walletId with the main asset (ie. ETH) at the end of each array so its makeSpend is called last + const bundledWalletAssets: MigrateWalletItem[][] = migrateWalletList.reduce((bundles: MigrateWalletItem[][], asset) => { + const { createWalletIds } = asset + const walletId = createWalletIds[0] + + // Find the bundle with the main currency at the end of it + const index = bundles.findIndex(bundle => walletId === bundle[0].createWalletIds[0]) + if (index === -1) { + bundles.push([asset]) // create bundle for this walletId } else { - bundles[index].push(asset) // mainnet to the back of the line - } - } - - return bundles - }, []) - - let successCount = 0 - const walletPromises = [] - for (const bundle of bundledWalletAssets) { - const wallet = currencyWallets[bundle[bundle.length - 1].createWalletIds[0]] - const { - currencyInfo: { pluginId } - } = wallet - - let feeTotal = '0' - const bundlesFeeTotals: Map = new Map(bundle.map(item => [item.key, '0'])) - - const assetPromises = bundle.map((asset, i) => { - return async () => { - const publicAddress = SPECIAL_CURRENCY_INFO[pluginId].dummyPublicAddress ?? (await wallet.getReceiveAddress({ tokenId: null })).publicAddress - const spendInfo: EdgeSpendInfo = { - tokenId: asset.tokenId, - spendTargets: [{ publicAddress }], - networkFeeOption: 'standard' + if (asset.tokenId != null) { + bundles[index].unshift(asset) // add token in front + } else { + bundles[index].push(asset) // mainnet to the back of the line } + } - try { - const maxAmount = await wallet.getMaxSpendable(spendInfo) - if (maxAmount === '0') { - throw new InsufficientFundsError({ tokenId: asset.tokenId }) - } - const maxSpendInfo = { ...spendInfo, spendTargets: [{ publicAddress, nativeAmount: maxAmount }] } - const edgeTransaction = await wallet.makeSpend(maxSpendInfo) - const txFee = edgeTransaction.parentNetworkFee ?? edgeTransaction.networkFee - bundlesFeeTotals.set(asset.key, txFee) - feeTotal = add(feeTotal, txFee) - - // While imperfect, sanity check that the total fee spent so far to send tokens + fee to send mainnet currency is under the total mainnet balance - if (i === bundle.length - 1 && lt(wallet.balanceMap.get(null) ?? '0', feeTotal)) { - throw new InsufficientFundsError({ tokenId: null, networkFee: feeTotal }) + return bundles + }, []) + + let successCount = 0 + const walletPromises = [] + for (const bundle of bundledWalletAssets) { + const wallet = currencyWallets[bundle[bundle.length - 1].createWalletIds[0]] + const { + currencyInfo: { pluginId } + } = wallet + + let feeTotal = '0' + const bundlesFeeTotals: Map = new Map(bundle.map(item => [item.key, '0'])) + + const assetPromises = bundle.map((asset, i) => { + return async () => { + const publicAddress = SPECIAL_CURRENCY_INFO[pluginId].dummyPublicAddress ?? (await wallet.getReceiveAddress({ tokenId: null })).publicAddress + const spendInfo: EdgeSpendInfo = { + tokenId: asset.tokenId, + spendTargets: [{ publicAddress }], + networkFeeOption: 'standard' } - } catch (e: any) { - for (const key of bundlesFeeTotals.keys()) { - if (e instanceof InsufficientFundsError) { - bundlesFeeTotals.set(key, e) - } else { - bundlesFeeTotals.set(key, Error(lstrings.migrate_unknown_error_fragment)) + + try { + const maxAmount = await wallet.getMaxSpendable(spendInfo) + if (maxAmount === '0') { + throw new InsufficientFundsError({ tokenId: asset.tokenId }) + } + const maxSpendInfo = { ...spendInfo, spendTargets: [{ publicAddress, nativeAmount: maxAmount }] } + const edgeTransaction = await wallet.makeSpend(maxSpendInfo) + const txFee = edgeTransaction.parentNetworkFee ?? edgeTransaction.networkFee + bundlesFeeTotals.set(asset.key, txFee) + feeTotal = add(feeTotal, txFee) + + // While imperfect, sanity check that the total fee spent so far to send tokens + fee to send mainnet currency is under the total mainnet balance + if (i === bundle.length - 1 && lt(wallet.balanceMap.get(null) ?? '0', feeTotal)) { + throw new InsufficientFundsError({ tokenId: null, networkFee: feeTotal }) + } + } catch (e: any) { + for (const key of bundlesFeeTotals.keys()) { + if (e instanceof InsufficientFundsError) { + bundlesFeeTotals.set(key, e) + } else { + bundlesFeeTotals.set(key, Error(lstrings.migrate_unknown_error_fragment)) + } } } } - } - }) + }) - walletPromises.push(async () => { - for (const promise of assetPromises) { - await promise() - } + walletPromises.push(async () => { + for (const promise of assetPromises) { + await promise() + } - const success = [...bundlesFeeTotals.values()].some(value => !(value instanceof Error)) - if (success) successCount++ + const success = [...bundlesFeeTotals.values()].some(value => !(value instanceof Error)) + if (success) successCount++ - if (mounted.current) { - setFeeState(prevState => new Map([...prevState, ...bundlesFeeTotals])) - } - }) - } + if (mounted.current) { + setFeeState(prevState => new Map([...prevState, ...bundlesFeeTotals])) + } + }) + } - await Promise.all(walletPromises.map(async promise => await promise())) + await Promise.all(walletPromises.map(async promise => await promise())) - if (mounted.current && successCount > 0) { - setSliderDisabled(false) - } + if (mounted.current && successCount > 0) { + setSliderDisabled(false) + } - return () => { - mounted.current = false - } - }, []) + return () => { + mounted.current = false + } + }, + [], + 'MigrateWalletCalculateFeeComponent' + ) const keyExtractor = useHandler((item: MigrateWalletItem) => item.key) diff --git a/src/components/scenes/MigrateWalletCompletionScene.tsx b/src/components/scenes/MigrateWalletCompletionScene.tsx index 26ac7234c90..b50cd9a01e8 100644 --- a/src/components/scenes/MigrateWalletCompletionScene.tsx +++ b/src/components/scenes/MigrateWalletCompletionScene.tsx @@ -83,125 +83,129 @@ const MigrateWalletCompletionComponent = (props: Props) => { } // Create the wallets and enable the tokens - useAsyncEffect(async () => { - const settings = await readSyncedSettings(account) - const securityCheckedWallets = { ...settings.securityCheckedWallets } - - const migrationPromises = [] - for (const bundle of sortedMigrateWalletListBundles) { - const mainnetItem = bundle[bundle.length - 1] - const { createWalletIds } = mainnetItem - const oldWalletId = createWalletIds[0] - - if (securityCheckedWallets[oldWalletId] == null) { - securityCheckedWallets[oldWalletId] = { checked: false, modalShown: 0 } - } - - const oldWallet = currencyWallets[oldWalletId] - const { - currencyInfo: { walletType }, - fiatCurrencyCode - } = oldWallet - const oldWalletName = getWalletName(oldWallet) - const newWalletName = `${oldWalletName}${lstrings.migrate_wallet_new_fragment}` - - // Create new wallet - const createNewWalletPromise = async () => { - const previouslyCreatedWalletInfo = account.allKeys.find( - keys => keys.migratedFromWalletId === oldWalletId && !keys.archived && !keys.deleted && !keys.hidden - ) - let newWallet = previouslyCreatedWalletInfo != null ? currencyWallets[previouslyCreatedWalletInfo.id] : undefined - - let createdNewWallet = false - if (newWallet == null) { - newWallet = await account.createCurrencyWallet(walletType, { - name: newWalletName, - fiatCurrencyCode, - migratedFromWalletId: oldWalletId - }) - createdNewWallet = true + useAsyncEffect( + async () => { + const settings = await readSyncedSettings(account) + const securityCheckedWallets = { ...settings.securityCheckedWallets } + + const migrationPromises = [] + for (const bundle of sortedMigrateWalletListBundles) { + const mainnetItem = bundle[bundle.length - 1] + const { createWalletIds } = mainnetItem + const oldWalletId = createWalletIds[0] + + if (securityCheckedWallets[oldWalletId] == null) { + securityCheckedWallets[oldWalletId] = { checked: false, modalShown: 0 } } - // Change old wallet name - if (createdNewWallet) await oldWallet.renameWallet(`${oldWalletName}${lstrings.migrate_wallet_old_fragment}`) - - const addressInfo = await newWallet.getReceiveAddress({ tokenId: null }) - const newPublicAddress = addressInfo.segwitAddress ?? addressInfo.publicAddress - - const tokenItems = bundle.filter((pair: any): pair is MigrateWalletTokenItem => pair.tokenId != null) - - // Enable tokens on new wallet - const tokenIdsToEnable = [...new Set([...newWallet.enabledTokenIds, ...tokenItems.map(pair => pair.tokenId)])] - await newWallet.changeEnabledTokenIds(tokenIdsToEnable) - - // Send tokens - let feeTotal = '0' - const hasError = false - const successfullyTransferredTokenIds: string[] = [] - for (const item of tokenItems) { - let tokenSpendInfo: EdgeSpendInfo = { - tokenId: item.tokenId, - spendTargets: [{ publicAddress: newPublicAddress }], - networkFeeOption: 'standard' - } - try { - const maxAmount = await oldWallet.getMaxSpendable(tokenSpendInfo) - tokenSpendInfo = { ...tokenSpendInfo, spendTargets: [{ ...tokenSpendInfo.spendTargets[0], nativeAmount: maxAmount }] } - const tx = await makeSpendSignAndBroadcast(oldWallet, tokenSpendInfo) - successfullyTransferredTokenIds.push(item.tokenId) - const txFee = tx.parentNetworkFee ?? tx.networkFee - feeTotal = add(feeTotal, txFee) - - handleItemStatus(item, 'complete') - } catch (e: any) { - handleItemStatus(item, 'error') + const oldWallet = currencyWallets[oldWalletId] + const { + currencyInfo: { walletType }, + fiatCurrencyCode + } = oldWallet + const oldWalletName = getWalletName(oldWallet) + const newWalletName = `${oldWalletName}${lstrings.migrate_wallet_new_fragment}` + + // Create new wallet + const createNewWalletPromise = async () => { + const previouslyCreatedWalletInfo = account.allKeys.find( + keys => keys.migratedFromWalletId === oldWalletId && !keys.archived && !keys.deleted && !keys.hidden + ) + let newWallet = previouslyCreatedWalletInfo != null ? currencyWallets[previouslyCreatedWalletInfo.id] : undefined + + let createdNewWallet = false + if (newWallet == null) { + newWallet = await account.createCurrencyWallet(walletType, { + name: newWalletName, + fiatCurrencyCode, + migratedFromWalletId: oldWalletId + }) + createdNewWallet = true } - } - - // Disable empty tokens - await oldWallet.changeEnabledTokenIds(tokenIdsToEnable.filter(tokenId => !successfullyTransferredTokenIds.includes(tokenId))) - if (!hasError) { - // Send mainnet - let spendInfo: EdgeSpendInfo = { - tokenId: null, - spendTargets: [{ publicAddress: newPublicAddress }], - metadata: { - category: 'Transfer', - name: newWalletName, - notes: sprintf(lstrings.migrate_wallet_tx_notes, newWalletName) - }, - networkFeeOption: 'standard' + // Change old wallet name + if (createdNewWallet) await oldWallet.renameWallet(`${oldWalletName}${lstrings.migrate_wallet_old_fragment}`) + + const addressInfo = await newWallet.getReceiveAddress({ tokenId: null }) + const newPublicAddress = addressInfo.segwitAddress ?? addressInfo.publicAddress + + const tokenItems = bundle.filter((pair: any): pair is MigrateWalletTokenItem => pair.tokenId != null) + + // Enable tokens on new wallet + const tokenIdsToEnable = [...new Set([...newWallet.enabledTokenIds, ...tokenItems.map(pair => pair.tokenId)])] + await newWallet.changeEnabledTokenIds(tokenIdsToEnable) + + // Send tokens + let feeTotal = '0' + const hasError = false + const successfullyTransferredTokenIds: string[] = [] + for (const item of tokenItems) { + let tokenSpendInfo: EdgeSpendInfo = { + tokenId: item.tokenId, + spendTargets: [{ publicAddress: newPublicAddress }], + networkFeeOption: 'standard' + } + try { + const maxAmount = await oldWallet.getMaxSpendable(tokenSpendInfo) + tokenSpendInfo = { ...tokenSpendInfo, spendTargets: [{ ...tokenSpendInfo.spendTargets[0], nativeAmount: maxAmount }] } + const tx = await makeSpendSignAndBroadcast(oldWallet, tokenSpendInfo) + successfullyTransferredTokenIds.push(item.tokenId) + const txFee = tx.parentNetworkFee ?? tx.networkFee + feeTotal = add(feeTotal, txFee) + + handleItemStatus(item, 'complete') + } catch (e: any) { + handleItemStatus(item, 'error') + } } - try { - const maxAmount = await oldWallet.getMaxSpendable(spendInfo) - spendInfo = { ...spendInfo, spendTargets: [{ ...spendInfo.spendTargets[0], nativeAmount: maxAmount }] } - const amountToSend = sub(maxAmount, feeTotal) - spendInfo = { ...spendInfo, spendTargets: [{ ...spendInfo.spendTargets[0], nativeAmount: amountToSend }] } - await makeSpendSignAndBroadcast(oldWallet, spendInfo) - handleItemStatus(mainnetItem, 'complete') - - const { modalShown } = securityCheckedWallets[oldWalletId] - securityCheckedWallets[oldWalletId] = { checked: true, modalShown } - } catch (e) { - showError(e) + + // Disable empty tokens + await oldWallet.changeEnabledTokenIds(tokenIdsToEnable.filter(tokenId => !successfullyTransferredTokenIds.includes(tokenId))) + + if (!hasError) { + // Send mainnet + let spendInfo: EdgeSpendInfo = { + tokenId: null, + spendTargets: [{ publicAddress: newPublicAddress }], + metadata: { + category: 'Transfer', + name: newWalletName, + notes: sprintf(lstrings.migrate_wallet_tx_notes, newWalletName) + }, + networkFeeOption: 'standard' + } + try { + const maxAmount = await oldWallet.getMaxSpendable(spendInfo) + spendInfo = { ...spendInfo, spendTargets: [{ ...spendInfo.spendTargets[0], nativeAmount: maxAmount }] } + const amountToSend = sub(maxAmount, feeTotal) + spendInfo = { ...spendInfo, spendTargets: [{ ...spendInfo.spendTargets[0], nativeAmount: amountToSend }] } + await makeSpendSignAndBroadcast(oldWallet, spendInfo) + handleItemStatus(mainnetItem, 'complete') + + const { modalShown } = securityCheckedWallets[oldWalletId] + securityCheckedWallets[oldWalletId] = { checked: true, modalShown } + } catch (e) { + showError(e) + handleItemStatus(mainnetItem, 'error') + } + } else { handleItemStatus(mainnetItem, 'error') } - } else { - handleItemStatus(mainnetItem, 'error') } + migrationPromises.push(createNewWalletPromise) } - migrationPromises.push(createNewWalletPromise) - } - for (const migration of migrationPromises) { - await migration() - } - await writeSyncedSettings(account, { ...settings, securityCheckedWallets }) + for (const migration of migrationPromises) { + await migration() + } + await writeSyncedSettings(account, { ...settings, securityCheckedWallets }) - setDone(true) - return () => {} - }, []) + setDone(true) + return () => {} + }, + [], + 'MigrateWalletCompletionComponent' + ) const renderStatus = useHandler((item: MigrateWalletItem) => { let icon = diff --git a/src/components/scenes/SendScene2.tsx b/src/components/scenes/SendScene2.tsx index 17fc8bb22f3..9deb04a21a7 100644 --- a/src/components/scenes/SendScene2.tsx +++ b/src/components/scenes/SendScene2.tsx @@ -895,85 +895,89 @@ const SendComponent = (props: Props) => { }) // Calculate the transaction - useAsyncEffect(async () => { - try { - setProcessingAmountChanged(true) - if (spendInfo.spendTargets[0].publicAddress == null) { - setEdgeTransaction(null) - setSpendingLimitExceeded(false) - setMaxSpendSetter(-1) - setProcessingAmountChanged(false) - return - } - if (maxSpendSetter === 0) { - spendInfo.spendTargets[0].nativeAmount = '0' // Some currencies error without a nativeAmount - const maxSpendable = await coreWallet.getMaxSpendable(spendInfo) - spendInfo.spendTargets[0].nativeAmount = maxSpendable - } - if (spendInfo.spendTargets[0].nativeAmount == null) { - flipInputModalRef.current?.setFees({ feeNativeAmount: '', feeTokenId: null }) - } - if (pinSpendingLimitsEnabled) { - const rate = exchangeRates[`${currencyCode}_${defaultIsoFiat}`] ?? INFINITY_STRING - const totalNativeAmount = spendInfo.spendTargets.reduce((prev, target) => add(target.nativeAmount ?? '0', prev), '0') - const totalExchangeAmount = div(totalNativeAmount, cryptoExchangeDenomination.multiplier, DECIMAL_PRECISION) - const fiatAmount = mul(totalExchangeAmount, rate) - const exceeded = gte(fiatAmount, pinSpendingLimitsAmount.toFixed(DECIMAL_PRECISION)) - setSpendingLimitExceeded(exceeded) - } + useAsyncEffect( + async () => { + try { + setProcessingAmountChanged(true) + if (spendInfo.spendTargets[0].publicAddress == null) { + setEdgeTransaction(null) + setSpendingLimitExceeded(false) + setMaxSpendSetter(-1) + setProcessingAmountChanged(false) + return + } + if (maxSpendSetter === 0) { + spendInfo.spendTargets[0].nativeAmount = '0' // Some currencies error without a nativeAmount + const maxSpendable = await coreWallet.getMaxSpendable(spendInfo) + spendInfo.spendTargets[0].nativeAmount = maxSpendable + } + if (spendInfo.spendTargets[0].nativeAmount == null) { + flipInputModalRef.current?.setFees({ feeNativeAmount: '', feeTokenId: null }) + } + if (pinSpendingLimitsEnabled) { + const rate = exchangeRates[`${currencyCode}_${defaultIsoFiat}`] ?? INFINITY_STRING + const totalNativeAmount = spendInfo.spendTargets.reduce((prev, target) => add(target.nativeAmount ?? '0', prev), '0') + const totalExchangeAmount = div(totalNativeAmount, cryptoExchangeDenomination.multiplier, DECIMAL_PRECISION) + const fiatAmount = mul(totalExchangeAmount, rate) + const exceeded = gte(fiatAmount, pinSpendingLimitsAmount.toFixed(DECIMAL_PRECISION)) + setSpendingLimitExceeded(exceeded) + } - if (minNativeAmount != null) { - for (const target of spendInfo.spendTargets) { - if (target.nativeAmount == null) continue - if (lt(target.nativeAmount, minNativeAmount)) { - const minDisplayAmount = div(minNativeAmount, cryptoDisplayDenomination.multiplier, DECIMAL_PRECISION) - const { name } = cryptoDisplayDenomination - - setError(new Error(sprintf(lstrings.error_spend_amount_less_then_min_s, `${minDisplayAmount} ${name}`))) - setEdgeTransaction(null) - setFeeNativeAmount('') - setProcessingAmountChanged(false) - return + if (minNativeAmount != null) { + for (const target of spendInfo.spendTargets) { + if (target.nativeAmount == null) continue + if (lt(target.nativeAmount, minNativeAmount)) { + const minDisplayAmount = div(minNativeAmount, cryptoDisplayDenomination.multiplier, DECIMAL_PRECISION) + const { name } = cryptoDisplayDenomination + + setError(new Error(sprintf(lstrings.error_spend_amount_less_then_min_s, `${minDisplayAmount} ${name}`))) + setEdgeTransaction(null) + setFeeNativeAmount('') + setProcessingAmountChanged(false) + return + } } } - } - makeSpendCounter.current++ - const localMakeSpendCounter = makeSpendCounter.current - const edgeTx = await coreWallet.makeSpend(spendInfo) - if (localMakeSpendCounter < makeSpendCounter.current) { - // This makeSpend result is out of date. Throw it away since a newer one is in flight. - // This is not REALLY needed since useAsyncEffect seems to serialize calls into the effect - // function, but if this code ever gets refactored to not use useAsyncEffect, this - // check MUST remain - return - } - setEdgeTransaction(edgeTx) - const { parentNetworkFee, networkFee } = edgeTx - const feeNativeAmount = parentNetworkFee ?? networkFee - const feeTokenId = parentNetworkFee == null ? tokenId : null - setFeeNativeAmount(feeNativeAmount) - flipInputModalRef.current?.setFees({ feeTokenId, feeNativeAmount }) - flipInputModalRef.current?.setError(null) - setError(undefined) - } catch (e: any) { - const insufficientFunds = asMaybeInsufficientFundsError(e) - if (insufficientFunds != null) { - if (insufficientFunds.tokenId != null) { - const errorCurrencyCode = getCurrencyCode(coreWallet, insufficientFunds.tokenId) - e.message = sprintf(lstrings.stake_error_insufficient_s, errorCurrencyCode) - } else { - e.message = lstrings.exchange_insufficient_funds_title + makeSpendCounter.current++ + const localMakeSpendCounter = makeSpendCounter.current + const edgeTx = await coreWallet.makeSpend(spendInfo) + if (localMakeSpendCounter < makeSpendCounter.current) { + // This makeSpend result is out of date. Throw it away since a newer one is in flight. + // This is not REALLY needed since useAsyncEffect seems to serialize calls into the effect + // function, but if this code ever gets refactored to not use useAsyncEffect, this + // check MUST remain + return + } + setEdgeTransaction(edgeTx) + const { parentNetworkFee, networkFee } = edgeTx + const feeNativeAmount = parentNetworkFee ?? networkFee + const feeTokenId = parentNetworkFee == null ? tokenId : null + setFeeNativeAmount(feeNativeAmount) + flipInputModalRef.current?.setFees({ feeTokenId, feeNativeAmount }) + flipInputModalRef.current?.setError(null) + setError(undefined) + } catch (e: any) { + const insufficientFunds = asMaybeInsufficientFundsError(e) + if (insufficientFunds != null) { + if (insufficientFunds.tokenId != null) { + const errorCurrencyCode = getCurrencyCode(coreWallet, insufficientFunds.tokenId) + e.message = sprintf(lstrings.stake_error_insufficient_s, errorCurrencyCode) + } else { + e.message = lstrings.exchange_insufficient_funds_title + } } - } - setError(e) - setEdgeTransaction(null) - flipInputModalRef.current?.setError(e.message) - flipInputModalRef.current?.setFees({ feeNativeAmount: '', feeTokenId: null }) - } - setProcessingAmountChanged(false) - }, [spendInfo, maxSpendSetter, walletId, pinSpendingLimitsEnabled, pinValue]) + setError(e) + setEdgeTransaction(null) + flipInputModalRef.current?.setError(e.message) + flipInputModalRef.current?.setFees({ feeNativeAmount: '', feeTokenId: null }) + } + setProcessingAmountChanged(false) + }, + [spendInfo, maxSpendSetter, walletId, pinSpendingLimitsEnabled, pinValue], + 'SendComponent' + ) const showSlider = spendInfo.spendTargets[0].publicAddress != null let disableSlider = false diff --git a/src/components/scenes/Staking/StakeOverviewScene.tsx b/src/components/scenes/Staking/StakeOverviewScene.tsx index 2deaf544625..07748b2648a 100644 --- a/src/components/scenes/Staking/StakeOverviewScene.tsx +++ b/src/components/scenes/Staking/StakeOverviewScene.tsx @@ -69,23 +69,27 @@ const StakeOverviewSceneComponent = (props: Props) => { return () => clearInterval(interval) }, []) - useAsyncEffect(async () => { - let sp: StakePosition - try { - if (stakePosition == null) { - sp = await stakePlugin.fetchStakePosition({ stakePolicyId, wallet, account }) - setStakePosition(sp) - } else { - const guiAllocations = getPositionAllocations(stakePosition) - setStakeAllocations(guiAllocations.staked) - setRewardAllocations(guiAllocations.earned) - setStakePosition(stakePosition) + useAsyncEffect( + async () => { + let sp: StakePosition + try { + if (stakePosition == null) { + sp = await stakePlugin.fetchStakePosition({ stakePolicyId, wallet, account }) + setStakePosition(sp) + } else { + const guiAllocations = getPositionAllocations(stakePosition) + setStakeAllocations(guiAllocations.staked) + setRewardAllocations(guiAllocations.earned) + setStakePosition(stakePosition) + } + } catch (err) { + showError(err) + console.error(err) } - } catch (err) { - showError(err) - console.error(err) - } - }, [account, stakePlugin, stakePolicyId, stakePosition, updateCounter, wallet]) + }, + [account, stakePlugin, stakePolicyId, stakePosition, updateCounter, wallet], + 'StakeOverviewSceneComponent' + ) // Handlers const handleModifyPress = (modification: ChangeQuoteRequest['action'] | 'unstakeAndClaim') => () => { diff --git a/src/components/scenes/WalletListScene.tsx b/src/components/scenes/WalletListScene.tsx index 92a529d9a8f..ca5d58d9ae2 100644 --- a/src/components/scenes/WalletListScene.tsx +++ b/src/components/scenes/WalletListScene.tsx @@ -63,7 +63,8 @@ export function WalletListScene(props: Props) { } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [], + 'WalletListScene' ) // rendering ------------------------------------------------------------- diff --git a/src/components/scenes/WcConnectScene.tsx b/src/components/scenes/WcConnectScene.tsx index b12a6551f09..c1920138222 100644 --- a/src/components/scenes/WcConnectScene.tsx +++ b/src/components/scenes/WcConnectScene.tsx @@ -61,10 +61,14 @@ export const WcConnectScene = (props: Props) => { return { subTitleText, bodyTitleText, dAppImage } }, [proposal]) - useAsyncEffect(async () => { - const r = await wallet.getReceiveAddress({ tokenId: null }) - setWalletAddress(r.publicAddress) - }, [wallet]) + useAsyncEffect( + async () => { + const r = await wallet.getReceiveAddress({ tokenId: null }) + setWalletAddress(r.publicAddress) + }, + [wallet], + 'WcConnectScene' + ) const dispatch = useDispatch() diff --git a/src/components/scenes/WcConnectionsScene.tsx b/src/components/scenes/WcConnectionsScene.tsx index 4407997910d..07df0966d20 100644 --- a/src/components/scenes/WcConnectionsScene.tsx +++ b/src/components/scenes/WcConnectionsScene.tsx @@ -46,11 +46,15 @@ export const WcConnectionsScene = (props: Props) => { if (uri != null) onScanSuccess(uri).catch(err => showError(err)) }) - useAsyncEffect(async () => { - const connections = await walletConnect.getActiveSessions() - setConnections(connections) - // We want to trigger another lookup whenever the props change ie. navigating from the connect or disconnect scenes - }, [walletConnect, props]) + useAsyncEffect( + async () => { + const connections = await walletConnect.getActiveSessions() + setConnections(connections) + // We want to trigger another lookup whenever the props change ie. navigating from the connect or disconnect scenes + }, + [walletConnect, props], + 'WcConnectionsScene' + ) const onScanSuccess = async (qrResult: string) => { setConnecting(true) diff --git a/src/components/services/DeepLinkingManager.ts b/src/components/services/DeepLinkingManager.ts index 92f1a218d4a..5053f0b71a1 100644 --- a/src/components/services/DeepLinkingManager.ts +++ b/src/components/services/DeepLinkingManager.ts @@ -45,15 +45,19 @@ export function DeepLinkingManager(props: Props) { } // Startup tasks: - useAsyncEffect(async () => { - const listener = Linking.addEventListener('url', async event => await handleUrl(event.url)) + useAsyncEffect( + async () => { + const listener = Linking.addEventListener('url', async event => await handleUrl(event.url)) - let url = await Linking.getInitialURL() - if (url == null && ENV.YOLO_DEEP_LINK != null) url = ENV.YOLO_DEEP_LINK - if (url != null) await handleUrl(url) + let url = await Linking.getInitialURL() + if (url == null && ENV.YOLO_DEEP_LINK != null) url = ENV.YOLO_DEEP_LINK + if (url != null) await handleUrl(url) - return () => listener.remove() - }, []) + return () => listener.remove() + }, + [], + 'DeepLinkingManager:getInitialURL' + ) const handlePushMessage = async (message: FirebaseMessagingTypes.RemoteMessage) => { try { @@ -69,31 +73,35 @@ export function DeepLinkingManager(props: Props) { } // Firebase messaging - useAsyncEffect(async () => { - /** - * Fires when the app launches from push notification - * */ - const remoteMessage = await messaging().getInitialNotification() - if (remoteMessage != null) { - await handlePushMessage(remoteMessage) - } - - /** - * Fires when the app is in background - * */ - messaging().onNotificationOpenedApp(remoteMessage => { - handlePushMessage(remoteMessage).catch(err => console.warn(err)) - }) - - /** - * Fires when the app is in foreground and receives a notification - * */ - const unsubscribe = messaging().onMessage(remoteMessage => { - // do nothing for now except return the unsubscribe function - }) + useAsyncEffect( + async () => { + /** + * Fires when the app launches from push notification + * */ + const remoteMessage = await messaging().getInitialNotification() + if (remoteMessage != null) { + await handlePushMessage(remoteMessage) + } - return () => unsubscribe() - }, []) + /** + * Fires when the app is in background + * */ + messaging().onNotificationOpenedApp(remoteMessage => { + handlePushMessage(remoteMessage).catch(err => console.warn(err)) + }) + + /** + * Fires when the app is in foreground and receives a notification + * */ + const unsubscribe = messaging().onMessage(remoteMessage => { + // do nothing for now except return the unsubscribe function + }) + + return () => unsubscribe() + }, + [], + 'DeepLinkingManager:getInitialNotification' + ) return null } diff --git a/src/components/services/EdgeCoreManager.tsx b/src/components/services/EdgeCoreManager.tsx index d25932b92cf..762a0a8125c 100644 --- a/src/components/services/EdgeCoreManager.tsx +++ b/src/components/services/EdgeCoreManager.tsx @@ -77,10 +77,14 @@ export function EdgeCoreManager(props: Props) { const isAppForeground = useIsAppForeground() // Keep the core in sync with the application state: - useAsyncEffect(async () => { - if (context == null) return - await context.changePaused(!isAppForeground, { secondsDelay: !isAppForeground ? 20 : 0 }) - }, [context, isAppForeground]) + useAsyncEffect( + async () => { + if (context == null) return + await context.changePaused(!isAppForeground, { secondsDelay: !isAppForeground ? 20 : 0 }) + }, + [context, isAppForeground], + 'EdgeCoreManager' + ) function hideSplash() { if (!splashHidden.current) { diff --git a/src/components/services/WalletConnectService.tsx b/src/components/services/WalletConnectService.tsx index 6a6acb5f520..e0a25a2950b 100644 --- a/src/components/services/WalletConnectService.tsx +++ b/src/components/services/WalletConnectService.tsx @@ -61,40 +61,44 @@ export const WalletConnectService = (props: Props) => { } } - useAsyncEffect(async () => { - if (walletConnectClient.client == null) { - let projectId: string | undefined - if (typeof ENV.WALLET_CONNECT_INIT === 'object' && ENV.WALLET_CONNECT_INIT.projectId != null) { - projectId = ENV.WALLET_CONNECT_INIT.projectId - } - - walletConnectClient.client = await Web3Wallet.init({ - core: new Core({ - projectId - }), - metadata: { - name: 'Edge Wallet', - description: 'Edge Wallet', - url: 'https://www.edge.app', - icons: ['https://content.edge.app/Edge_logo_Icon.png'] + useAsyncEffect( + async () => { + if (walletConnectClient.client == null) { + let projectId: string | undefined + if (typeof ENV.WALLET_CONNECT_INIT === 'object' && ENV.WALLET_CONNECT_INIT.projectId != null) { + projectId = ENV.WALLET_CONNECT_INIT.projectId } - }) - } - const handleSessionRequestSync = (event: Web3WalletTypes.SessionRequest) => { - handleSessionRequest(event).catch(err => showError(err)) - } + walletConnectClient.client = await Web3Wallet.init({ + core: new Core({ + projectId + }), + metadata: { + name: 'Edge Wallet', + description: 'Edge Wallet', + url: 'https://www.edge.app', + icons: ['https://content.edge.app/Edge_logo_Icon.png'] + } + }) + } - if (walletConnectClient.client?.events.listenerCount('session_request') === 0) { - walletConnectClient.client.on('session_request', handleSessionRequestSync) - } - console.log('WalletConnect initialized') - waitingClients.forEach(f => f(walletConnectClient.client as Web3Wallet)) + const handleSessionRequestSync = (event: Web3WalletTypes.SessionRequest) => { + handleSessionRequest(event).catch(err => showError(err)) + } - return () => { - walletConnectClient.client?.events.removeListener('session_request', handleSessionRequestSync) - } - }, []) + if (walletConnectClient.client?.events.listenerCount('session_request') === 0) { + walletConnectClient.client.on('session_request', handleSessionRequestSync) + } + console.log('WalletConnect initialized') + waitingClients.forEach(f => f(walletConnectClient.client as Web3Wallet)) + + return () => { + walletConnectClient.client?.events.removeListener('session_request', handleSessionRequestSync) + } + }, + [], + 'WalletConnectService' + ) return null } diff --git a/src/components/themed/EdgeProviderComponent.tsx b/src/components/themed/EdgeProviderComponent.tsx index 516cc6b24b7..86f067eeede 100644 --- a/src/components/themed/EdgeProviderComponent.tsx +++ b/src/components/themed/EdgeProviderComponent.tsx @@ -67,18 +67,22 @@ export function EdgeProviderComponent(props: Props): JSX.Element { }, [accountPlugins, accountReferral, pluginId]) // Make sure we have the permissions the plugin requires: - useAsyncEffect(async () => { - for (const permission of permissions) { - const deniedPermission = await requestPermissionOnSettings(disklet, permission, displayName, mandatoryPermissions) - if (deniedPermission) { - navigation.goBack() - return + useAsyncEffect( + async () => { + for (const permission of permissions) { + const deniedPermission = await requestPermissionOnSettings(disklet, permission, displayName, mandatoryPermissions) + if (deniedPermission) { + navigation.goBack() + return + } } - } - // Now show the promo message, if we have one: - if (promoMessage != null) showToast(promoMessage) - }, [displayName, mandatoryPermissions, navigation, permissions, promoMessage, disklet]) + // Now show the promo message, if we have one: + if (promoMessage != null) showToast(promoMessage) + }, + [displayName, mandatoryPermissions, navigation, permissions, promoMessage, disklet], + 'EdgeProviderComponent' + ) // Sign up for back-button events: const webView = React.useRef(null) diff --git a/src/components/ui4/scenes/HomeSceneUi4.tsx b/src/components/ui4/scenes/HomeSceneUi4.tsx index 347966c7d30..0e701800f29 100644 --- a/src/components/ui4/scenes/HomeSceneUi4.tsx +++ b/src/components/ui4/scenes/HomeSceneUi4.tsx @@ -79,7 +79,8 @@ export const HomeSceneUi4 = (props: Props) => { } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [] + [], + 'HomeSceneUi4' ) return ( diff --git a/src/hooks/useAsyncEffect.ts b/src/hooks/useAsyncEffect.ts index 3df9c36d902..ad0af187bd6 100644 --- a/src/hooks/useAsyncEffect.ts +++ b/src/hooks/useAsyncEffect.ts @@ -18,7 +18,7 @@ interface State { * Runs an effect when its dependencies change, just like `useEffect`, * but awaits the returned promise before starting the next run. */ -export function useAsyncEffect(effect: AsyncEffect, deps?: unknown[], tag?: string): void { +export function useAsyncEffect(effect: AsyncEffect, deps: unknown[] = [], tag: string): void { const state = React.useRef({ closed: false, dirty: false, @@ -41,7 +41,7 @@ export function useAsyncEffect(effect: AsyncEffect, deps?: unknown[], tag?: stri if (!matchDeps(deps, state.current.lastDeps)) state.current.dirty = true state.current.lastDeps = deps state.current.effect = effect - wakeup(state.current) + wakeup(state.current, tag) } /** @@ -79,12 +79,12 @@ function wakeup(state: State, tag?: string): void { .then(cleanup => { state.lastCleanup = cleanup ?? undefined state.running = false - wakeup(state) + wakeup(state, tag) }) .catch(error => { showError(error, { tag }) state.running = false - wakeup(state) + wakeup(state, tag) }) } } diff --git a/src/hooks/useAsyncValue.ts b/src/hooks/useAsyncValue.ts index 37f85da0dc0..f584209a719 100644 --- a/src/hooks/useAsyncValue.ts +++ b/src/hooks/useAsyncValue.ts @@ -11,21 +11,25 @@ export function useAsyncValue(effect: () => Promise, deps?: unknown[]): [T const [error, setError] = React.useState(undefined) let cancel = false - useAsyncEffect(async () => { - try { - const value = await effect() - if (cancel) return - setValue(value) - setError(undefined) - } catch (error: any) { - if (cancel) return - setValue(undefined) - setError(error) - } - return () => { - cancel = true - } - }, deps) + useAsyncEffect( + async () => { + try { + const value = await effect() + if (cancel) return + setValue(value) + setError(undefined) + } catch (error: any) { + if (cancel) return + setValue(undefined) + setError(error) + } + return () => { + cancel = true + } + }, + deps, + 'useAsyncValue' + ) return [value, error] } diff --git a/src/plugins/gui/scenes/AddressFormScene.tsx b/src/plugins/gui/scenes/AddressFormScene.tsx index cfe39d12c36..3e707a1b4a1 100644 --- a/src/plugins/gui/scenes/AddressFormScene.tsx +++ b/src/plugins/gui/scenes/AddressFormScene.tsx @@ -237,12 +237,16 @@ export const AddressFormScene = React.memo((props: Props) => { }, [searchResults]) // Initialize scene with any saved form data from disklet - useAsyncEffect(async () => { - const diskletFormData = await getDiskletFormData(disklet, ADDRESS_FORM_DISKLET_NAME, asHomeAddress) - if (diskletFormData != null && diskletFormData.country === countryCode) { - setFormData(diskletFormData) - } - }, []) + useAsyncEffect( + async () => { + const diskletFormData = await getDiskletFormData(disklet, ADDRESS_FORM_DISKLET_NAME, asHomeAddress) + if (diskletFormData != null && diskletFormData.country === countryCode) { + setFormData(diskletFormData) + } + }, + [], + 'AddressFormScene' + ) const disableNextButton = (Object.keys(formData) as Array).some( key => diff --git a/src/plugins/gui/scenes/SepaFormScene.tsx b/src/plugins/gui/scenes/SepaFormScene.tsx index 5936f079a98..e074e390b4b 100644 --- a/src/plugins/gui/scenes/SepaFormScene.tsx +++ b/src/plugins/gui/scenes/SepaFormScene.tsx @@ -48,14 +48,18 @@ export const SepaFormScene = React.memo((props: Props) => { }) // Initialize scene with any saved forms from disklet - useAsyncEffect(async () => { - const diskletFormData: SepaInfo | undefined = await getDiskletFormData(disklet, SEPA_FORM_DISKLET_NAME, asSepaInfo) - if (diskletFormData != null) { - setName(diskletFormData.name) - setIban(diskletFormData.iban) - setSwift(diskletFormData.swift) - } - }, []) + useAsyncEffect( + async () => { + const diskletFormData: SepaInfo | undefined = await getDiskletFormData(disklet, SEPA_FORM_DISKLET_NAME, asSepaInfo) + if (diskletFormData != null) { + setName(diskletFormData.name) + setIban(diskletFormData.iban) + setSwift(diskletFormData.swift) + } + }, + [], + 'SepaFormScene' + ) return ( From 10ebf67ed4ca094dbe1bb5a3ed3212ce9f14a47c Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 9 Jan 2024 17:33:10 -0800 Subject: [PATCH 3/3] Ignore unknown enabledTokenIds --- src/actions/ExchangeRateActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/actions/ExchangeRateActions.ts b/src/actions/ExchangeRateActions.ts index 0a6016cf9c6..f22500b0f29 100644 --- a/src/actions/ExchangeRateActions.ts +++ b/src/actions/ExchangeRateActions.ts @@ -54,6 +54,7 @@ async function buildExchangeRates(state: RootState): Promise { exchangeRates.push({ currency_pair: `iso:USD_${walletIsoFiat}` }) } for (const tokenId of wallet.enabledTokenIds) { + if (wallet.currencyConfig.allTokens[tokenId] == null) continue const { currencyCode: tokenCode } = wallet.currencyConfig.allTokens[tokenId] if (tokenCode !== currencyCode) { exchangeRates.push({ currency_pair: `${tokenCode}_${walletIsoFiat}` })