From 3a18b2b97bd3bf8fadfce736f796e523a539cc8f Mon Sep 17 00:00:00 2001 From: gregs Date: Mon, 26 Aug 2024 15:26:33 -0300 Subject: [PATCH 01/64] sortDirection --- .../WrappedCollectiblesHeader.tsx | 8 ++--- src/hooks/useNFTsSortBy.ts | 36 +++++++++---------- src/hooks/useRefreshAccountData.ts | 28 +++++++-------- src/hooks/useWalletSectionsData.ts | 11 +++--- src/resources/nfts/index.ts | 32 ++++++++++------- 5 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index bb233041e82..a98d7730bfd 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { Box, Inline, Text } from '@/design-system'; -import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; +import { Box, Inline, Text } from '@/design-system'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; -import useNftSort from '@/hooks/useNFTsSortBy'; +import { useNftSort } from '@/hooks/useNFTsSortBy'; +import * as i18n from '@/languages'; +import React from 'react'; const TokenFamilyHeaderHeight = 48; diff --git a/src/hooks/useNFTsSortBy.ts b/src/hooks/useNFTsSortBy.ts index 345f20132a7..306777b56b8 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -1,31 +1,31 @@ -import { useCallback } from 'react'; +import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; import { MMKV, useMMKVString } from 'react-native-mmkv'; import useAccountSettings from './useAccountSettings'; -import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const mmkv = new MMKV(); const getStorageKey = (accountAddress: string) => `nfts-sort-${accountAddress}`; +const parseNftSort = (s: string | undefined) => { + if (!s) return []; + return s.split('|') as [sortBy: NftCollectionSortCriterion, sortDirection?: SortDirection]; +}; + export const getNftSortForAddress = (accountAddress: string) => { - return mmkv.getString(getStorageKey(accountAddress)) as NftCollectionSortCriterion; + const [sortBy] = parseNftSort(mmkv.getString(getStorageKey(accountAddress))); + return sortBy; }; -export default function useNftSort(): { - nftSort: NftCollectionSortCriterion; - updateNFTSort: (sortBy: NftCollectionSortCriterion) => void; -} { - const { accountAddress } = useAccountSettings(); - const [nftSort, setNftSort] = useMMKVString(getStorageKey(accountAddress)); +const changeDirection = (sortDirection: SortDirection) => (sortDirection === SortDirection.Asc ? SortDirection.Desc : SortDirection.Asc); - const updateNFTSort = useCallback( - (sortBy: NftCollectionSortCriterion) => { - setNftSort(sortBy); - }, - [setNftSort] - ); +export function useNftSort() { + const { accountAddress } = useAccountSettings(); + const [nftSortData, setNftSortData] = useMMKVString(getStorageKey(accountAddress)); + const [nftSort = NftCollectionSortCriterion.MostRecent, nftSortDirection = SortDirection.Desc] = parseNftSort(nftSortData); - return { - updateNFTSort, - nftSort: (nftSort as NftCollectionSortCriterion) || NftCollectionSortCriterion.MostRecent, + const updateNFTSort = (sortBy: NftCollectionSortCriterion) => { + const sortDirection = sortBy === nftSort ? changeDirection(nftSortDirection) : nftSortDirection; + setNftSortData(`${sortBy}|${sortDirection}`); }; + + return { updateNFTSort, nftSort, nftSortDirection }; } diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index ad94660351a..51d977197c2 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,21 +1,21 @@ +import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; +import { PROFILES, useExperimentalFlag } from '@/config'; +import { getIsHardhatConnected } from '@/handlers/web3'; +import { queryClient } from '@/react-query'; +import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import { nftsQueryKey } from '@/resources/nfts'; +import { addysSummaryQueryKey } from '@/resources/summary/summary'; +import logger from '@/utils/logger'; import { captureException } from '@sentry/react-native'; import delay from 'delay'; import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { getIsHardhatConnected } from '@/handlers/web3'; +import { Address } from 'viem'; import { walletConnectLoadState } from '../redux/walletconnect'; import { fetchWalletENSAvatars, fetchWalletNames } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; -import { PROFILES, useExperimentalFlag } from '@/config'; -import logger from '@/utils/logger'; -import { queryClient } from '@/react-query'; -import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; -import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; -import { nftsQueryKey } from '@/resources/nfts'; -import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; -import useNftSort from './useNFTsSortBy'; -import { Address } from 'viem'; -import { addysSummaryQueryKey } from '@/resources/summary/summary'; +import { useNftSort } from './useNFTsSortBy'; import useWallets from './useWallets'; export default function useRefreshAccountData() { @@ -23,7 +23,7 @@ export default function useRefreshAccountData() { const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); - const { nftSort } = useNftSort(); + const { nftSort, nftSortDirection } = useNftSort(); const { wallets } = useWallets(); @@ -35,7 +35,7 @@ export default function useRefreshAccountData() { const fetchAccountData = useCallback(async () => { const connectedToHardhat = getIsHardhatConnected(); - queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort })); + queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort, sortDirection: nftSortDirection })); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat })); @@ -58,7 +58,7 @@ export default function useRefreshAccountData() { captureException(error); throw error; } - }, [accountAddress, dispatch, nativeCurrency, profilesEnabled]); + }, [accountAddress, allAddresses, dispatch, nativeCurrency, nftSort, nftSortDirection, profilesEnabled]); const refresh = useCallback(async () => { if (isRefreshing) return; diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 72e89e3de55..9b822ce2102 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -1,16 +1,16 @@ +import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections'; +import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; +import { useLegacyNFTs } from '@/resources/nfts'; import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import useCoinListEditOptions from './useCoinListEditOptions'; import useCoinListEdited from './useCoinListEdited'; import useHiddenTokens from './useHiddenTokens'; import useIsWalletEthZero from './useIsWalletEthZero'; +import { useNftSort } from './useNFTsSortBy'; import useSendableUniqueTokens from './useSendableUniqueTokens'; import useShowcaseTokens from './useShowcaseTokens'; import useWallets from './useWallets'; -import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections'; -import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; -import { useLegacyNFTs } from '@/resources/nfts'; -import useNftSort from './useNFTsSortBy'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; export default function useWalletSectionsData({ @@ -22,7 +22,7 @@ export default function useWalletSectionsData({ const { isLoading: isLoadingUserAssets, data: sortedAssets = [] } = useSortedUserAssets(); const isWalletEthZero = useIsWalletEthZero(); - const { nftSort } = useNftSort(); + const { nftSort, nftSortDirection } = useNftSort(); const { accountAddress, language, network, nativeCurrency } = useAccountSettings(); const { sendableUniqueTokens } = useSendableUniqueTokens(); @@ -32,6 +32,7 @@ export default function useWalletSectionsData({ } = useLegacyNFTs({ address: accountAddress, sortBy: nftSort, + sortDirection: nftSortDirection, }); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index a39f3c6848d..bf91e05c799 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,22 +1,28 @@ -import { QueryFunction, useQuery } from '@tanstack/react-query'; +import { UniqueAsset } from '@/entities'; +import { arcClient } from '@/graphql'; +import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; +import { Network } from '@/helpers'; import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; -import { NFT } from '@/resources/nfts/types'; +import { AppState } from '@/redux/store'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; +import { QueryFunction, useQuery } from '@tanstack/react-query'; import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { Network } from '@/helpers'; -import { UniqueAsset } from '@/entities'; -import { arcClient } from '@/graphql'; import { createSelector } from 'reselect'; -import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour const NFTS_CACHE_TIME_INTERNAL = 604800000; // 1 week -export const nftsQueryKey = ({ address, sortBy }: { address: string; sortBy: NftCollectionSortCriterion }) => - createQueryKey('nfts', { address, sortBy }, { persisterVersion: 4 }); +export const nftsQueryKey = ({ + address, + sortBy, + sortDirection, +}: { + address: string; + sortBy: NftCollectionSortCriterion; + sortDirection: SortDirection; +}) => createQueryKey('nfts', { address, sortBy, sortDirection }, { persisterVersion: 41123123313 }); export const nftListingQueryKey = ({ contractAddress, @@ -54,8 +60,8 @@ interface NFTData { type NFTQueryKey = ReturnType; const fetchNFTData: QueryFunction = async ({ queryKey }) => { - const [{ address, sortBy }] = queryKey; - const queryResponse = await arcClient.getNFTs({ walletAddress: address, sortBy }); + const [{ address, sortBy, sortDirection }] = queryKey; + const queryResponse = await arcClient.getNFTs({ walletAddress: address, sortBy, sortDirection }); const nfts = queryResponse?.nftsV2?.map(nft => simpleHashNFTToUniqueAsset(nft, address)); @@ -78,15 +84,17 @@ const FALLBACK_DATA: NFTData = { nfts: [], nftsMap: {} }; export function useLegacyNFTs({ address, sortBy = NftCollectionSortCriterion.MostRecent, + sortDirection = SortDirection.Desc, config, }: { address: string; sortBy?: NftCollectionSortCriterion; + sortDirection?: SortDirection; config?: QueryConfigWithSelect; }) { const isImportedWallet = useSelector((state: AppState) => isImportedWalletSelector(state, address)); - const { data, error, isLoading, isInitialLoading } = useQuery(nftsQueryKey({ address, sortBy }), fetchNFTData, { + const { data, error, isLoading, isInitialLoading } = useQuery(nftsQueryKey({ address, sortBy, sortDirection }), fetchNFTData, { cacheTime: isImportedWallet ? NFTS_CACHE_TIME_INTERNAL : NFTS_CACHE_TIME_EXTERNAL, enabled: !!address, retry: 3, From 1df73e803b9e1b39b575eca19e2afc692a91ada7 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 27 Aug 2024 02:34:58 -0300 Subject: [PATCH 02/64] asc/desc menu --- .../WrappedCollectiblesHeader.tsx | 53 +++++++++++++++---- src/components/list/ListHeaderMenu.tsx | 17 +++--- .../native-context-menu/contextMenu.js | 6 --- .../native-context-menu/contextMenu.tsx | 7 +++ src/hooks/useNFTsSortBy.ts | 23 ++++---- src/hooks/useWatchPendingTxs.ts | 25 +++++---- src/resources/nfts/index.ts | 8 ++- 7 files changed, 85 insertions(+), 54 deletions(-) delete mode 100644 src/components/native-context-menu/contextMenu.js create mode 100644 src/components/native-context-menu/contextMenu.tsx diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index a98d7730bfd..60a1cf8b23a 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -1,8 +1,9 @@ import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { Box, Inline, Text } from '@/design-system'; -import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; -import { useNftSort } from '@/hooks/useNFTsSortBy'; +import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; +import { NftSort, useNftSort } from '@/hooks/useNFTsSortBy'; import * as i18n from '@/languages'; +import { colors } from '@/styles'; import React from 'react'; const TokenFamilyHeaderHeight = 48; @@ -30,7 +31,7 @@ const getMenuItemIcon = (value: NftCollectionSortCriterion) => { }; const CollectiblesHeader = () => { - const { nftSort, updateNFTSort } = useNftSort(); + const { nftSort, nftSortDirection, updateNFTSort } = useNftSort(); return ( { ({ - actionKey: value, - actionTitle: i18n.t(i18n.l.nfts.sort[value]), - icon: { iconType: 'SYSTEM', iconValue: getMenuItemIcon(value) }, - menuState: nftSort === key ? 'on' : 'off', - }))} - selectItem={string => updateNFTSort(string as NftCollectionSortCriterion)} + selected={`${nftSort}|${nftSortDirection}`} + menuItems={Object.values(NftCollectionSortCriterion).map(sortCriterion => { + return { + icon: { iconType: 'SYSTEM', iconValue: getMenuItemIcon(sortCriterion) }, + ...(nftSort === sortCriterion + ? { + menuTitle: i18n.t(i18n.l.nfts.sort[sortCriterion]), + menuPreferredElementSize: 'small', + menuState: 'on', + menuItems: [ + { + actionKey: `${sortCriterion}|${SortDirection.Asc}`, + actionTitle: 'Ascending order', + icon: { + iconType: 'SYSTEM', + iconValue: 'arrow.up.circle', + iconTint: nftSortDirection === SortDirection.Asc ? undefined : colors.grey, + }, + }, + { + actionKey: `${sortCriterion}|${SortDirection.Desc}`, + actionTitle: 'Descending order', + icon: { + iconType: 'SYSTEM', + iconValue: 'arrow.down.circle', + iconTint: nftSortDirection === SortDirection.Desc ? undefined : colors.grey, + }, + }, + ], + } + : { + actionKey: `${sortCriterion}|${SortDirection.Desc}`, + actionTitle: i18n.t(i18n.l.nfts.sort[sortCriterion]), + menuState: 'off', + }), + }; + })} + selectItem={string => updateNFTSort(string as NftSort)} icon={getIconForSortType(nftSort)} text={i18n.t(i18n.l.nfts.sort[nftSort])} /> diff --git a/src/components/list/ListHeaderMenu.tsx b/src/components/list/ListHeaderMenu.tsx index f39df68a365..a3cb4a2c833 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -1,19 +1,14 @@ -import React from 'react'; -import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { ButtonPressAnimation } from '@/components/animations'; +import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Bleed, Box, Inline, Text, useForegroundColor } from '@/design-system'; +import { NftSort } from '@/hooks/useNFTsSortBy'; import { haptics } from '@/utils'; -import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; - -type MenuItem = { - actionKey: string; - actionTitle: string; - menuState?: 'on' | 'off'; -}; +import React from 'react'; +import { MenuConfig } from 'react-native-ios-context-menu'; type ListHeaderMenuProps = { - selected: NftCollectionSortCriterion; - menuItems: MenuItem[]; + selected: NftSort; + menuItems: MenuConfig['menuItems']; selectItem: (item: string) => void; icon: string; text: string; diff --git a/src/components/native-context-menu/contextMenu.js b/src/components/native-context-menu/contextMenu.js deleted file mode 100644 index fbe320a1a44..00000000000 --- a/src/components/native-context-menu/contextMenu.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { ContextMenuButton } from 'react-native-ios-context-menu'; - -export default function ContextMenu(props) { - return ; -} diff --git a/src/components/native-context-menu/contextMenu.tsx b/src/components/native-context-menu/contextMenu.tsx new file mode 100644 index 00000000000..95a134cb757 --- /dev/null +++ b/src/components/native-context-menu/contextMenu.tsx @@ -0,0 +1,7 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { ContextMenuButton, ContextMenuButtonProps } from 'react-native-ios-context-menu'; + +export default function ContextMenu(props: ContextMenuButtonProps) { + return ; +} diff --git a/src/hooks/useNFTsSortBy.ts b/src/hooks/useNFTsSortBy.ts index 306777b56b8..a37f69b6a16 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -6,26 +6,27 @@ const mmkv = new MMKV(); const getStorageKey = (accountAddress: string) => `nfts-sort-${accountAddress}`; const parseNftSort = (s: string | undefined) => { - if (!s) return []; - return s.split('|') as [sortBy: NftCollectionSortCriterion, sortDirection?: SortDirection]; + const [sortBy = NftCollectionSortCriterion.MostRecent, sortDirection = SortDirection.Desc] = (s?.split('|') || []) as [ + sortBy?: NftCollectionSortCriterion, + sortDirection?: SortDirection, + ]; + return { sortBy, sortDirection } as const; }; export const getNftSortForAddress = (accountAddress: string) => { - const [sortBy] = parseNftSort(mmkv.getString(getStorageKey(accountAddress))); - return sortBy; + return parseNftSort(mmkv.getString(getStorageKey(accountAddress))); }; -const changeDirection = (sortDirection: SortDirection) => (sortDirection === SortDirection.Asc ? SortDirection.Desc : SortDirection.Asc); +export type NftSort = `${NftCollectionSortCriterion}|${SortDirection}`; export function useNftSort() { const { accountAddress } = useAccountSettings(); const [nftSortData, setNftSortData] = useMMKVString(getStorageKey(accountAddress)); - const [nftSort = NftCollectionSortCriterion.MostRecent, nftSortDirection = SortDirection.Desc] = parseNftSort(nftSortData); + const { sortBy, sortDirection } = parseNftSort(nftSortData); - const updateNFTSort = (sortBy: NftCollectionSortCriterion) => { - const sortDirection = sortBy === nftSort ? changeDirection(nftSortDirection) : nftSortDirection; - setNftSortData(`${sortBy}|${sortDirection}`); + return { + updateNFTSort: (nftSort: NftSort) => setNftSortData(nftSort), + nftSort: sortBy, + nftSortDirection: sortDirection, }; - - return { updateNFTSort, nftSort, nftSortDirection }; } diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 51a710ff73d..c065a77d255 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,21 +1,20 @@ -import { useCallback, useMemo } from 'react'; -import useAccountSettings from './useAccountSettings'; -import { RainbowTransaction, MinedTransaction } from '@/entities/transactions/transaction'; -import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; -import { transactionFetchQuery } from '@/resources/transactions/transaction'; -import { RainbowError, logger } from '@/logger'; -import { Network } from '@/networks/types'; +import { MinedTransaction, RainbowTransaction } from '@/entities/transactions/transaction'; +import { getTransactionFlashbotStatus } from '@/handlers/transactions'; import { getIsHardhatConnected, getProviderForNetwork } from '@/handlers/web3'; -import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; +import { RainbowError, logger } from '@/logger'; import { RainbowNetworks } from '@/networks'; +import { Network } from '@/networks/types'; import { queryClient } from '@/react-query/queryClient'; -import { getTransactionFlashbotStatus } from '@/handlers/transactions'; -import { usePendingTransactionsStore } from '@/state/pendingTransactions'; +import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { invalidateAddressNftsQueries } from '@/resources/nfts'; +import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; +import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { useNonceStore } from '@/state/nonces'; +import { usePendingTransactionsStore } from '@/state/pendingTransactions'; +import { useCallback, useMemo } from 'react'; import { Address } from 'viem'; -import { nftsQueryKey } from '@/resources/nfts'; -import { getNftSortForAddress } from './useNFTsSortBy'; +import useAccountSettings from './useAccountSettings'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ @@ -47,7 +46,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => testnetMode: !!connectedToHardhat, }) ); - queryClient.invalidateQueries(nftsQueryKey({ address, sortBy: getNftSortForAddress(address) })); + invalidateAddressNftsQueries(address); }, [address, nativeCurrency] ); diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index bf91e05c799..c2f78124ffc 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -2,7 +2,7 @@ import { UniqueAsset } from '@/entities'; import { arcClient } from '@/graphql'; import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; import { Network } from '@/helpers'; -import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { QueryConfigWithSelect, createQueryKey, queryClient } from '@/react-query'; import { AppState } from '@/redux/store'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; @@ -22,7 +22,11 @@ export const nftsQueryKey = ({ address: string; sortBy: NftCollectionSortCriterion; sortDirection: SortDirection; -}) => createQueryKey('nfts', { address, sortBy, sortDirection }, { persisterVersion: 41123123313 }); +}) => createQueryKey('nfts', { address, sortBy, sortDirection }, { persisterVersion: 1 }); + +export const invalidateAddressNftsQueries = (address: string) => { + queryClient.invalidateQueries(createQueryKey('nfts', { address })); +}; export const nftListingQueryKey = ({ contractAddress, From d6ede1c3b7a964fcb0bbaeacfb15cb0cac0ec7a0 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 27 Aug 2024 14:02:24 -0300 Subject: [PATCH 03/64] imports --- .../RecyclerAssetList2/WrappedCollectiblesHeader.tsx | 6 +++--- src/components/list/ListHeaderMenu.tsx | 4 ++-- src/hooks/useRefreshAccountData.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 60a1cf8b23a..6d9d3b4faf7 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -1,10 +1,10 @@ -import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; +import React from 'react'; import { Box, Inline, Text } from '@/design-system'; +import * as i18n from '@/languages'; +import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; import { NftSort, useNftSort } from '@/hooks/useNFTsSortBy'; -import * as i18n from '@/languages'; import { colors } from '@/styles'; -import React from 'react'; const TokenFamilyHeaderHeight = 48; diff --git a/src/components/list/ListHeaderMenu.tsx b/src/components/list/ListHeaderMenu.tsx index a3cb4a2c833..48ad813b2b6 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -1,9 +1,9 @@ -import { ButtonPressAnimation } from '@/components/animations'; +import React from 'react'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; +import { ButtonPressAnimation } from '@/components/animations'; import { Bleed, Box, Inline, Text, useForegroundColor } from '@/design-system'; import { NftSort } from '@/hooks/useNFTsSortBy'; import { haptics } from '@/utils'; -import React from 'react'; import { MenuConfig } from 'react-native-ios-context-menu'; type ListHeaderMenuProps = { diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index 51d977197c2..ecda572e737 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -4,7 +4,7 @@ import { getIsHardhatConnected } from '@/handlers/web3'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; -import { nftsQueryKey } from '@/resources/nfts'; +import { invalidateAddressNftsQueries } from '@/resources/nfts'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; import logger from '@/utils/logger'; import { captureException } from '@sentry/react-native'; @@ -35,7 +35,7 @@ export default function useRefreshAccountData() { const fetchAccountData = useCallback(async () => { const connectedToHardhat = getIsHardhatConnected(); - queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort, sortDirection: nftSortDirection })); + invalidateAddressNftsQueries(accountAddress); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat })); From 1d355a9691245bc91b9ca57a8fed3e8f52f19092 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 27 Aug 2024 14:06:57 -0300 Subject: [PATCH 04/64] imports --- src/hooks/useRefreshAccountData.ts | 20 ++++++++++---------- src/hooks/useWalletSectionsData.ts | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index ecda572e737..8b9a5f1d839 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,20 +1,20 @@ -import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; -import { PROFILES, useExperimentalFlag } from '@/config'; -import { getIsHardhatConnected } from '@/handlers/web3'; -import { queryClient } from '@/react-query'; -import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; -import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; -import { invalidateAddressNftsQueries } from '@/resources/nfts'; -import { addysSummaryQueryKey } from '@/resources/summary/summary'; -import logger from '@/utils/logger'; import { captureException } from '@sentry/react-native'; import delay from 'delay'; import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Address } from 'viem'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { walletConnectLoadState } from '../redux/walletconnect'; import { fetchWalletENSAvatars, fetchWalletNames } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; +import { PROFILES, useExperimentalFlag } from '@/config'; +import logger from '@/utils/logger'; +import { queryClient } from '@/react-query'; +import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; +import { invalidateAddressNftsQueries } from '@/resources/nfts'; +import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import { Address } from 'viem'; +import { addysSummaryQueryKey } from '@/resources/summary/summary'; import { useNftSort } from './useNFTsSortBy'; import useWallets from './useWallets'; diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 9b822ce2102..88bd12c8be4 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -1,6 +1,3 @@ -import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections'; -import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; -import { useLegacyNFTs } from '@/resources/nfts'; import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import useCoinListEditOptions from './useCoinListEditOptions'; @@ -11,6 +8,9 @@ import { useNftSort } from './useNFTsSortBy'; import useSendableUniqueTokens from './useSendableUniqueTokens'; import useShowcaseTokens from './useShowcaseTokens'; import useWallets from './useWallets'; +import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections'; +import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; +import { useLegacyNFTs } from '@/resources/nfts'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; export default function useWalletSectionsData({ From d07935de751d868d794580270c9168e1b40dcd6a Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 27 Aug 2024 14:20:36 -0300 Subject: [PATCH 05/64] i18n --- .../RecyclerAssetList2/WrappedCollectiblesHeader.tsx | 4 ++-- src/languages/en_US.json | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 6d9d3b4faf7..1954febef5c 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -59,7 +59,7 @@ const CollectiblesHeader = () => { menuItems: [ { actionKey: `${sortCriterion}|${SortDirection.Asc}`, - actionTitle: 'Ascending order', + actionTitle: i18n.t(i18n.l.nfts.sort.order.asc), icon: { iconType: 'SYSTEM', iconValue: 'arrow.up.circle', @@ -68,7 +68,7 @@ const CollectiblesHeader = () => { }, { actionKey: `${sortCriterion}|${SortDirection.Desc}`, - actionTitle: 'Descending order', + actionTitle: i18n.t(i18n.l.nfts.sort.order.desc), icon: { iconType: 'SYSTEM', iconValue: 'arrow.down.circle', diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 5130d449fc6..c336b050e56 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1320,7 +1320,11 @@ "MOST_RECENT": "Recent", "most_recent": "Recent", "FLOOR_PRICE": "Floor Price", - "floor_price": "Floor Price" + "floor_price": "Floor Price", + "order": { + "asc": "Ascending order", + "desc": "Descending order" + } }, "empty": "Collectibles", "collect_now": "Collect Now", From 473351bf049d8aa391c917c26bbce587c2bf6e61 Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 30 Aug 2024 14:54:09 -0300 Subject: [PATCH 06/64] Merge remote-tracking branch origin/develop into gregs/app-1756-sorting-nfts-on-android-is-broken --- .github/workflows/macstadium-android-e2e.yml | 94 ++- .github/workflows/macstadium-e2e.yml | 4 +- CHANGELOG.md | 10 + android/app/build.gradle | 55 +- .../main/java/me/rainbow/MainApplication.kt | 2 + .../NavbarHeight/NavbarHeightModule.java | 91 +++ .../NavbarHeight/NavbarHeightPackage.java | 28 + android/build.gradle | 4 +- android/settings.gradle | 4 +- e2e/3_homeScreen.spec.ts | 14 +- e2e/9_swaps.spec.ts | 143 +++++ e2e/helpers.ts | 39 +- ios/Gemfile.lock | 79 +-- ios/Podfile.lock | 84 ++- ios/Rainbow.xcodeproj/project.pbxproj | 428 ++++++------- ios/Rainbow/Info.plist | 2 + package.json | 47 +- .../@candlefinance+faster-image+1.5.0.patch | 29 - .../@candlefinance+faster-image+1.6.2.patch | 24 + ...react-native-gesture-handler+2.18.1.patch} | 0 ...h => react-native-reanimated+3.15.0.patch} | 0 .../react-native-text-input-mask+2.0.0.patch | 310 ++++++++- ...atch => react-native-tooltips+1.0.4.patch} | 0 scripts/codegen-translations.js | 2 +- shim.js | 8 +- src/App.tsx | 24 +- src/__swaps__/screens/Swap/Swap.tsx | 4 +- .../components/AnimatedChainImage.android.tsx | 2 +- .../components/AnimatedChainImage.ios.tsx | 2 +- .../screens/Swap/components/CoinRow.tsx | 26 +- .../Swap/components/FastSwapCoinIconImage.tsx | 88 --- .../screens/Swap/components/FlipButton.tsx | 2 +- .../screens/Swap/components/GasButton.tsx | 2 +- .../screens/Swap/components/GasPanel.tsx | 2 +- .../NavigateToSwapSettingsTrigger.tsx | 29 + .../screens/Swap/components/ReviewPanel.tsx | 7 +- .../Swap/components/SwapActionButton.tsx | 16 +- .../Swap/components/SwapBackground.tsx | 11 +- .../Swap/components/SwapBottomPanel.tsx | 1 + .../screens/Swap/components/SwapCoinIcon.tsx | 2 +- .../Swap/components/SwapInputAsset.tsx | 12 +- .../Swap/components/SwapOutputAsset.tsx | 9 +- .../components/TokenList/ChainSelection.tsx | 6 +- .../components/TokenList/TokenToBuyList.tsx | 9 +- src/__swaps__/screens/Swap/constants.ts | 32 +- .../Swap/hooks/useAnimatedSwapStyles.ts | 2 +- .../screens/Swap/hooks/useAssetsToSell.ts | 4 + .../screens/Swap/hooks/useCustomGas.ts | 2 +- .../screens/Swap/hooks/useEstimatedGasFee.ts | 2 +- .../Swap/hooks/useNativeAssetForChain.ts | 6 +- .../Swap/hooks/useSearchCurrencyLists.ts | 5 +- .../screens/Swap/hooks/useSelectedGas.ts | 2 +- .../Swap/hooks/useSwapEstimatedGasLimit.ts | 2 +- .../Swap/hooks/useSwapInputsController.ts | 3 +- .../SyncSwapStateAndSharedValues.tsx | 2 +- .../screens/Swap/providers/swap-provider.tsx | 10 +- .../Swap/resources/_selectors/assets.ts | 2 +- .../screens/Swap/resources/assets/assets.ts | 4 +- .../Swap/resources/assets/userAssets.ts | 38 +- .../resources/assets/userAssetsByChain.ts | 4 +- .../Swap/resources/search/discovery.ts | 4 +- .../screens/Swap/resources/search/search.ts | 4 +- .../screens/Swap/resources/search/utils.ts | 2 +- src/__swaps__/types/assets.ts | 2 +- src/__swaps__/types/chains.ts | 211 ------- src/__swaps__/types/refraction.ts | 2 +- src/__swaps__/types/search.ts | 2 +- src/__swaps__/utils/address.ts | 2 +- src/__swaps__/utils/assets.ts | 3 +- src/__swaps__/utils/chains.ts | 2 +- src/__swaps__/utils/decimalFormatter.ts | 4 +- src/__swaps__/utils/gasUtils.ts | 2 +- src/__swaps__/utils/meteorology.ts | 6 +- src/__swaps__/utils/swaps.ts | 2 +- src/__swaps__/utils/userChains.ts | 2 +- src/analytics/event.ts | 3 +- src/analytics/index.ts | 10 +- src/analytics/utils.ts | 13 +- src/appIcons/appIcons.ts | 2 +- src/components/AddFundsInterstitial.js | 4 +- src/components/ChainLogo.js | 18 +- src/components/ContactRowInfoButton.js | 18 +- src/components/DappBrowser/Dimensions.ts | 4 +- src/components/DappBrowser/Homepage.tsx | 79 ++- .../control-panel/ControlPanel.tsx | 63 +- .../DappBrowser/handleProviderRequest.ts | 62 +- .../hooks/useScreenshotAndScrollTriggers.ts | 2 +- src/components/DappBrowser/screenshots.ts | 10 +- src/components/DappBrowser/utils.ts | 2 +- .../FeaturedResult/FeaturedResultCard.tsx | 60 ++ .../FeaturedResult/FeaturedResultStack.tsx | 48 ++ src/components/L2Disclaimer.js | 8 +- src/components/activity-list/ActivityList.js | 4 +- src/components/animations/animationConfigs.ts | 44 +- .../FastComponents/FastBalanceCoinRow.tsx | 14 +- .../FastComponents/FastCoinBadge.tsx | 2 +- .../FastCurrencySelectionRow.tsx | 2 +- .../RecyclerAssetList2/NFTEmptyState.tsx | 4 +- .../core/ExternalScrollView.tsx | 2 +- .../core/RawRecyclerList.tsx | 4 +- .../ProfileActionButtonsRow.tsx | 4 +- .../avatar-builder/EmojiContent.tsx | 6 +- .../backup/BackupChooseProviderStep.tsx | 8 +- src/components/backup/BackupManuallyStep.tsx | 4 +- src/components/backup/CloudBackupProvider.tsx | 12 +- src/components/backup/RestoreCloudStep.tsx | 9 +- src/components/cards/EthCard.tsx | 4 +- src/components/cards/FeaturedMintCard.tsx | 4 +- .../cards/MintsCard/CollectionCell.tsx | 5 +- src/components/cards/NFTOffersCard/Offer.tsx | 5 +- src/components/cards/OpRewardsCard.tsx | 3 +- src/components/change-wallet/WalletList.tsx | 6 +- src/components/coin-icon/ChainBadge.js | 3 +- src/components/coin-icon/ChainImage.tsx | 3 +- src/components/coin-icon/EthCoinIcon.tsx | 2 +- src/components/coin-icon/RainbowCoinIcon.tsx | 2 +- .../coin-icon/RequestVendorLogoIcon.js | 6 +- src/components/coin-icon/TwoCoinsIcon.tsx | 2 +- src/components/coin-row/CoinRowInfoButton.js | 6 +- .../coin-row/FastTransactionCoinRow.tsx | 5 +- src/components/coin-row/SendCoinRow.js | 6 +- src/components/contacts/ContactRow.js | 6 +- .../context-menu-buttons/ChainContextMenu.tsx | 2 +- .../discover/DiscoverSearchInput.js | 7 +- .../ActionButtons/ActionButtons.tsx | 2 +- .../ens-profile/ActionButtons/MoreButton.tsx | 8 +- src/components/error-boundary/Fallback.tsx | 2 +- .../exchange/ConfirmExchangeButton.js | 6 +- src/components/exchange/ExchangeAssetList.tsx | 1 - src/components/exchange/ExchangeField.tsx | 3 +- .../exchange/ExchangeInputField.tsx | 3 +- .../exchange/ExchangeOutputField.tsx | 2 +- src/components/exchange/ExchangeTokenRow.tsx | 12 +- src/components/exchange/NetworkSwitcher.js | 9 +- src/components/exchange/NetworkSwitcherv2.tsx | 18 +- .../exchangeAssetRowContextMenuProps.ts | 6 +- .../expanded-state/AvailableNetworks.js | 28 +- .../expanded-state/AvailableNetworksv2.tsx | 87 ++- .../expanded-state/ContactProfileState.js | 8 +- .../expanded-state/CustomGasState.js | 4 +- .../UniqueTokenExpandedState.tsx | 14 +- .../asset/ChartExpandedState.js | 31 +- .../expanded-state/asset/SocialLinks.js | 4 +- .../chart/ChartContextButton.js | 4 +- .../chart/ChartExpandedStateHeader.js | 6 +- .../expanded-state/custom-gas/FeesPanel.tsx | 14 +- .../swap-details/CurrencyTile.js | 2 +- .../swap-details/SwapDetailsContent.js | 2 +- .../swap-details/SwapDetailsContractRow.js | 12 +- .../swap-details/SwapDetailsExchangeRow.js | 5 +- .../swap-details/SwapDetailsRewardRow.tsx | 2 +- .../swap-settings/MaxToleranceInput.tsx | 256 ++++---- .../swap-settings/SwapSettingsState.js | 4 +- .../unique-token/NFTBriefTokenInfoRow.tsx | 2 +- .../UniqueTokenExpandedStateHeader.tsx | 12 +- .../unique-token/ZoomableWrapper.android.js | 4 +- src/components/gas/GasSpeedButton.js | 6 +- src/components/investment-cards/PoolValue.js | 4 +- src/components/list/NoResults.tsx | 2 +- src/components/positions/PositionsCard.tsx | 3 +- src/components/qr-code/QRCode.js | 2 +- .../check-fns/hasNonZeroAssetBalance.ts | 32 - .../check-fns/hasSwapTxn.ts | 4 +- .../remote-promo-sheet/check-fns/index.ts | 1 - .../remote-promo-sheet/checkForCampaign.ts | 22 +- .../remote-promo-sheet/localCampaignChecks.ts | 2 +- .../notificationsPromoCampaign.ts | 10 +- .../remote-promo-sheet/runChecks.ts | 2 +- .../secret-display/SecretDisplaySection.tsx | 5 +- .../sheet-action-buttons/SwapActionButton.tsx | 2 +- src/components/svg/SvgImage.js | 10 +- src/components/toasts/OfflineToast.js | 4 +- src/components/toasts/TestnetToast.js | 20 +- .../token-info/TokenInfoBalanceValue.js | 4 +- src/components/value-chart/Chart.js | 2 +- src/components/video/SimpleVideo.tsx | 4 +- .../WalletConnectListItem.js | 6 +- .../WalletConnectV2ListItem.tsx | 6 +- src/config/experimental.ts | 4 +- src/debugging/network.js | 36 +- src/debugging/useDependencyDebugger.ts | 4 +- src/design-system/components/Box/Box.tsx | 15 +- src/ens-avatar/src/utils.ts | 2 +- src/entities/tokens.ts | 17 +- src/entities/transactions/transaction.ts | 3 +- src/entities/uniqueAssets.ts | 3 +- src/featuresToUnlock/tokenGatedUtils.ts | 8 +- .../unlockableAppIconCheck.ts | 10 +- src/graphql/index.ts | 20 +- src/graphql/queries/arc.graphql | 40 ++ src/graphql/queries/metadata.graphql | 13 +- src/handlers/LedgerSigner.ts | 6 +- src/handlers/__mocks__/web3.ts | 2 +- src/handlers/assets.ts | 14 +- src/handlers/authentication.ts | 6 +- src/handlers/cloudBackup.ts | 12 +- src/handlers/cloudinary.ts | 2 +- src/handlers/deeplinks.ts | 42 +- src/handlers/dispersion.ts | 8 +- src/handlers/ens.ts | 38 +- src/handlers/gasFees.ts | 5 +- src/handlers/imgix.ts | 9 +- src/handlers/localstorage/common.ts | 10 +- src/handlers/localstorage/globalSettings.ts | 8 +- src/handlers/localstorage/removeWallet.ts | 12 +- src/handlers/localstorage/theme.ts | 2 +- src/handlers/swap.ts | 51 +- src/handlers/tokenSearch.ts | 13 +- src/handlers/transactions.ts | 4 +- src/handlers/walletReadyEvents.ts | 10 +- src/handlers/web3.ts | 177 ++---- src/helpers/RainbowContext.tsx | 28 +- src/helpers/accountInfo.ts | 2 +- src/helpers/ens.ts | 13 +- src/helpers/findWalletWithAccount.ts | 2 +- src/helpers/gas.ts | 12 +- src/helpers/index.ts | 1 - src/helpers/networkInfo.ts | 26 +- src/helpers/networkTypes.ts | 31 - src/helpers/signingWallet.ts | 15 +- src/helpers/statusBarHelper.ts | 5 - src/helpers/validators.ts | 6 +- src/helpers/walletConnectNetworks.ts | 11 +- src/hooks/charts/useChartInfo.ts | 46 +- src/hooks/charts/useChartThrottledPoints.ts | 3 +- src/hooks/useAccountAsset.ts | 3 +- src/hooks/useAccountTransactions.ts | 26 +- src/hooks/useAdditionalAssetData.ts | 20 +- src/hooks/useAppVersion.ts | 2 +- src/hooks/useAsset.ts | 5 +- src/hooks/useCloudBackups.ts | 12 +- src/hooks/useContacts.ts | 10 +- src/hooks/useDeleteWallet.ts | 2 +- src/hooks/useENSRegistrationActionHandler.ts | 37 +- src/hooks/useENSRegistrationCosts.ts | 9 +- src/hooks/useENSRegistrationStepHandler.tsx | 26 +- src/hooks/useENSSearch.ts | 6 +- src/hooks/useEffectDebugger.ts | 14 +- src/hooks/useGas.ts | 44 +- src/hooks/useHideSplashScreen.ts | 4 +- src/hooks/useImportingWallet.ts | 25 +- src/hooks/useInitializeAccountData.ts | 6 +- src/hooks/useInitializeWallet.ts | 32 +- src/hooks/useLedgerConnect.ts | 8 +- src/hooks/useLedgerImport.ts | 21 +- src/hooks/useLoadAccountData.ts | 4 +- src/hooks/useLoadAccountLateData.ts | 4 +- src/hooks/useLoadGlobalEarlyData.ts | 4 +- src/hooks/useLoadGlobalLateData.ts | 4 +- src/hooks/useManageCloudBackups.ts | 4 +- src/hooks/useOnAvatarPress.ts | 4 +- src/hooks/usePriceImpactDetails.ts | 5 +- src/hooks/useRainbowFee.js | 2 +- src/hooks/useRefreshAccountData.ts | 21 +- src/hooks/useSafeImageUri.ts | 2 +- src/hooks/useScanner.ts | 14 +- src/hooks/useSearchCurrencyList.ts | 13 +- src/hooks/useSendableUniqueTokens.ts | 2 +- src/hooks/useStepper.ts | 2 +- src/hooks/useSwapCurrencyHandlers.ts | 3 - src/hooks/useSwapCurrencyList.ts | 142 ++--- src/hooks/useSwapDerivedOutputs.ts | 44 +- src/hooks/useSwapInputHandlers.ts | 6 +- src/hooks/useSwapRefuel.ts | 28 +- src/hooks/useSwappableUserAssets.ts | 9 +- src/hooks/useUserAccounts.ts | 18 +- src/hooks/useWalletBalances.ts | 4 +- src/hooks/useWalletCloudBackup.ts | 18 +- src/hooks/useWalletManualBackup.ts | 8 +- src/hooks/useWalletsWithBalancesAndNames.ts | 2 +- src/hooks/useWatchPendingTxs.ts | 104 ++- src/hooks/useWatchWallet.ts | 13 +- src/hooks/useWebData.ts | 12 +- src/keychain/index.ts | 54 +- src/languages/ar_AR.json | 15 +- src/languages/en_US.json | 1 + src/languages/es_419.json | 13 +- src/languages/fr_FR.json | 13 +- src/languages/hi_IN.json | 15 +- src/languages/id_ID.json | 13 +- src/languages/ja_JP.json | 15 +- src/languages/ko_KR.json | 15 +- src/languages/pt_BR.json | 13 +- src/languages/ru_RU.json | 13 +- src/languages/th_TH.json | 15 +- src/languages/tr_TR.json | 13 +- src/languages/zh_CN.json | 13 +- src/logger/sentry.ts | 6 +- src/migrations/index.ts | 14 +- src/migrations/migrations/migrateFavorites.ts | 3 +- .../migrateRemotePromoSheetsToZustand.ts | 2 +- src/model/backup.ts | 30 +- src/model/migrations.ts | 123 ++-- src/model/preferences.ts | 11 +- src/model/remoteConfig.ts | 19 +- src/model/wallet.ts | 127 ++-- src/navigation/HardwareWalletTxNavigator.tsx | 6 +- src/navigation/SwipeNavigator.tsx | 17 +- .../views/BottomSheetBackground.tsx | 8 +- .../views/BottomSheetNavigatorView.tsx | 12 +- src/navigation/config.tsx | 7 +- src/navigation/types.ts | 3 + src/networks/README.md | 8 +- src/networks/arbitrum.ts | 5 +- src/networks/avalanche.ts | 5 +- src/networks/base.ts | 5 +- src/networks/blast.ts | 5 +- src/networks/bsc.ts | 5 +- src/networks/degen.ts | 5 +- src/networks/gnosis.ts | 5 +- src/networks/goerli.ts | 5 +- src/networks/index.ts | 64 +- src/networks/mainnet.ts | 10 +- src/networks/optimism.ts | 5 +- src/networks/polygon.ts | 5 +- src/networks/types.ts | 199 +++++- src/networks/zora.ts | 5 +- src/notifications/NotificationsHandler.tsx | 13 +- src/notifications/foregroundHandler.ts | 4 +- src/notifications/permissions.ts | 6 +- src/notifications/settings/firebase.ts | 8 +- src/notifications/settings/initialization.ts | 4 +- src/notifications/tokens.ts | 4 +- src/parsers/accounts.js | 72 --- src/parsers/accounts.ts | 32 + src/parsers/gas.ts | 4 +- src/parsers/index.ts | 2 +- src/parsers/requests.js | 8 +- src/parsers/transactions.ts | 10 +- src/raps/actions/claimBridge.ts | 16 +- src/raps/actions/crosschainSwap.ts | 22 +- src/raps/actions/ens.ts | 56 +- src/raps/actions/swap.ts | 21 +- src/raps/actions/unlock.ts | 38 +- src/raps/common.ts | 2 +- src/raps/execute.ts | 2 +- src/raps/references.ts | 2 +- src/raps/unlockAndSwap.ts | 2 +- src/raps/utils.ts | 3 +- .../interpolations/bSplineInterpolation.js | 14 +- .../createNativeStackNavigator.js | 4 +- src/redux/contacts.ts | 10 +- src/redux/ensRegistration.ts | 8 +- src/redux/explorer.ts | 10 +- src/redux/gas.ts | 153 +++-- src/redux/requests.ts | 8 +- src/redux/settings.ts | 45 +- src/redux/showcaseTokens.ts | 4 +- src/redux/swap.ts | 4 +- src/redux/walletconnect.ts | 40 +- src/redux/wallets.ts | 64 +- src/references/chain-assets.json | 25 +- src/references/gasUnits.ts | 2 +- src/references/index.ts | 32 +- src/references/rainbow-token-list/index.ts | 19 +- src/references/shitcoins.ts | 2 +- src/references/testnet-assets-by-chain.ts | 69 ++ src/resources/assets/UserAssetsQuery.ts | 55 +- src/resources/assets/assetSelectors.ts | 6 +- src/resources/assets/assets.ts | 7 +- src/resources/assets/externalAssetsQuery.ts | 18 +- src/resources/assets/hardhatAssets.ts | 104 ++- src/resources/assets/types.ts | 4 +- src/resources/assets/useSortedUserAssets.ts | 4 +- src/resources/assets/useUserAsset.ts | 6 +- src/resources/assets/useUserAssetCount.ts | 4 +- src/resources/defi/PositionsQuery.ts | 4 +- src/resources/ens/ensAddressQuery.ts | 5 +- src/resources/favorites.ts | 3 +- .../_selectors/getFeaturedResultById.ts | 6 + .../_selectors/getFeaturedResultIds.ts | 5 + .../getFeaturedResultsForPlacement.ts | 6 + .../getFeaturedResultsForPlacementWithIds.ts | 13 + .../featuredResults/getFeaturedResults.ts | 30 + .../featuredResults/trackFeaturedResult.ts | 25 + src/resources/metadata/dapps.tsx | 2 +- src/resources/nfts/index.ts | 30 +- src/resources/nfts/simplehash/index.ts | 48 +- src/resources/nfts/simplehash/types.ts | 2 +- src/resources/nfts/simplehash/utils.ts | 3 +- src/resources/nfts/types.ts | 2 +- src/resources/nfts/utils.ts | 4 +- src/resources/reservoir/mints.ts | 17 +- src/resources/reservoir/utils.ts | 2 +- .../transactions/consolidatedTransactions.ts | 6 +- .../firstTransactionTimestampQuery.ts | 8 +- src/resources/transactions/transaction.ts | 37 +- .../AddCash/components/ProviderCard.tsx | 22 +- src/screens/AddCash/index.tsx | 2 +- .../AddCash/providers/Coinbase/index.tsx | 4 +- .../AddCash/providers/Moonpay/index.tsx | 4 +- src/screens/AddCash/providers/Ramp/index.tsx | 4 +- src/screens/AddCash/utils.ts | 16 +- src/screens/AddWalletSheet.tsx | 18 +- src/screens/ChangeWalletSheet.tsx | 12 +- src/screens/CheckIdentifierScreen.tsx | 6 +- src/screens/CurrencySelectModal.tsx | 25 +- .../Diagnostics/DiagnosticsItemRow.tsx | 2 +- .../helpers/createAndShareStateDumpFile.ts | 2 +- src/screens/Diagnostics/index.tsx | 2 +- src/screens/ENSConfirmRegisterSheet.tsx | 2 +- src/screens/ExchangeModal.tsx | 51 +- src/screens/ExplainSheet.js | 126 ++-- src/screens/MintsSheet/card/Card.tsx | 11 +- src/screens/NFTOffersSheet/OfferRow.tsx | 3 +- src/screens/NFTSingleOfferSheet/index.tsx | 22 +- src/screens/NotificationsPromoSheet/index.tsx | 6 +- src/screens/SendConfirmationSheet.tsx | 32 +- src/screens/SendSheet.js | 83 ++- .../components/AppIconSection.tsx | 8 +- .../components/Backups/ViewWalletBackup.tsx | 16 +- .../components/Backups/WalletsAndBackup.tsx | 12 +- .../components/CurrencySection.tsx | 2 +- .../SettingsSheet/components/DevSection.tsx | 33 +- .../components/GoogleAccountSection.tsx | 4 +- .../components/NetworkSection.tsx | 26 +- .../components/NotificationsSection.tsx | 6 +- .../components/PrivacySection.tsx | 4 +- .../components/SettingsSection.tsx | 21 - .../SettingsSheet/useVisibleWallets.ts | 2 +- src/screens/SignTransactionSheet.tsx | 90 ++- src/screens/SpeedUpAndCancelSheet.js | 73 ++- src/screens/WalletConnectApprovalSheet.js | 49 +- src/screens/WalletScreen/index.tsx | 31 +- src/screens/WelcomeScreen/index.tsx | 4 +- .../DiscoverFeaturedResultsCard.tsx | 38 ++ .../{DiscoverHome.js => DiscoverHome.tsx} | 43 +- .../discover/components/DiscoverSearch.js | 5 +- .../PairHardwareWalletSigningSheet.tsx | 7 +- src/screens/mints/MintSheet.tsx | 33 +- .../points/claim-flow/ClaimRewardsPanel.tsx | 15 +- .../points/components/LeaderboardRow.tsx | 10 +- src/screens/points/content/PointsContent.tsx | 2 +- .../points/content/ReferralContent.tsx | 2 +- .../points/contexts/PointsProfileContext.tsx | 10 +- ...ransactionDetailsHashAndActionsSection.tsx | 2 +- .../TransactionDetailsValueAndFeeSection.tsx | 4 +- .../components/TransactionMasthead.tsx | 4 +- src/state/appSessions/index.test.ts | 38 +- src/state/appSessions/index.ts | 68 +- src/state/assets/userAssets.ts | 20 +- src/state/browser/browserStore.ts | 10 +- src/state/connectedToHardhat/index.ts | 28 + src/state/featuredResults/featuredResults.ts | 0 src/state/internal/createRainbowStore.ts | 14 +- src/state/nonces/index.ts | 61 +- src/state/pendingTransactions/index.ts | 16 +- src/state/remoteCards/remoteCards.ts | 12 +- .../remotePromoSheets/remotePromoSheets.ts | 10 +- src/state/staleBalances/index.test.ts | 165 +++++ src/state/staleBalances/index.ts | 99 +++ src/state/swaps/swapsStore.ts | 10 +- src/state/sync/UserAssetsSync.tsx | 10 +- src/storage/legacy.ts | 2 +- src/styles/colors.ts | 49 +- src/utils/actionsheet.ts | 4 +- src/utils/bluetoothPermissions.ts | 8 +- src/utils/branch.ts | 24 +- src/utils/contenthash.ts | 2 +- src/utils/deviceUtils.ts | 13 +- src/utils/doesWalletsContainAddress.ts | 2 +- src/utils/ethereumUtils.ts | 91 +-- src/utils/getTokenMetadata.ts | 7 - src/utils/getUrlForTrustIconFallback.ts | 13 +- src/utils/index.ts | 2 - src/utils/languageLocaleToCountry.ts | 16 + src/utils/ledger.ts | 8 +- src/utils/logger.ts | 117 ---- src/utils/memoFn.ts | 8 +- src/utils/methodRegistry.ts | 5 +- src/utils/poaps.ts | 4 +- src/utils/requestNavigationHandlers.ts | 12 +- src/utils/reviewAlert.ts | 8 +- src/utils/simplifyChartData.ts | 16 +- src/walletConnect/index.tsx | 204 +++--- tsconfig.json | 1 - yarn.lock | 590 ++++++++++-------- 477 files changed, 5737 insertions(+), 4364 deletions(-) create mode 100644 android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java create mode 100644 android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java create mode 100644 e2e/9_swaps.spec.ts delete mode 100644 patches/@candlefinance+faster-image+1.5.0.patch create mode 100644 patches/@candlefinance+faster-image+1.6.2.patch rename patches/{react-native-gesture-handler+2.17.1.patch => react-native-gesture-handler+2.18.1.patch} (100%) rename patches/{react-native-reanimated+3.14.0.patch => react-native-reanimated+3.15.0.patch} (100%) rename patches/{react-native-tooltips+1.0.3.patch => react-native-tooltips+1.0.4.patch} (100%) delete mode 100644 src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx create mode 100644 src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx delete mode 100644 src/__swaps__/types/chains.ts create mode 100644 src/components/FeaturedResult/FeaturedResultCard.tsx create mode 100644 src/components/FeaturedResult/FeaturedResultStack.tsx delete mode 100644 src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts delete mode 100644 src/helpers/networkTypes.ts delete mode 100644 src/parsers/accounts.js create mode 100644 src/parsers/accounts.ts create mode 100644 src/references/testnet-assets-by-chain.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultById.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultIds.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts create mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts create mode 100644 src/resources/featuredResults/getFeaturedResults.ts create mode 100644 src/resources/featuredResults/trackFeaturedResult.ts create mode 100644 src/screens/discover/components/DiscoverFeaturedResultsCard.tsx rename src/screens/discover/components/{DiscoverHome.js => DiscoverHome.tsx} (69%) create mode 100644 src/state/connectedToHardhat/index.ts create mode 100644 src/state/featuredResults/featuredResults.ts create mode 100644 src/state/staleBalances/index.test.ts create mode 100644 src/state/staleBalances/index.ts delete mode 100644 src/utils/getTokenMetadata.ts create mode 100644 src/utils/languageLocaleToCountry.ts delete mode 100644 src/utils/logger.ts diff --git a/.github/workflows/macstadium-android-e2e.yml b/.github/workflows/macstadium-android-e2e.yml index 44e6bdb75c4..facc0b7861a 100644 --- a/.github/workflows/macstadium-android-e2e.yml +++ b/.github/workflows/macstadium-android-e2e.yml @@ -1,56 +1,82 @@ -# This is a basic workflow to help you get started with Actions - name: Android e2e tests -# Controls when the workflow will run -on: [pull_request, workflow_dispatch] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel +on: [] jobs: - # This workflow contains a single job called "android-e2e" android-e2e: - if: startsWith(github.head_ref, 'android-ci') - # The type of runner that the job will run on - runs-on: ["self-hosted", "ci-5"] - # Cancel current builds if there's a newer commit on the same branch - concurrency: + runs-on: ["self-hosted"] + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - # Steps represent a sequence of tasks that will be executed as part of the job + permissions: + contents: read + steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - name: Checkout repo + uses: actions/checkout@v4 - name: Set up github keys run: git config core.sshCommand "ssh -i ~/.ssh/id_ed25519 -F /dev/null" + - name: Clean Android app + run: yarn clean:android > /dev/null 2>&1 || true + - name: Set up ENV vars & scripts + env: + CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} run: | - # read local env vars - source ~/.bashrc - # fetch env vars + source ~/.zshrc git clone git@github.com:rainbow-me/rainbow-env.git - # unpack dotenv - mv rainbow-env/android/app/google-services.json android/app mv rainbow-env/dotenv .env && rm -rf rainbow-env - # run CI scripts - eval $CI_SCRIPTS - # tweak dotenv for e2e - sed -i''-e "s/\IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - # set up password - cp android/keystores/debug.keystore android/keystores/rainbow-key.keystore - sed -i -e "s:rainbow-alias:androiddebugkey:g" android/app/build.gradle - export RAINBOW_KEY_ANDROID_PASSWORD=android - - name: Install deps via Yarn - run: yarn setup-ci + eval $CI_SCRIPTS_RN_UPGRADE + sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e + + - name: Get Yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache Yarn dependencies + uses: actions/cache@v4 + with: + path: | + ${{ steps.yarn-cache-dir-path.outputs.dir }} + .yarn/cache + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + run: | + yarn cache clean --all && yarn install && yarn setup + + - name: Check for frozen lockfile + run: ./scripts/check-lockfile.sh + + - name: Audit CI + run: yarn audit-ci --config audit-ci.jsonc + + - name: Lint + run: yarn lint:ci + + - name: Unit tests + run: yarn test - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache + + - name: Version debug + run: | + npx react-native info - - name: Build the app in Release mode - run: ./node_modules/.bin/detox build --configuration android.emu.release + - name: Fix permissions + run: | + chmod -R +x node_modules/react-native/scripts + chmod -R +x node_modules/@sentry/react-native/scripts - - name: Run Android e2e tests - run: ./node_modules/.bin/detox test -R 5 --configuration android.emu.release --forceExit + - name: Build the app in Release mode + run: yarn detox build --configuration android.emu.release + # change the '3' here to how many times you want the tests to rerun on failure + - name: Run iOS e2e tests with retry + run: ./scripts/run-retry-tests.sh 3 diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-e2e.yml index 0ae2e69dd9d..fd826523eaa 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-e2e.yml @@ -23,12 +23,12 @@ jobs: - name: Set up ENV vars & scripts env: - CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} + CI_SCRIPTS: ${{ secrets.CI_SCRIPTS }} run: | source ~/.zshrc git clone git@github.com:rainbow-me/rainbow-env.git mv rainbow-env/dotenv .env && rm -rf rainbow-env - eval $CI_SCRIPTS_RN_UPGRADE + eval $CI_SCRIPTS sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - name: Get Yarn cache directory path diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f2c3056133..45ffe98480f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.36] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.36) + +### Fixed + +- Fixed Sentry logging issues (#6012, #6018, #6019) +- Fixed issue in swaps where certain errors were not being handled (#6017) +- Fixed a bug with wrapping and unwrapping ETH (#6022, #6026) +- Fixed a crash that was happening on asset balance (#6025) +- Fixed missing pricing on swaps (#6023) + ## [1.9.35] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.35) ### Added diff --git a/android/app/build.gradle b/android/app/build.gradle index 5cca331f4c6..7297b466b66 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: "org.jetbrains.kotlin.android" apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' - def getPassword(String currentUser, String keyChain) { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() @@ -30,15 +29,9 @@ def getPassword(String currentUser, String keyChain) { stdout.toString().trim() } - import com.android.build.OutputFile - - apply plugin: "com.facebook.react" - - - apply from: "../../node_modules/@sentry/react-native/sentry.gradle" react { @@ -114,11 +107,9 @@ def enableProguardInReleaseBuilds = false */ def jscFlavor = 'org.webkit:android-jsc:+' - - android { def envFile = project.file('../../.env') - def env =[:] + def env = [:] envFile.eachLine { if (it.contains('=') && (!it.startsWith("#"))) { def (key, value) = it.split('=') @@ -139,25 +130,25 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 227 - versionName "1.9.36" + versionCode 228 + versionName "1.9.37" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true multiDexEnabled true - testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type + testBuildType System.getProperty('testBuildType', 'debug') + missingDimensionStrategy 'detox', 'full' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' manifestPlaceholders = [ - BRANCH_KEY: env.get('BRANCH_KEY') - ] + BRANCH_KEY: env.get('BRANCH_KEY') + ] ndk { - abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' } } - signingConfigs { debug { storeFile file('../keystores/debug.keystore') @@ -187,7 +178,7 @@ android { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" // Detox-specific additions to pro-guard - //proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" + proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } @@ -199,8 +190,17 @@ android { pickFirst '**/x86_64/libc++_shared.so' pickFirst '**/x86/libjsc.so' pickFirst '**/armeabi-v7a/libjsc.so' + // exclude + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + exclude("META-INF/*.kotlin_module") } - } dependencies { @@ -208,7 +208,15 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation 'io.github.novacrypto:BIP39:2019.01.27' + implementation("com.github.NovaCrypto:BIP39:0e7fa95f80") { + exclude group: "io.github.novacrypto", module: "ToRuntime" + exclude group: "io.github.novacrypto", module: "SHA256" + } + implementation("com.github.NovaCrypto:Sha256:57bed72da5") { + exclude group: "io.github.novacrypto", module: "ToRuntime" + } + implementation "com.github.NovaCrypto:ToRuntime:c3ae3080eb" + implementation 'com.google.android.play:review:2.0.1' implementation 'com.google.android.play:app-update:2.1.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' @@ -221,8 +229,8 @@ dependencies { } // DETOX - //androidTestImplementation('com.wix:detox:+') - + androidTestImplementation('com.wix:detox:+') { transitive = true } + androidTestImplementation(project(path: ":detox")) } // Run this once to be able to run the application with BUCK @@ -233,6 +241,3 @@ task copyDownloadableDepsToLibs(type: Copy) { } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) - - - diff --git a/android/app/src/main/java/me/rainbow/MainApplication.kt b/android/app/src/main/java/me/rainbow/MainApplication.kt index c096db243ff..08d0640db6a 100644 --- a/android/app/src/main/java/me/rainbow/MainApplication.kt +++ b/android/app/src/main/java/me/rainbow/MainApplication.kt @@ -21,6 +21,7 @@ import me.rainbow.NativeModules.RNStartTime.RNStartTimePackage import me.rainbow.NativeModules.RNTextAnimatorPackage.RNTextAnimatorPackage import me.rainbow.NativeModules.RNZoomableButton.RNZoomableButtonPackage import me.rainbow.NativeModules.SystemNavigationBar.SystemNavigationBarPackage +import me.rainbow.NativeModules.NavbarHeight.NavbarHeightPackage class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { @@ -41,6 +42,7 @@ class MainApplication : Application(), ReactApplication { packages.add(KeychainPackage(KeychainModuleBuilder().withoutWarmUp())) packages.add(RNStartTimePackage(START_MARK)) packages.add(RNHapticsPackage()) + packages.add(NavbarHeightPackage()) return packages } diff --git a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java new file mode 100644 index 00000000000..2a5884a4311 --- /dev/null +++ b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java @@ -0,0 +1,91 @@ +package me.rainbow.NativeModules.NavbarHeight; + +import androidx.annotation.NonNull; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.module.annotations.ReactModule; +import android.graphics.Point; +import android.view.WindowManager; +import android.view.Display; +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; +import java.lang.NoSuchMethodException; +import android.view.WindowInsets; +import android.os.Build; +import android.content.Context; + +@ReactModule(name = NavbarHeightModule.NAME) +public class NavbarHeightModule extends ReactContextBaseJavaModule { + public static final String NAME = "NavbarHeight"; + + public NavbarHeightModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + @NonNull + public String getName() { + return NAME; + } + + // Example method + // See https://reactnative.dev/docs/native-modules-android + @ReactMethod + public double getNavigationBarHeightSync() { + Context context = getReactApplicationContext(); + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + if (Build.VERSION.SDK_INT >= 30) { + return windowManager + .getCurrentWindowMetrics() + .getWindowInsets() + .getInsets(WindowInsets.Type.navigationBars()) + .bottom; + } else { + Point appUsableSize = getAppUsableScreenSize(context); + Point realScreenSize = getRealScreenSize(context); + + // navigation bar on the side + if (appUsableSize.x < realScreenSize.x) { + return appUsableSize.y; + } + + // navigation bar at the bottom + if (appUsableSize.y < realScreenSize.y) { + return realScreenSize.y - appUsableSize.y; + } + + // navigation bar is not present + return 0; + } + } + public Point getAppUsableScreenSize(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + return size; + } + public Point getRealScreenSize(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + Point size = new Point(); + + if (Build.VERSION.SDK_INT >= 17) { + display.getRealSize(size); + } else if (Build.VERSION.SDK_INT >= 14) { + try { + size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display); + size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display); + } catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {} + } + + return size; + } + @ReactMethod(isBlockingSynchronousMethod = true) + public double getNavigationBarHeight() { + return getNavigationBarHeightSync(); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java new file mode 100644 index 00000000000..0f4df602636 --- /dev/null +++ b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java @@ -0,0 +1,28 @@ +package me.rainbow.NativeModules.NavbarHeight; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class NavbarHeightPackage implements ReactPackage { + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new NavbarHeightModule(reactContext)); + return modules; + } + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 8199414d720..1de07c21f7c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,11 +22,13 @@ buildscript { url 'https://maven.fabric.io/public' } mavenCentral() + maven { + url("${rootProject.projectDir}/../node_modules/detox/Detox-android") + } } dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") -// classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" classpath("de.undercouch:gradle-download-task:5.0.1") classpath 'com.google.gms:google-services:4.3.15' diff --git a/android/settings.gradle b/android/settings.gradle index b03e948f98f..9bc55e25785 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -6,7 +6,7 @@ project(':react-native-palette-full').projectDir = new File(rootProject.projectD apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' -/*include ':detox' +include ':detox' project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') -*/ + includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/e2e/3_homeScreen.spec.ts b/e2e/3_homeScreen.spec.ts index c090519ded0..94fea2546f0 100644 --- a/e2e/3_homeScreen.spec.ts +++ b/e2e/3_homeScreen.spec.ts @@ -5,8 +5,9 @@ import { checkIfExists, checkIfExistsByText, swipe, - waitAndTap, afterAllcleanApp, + tap, + delayTime, } from './helpers'; const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth'; @@ -41,19 +42,20 @@ describe('Home Screen', () => { }); it('tapping "Swap" opens the swap screen', async () => { - await waitAndTap('swap-button'); + await tap('swap-button'); + await delayTime('long'); await checkIfExists('swap-screen'); - await swipe('swap-screen', 'down', 'slow'); + await swipe('swap-screen', 'down', 'fast'); }); it('tapping "Send" opens the send screen', async () => { - await waitAndTap('send-button'); + await tap('send-button'); await checkIfVisible('send-asset-form-field'); - await swipe('send-asset-form-field', 'down'); + await swipe('send-asset-form-field', 'down', 'fast'); }); it('tapping "Copy" shows copy address toast', async () => { - await waitAndTap('receive-button'); + await tap('receive-button'); await checkIfVisible('address-copied-toast'); }); }); diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts new file mode 100644 index 00000000000..4655c019275 --- /dev/null +++ b/e2e/9_swaps.spec.ts @@ -0,0 +1,143 @@ +/* + * // Other tests to consider: + * - Flip assets + * - exchange button onPress + * - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged + * - swap execution + * - token search (both from userAssets and output token list) + * - custom gas panel + * - flashbots + * - slippage + * - explainer sheets + * - switching wallets inside of swap screen + */ + +import { + importWalletFlow, + sendETHtoTestWallet, + checkIfVisible, + beforeAllcleanApp, + afterAllcleanApp, + fetchElementAttributes, + tap, + delayTime, + swipeUntilVisible, + tapAndLongPress, + swipe, +} from './helpers'; + +import { expect } from '@jest/globals'; +import { WALLET_VARS } from './testVariables'; + +describe('Swap Sheet Interaction Flow', () => { + beforeAll(async () => { + await beforeAllcleanApp({ hardhat: true }); + }); + afterAll(async () => { + await afterAllcleanApp({ hardhat: true }); + }); + + it('Import a wallet and go to welcome', async () => { + await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK); + }); + + it('Should send ETH to test wallet', async () => { + // send 20 eth + await sendETHtoTestWallet(); + }); + + it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => { + await tap('dev-button-hardhat'); + await checkIfVisible('testnet-toast-Hardhat'); + + // doesn't work atm + // validate it has the expected funds of 20 eth + // const attributes = await fetchElementAttributes('fast-coin-info'); + // expect(attributes.label).toContain('Ethereum'); + // expect(attributes.label).toContain('20'); + }); + + it('Should open swap screen with 50% inputAmount for inputAsset', async () => { + await device.disableSynchronization(); + await tap('swap-button'); + await delayTime('long'); + + await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100); + await swipe('token-to-buy-list', 'up', 'slow', 0.1); + + await tap('token-to-buy-dai-1'); + await delayTime('medium'); + + const swapInput = await fetchElementAttributes('swap-asset-input'); + + expect(swapInput.label).toContain('10'); + expect(swapInput.label).toContain('ETH'); + }); + + it('Should be able to go to review and execute a swap', async () => { + await tap('swap-bottom-action-button'); + const inputAssetActionButton = await fetchElementAttributes('swap-input-asset-action-button'); + const outputAssetActionButton = await fetchElementAttributes('swap-output-asset-action-button'); + const holdToSwapButton = await fetchElementAttributes('swap-bottom-action-button'); + + expect(inputAssetActionButton.label).toBe('ETH 􀆏'); + expect(outputAssetActionButton.label).toBe('DAI 􀆏'); + expect(holdToSwapButton.label).toBe('􀎽 Hold to Swap'); + + await tapAndLongPress('swap-bottom-action-button', 1500); + + // TODO: This doesn't work so need to figure this out eventually... + // await checkIfVisible('profile-screen'); + }); + + it.skip('Should be able to verify swap is happening', async () => { + // await delayTime('very-long'); + // const activityListElements = await fetchElementAttributes('wallet-activity-list'); + // expect(activityListElements.label).toContain('ETH'); + // expect(activityListElements.label).toContain('DAI'); + // await tapByText('Swapping'); + // await delayTime('long'); + // const transactionSheet = await checkIfVisible('transaction-details-sheet'); + // expect(transactionSheet).toBeTruthy(); + }); + + it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should open swap screen from asset chart with that asset selected', async () => { + /** + * tap any user asset (store const uniqueId here) + * wait for Swap header to be visible + * expect inputAsset.uniqueId === const uniqueId ^^ + */ + }); + + it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * grab highest user asset balance from userAssetsStore + * expect inputAsset.uniqueId === highest user asset uniqueId + */ + }); + + it.skip('Should not be able to type in output amount if cross-chain quote', async () => { + /** + * tap swap button + * wait for Swap header to be visible + * select different chain in output list chain selector + * select any asset in output token list + * focus output amount + * attempt to type any number in the SwapNumberPad + * attempt to remove a character as well + * + * ^^ expect both of those to not change the outputAmount + */ + }); +}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 55344f6052c..7ecb9811fc3 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -4,8 +4,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { expect, device, element, by, waitFor } from 'detox'; import { parseEther } from '@ethersproject/units'; +import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox'; -const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608'; +const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F'; const DEFAULT_TIMEOUT = 20_000; const android = device.getPlatform() === 'android'; @@ -70,6 +71,16 @@ export async function tap(elementId: string | RegExp) { } } +interface CustomElementAttributes { + elements: Array; +} + +type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes; + +export const fetchElementAttributes = async (testId: string): Promise => { + return (await element(by.id(testId)).getAttributes()) as ElementAttributes; +}; + export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) { await delayTime('medium'); try { @@ -188,17 +199,17 @@ export async function clearField(elementId: string | RegExp) { } } -export async function tapAndLongPress(elementId: string | RegExp) { +export async function tapAndLongPress(elementId: string | RegExp, duration?: number) { try { - return await element(by.id(elementId)).longPress(); + return await element(by.id(elementId)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by id "${elementId}": ${error}`); } } -export async function tapAndLongPressByText(text: string | RegExp) { +export async function tapAndLongPressByText(text: string | RegExp, duration?: number) { try { - return await element(by.text(text)).longPress(); + return await element(by.text(text)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by text "${text}": ${error}`); } @@ -263,15 +274,22 @@ export async function scrollTo(scrollviewId: string | RegExp, edge: Direction) { } } -export async function swipeUntilVisible(elementId: string | RegExp, scrollViewId: string, direction: Direction, pctVisible = 75) { +export async function swipeUntilVisible( + elementId: string | RegExp, + scrollViewId: string, + direction: Direction, + percentageVisible?: number +) { let stop = false; + while (!stop) { try { await waitFor(element(by.id(elementId))) - .toBeVisible(pctVisible) + .toBeVisible(percentageVisible) .withTimeout(500); + stop = true; - } catch { + } catch (e) { await swipe(scrollViewId, direction, 'slow', 0.2); } } @@ -454,6 +472,11 @@ export async function sendETHtoTestWallet() { to: TESTING_WALLET, value: parseEther('20'), }); + await delayTime('long'); + const balance = await provider.getBalance(TESTING_WALLET); + if (balance.lt(parseEther('20'))) { + throw Error('Error sending ETH to test wallet'); + } return true; } diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index ca3dbe8fdae..1af4554f95c 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -1,37 +1,40 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml activesupport (7.0.5.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.15) + artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.882.0) - aws-sdk-core (3.190.3) + aws-partitions (1.969.0) + aws-sdk-core (3.202.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.76.0) - aws-sdk-core (~> 3, >= 3.188.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.142.0) - aws-sdk-core (~> 3, >= 3.189.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.159.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) - aws-sigv4 (1.8.0) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.9.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) + base64 (0.2.0) claide (1.1.0) cocoapods (1.14.3) addressable (~> 2.8) @@ -84,7 +87,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.109.0) + excon (0.111.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -106,22 +109,22 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.222.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -142,10 +145,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -154,7 +157,7 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) @@ -175,12 +178,12 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) + google-cloud-errors (1.4.0) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -196,41 +199,44 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.7) domain_name (~> 0.5) httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) - jwt (2.7.1) - mini_magick (4.12.0) + json (2.7.2) + jwt (2.8.2) + base64 + mini_magick (4.13.2) mini_mime (1.1.5) minitest (5.18.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.1) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - optparse (0.4.0) + nkf (0.2.0) + optparse (0.5.0) os (1.1.4) plist (3.7.1) public_suffix (4.0.7) - rake (13.1.0) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.3.6) + strscan rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) - signet (0.18.0) + security (0.1.5) + signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -238,6 +244,7 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -253,13 +260,13 @@ GEM uber (0.1.0) unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.23.0) + xcodeproj (1.25.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.2, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ea5bcba53fc..1c400b0f1c1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,13 +5,13 @@ PODS: - React-Core - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FasterImage (1.5.0): - - FasterImage/Nuke (= 1.5.0) - - FasterImage/NukeUI (= 1.5.0) + - FasterImage (1.6.2): + - FasterImage/Nuke (= 1.6.2) + - FasterImage/NukeUI (= 1.6.2) - React-Core - - FasterImage/Nuke (1.5.0): + - FasterImage/Nuke (1.6.2): - React-Core - - FasterImage/NukeUI (1.5.0): + - FasterImage/NukeUI (1.6.2): - React-Core - FBLazyVector (0.74.3) - Firebase (10.27.0): @@ -1169,9 +1169,9 @@ PODS: - Yoga - react-native-change-icon (4.0.0): - React-Core - - react-native-cloud-fs (2.6.1): + - react-native-cloud-fs (2.6.2): - React - - react-native-compat (2.11.2): + - react-native-compat (2.15.1): - DoubleConversion - glog - hermes-engine @@ -1241,7 +1241,7 @@ PODS: - React-Core - react-native-screen-corner-radius (0.2.2): - React - - react-native-skia (1.3.8): + - react-native-skia (1.3.11): - DoubleConversion - glog - hermes-engine @@ -1624,7 +1624,7 @@ PODS: - Yoga - RNFS (2.16.6): - React - - RNGestureHandler (2.17.1): + - RNGestureHandler (2.18.1): - DoubleConversion - glog - hermes-engine @@ -1670,7 +1670,51 @@ PODS: - React-Core - RNReactNativeHapticFeedback (2.2.0): - React-Core - - RNReanimated (3.14.0): + - RNReanimated (3.15.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated (= 3.15.0) + - RNReanimated/worklets (= 3.15.0) + - Yoga + - RNReanimated/reanimated (3.15.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNReanimated/worklets (3.15.0): - DoubleConversion - glog - hermes-engine @@ -1694,7 +1738,7 @@ PODS: - RNRudderSdk (1.12.1): - React - Rudder (< 2.0.0, >= 1.24.1) - - RNScreens (3.32.0): + - RNScreens (3.34.0): - DoubleConversion - glog - hermes-engine @@ -1728,7 +1772,7 @@ PODS: - RNSound/Core (= 0.11.2) - RNSound/Core (0.11.2): - React-Core - - RNSVG (15.3.0): + - RNSVG (15.6.0): - React-Core - RNTextSize (4.0.0-rc.1): - React @@ -2214,7 +2258,7 @@ SPEC CHECKSUMS: BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 - FasterImage: 1f65cdb2966c47b2a7b83162e5fc712534e50149 + FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe @@ -2277,8 +2321,8 @@ SPEC CHECKSUMS: react-native-branch: 960c897d57b9f4912b08b9d06a25284b6e9879da react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22 react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 - react-native-cloud-fs: e7103d1f693c57b481f820fa5e6b6b0522a5a31e - react-native-compat: 0468620f537a51ce0d3f4ba65bda6872240b9a0d + react-native-cloud-fs: c90379f513b8d7ad5cfed610ccf50f27d837016e + react-native-compat: 408a4b320fa4426f2c25ab74ed5764be6b3eb5aa react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 @@ -2293,7 +2337,7 @@ SPEC CHECKSUMS: react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d react-native-screen-corner-radius: 67064efbb78f2d48f01626713ae8142f6a20f925 - react-native-skia: 929b6d19b41e3483a33a410f5c1efdf002929230 + react-native-skia: 8da84ea9410504bf27f0db229539a43f6caabb6a react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 react-native-text-input-mask: 07227297075f9653315f43b0424d596423a01736 react-native-udp: 96a517e5a121cfe69f4b05eeeafefe00c623debf @@ -2338,7 +2382,7 @@ SPEC CHECKSUMS: RNFBRemoteConfig: a8325e2d3772127358dd2e7d5e39c9ad3e3379ac RNFlashList: e9b57a5553639f9b528cc50ab53f25831722ed62 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: 8dbcccada4a7e702e7dec9338c251b1cf393c960 + RNGestureHandler: efed690b8493a00b99654043daeb1335276ac4a2 RNImageCropPicker: 13eab07a785c7a8f8047a1146f7e59d1911c7bb8 RNInputMask: 815461ebdf396beb62cf58916c35cf6930adb991 RNKeyboard: 14793d75953d99c6d950090b8e9698b234c5d08c @@ -2347,13 +2391,13 @@ SPEC CHECKSUMS: RNOS: 31db6fa4a197d179afbba9e6b4d28d450a7f250b RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 - RNReanimated: f4ff116e33e0afc3d127f70efe928847c7c66355 + RNReanimated: 45553a3ae29a75a76269595f8554d07d4090e392 RNRudderSdk: 805d4b7064714f3295bf5f152d3812cc67f67a93 - RNScreens: 5aeecbb09aa7285379b6e9f3c8a3c859bb16401c + RNScreens: aa943ad421c3ced3ef5a47ede02b0cbfc43a012e RNSentry: 7ae2a06a5563de39ec09dcb1e751ac0c67f1883c RNShare: eaee3dd5a06dad397c7d3b14762007035c5de405 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 - RNSVG: a48668fd382115bc89761ce291a81c4ca5f2fd2e + RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b RNTextSize: 21c94a67819fbf2c358a219bf6d251e3be9e5f29 RSCrashReporter: 6b8376ac729b0289ebe0908553e5f56d8171f313 Rudder: 3cfcd9e6c5359cd6d49f411101ed9b894e3b64bd diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index fe4d0b3f373..2dcbfe439c8 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0299CE7B2886202800B5C7E7 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0299CE7A2886202800B5C7E7 /* NotificationService.m */; }; 0299CE7F2886202800B5C7E7 /* ImageNotification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0299CE772886202800B5C7E7 /* ImageNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0C2E322C01EE0F31C4776094 /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -34,8 +35,7 @@ 66A1FEB624AB641100C3F539 /* RNCMScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEB324AB641100C3F539 /* RNCMScreen.m */; }; 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */; }; 66A28EB024CAF1B500410A88 /* TestFlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A28EAF24CAF1B500410A88 /* TestFlight.m */; }; - 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */; }; - 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */; }; + A2AAF523E5B2B1EBC8137D09 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -68,7 +68,6 @@ B5CE8FFF29A5758100EB1EFA /* pooly@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */; }; B5D7F2F029E8D41E003D6A54 /* finiliar@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */; }; B5D7F2F129E8D41E003D6A54 /* finiliar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */; }; - B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */; }; C04D10F025AFC8C1003BEF7A /* Extras.json in Resources */ = {isa = PBXBuildFile; fileRef = C04D10EF25AFC8C1003BEF7A /* Extras.json */; }; C1038325273C2D0C00B18210 /* PriceWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF75272BA7AA00FF5C78 /* PriceWidgetView.swift */; }; C1038337273C5C4200B18210 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF62272BA6EF00FF5C78 /* PriceWidget.swift */; }; @@ -129,6 +128,7 @@ C1C61A932731A05700E5C0B3 /* RainbowTokenList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */; }; C1EB01302731B68400830E70 /* TokenDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EB012E2731B68400830E70 /* TokenDetails.swift */; }; C1EB01312731B68400830E70 /* TokenDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EB012E2731B68400830E70 /* TokenDetails.swift */; }; + C5931485B6F3169BD50DFD30 /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */; }; C72F456C99A646399192517D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 98AED33BAB4247CEBEF8464D /* libz.tbd */; }; C97EAD8D2BD6C6DF00322D53 /* RCTDeviceUUID.m in Sources */ = {isa = PBXBuildFile; fileRef = C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */; }; C9B378A22C5159880085E5D0 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */; }; @@ -140,7 +140,7 @@ C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; - F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */; }; + F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,13 +203,14 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; + 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; 0299CE772886202800B5C7E7 /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE792886202800B5C7E7 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; - 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; + 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; + 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -235,9 +236,6 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; - 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; - 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; - 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -250,16 +248,16 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; - 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; - 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; - 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; - 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; + 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; + 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; @@ -275,14 +273,19 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; + 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; + 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ButtonManager.m; sourceTree = ""; }; + A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; AA0B1CB82B00C5E100EAF77D /* SF-Mono-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Semibold.otf"; path = "../src/assets/fonts/SF-Mono-Semibold.otf"; sourceTree = ""; }; AA0B1CB92B00C5E100EAF77D /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Bold.otf"; path = "../src/assets/fonts/SF-Mono-Bold.otf"; sourceTree = ""; }; AA0B1CBA2B00C5E100EAF77D /* SF-Pro-Rounded-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Black.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Black.otf"; sourceTree = ""; }; @@ -293,6 +296,8 @@ AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; + B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; B52242E428B1B11F0024D19D /* smol@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "smol@2x.png"; sourceTree = ""; }; @@ -313,6 +318,7 @@ B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pooly@3x.png"; sourceTree = ""; }; B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@3x.png"; sourceTree = ""; }; B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@2x.png"; sourceTree = ""; }; + BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C04D10EF25AFC8C1003BEF7A /* Extras.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Extras.json; sourceTree = ""; }; C11640E7274DC10B00C9120A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; C1272389274EBBB6006AC743 /* CurrencyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyDetails.swift; sourceTree = ""; }; @@ -340,7 +346,6 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; - C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -353,15 +358,10 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; - E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; - FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */, + F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */, + C5931485B6F3169BD50DFD30 /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */, + 0C2E322C01EE0F31C4776094 /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */, + A2AAF523E5B2B1EBC8137D09 /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */, - 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */, - 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */, - 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */, + BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */, + 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */, + 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */, + 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */, - 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */, - C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */, - 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */, - E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */, - FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */, - 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */, - 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */, - E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */, - 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */, - 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */, - 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */, - 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */, - 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */, - D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */, + 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */, + 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */, + 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */, + 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */, + 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */, + 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */, + 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */, + A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */, + 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */, + 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */, + 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */, + B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */, + B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */, + EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */, + 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */, + 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */, + A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */, + 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */, - B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */, - 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */, + E25F1DAB12B818820CCCC7DA /* [CP] Embed Pods Frameworks */, + 89D80C8A947BAC7AC4044C2B /* [CP] Copy Pods Resources */, + 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */, + E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */, + 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */, + F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */, + 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -925,12 +925,12 @@ 0299CE762886202800B5C7E7 = { CreatedOnToolsVersion = 13.3.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; 13B07F861A680F5B00A75B9A = { DevelopmentTeam = L74NQAQB8H; LastSwiftMigration = 1120; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Push = { enabled = 1; @@ -943,22 +943,22 @@ C16DCF5B272BA6EF00FF5C78 = { CreatedOnToolsVersion = 12.5.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; C16DCF7E272BAB9500FF5C78 = { CreatedOnToolsVersion = 12.5.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; C9B3789F2C5159880085E5D0 = { CreatedOnToolsVersion = 15.4; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; C9B378B72C515A860085E5D0 = { CreatedOnToolsVersion = 15.4; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; }; }; @@ -1098,7 +1098,7 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */ = { + 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1111,7 +1111,7 @@ shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */ = { + 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1133,31 +1133,13 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */ = { + 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1184,25 +1166,10 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Upload Debug Symbols to Sentry"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; - }; - B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */ = { + 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1217,14 +1184,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */ = { + 89D80C8A947BAC7AC4044C2B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1280,13 +1247,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */ = { + 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1297,7 +1264,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1311,20 +1277,34 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */ = { + 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", + ); + name = "Upload Debug Symbols to Sentry"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; + }; + A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1335,6 +1315,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1348,13 +1329,14 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */ = { + E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1369,14 +1351,32 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */ = { + E25F1DAB12B818820CCCC7DA /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1391,7 +1391,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1602,8 +1602,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -1623,7 +1623,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1633,7 +1633,7 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */; + baseConfigurationReference = 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1652,8 +1652,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1671,7 +1671,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.ImageNotification"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1681,7 +1681,7 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */; + baseConfigurationReference = 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1700,8 +1700,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1719,7 +1719,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1729,7 +1729,7 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */; + baseConfigurationReference = 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1748,8 +1748,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1767,7 +1767,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1777,7 +1777,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */; + baseConfigurationReference = A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1787,8 +1787,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = time; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/RainbowDebug.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; @@ -1833,7 +1833,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1843,7 +1843,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1854,7 +1854,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1895,7 +1895,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1981,8 +1981,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = ""; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/Rainbow.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2011,7 +2011,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2021,7 +2021,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; SWIFT_VERSION = 5.0; @@ -2087,7 +2087,7 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */; + baseConfigurationReference = 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2097,8 +2097,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = time; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/Rainbow.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2127,7 +2127,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.36; + MARKETING_VERSION = 1.9.37; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2137,7 +2137,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; SWIFT_VERSION = 5.0; @@ -2260,7 +2260,7 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */; + baseConfigurationReference = 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2282,8 +2282,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; @@ -2297,7 +2297,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -2309,7 +2309,7 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */; + baseConfigurationReference = 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2331,8 +2331,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2345,7 +2345,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2356,7 +2356,7 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */; + baseConfigurationReference = 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2378,8 +2378,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2392,7 +2392,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2403,7 +2403,7 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */; + baseConfigurationReference = 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2425,8 +2425,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2439,7 +2439,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2450,7 +2450,7 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */; + baseConfigurationReference = B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2470,8 +2470,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; @@ -2485,7 +2485,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -2497,7 +2497,7 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */; + baseConfigurationReference = B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2517,8 +2517,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2531,7 +2531,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2542,7 +2542,7 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */; + baseConfigurationReference = EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2562,8 +2562,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2576,7 +2576,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2587,7 +2587,7 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */; + baseConfigurationReference = 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2607,8 +2607,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2621,7 +2621,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2652,12 +2652,11 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2679,7 +2678,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2711,13 +2709,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2733,7 +2730,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2764,13 +2760,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2786,7 +2781,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2817,13 +2811,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2839,7 +2832,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2870,12 +2862,11 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2897,7 +2888,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2929,13 +2919,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2951,7 +2940,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2982,13 +2970,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -3004,7 +2991,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -3035,13 +3021,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; + DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -3057,7 +3042,6 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/ios/Rainbow/Info.plist b/ios/Rainbow/Info.plist index 4d31e1cc358..91736a5af8d 100644 --- a/ios/Rainbow/Info.plist +++ b/ios/Rainbow/Info.plist @@ -187,6 +187,8 @@ NSAllowsArbitraryLoads + NSAllowsArbitraryLoadsInWebContent + NSAllowsLocalNetworking diff --git a/package.json b/package.json index e95e1737304..52c229cff23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.36-1", + "version": "1.9.37-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", @@ -21,11 +21,13 @@ "clean:packager": "watchman watch-del-all", "clean:node": "rm -rf node_modules", "nuke": "./scripts/nuke.sh", - "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", - "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1", - "detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests", - "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1", + "detox:android:tests": "detox test -c android.emu.debug --maxWorkers 2 -- --bail 1", + "detox:android": "detox build -c android.emu.debug && yarn detox:android:tests", + "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", + "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", + "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", + "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1", "ds:install": "cd src/design-system/docs && yarn install", "ds": "cd src/design-system/docs && yarn dev", "fast": "yarn install && yarn setup && yarn install-pods-fast", @@ -42,8 +44,8 @@ "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", - "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js --quiet", - "lint:js": "eslint --cache .", + "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js", + "lint:js": "eslint --cache . --quiet", "lint:ts": "yarn tsc --skipLibCheck --noEmit", "postinstall": "./scripts/postinstall.sh", "start": "react-native start", @@ -66,7 +68,7 @@ "dependencies": { "@bankify/react-native-animate-number": "0.2.1", "@bradgarropy/use-countdown": "1.4.1", - "@candlefinance/faster-image": "1.5.0", + "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", @@ -121,7 +123,7 @@ "@rudderstack/rudder-sdk-react-native": "1.12.1", "@sentry/react-native": "5.22.0", "@shopify/flash-list": "1.7.0", - "@shopify/react-native-skia": "1.3.8", + "@shopify/react-native-skia": "1.3.11", "@tanstack/query-async-storage-persister": "4.2.1", "@tanstack/react-query": "4.2.1", "@tanstack/react-query-persist-client": "4.2.1", @@ -134,12 +136,12 @@ "@unstoppabledomains/resolution": "7.1.4", "@wagmi/chains": "1.8.0", "@walletconnect/client": "1.8.0", - "@walletconnect/core": "2.11.2", + "@walletconnect/core": "2.15.1", "@walletconnect/legacy-utils": "2.0.0", - "@walletconnect/react-native-compat": "2.11.2", - "@walletconnect/types": "2.11.2", - "@walletconnect/utils": "2.11.2", - "@walletconnect/web3wallet": "1.10.2", + "@walletconnect/react-native-compat": "2.15.1", + "@walletconnect/types": "2.15.1", + "@walletconnect/utils": "2.15.1", + "@walletconnect/web3wallet": "1.14.1", "assert": "1.5.0", "async-mutex": "0.3.2", "asyncstorage-down": "4.2.0", @@ -191,7 +193,6 @@ "moti": "0.28", "multiformats": "9.6.2", "nanoid": "3.2.0", - "p-queue": "7.2.0", "p-wait-for": "4.1.0", "pako": "2.0.4", "parse-ms": "2.1.0", @@ -218,7 +219,7 @@ "react-native-branch": "5.3.1", "react-native-change-icon": "4.0.0", "react-native-circular-progress": "1.3.8", - "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a", + "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a", "react-native-crypto": "2.2.0", "react-native-dark-mode": "0.2.2", "react-native-device-info": "5.3.1", @@ -226,7 +227,7 @@ "react-native-extra-dimensions-android": "1.2.2", "react-native-fast-image": "8.5.11", "react-native-fs": "2.16.6", - "react-native-gesture-handler": "2.17.1", + "react-native-gesture-handler": "2.18.1", "react-native-get-random-values": "1.5.0", "react-native-haptic-feedback": "2.2.0", "react-native-image-crop-picker": "0.41.0", @@ -247,25 +248,25 @@ "react-native-quick-md5": "3.0.6", "react-native-radial-gradient": "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c", "react-native-randombytes": "3.5.3", - "react-native-reanimated": "3.14.0", + "react-native-reanimated": "3.15.0", "react-native-redash": "16.3.0", "react-native-restart": "0.0.22", "react-native-safe-area-context": "4.10.1", "react-native-safe-area-view": "rainbow-me/react-native-safe-area-view", "react-native-screen-corner-radius": "0.2.2", - "react-native-screens": "3.32.0", + "react-native-screens": "3.34.0", "react-native-section-list-get-item-layout": "2.2.3", "react-native-share": "8.2.1", "react-native-sound": "0.11.2", "react-native-splash-screen": "3.3.0", "react-native-storage": "1.0.1", - "react-native-svg": "15.3.0", + "react-native-svg": "15.6.0", "react-native-tab-view": "3.5.1", "react-native-tcp": "3.3.2", "react-native-text-input-mask": "2.0.0", "react-native-text-size": "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a", "react-native-tooltip": "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590", - "react-native-tooltips": "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3", + "react-native-tooltips": "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa", "react-native-udp": "2.7.0", "react-native-url-polyfill": "2.0.0", "react-native-version-number": "0.3.6", @@ -343,7 +344,7 @@ "babel-plugin-date-fns": "2.0.0", "babel-plugin-graphql-tag": "2.5.0", "babel-plugin-lodash": "3.3.4", - "babel-plugin-module-resolver": "4.0.0", + "babel-plugin-module-resolver": "5.0.2", "babel-plugin-rewire": "1.2.0", "babel-plugin-styled-components": "1.11.1", "babel-plugin-transform-remove-console": "6.9.4", @@ -370,7 +371,7 @@ "ts-migrate": "0.1.26", "typescript": "5.1.6", "typescript-coverage-report": "0.6.1", - "webpack": "5.90.3", + "webpack": "5.94.0", "webpack-cli": "5.1.4" }, "engines": { diff --git a/patches/@candlefinance+faster-image+1.5.0.patch b/patches/@candlefinance+faster-image+1.5.0.patch deleted file mode 100644 index f29f47af8db..00000000000 --- a/patches/@candlefinance+faster-image+1.5.0.patch +++ /dev/null @@ -1,29 +0,0 @@ -diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -index a0b0ac6..e093754 100644 ---- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -+++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -@@ -41,7 +41,10 @@ final class FasterImageView: UIView { - lazyImageView.trailingAnchor.constraint(equalTo: trailingAnchor), - ]) - lazyImageView.pipeline = .shared -- lazyImageView.priority = .high -+ // 🌈🌈 -+ lazyImageView.priority = .veryLow -+// lazyImageView.priority = .high -+ // 🌈🌈 - lazyImageView.onCompletion = { [weak self] result in - self?.completionHandler(with: result) - } -@@ -121,11 +124,7 @@ final class FasterImageView: UIView { - } - } - -- var showActivityIndicator = false { -- didSet { -- lazyImageView.placeholderView = UIActivityIndicatorView() -- } -- } -+ var showActivityIndicator = false - - var resizeMode = "contain" { - didSet { diff --git a/patches/@candlefinance+faster-image+1.6.2.patch b/patches/@candlefinance+faster-image+1.6.2.patch new file mode 100644 index 00000000000..238dfac6712 --- /dev/null +++ b/patches/@candlefinance+faster-image+1.6.2.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +index a8dee30..e8e38d3 100644 +--- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift ++++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +@@ -233,17 +233,9 @@ final class FasterImageView: UIView { + } + } + +- var showActivityIndicator = false { +- didSet { +- let activity = UIActivityIndicatorView() +- if self.activityColor != nil { +- activity.color = self.activityColor +- } +- lazyImageView.placeholderView = activity +- } +- } ++ var showActivityIndicator = false + +- var activityColor: UIColor? ++ var activityColor: UIColor? = Color.clear + + var resizeMode = "contain" { + didSet { diff --git a/patches/react-native-gesture-handler+2.17.1.patch b/patches/react-native-gesture-handler+2.18.1.patch similarity index 100% rename from patches/react-native-gesture-handler+2.17.1.patch rename to patches/react-native-gesture-handler+2.18.1.patch diff --git a/patches/react-native-reanimated+3.14.0.patch b/patches/react-native-reanimated+3.15.0.patch similarity index 100% rename from patches/react-native-reanimated+3.14.0.patch rename to patches/react-native-reanimated+3.15.0.patch diff --git a/patches/react-native-text-input-mask+2.0.0.patch b/patches/react-native-text-input-mask+2.0.0.patch index b0dc705aa3c..9ebb2b5fb33 100644 --- a/patches/react-native-text-input-mask+2.0.0.patch +++ b/patches/react-native-text-input-mask+2.0.0.patch @@ -1,7 +1,310 @@ diff --git a/node_modules/react-native-text-input-mask/.DS_Store b/node_modules/react-native-text-input-mask/.DS_Store new file mode 100644 -index 0000000..5902d8c -Binary files /dev/null and b/node_modules/react-native-text-input-mask/.DS_Store differ +index 0000000..e69de29 +diff --git a/node_modules/react-native-text-input-mask/android/build.gradle b/node_modules/react-native-text-input-mask/android/build.gradle +index ba1e645..18630dc 100644 +--- a/node_modules/react-native-text-input-mask/android/build.gradle ++++ b/node_modules/react-native-text-input-mask/android/build.gradle +@@ -26,6 +26,6 @@ android { + + dependencies { + implementation 'com.facebook.react:react-native:+' +- implementation 'com.redmadrobot:inputmask:4.1.0' ++ implementation 'com.github.RedMadRobot:input-mask-android:4.1.0' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' + } +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin +new file mode 100644 +index 0000000..0d259dd +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin +@@ -0,0 +1 @@ ++o/classes +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex +new file mode 100644 +index 0000000..b937d77 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin +new file mode 100644 +index 0000000..5ff383e +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin +@@ -0,0 +1 @@ ++o/debug +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex +new file mode 100644 +index 0000000..530bd9a +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex +new file mode 100644 +index 0000000..b42ab6e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex +new file mode 100644 +index 0000000..5b4c31c +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex +new file mode 100644 +index 0000000..e4d045f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex +new file mode 100644 +index 0000000..6f7f156 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex differ +diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin +new file mode 100644 +index 0000000..5cb003f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin differ +diff --git a/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java +new file mode 100644 +index 0000000..5187c84 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java +@@ -0,0 +1,10 @@ ++/** ++ * Automatically generated file. DO NOT MODIFY ++ */ ++package com.RNTextInputMask; ++ ++public final class BuildConfig { ++ public static final boolean DEBUG = Boolean.parseBoolean("true"); ++ public static final String LIBRARY_PACKAGE_NAME = "com.RNTextInputMask"; ++ public static final String BUILD_TYPE = "debug"; ++} +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +new file mode 100644 +index 0000000..32b79dc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +new file mode 100644 +index 0000000..97e5bd9 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +@@ -0,0 +1,18 @@ ++{ ++ "version": 3, ++ "artifactType": { ++ "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", ++ "kind": "Directory" ++ }, ++ "applicationId": "com.RNTextInputMask", ++ "variantName": "debug", ++ "elements": [ ++ { ++ "type": "SINGLE", ++ "filters": [], ++ "attributes": [], ++ "outputFile": "AndroidManifest.xml" ++ } ++ ], ++ "elementType": "File" ++} +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +new file mode 100644 +index 0000000..1211b1e +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +@@ -0,0 +1,6 @@ ++aarFormatVersion=1.0 ++aarMetadataVersion=1.0 ++minCompileSdk=1 ++minCompileSdkExtension=0 ++minAndroidGradlePluginVersion=1.0.0 ++coreLibraryDesugaringEnabled=false +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +new file mode 100644 +index 0000000..9e26dfe +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +@@ -0,0 +1 @@ ++{} +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +new file mode 100644 +index 0000000..1a5b0f8 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar +new file mode 100644 +index 0000000..82ba69f +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +new file mode 100644 +index 0000000..e40d27b +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +@@ -0,0 +1 @@ ++#Wed Aug 21 15:12:29 EDT 2024 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +new file mode 100644 +index 0000000..87dfc96 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +new file mode 100644 +index 0000000..b65d916 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +new file mode 100644 +index 0000000..85962a0 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml +new file mode 100644 +index 0000000..4f8b0d8 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class +new file mode 100644 +index 0000000..c38e5af +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class +new file mode 100644 +index 0000000..7aa7e43 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class +new file mode 100644 +index 0000000..55f4795 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class +new file mode 100644 +index 0000000..2a9601e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class +new file mode 100644 +index 0000000..a7a3f00 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +new file mode 100644 +index 0000000..78ac5b8 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +@@ -0,0 +1,2 @@ ++R_DEF: Internal format may change without notice ++local +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +new file mode 100644 +index 0000000..fbfd233 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +@@ -0,0 +1,7 @@ ++1 ++2 ++4 ++5 ++6 ++7 +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +new file mode 100644 +index 0000000..32b79dc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json +new file mode 100644 +index 0000000..0637a08 +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json +@@ -0,0 +1 @@ ++[] +\ No newline at end of file +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class +new file mode 100644 +index 0000000..c38e5af +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class +new file mode 100644 +index 0000000..7aa7e43 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class +new file mode 100644 +index 0000000..55f4795 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class +new file mode 100644 +index 0000000..2a9601e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class +new file mode 100644 +index 0000000..a7a3f00 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +new file mode 100644 +index 0000000..c69e81e +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar differ +diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +new file mode 100644 +index 0000000..345473c +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +@@ -0,0 +1 @@ ++com.RNTextInputMask +diff --git a/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt +new file mode 100644 +index 0000000..3c35afc +--- /dev/null ++++ b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt +@@ -0,0 +1,17 @@ ++-- Merging decision tree log --- ++manifest ++ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 ++ package ++ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:2:11-40 ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ xmlns:android ++ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:11-69 ++uses-sdk ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml reason: use-sdk injection requested ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ android:targetSdkVersion ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml ++ android:minSdkVersion ++ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml +diff --git a/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +new file mode 100644 +index 0000000..dc8c43a +Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin differ diff --git a/node_modules/react-native-text-input-mask/index.js b/node_modules/react-native-text-input-mask/index.js index 408edad..3905a6f 100644 --- a/node_modules/react-native-text-input-mask/index.js @@ -61,5 +364,4 @@ index 408edad..3905a6f 100644 +export default forwardRef(ForwardedTextInputMask); diff --git a/node_modules/react-native-text-input-mask/ios/.DS_Store b/node_modules/react-native-text-input-mask/ios/.DS_Store new file mode 100644 -index 0000000..66750e8 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/ios/.DS_Store differ +index 0000000..e69de29 diff --git a/patches/react-native-tooltips+1.0.3.patch b/patches/react-native-tooltips+1.0.4.patch similarity index 100% rename from patches/react-native-tooltips+1.0.3.patch rename to patches/react-native-tooltips+1.0.4.patch diff --git a/scripts/codegen-translations.js b/scripts/codegen-translations.js index 3f18125d4dc..56631ab0f7f 100644 --- a/scripts/codegen-translations.js +++ b/scripts/codegen-translations.js @@ -97,7 +97,7 @@ type ValidScope =${validTagsAsArrays.map(generateTypeForTag).join('')}; * keys. */ function pushNestedKeysAsArrays(keysArray, object, prefixArray) { - for (let key in object) { + for (const key in object) { const keyRepresentation = prefixArray.concat([key]); keysArray.push(keyRepresentation); diff --git a/shim.js b/shim.js index 6a079f429a3..bc700f0239f 100644 --- a/shim.js +++ b/shim.js @@ -6,7 +6,7 @@ import ReactNative from 'react-native'; import Storage from 'react-native-storage'; // import { debugLayoutAnimations } from './src/config/debug'; import { mmkvStorageBackend } from '@/handlers/localstorage/mmkvStorageBackend'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import 'fast-text-encoding'; import globalVariables from './globalVariables'; @@ -47,7 +47,7 @@ for (const [key, value] of Object.entries(globalVariables)) { Object.defineProperty(global, key, { get: () => value, set: () => { - logger.sentry(`Trying to override internal Rainbow var ${key}`); + logger.debug(`[shim]: Trying to override internal Rainbow var ${key}`); }, }); } @@ -108,7 +108,7 @@ ReactNative.LayoutAnimation.configureNext = () => null; // debugLayoutAnimations // ) { // ReactNative.LayoutAnimation.configureNext = (...args) => { -// logger.sentry('LayoutAnimation.configureNext', args); +// logger.debug('[shim]: LayoutAnimation.configureNext', args); // oldConfigureNext(...args); // }; // ReactNative.LayoutAnimation.configureNext.__shimmed = true; @@ -122,7 +122,7 @@ if (!ReactNative.InteractionManager._shimmed) { if (finishAutomatically) { setTimeout(() => { ReactNative.InteractionManager.clearInteractionHandle(handle); - logger.sentry(`Interaction finished automatically`); + logger.debug(`[shim]: Interaction finished automatically`); }, 3000); } return handle; diff --git a/src/App.tsx b/src/App.tsx index 93ec4a4f782..02c836a846e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -79,17 +79,17 @@ function App({ walletReady }: AppProps) { const initialUrl = await Linking.getInitialURL(); branchListenerRef.current = await branchListener(url => { - logger.debug(`Branch: listener called`, {}, logger.DebugContext.deeplinks); + logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); try { handleDeeplink(url, initialRoute); } catch (error) { if (error instanceof Error) { - logger.error(new RainbowError('Error opening deeplink'), { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { message: error.message, url, }); } else { - logger.error(new RainbowError('Error opening deeplink'), { + logger.error(new RainbowError(`[App]: Error opening deeplink`), { message: 'Unknown error', url, }); @@ -98,7 +98,7 @@ function App({ walletReady }: AppProps) { }); if (initialUrl) { - logger.debug(`App: has initial URL, opening with Branch`, { initialUrl }); + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); branch.openURL(initialUrl); } }, [initialRoute]); @@ -140,7 +140,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (!__DEV__ && isTestFlight) { - logger.info(`Test flight usage - ${isTestFlight}`); + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); } identifyFlow(); eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); @@ -162,7 +162,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (walletReady) { - logger.info('✅ Wallet ready!'); + logger.debug(`[App]: ✅ Wallet ready!`); runWalletBackupStatusChecks(); } }, [walletReady]); @@ -249,7 +249,7 @@ function Root() { */ if (deviceIdWasJustCreated && !isReturningUser) { // on very first open, set some default data and fire event - logger.info(`User opened application for the first time`); + logger.debug(`[App]: User opened application for the first time`); const { width: screenWidth, height: screenHeight, scale: screenScale } = Dimensions.get('screen'); @@ -271,13 +271,17 @@ function Root() { initializeApplication() .then(() => { - logger.debug(`Application initialized with Sentry and analytics`); + logger.debug(`[App]: Application initialized with Sentry and analytics`); // init complete, load the rest of the app setInitializing(false); }) - .catch(() => { - logger.error(new RainbowError(`initializeApplication failed`)); + .catch(error => { + logger.error(new RainbowError(`[App]: initializeApplication failed`), { + data: { + error, + }, + }); // for failure, continue to rest of the app for now setInitializing(false); diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index 6634ee6b99b..b3b8f05f449 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -18,7 +18,7 @@ import { SwapInputAsset } from '@/__swaps__/screens/Swap/components/SwapInputAss import { SwapNavbar } from '@/__swaps__/screens/Swap/components/SwapNavbar'; import { SwapOutputAsset } from '@/__swaps__/screens/Swap/components/SwapOutputAsset'; import { SwapSheetGestureBlocker } from '@/__swaps__/screens/Swap/components/SwapSheetGestureBlocker'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; import { AbsolutePortalRoot } from '@/components/AbsolutePortal'; @@ -28,6 +28,7 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { SwapWarning } from './components/SwapWarning'; import { clearCustomGasSettings } from './hooks/useCustomGas'; import { SwapProvider, useSwapContext } from './providers/swap-provider'; +import { NavigateToSwapSettingsTrigger } from './components/NavigateToSwapSettingsTrigger'; /** README * This prototype is largely driven by Reanimated and Gesture Handler, which @@ -85,6 +86,7 @@ export function SwapScreen() { + ); } diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx index 96873dce266..689f9374507 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx @@ -13,7 +13,7 @@ const AvalancheBadge = require('@/assets/badges/avalanche.png'); const BlastBadge = require('@/assets/badges/blast.png'); const DegenBadge = require('@/assets/badges/degen.png'); -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { globalColors } from '@/design-system'; import { PIXEL_RATIO } from '@/utils/deviceUtils'; import { useSwapsStore } from '@/state/swaps/swapsStore'; diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index ceae4f7d750..3ed30eca49b 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -11,7 +11,7 @@ import ZoraBadge from '@/assets/badges/zora.png'; import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { useAnimatedProps } from 'react-native-reanimated'; import { AddressOrEth } from '@/__swaps__/types/assets'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 02e439c90db..37c7feb897c 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -1,14 +1,14 @@ import { BalancePill } from '@/__swaps__/screens/Swap/components/BalancePill'; import { CoinRowButton } from '@/__swaps__/screens/Swap/components/CoinRowButton'; import { AddressOrEth, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ButtonPressAnimation } from '@/components/animations'; import { ContextMenuButton } from '@/components/context-menu'; import { Box, Column, Columns, HitSlop, Inline, Text } from '@/design-system'; import { setClipboard } from '@/hooks/useClipboard'; import * as i18n from '@/languages'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { BASE_DEGEN_ADDRESS, DEGEN_CHAIN_DEGEN_ADDRESS, ETH_ADDRESS } from '@/references'; import { toggleFavorite } from '@/resources/favorites'; import { userAssetsStore } from '@/state/assets/userAssets'; @@ -52,6 +52,7 @@ interface InputCoinRowProps { onPress: (asset: ParsedSearchAsset | null) => void; output?: false | undefined; uniqueId: string; + testID?: string; } type PartialAsset = Pick; @@ -62,11 +63,12 @@ interface OutputCoinRowProps extends PartialAsset { output: true; nativePriceChange?: string; isTrending?: boolean; + testID?: string; } type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { +export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -116,7 +118,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps } if (!address || !chainId) return null; return ( - + @@ -183,7 +185,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps } } const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) => { - const network = RainbowNetworks.find(network => network.id === chainId)?.value; + const networkObject = RainbowNetworkObjects.find(networkObject => networkObject.id === chainId)?.value; const handleCopy = useCallback(() => { haptics.selection(); @@ -195,11 +197,11 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) title: i18n.t(i18n.l.exchange.coin_row.copy_contract_address), action: handleCopy, }, - ...(network + ...(networkObject ? { blockExplorer: { - title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)) }), - action: () => ethereumUtils.openAddressInBlockExplorer(address, chainId), + title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })) }), + action: () => ethereumUtils.openAddressInBlockExplorer({ address, chainId }), }, } : {}), @@ -215,7 +217,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) iconValue: 'doc.on.doc', }, }, - ...(network + ...(networkObject ? [ { actionKey: 'blockExplorer', @@ -234,7 +236,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const handlePressMenuItem = async ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => { if (actionKey === 'copyAddress') { options.copy.action(); - } else if (actionKey === 'blockExplorer' && network) { + } else if (actionKey === 'blockExplorer' && networkObject) { options.blockExplorer?.action(); } }; @@ -242,14 +244,14 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const onPressAndroid = () => showActionSheetWithOptions( { - options: [options.copy.title, ...(network ? [options.blockExplorer?.title] : [])], + options: [options.copy.title, ...(networkObject ? [options.blockExplorer?.title] : [])], showSeparators: true, }, (idx: number) => { if (idx === 0) { options.copy.action(); } - if (idx === 1 && network) { + if (idx === 1 && networkObject) { options.blockExplorer?.action(); } } diff --git a/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx b/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx deleted file mode 100644 index ed659118401..00000000000 --- a/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import { StyleSheet, View } from 'react-native'; -import { ImgixImage } from '@/components/images'; -import { Network } from '@/networks/types'; -import { getUrlForTrustIconFallback } from '@/utils'; - -export const FastSwapCoinIconImage = React.memo(function FastSwapCoinIconImage({ - address, - disableShadow = true, - network, - shadowColor, - size, -}: { - address: string; - children: () => React.ReactNode; - disableShadow?: boolean; - network: Network; - shadowColor: string; - size?: number; -}) { - const imageUrl = getUrlForTrustIconFallback(address, network); - - return ( - - - - ); -}); - -const sx = StyleSheet.create({ - coinIconContainer: { - alignItems: 'center', - borderRadius: 20, - height: 40, - justifyContent: 'center', - overflow: 'visible', - width: 40, - }, - coinIconFallback: { - borderRadius: 20, - height: 40, - overflow: 'hidden', - width: 40, - }, - container: { - elevation: 6, - height: 59, - overflow: 'visible', - paddingTop: 9, - }, - contract: { - height: 40, - width: 40, - }, - fallbackWrapper: { - left: 0, - position: 'absolute', - top: 0, - }, - reactCoinIconContainer: { - alignItems: 'center', - justifyContent: 'center', - }, - reactCoinIconImage: { - height: '100%', - width: '100%', - }, - withShadow: { - elevation: 6, - shadowOffset: { - height: 4, - width: 0, - }, - shadowOpacity: 0.2, - shadowRadius: 6, - }, -}); diff --git a/src/__swaps__/screens/Swap/components/FlipButton.tsx b/src/__swaps__/screens/Swap/components/FlipButton.tsx index 1bc69c1df28..0b71487dfcf 100644 --- a/src/__swaps__/screens/Swap/components/FlipButton.tsx +++ b/src/__swaps__/screens/Swap/components/FlipButton.tsx @@ -14,7 +14,7 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { GestureHandlerButton } from './GestureHandlerButton'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const FlipButton = () => { const { isDarkMode } = useColorMode(); diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index bdef2deda67..6f96e02db53 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,4 +1,3 @@ -import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -24,6 +23,7 @@ import { NavigationSteps, useSwapContext } from '../providers/swap-provider'; import { EstimatedSwapGasFee, EstimatedSwapGasFeeSlot } from './EstimatedSwapGasFee'; import { GestureHandlerButton } from './GestureHandlerButton'; import { UnmountOnAnimatedReaction } from './UnmountOnAnimatedReaction'; +import { ChainId } from '@/networks/types'; const { SWAP_GAS_ICONS } = gasUtils; const GAS_BUTTON_HIT_SLOP = 16; diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 77f6a7dbf0a..85167b3e817 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -4,7 +4,7 @@ import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, withDelay, wi import { MIN_FLASHBOTS_PRIORITY_FEE, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; import { diff --git a/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx b/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx new file mode 100644 index 00000000000..912f20c8a3e --- /dev/null +++ b/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx @@ -0,0 +1,29 @@ +import { useNavigation } from '@/navigation'; +import { RootStackParamList } from '@/navigation/types'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; +import { useSwapContext } from '../providers/swap-provider'; + +export const NavigateToSwapSettingsTrigger = () => { + const route = useRoute>(); + const { setParams } = useNavigation(); + const { SwapNavigation } = useSwapContext(); + + useAnimatedReaction( + () => route.params, + (current, previous) => { + if (!current || current === previous) return; + + if (current.action === 'open_swap_settings') { + SwapNavigation.handleShowSettings(); + runOnJS(setParams)({ + ...route.params, + action: undefined, + }); + } + }, + [route.params?.action, setParams] + ); + + return null; +}; diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 9441ba77056..029407d9310 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -2,8 +2,7 @@ import { AnimatedChainImage } from '@/__swaps__/screens/Swap/components/Animated import { ReviewGasButton } from '@/__swaps__/screens/Swap/components/GasButton'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { useNativeAssetForChain } from '@/__swaps__/screens/Swap/hooks/useNativeAssetForChain'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; -import { chainNameFromChainId } from '@/__swaps__/utils/chains'; +import { ChainNameDisplay, ChainId } from '@/networks/types'; import { useEstimatedTime } from '@/__swaps__/utils/meteorology'; import { convertRawAmountToBalance, @@ -364,10 +363,10 @@ export function ReviewPanel() { }); const openGasExplainer = useCallback(async () => { - const nativeAsset = await getNativeAssetForNetwork(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet); + const nativeAsset = await getNativeAssetForNetwork({ chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet }); navigate(Routes.EXPLAIN_SHEET, { - network: chainNameFromChainId(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet), + chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet, type: 'gas', nativeAsset, }); diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index e407de019ab..4530a28db5d 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -34,6 +34,7 @@ function SwapButton({ disabled, opacity, children, + testID, }: { asset: DerivedValue; borderRadius?: number; @@ -47,6 +48,7 @@ function SwapButton({ disabled?: DerivedValue; opacity?: DerivedValue; children?: React.ReactNode; + testID?: string; }) { const { isDarkMode } = useColorMode(); const fallbackColor = useForegroundColor('label'); @@ -110,6 +112,7 @@ function SwapButton({ return ( - + {labelValue} @@ -225,6 +235,7 @@ export const SwapActionButton = ({ scaleTo, style, disabled, + testID, ...props }: { asset: DerivedValue; @@ -248,6 +259,7 @@ export const SwapActionButton = ({ style?: ViewStyle; disabled?: DerivedValue; opacity?: DerivedValue; + testID?: string; }) => { const disabledWrapper = useAnimatedStyle(() => { return { @@ -268,7 +280,7 @@ export const SwapActionButton = ({ style={[hugContent && feedActionButtonStyles.buttonWrapper, style]} > {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {holdProgress && } diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 8fc56cf4f0e..594b33f61bc 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -1,11 +1,11 @@ import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia'; import React from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { useDerivedValue, withTiming } from 'react-native-reanimated'; import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useColorMode } from '@/design-system'; -import { IS_ANDROID } from '@/env'; +import { IS_ANDROID, IS_TEST } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swaps__/utils/swaps'; import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -18,12 +18,15 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig ); }); + const animatedBottomColor = useDerivedValue(() => { + if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig @@ -34,6 +37,10 @@ export const SwapBackground = () => { return [animatedTopColor.value, animatedBottomColor.value]; }); + if (IS_TEST) { + return ; + } + return ( diff --git a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx index d41f5d28733..87298a06eb8 100644 --- a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx @@ -98,6 +98,7 @@ export function SwapBottomPanel() { } style={styles.inputTextMask}> - + {SwapInputController.formattedInputAmount} @@ -123,7 +131,7 @@ export function SwapInputAsset() { return ( - + diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index ac2dc4a4aba..b3546fef19c 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -15,7 +15,7 @@ import { TokenList } from '@/__swaps__/screens/Swap/components/TokenList/TokenLi import { BASE_INPUT_WIDTH, INPUT_INNER_WIDTH, INPUT_PADDING, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { IS_ANDROID, IS_IOS } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; @@ -39,6 +39,7 @@ function SwapOutputActionButton() { return ( { if (isLoading) return null; + const getFormattedTestId = (name: string, chainId: ChainId) => { + return `token-to-buy-${name}-${chainId}`.toLowerCase().replace(/\s+/g, '-'); + }; + return ( - + } @@ -160,6 +164,7 @@ export const TokenToBuyList = () => { } return ( (config: T): T => { + if (!IS_TEST) return config; + return { + ...config, + duration: 0, + } as T; +}; + +export const buttonPressConfig = disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }); +export const caretConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }); +export const fadeConfig = disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const pulsingConfig = disableForTestingEnvironment({ duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }); +export const sliderConfig = disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }); +export const slowFadeConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }); +export const snappySpringConfig = disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }); +export const snappierSpringConfig = disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }); +export const springConfig = disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }); // // /---- END animation configs ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts index b07a99dfdd1..2c2c791ad81 100644 --- a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts @@ -23,7 +23,7 @@ import { safeAreaInsetValues } from '@/utils'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; import { DerivedValue, SharedValue, interpolate, useAnimatedStyle, useDerivedValue, withSpring, withTiming } from 'react-native-reanimated'; import { NavigationSteps } from './useSwapNavigation'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; const INSET_BOTTOM = IS_ANDROID ? getSoftMenuBarHeight() - 24 : safeAreaInsetValues.bottom + 16; diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts index 13f6d54e02d..53efd2f9cc1 100644 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts @@ -10,6 +10,7 @@ import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; import { useAccountSettings, useDebounce } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const sortBy = (by: UserAssetFilter) => { switch (by) { @@ -28,10 +29,13 @@ export const useAssetsToSell = () => { const debouncedAssetToSellFilter = useDebounce(searchQuery, 200); + const { connectedToHardhat } = useConnectedToHardhatStore(); + const { data: userAssets = [] } = useUserAssets( { address: currentAddress as Address, currency: currentCurrency, + testnetMode: connectedToHardhat, }, { select: data => diff --git a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts index b6cfa892e40..13aa6388d50 100644 --- a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; export type EIP1159GasSettings = { diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index ab3729c448c..9f8af50f901 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { add, convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/__swaps__/utils/numbers'; import { useNativeAsset } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index 1f03494ed2c..95c886d31d0 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SharedValue, runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { ParsedAddressAsset } from '@/entities'; @@ -9,11 +9,11 @@ import { ethereumUtils } from '@/utils'; export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? ChainId.mainnet); - const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset(chainId.value)); + const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset({ chainId: chainId.value })); const getNativeAssetForNetwork = useCallback( (chainId: ChainId) => { - const asset = ethereumUtils.getNetworkNativeAsset(chainId); + const asset = ethereumUtils.getNetworkNativeAsset({ chainId }); nativeAsset.value = asset; }, [nativeAsset] diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index 5d37829f703..a1ba4437ff2 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -1,6 +1,6 @@ import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search/search'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset, TokenSearchAssetKey, TokenSearchThreshold } from '@/__swaps__/types/search'; import { addHexPrefix } from '@/__swaps__/utils/hex'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; @@ -27,6 +27,7 @@ export interface AssetToBuySection { const MAX_UNVERIFIED_RESULTS = 8; const MAX_VERIFIED_RESULTS = 48; +const MAX_POPULAR_RESULTS = 3; const mergeAssetsFavoriteStatus = ({ assets, @@ -177,7 +178,7 @@ const buildListSectionsData = ({ assets: combinedData.popularAssets, recentSwaps: combinedData.recentSwaps, filteredBridgeAssetAddress, - }).slice(0, 6); + }).slice(0, MAX_POPULAR_RESULTS); addSection( 'popular', mergeAssetsFavoriteStatus({ diff --git a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts index 3cf7f91cd48..40f066fec79 100644 --- a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { getCachedGasSuggestions, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts index 9149ee472bb..bffed48cf60 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts @@ -2,7 +2,7 @@ import { CrosschainQuote, Quote, QuoteError, SwapType } from '@rainbow-me/swaps' import { useQuery } from '@tanstack/react-query'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { estimateUnlockAndCrosschainSwap } from '@/raps/unlockAndCrosschainSwap'; import { estimateUnlockAndSwap } from '@/raps/unlockAndSwap'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey } from '@/react-query'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 2d1944a544b..1953277fb87 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,7 +1,7 @@ import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { valueBasedDecimalFormatter } from '@/__swaps__/utils/decimalFormatter'; import { getInputValuesForSliderPositionWorklet, updateInputValuesAfterFlip } from '@/__swaps__/utils/flipAssets'; @@ -872,7 +872,6 @@ export function useSwapInputsController({ } } ); - return { debouncedFetchQuote, formattedInputAmount, diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 474f44943a7..090ec7a11a7 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -10,7 +10,7 @@ import { toScaledIntegerWorklet, } from '@/__swaps__/safe-math/SafeMath'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { add } from '@/__swaps__/utils/numbers'; import { ParsedAddressAsset } from '@/entities'; import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset'; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 5ae640688a2..7c069fc1a20 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -24,12 +24,12 @@ import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextSty import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; import { getDefaultSlippageWorklet, isUnwrapEthWorklet, isWrapEthWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { getFlashbotsProvider, getIsHardhatConnected, getProvider, isHardHat } from '@/handlers/web3'; +import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; @@ -56,6 +56,7 @@ import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisable import { SyncGasStateToSharedValues, SyncQuoteSharedValuesToState } from './SyncSwapStateAndSharedValues'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const swapping = i18n.t(i18n.l.swap.actions.swapping); const holdToSwap = i18n.t(i18n.l.swap.actions.hold_to_swap); @@ -199,8 +200,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { parameters.flashbots && getNetworkObject({ chainId: parameters.chainId }).features.flashbots ? await getFlashbotsProvider() : getProvider({ chainId: parameters.chainId }); - const providerUrl = provider?.connection?.url; - const connectedToHardhat = !!providerUrl && isHardHat(providerUrl); + const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; const isDegenModeEnabled = swapsStore.getState().degenMode; @@ -258,7 +258,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }; } - const chainId = getIsHardhatConnected() ? ChainId.hardhat : parameters.chainId; + const chainId = connectedToHardhat ? ChainId.hardhat : parameters.chainId; const { errorMessage } = await performanceTracking.getState().executeFn({ fn: walletExecuteRap, screen: Screens.SWAPS, diff --git a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts index 9e2e29c05cf..f8c829cc06b 100644 --- a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts +++ b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts @@ -1,5 +1,5 @@ import { ParsedAssetsDict, ParsedAssetsDictByChain, ParsedUserAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { deriveAddressAndChainWithUniqueId } from '@/__swaps__/utils/address'; import { add } from '@/__swaps__/utils/numbers'; diff --git a/src/__swaps__/screens/Swap/resources/assets/assets.ts b/src/__swaps__/screens/Swap/resources/assets/assets.ts index 81108a88786..85d800e5be5 100644 --- a/src/__swaps__/screens/Swap/resources/assets/assets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/assets.ts @@ -4,7 +4,7 @@ import { requestMetadata } from '@/graphql'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { AddressOrEth, AssetMetadata, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { chunkArray, createAssetQuery, parseAssetMetadata } from '@/__swaps__/utils/assets'; import { RainbowError, logger } from '@/logger'; export const ASSETS_TIMEOUT_DURATION = 10000; @@ -50,7 +50,7 @@ export async function assetsQueryFunction({ const parsedAssets = parseAssets(results, chainId, currency); return parsedAssets; } catch (e) { - logger.error(new RainbowError('assetsQueryFunction: '), { + logger.error(new RainbowError('[assetsQueryFunction]: Failed to fetch assets'), { message: (e as Error)?.message, }); return {}; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index cd0e6abdca8..37dc8c9f185 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -4,17 +4,17 @@ import { Address } from 'viem'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; -import { getIsHardhatConnected } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; import { SupportedCurrencyKey, SUPPORTED_CHAIN_IDS } from '@/references'; import { ParsedAssetsDictByChain, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { parseUserAsset } from '@/__swaps__/utils/assets'; import { greaterThan } from '@/__swaps__/utils/numbers'; import { fetchUserAssetsByChain } from './userAssetsByChain'; +import { fetchHardhatBalances, fetchHardhatBalancesByChainId } from '@/resources/assets/hardhatAssets'; const addysHttp = new RainbowFetchClient({ baseURL: 'https://addys.p.rainbow.me/v3', @@ -81,10 +81,32 @@ export const userAssetsSetQueryData = ({ address, currency, userAssets, testnetM queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), userAssets); }; -async function userAssetsQueryFunction({ queryKey: [{ address, currency, testnetMode }] }: QueryFunctionArgs) { +async function userAssetsQueryFunction({ + queryKey: [{ address, currency, testnetMode }], +}: QueryFunctionArgs): Promise { if (!address) { return {}; } + if (testnetMode) { + const { assets, chainIdsInResponse } = await fetchHardhatBalancesByChainId(address); + const parsedAssets: Array<{ + asset: ZerionAsset; + quantity: string; + small_balances: boolean; + }> = Object.values(assets).map(asset => ({ + asset: asset.asset, + quantity: asset.quantity, + small_balances: false, + })); + + const parsedAssetsDict = await parseUserAssets({ + assets: parsedAssets, + chainIds: chainIdsInResponse, + currency, + }); + + return parsedAssetsDict; + } const cache = queryClient.getQueryCache(); const cachedUserAssets = (cache.find(userAssetsQueryKey({ address, currency, testnetMode }))?.state?.data || {}) as ParsedAssetsDictByChain; @@ -123,7 +145,7 @@ async function userAssetsQueryFunction({ queryKey: [{ address, currency, testnet } return cachedUserAssets; } catch (e) { - logger.error(new RainbowError('userAssetsQueryFunction: '), { + logger.error(new RainbowError('[userAssetsQueryFunction]: Failed to fetch user assets'), { message: (e as Error)?.message, }); return cachedUserAssets; @@ -169,7 +191,7 @@ async function userAssetsQueryFunctionRetryByChain({ } queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), cachedUserAssets); } catch (e) { - logger.error(new RainbowError('userAssetsQueryFunctionRetryByChain: '), { + logger.error(new RainbowError('[userAssetsQueryFunctionRetryByChain]: Failed to retry fetching user assets'), { message: (e as Error)?.message, }); } @@ -208,12 +230,10 @@ export async function parseUserAssets({ // Query Hook export function useUserAssets( - { address, currency }: UserAssetsArgs, + { address, currency, testnetMode }: UserAssetsArgs, config: QueryConfigWithSelect = {} ) { - const isHardhatConnected = getIsHardhatConnected(); - - return useQuery(userAssetsQueryKey({ address, currency, testnetMode: isHardhatConnected }), userAssetsQueryFunction, { + return useQuery(userAssetsQueryKey({ address, currency, testnetMode }), userAssetsQueryFunction, { ...config, refetchInterval: USER_ASSETS_REFETCH_INTERVAL, staleTime: process.env.IS_TESTING === 'true' ? 0 : 1000, diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts index 9652ecf2ae4..b2b130aea98 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { ParsedAssetsDictByChain, ParsedUserAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { RainbowError, logger } from '@/logger'; @@ -82,7 +82,7 @@ export async function userAssetsByChainQueryFunction({ return cachedDataForChain; } } catch (e) { - logger.error(new RainbowError(`userAssetsByChainQueryFunction - chainId = ${chainId}:`), { + logger.error(new RainbowError(`[userAssetsByChainQueryFunction]: Failed to fetch user assets for ${chainId}`), { message: (e as Error)?.message, }); return cachedDataForChain; diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts index b64c680ba04..269f44441b9 100644 --- a/src/__swaps__/screens/Swap/resources/search/discovery.ts +++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; @@ -28,7 +28,7 @@ async function tokenSearchQueryFunction({ queryKey: [{ chainId }] }: QueryFuncti const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); return parseTokenSearch(tokenSearch.data.data, chainId); } catch (e) { - logger.error(new RainbowError('Token discovery failed'), { url }); + logger.error(new RainbowError('[tokenSearchQueryFunction]: Token discovery failed'), { url }); return []; } } diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts index 86c45602cdd..8f29f7eb1d9 100644 --- a/src/__swaps__/screens/Swap/resources/search/search.ts +++ b/src/__swaps__/screens/Swap/resources/search/search.ts @@ -1,5 +1,5 @@ /* eslint-disable no-nested-ternary */ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; @@ -90,7 +90,7 @@ async function tokenSearchQueryFunction({ return parseTokenSearch(tokenSearch.data.data, chainId); } } catch (e) { - logger.error(new RainbowError('Token search failed'), { url }); + logger.error(new RainbowError('[tokenSearchQueryFunction]: Token search failed'), { url }); return []; } } diff --git a/src/__swaps__/screens/Swap/resources/search/utils.ts b/src/__swaps__/screens/Swap/resources/search/utils.ts index 0ed74aeb80e..4c649d04cae 100644 --- a/src/__swaps__/screens/Swap/resources/search/utils.ts +++ b/src/__swaps__/screens/Swap/resources/search/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ARBITRUM_ETH_ADDRESS, diff --git a/src/__swaps__/types/assets.ts b/src/__swaps__/types/assets.ts index 05f78261ff1..73946574888 100644 --- a/src/__swaps__/types/assets.ts +++ b/src/__swaps__/types/assets.ts @@ -1,7 +1,7 @@ import type { Address } from 'viem'; import { ETH_ADDRESS } from '@/references'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { SearchAsset } from '@/__swaps__/types/search'; import { ResponseByTheme } from '../utils/swaps'; diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts deleted file mode 100644 index 98105ad5628..00000000000 --- a/src/__swaps__/types/chains.ts +++ /dev/null @@ -1,211 +0,0 @@ -import * as chain from 'viem/chains'; -import type { Chain } from 'viem/chains'; - -const HARDHAT_CHAIN_ID = 1337; -const HARDHAT_OP_CHAIN_ID = 1338; - -export const chainHardhat: Chain = { - id: HARDHAT_CHAIN_ID, - name: 'Hardhat', - nativeCurrency: { - decimals: 18, - name: 'Hardhat', - symbol: 'ETH', - }, - rpcUrls: { - public: { http: ['http://127.0.0.1:8545'] }, - default: { http: ['http://127.0.0.1:8545'] }, - }, - testnet: true, -}; - -export const chainHardhatOptimism: Chain = { - id: HARDHAT_OP_CHAIN_ID, - name: 'Hardhat OP', - nativeCurrency: { - decimals: 18, - name: 'Hardhat OP', - symbol: 'ETH', - }, - rpcUrls: { - public: { http: ['http://127.0.0.1:8545'] }, - default: { http: ['http://127.0.0.1:8545'] }, - }, - testnet: true, -}; - -export enum ChainName { - arbitrum = 'arbitrum', - arbitrumNova = 'arbitrum-nova', - arbitrumSepolia = 'arbitrum-sepolia', - avalanche = 'avalanche', - avalancheFuji = 'avalanche-fuji', - base = 'base', - blast = 'blast', - blastSepolia = 'blast-sepolia', - bsc = 'bsc', - celo = 'celo', - degen = 'degen', - gnosis = 'gnosis', - linea = 'linea', - manta = 'manta', - optimism = 'optimism', - polygon = 'polygon', - polygonZkEvm = 'polygon-zkevm', - rari = 'rari', - scroll = 'scroll', - zora = 'zora', - mainnet = 'mainnet', - holesky = 'holesky', - hardhat = 'hardhat', - hardhatOptimism = 'hardhat-optimism', - sepolia = 'sepolia', - optimismSepolia = 'optimism-sepolia', - bscTestnet = 'bsc-testnet', - polygonMumbai = 'polygon-mumbai', - baseSepolia = 'base-sepolia', - zoraSepolia = 'zora-sepolia', - polygonAmoy = 'polygon-amoy', -} - -export enum ChainId { - arbitrum = chain.arbitrum.id, - arbitrumNova = chain.arbitrumNova.id, - arbitrumSepolia = chain.arbitrumSepolia.id, - avalanche = chain.avalanche.id, - avalancheFuji = chain.avalancheFuji.id, - base = chain.base.id, - baseSepolia = chain.baseSepolia.id, - blast = chain.blast.id, - blastSepolia = chain.blastSepolia.id, - bsc = chain.bsc.id, - bscTestnet = chain.bscTestnet.id, - celo = chain.celo.id, - degen = chain.degen.id, - gnosis = chain.gnosis.id, - goerli = chain.goerli.id, - hardhat = HARDHAT_CHAIN_ID, - hardhatOptimism = chainHardhatOptimism.id, - holesky = chain.holesky.id, - linea = chain.linea.id, - mainnet = chain.mainnet.id, - manta = chain.manta.id, - optimism = chain.optimism.id, - optimismSepolia = chain.optimismSepolia.id, - polygon = chain.polygon.id, - polygonAmoy = chain.polygonAmoy.id, - polygonMumbai = chain.polygonMumbai.id, - polygonZkEvm = chain.polygonZkEvm.id, - rari = 1380012617, - scroll = chain.scroll.id, - sepolia = chain.sepolia.id, - zora = chain.zora.id, - zoraSepolia = chain.zoraSepolia.id, -} - -export const chainNameToIdMapping: { - [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; -} = { - ['ethereum']: ChainId.mainnet, - [ChainName.arbitrum]: ChainId.arbitrum, - [ChainName.arbitrumNova]: ChainId.arbitrumNova, - [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, - [ChainName.avalanche]: ChainId.avalanche, - [ChainName.avalancheFuji]: ChainId.avalancheFuji, - [ChainName.base]: ChainId.base, - [ChainName.bsc]: ChainId.bsc, - [ChainName.celo]: ChainId.celo, - [ChainName.degen]: ChainId.degen, - [ChainName.gnosis]: ChainId.gnosis, - [ChainName.linea]: ChainId.linea, - [ChainName.manta]: ChainId.manta, - [ChainName.optimism]: ChainId.optimism, - [ChainName.polygon]: ChainId.polygon, - [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, - [ChainName.rari]: ChainId.rari, - [ChainName.scroll]: ChainId.scroll, - [ChainName.zora]: ChainId.zora, - [ChainName.mainnet]: ChainId.mainnet, - [ChainName.holesky]: ChainId.holesky, - [ChainName.hardhat]: ChainId.hardhat, - [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, - ['ethereum-sepolia']: ChainId.sepolia, - [ChainName.sepolia]: ChainId.sepolia, - [ChainName.optimismSepolia]: ChainId.optimismSepolia, - [ChainName.bscTestnet]: ChainId.bscTestnet, - [ChainName.polygonMumbai]: ChainId.polygonMumbai, - [ChainName.baseSepolia]: ChainId.baseSepolia, - [ChainName.zoraSepolia]: ChainId.zoraSepolia, - [ChainName.blast]: ChainId.blast, - [ChainName.blastSepolia]: ChainId.blastSepolia, - [ChainName.polygonAmoy]: ChainId.polygonAmoy, -}; - -export const chainIdToNameMapping: { - [key in ChainId]: ChainName; -} = { - [ChainId.arbitrum]: ChainName.arbitrum, - [ChainId.arbitrumNova]: ChainName.arbitrumNova, - [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, - [ChainId.avalanche]: ChainName.avalanche, - [ChainId.avalancheFuji]: ChainName.avalancheFuji, - [ChainId.base]: ChainName.base, - [ChainId.blast]: ChainName.blast, - [ChainId.blastSepolia]: ChainName.blastSepolia, - [ChainId.bsc]: ChainName.bsc, - [ChainId.celo]: ChainName.celo, - [ChainId.degen]: ChainName.degen, - [ChainId.gnosis]: ChainName.gnosis, - [ChainId.linea]: ChainName.linea, - [ChainId.manta]: ChainName.manta, - [ChainId.optimism]: ChainName.optimism, - [ChainId.polygon]: ChainName.polygon, - [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, - [ChainId.rari]: ChainName.rari, - [ChainId.scroll]: ChainName.scroll, - [ChainId.zora]: ChainName.zora, - [ChainId.mainnet]: ChainName.mainnet, - [ChainId.holesky]: ChainName.holesky, - [ChainId.hardhat]: ChainName.hardhat, - [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, - [ChainId.sepolia]: ChainName.sepolia, - [ChainId.optimismSepolia]: ChainName.optimismSepolia, - [ChainId.bscTestnet]: ChainName.bscTestnet, - [ChainId.polygonMumbai]: ChainName.polygonMumbai, - [ChainId.baseSepolia]: ChainName.baseSepolia, - [ChainId.zoraSepolia]: ChainName.zoraSepolia, - [ChainId.polygonAmoy]: ChainName.polygonAmoy, -}; - -export const ChainNameDisplay = { - [ChainId.arbitrum]: 'Arbitrum', - [ChainId.arbitrumNova]: chain.arbitrumNova.name, - [ChainId.avalanche]: 'Avalanche', - [ChainId.avalancheFuji]: 'Avalanche Fuji', - [ChainId.base]: 'Base', - [ChainId.blast]: 'Blast', - [ChainId.blastSepolia]: 'Blast Sepolia', - [ChainId.bsc]: 'BSC', - [ChainId.celo]: chain.celo.name, - [ChainId.degen]: 'Degen Chain', - [ChainId.linea]: 'Linea', - [ChainId.manta]: 'Manta', - [ChainId.optimism]: 'Optimism', - [ChainId.polygon]: 'Polygon', - [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, - [ChainId.rari]: 'RARI Chain', - [ChainId.scroll]: chain.scroll.name, - [ChainId.zora]: 'Zora', - [ChainId.mainnet]: 'Ethereum', - [ChainId.hardhat]: 'Hardhat', - [ChainId.hardhatOptimism]: chainHardhatOptimism.name, - [ChainId.sepolia]: chain.sepolia.name, - [ChainId.holesky]: chain.holesky.name, - [ChainId.optimismSepolia]: chain.optimismSepolia.name, - [ChainId.bscTestnet]: 'BSC Testnet', - [ChainId.polygonMumbai]: chain.polygonMumbai.name, - [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, - [ChainId.baseSepolia]: chain.baseSepolia.name, - [ChainId.zoraSepolia]: 'Zora Sepolia', - [ChainId.polygonAmoy]: 'Polygon Amoy', -} as const; diff --git a/src/__swaps__/types/refraction.ts b/src/__swaps__/types/refraction.ts index e18be684eb0..9afc8ce9cf3 100644 --- a/src/__swaps__/types/refraction.ts +++ b/src/__swaps__/types/refraction.ts @@ -1,5 +1,5 @@ import { ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { PaginatedTransactionsApiResponse } from '@/resources/transactions/types'; /** diff --git a/src/__swaps__/types/search.ts b/src/__swaps__/types/search.ts index 5acf427de4e..9e0aff40e3d 100644 --- a/src/__swaps__/types/search.ts +++ b/src/__swaps__/types/search.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, AssetType, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { AssetToBuySectionId } from '../screens/Swap/hooks/useSearchCurrencyLists'; export type TokenSearchAssetKey = keyof ParsedAsset; diff --git a/src/__swaps__/utils/address.ts b/src/__swaps__/utils/address.ts index 39fd20091c1..1b56b2ba57e 100644 --- a/src/__swaps__/utils/address.ts +++ b/src/__swaps__/utils/address.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function truncateAddress(address?: AddressOrEth) { if (!address) return ''; diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts index 8b888c36fc2..420aee6a9b8 100644 --- a/src/__swaps__/utils/assets.ts +++ b/src/__swaps__/utils/assets.ts @@ -1,5 +1,4 @@ import { AddressZero } from '@ethersproject/constants'; -import isValidDomain from 'is-valid-domain'; import { ETH_ADDRESS, SupportedCurrencyKey } from '@/references'; import { @@ -13,7 +12,7 @@ import { ZerionAsset, ZerionAssetPrice, } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import * as i18n from '@/languages'; import { SearchAsset } from '@/__swaps__/types/search'; diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts index 1b9ca2bd496..63f821fc1b7 100644 --- a/src/__swaps__/utils/chains.ts +++ b/src/__swaps__/utils/chains.ts @@ -1,7 +1,7 @@ import { celo, fantom, harmonyOne, moonbeam } from 'viem/chains'; import { NATIVE_ASSETS_PER_CHAIN } from '@/references'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/__swaps__/types/chains'; +import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/networks/types'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/utils/decimalFormatter.ts b/src/__swaps__/utils/decimalFormatter.ts index afe646b1653..fc0f9a943ec 100644 --- a/src/__swaps__/utils/decimalFormatter.ts +++ b/src/__swaps__/utils/decimalFormatter.ts @@ -36,7 +36,7 @@ export function valueBasedDecimalFormatter({ maximumDecimalPlaces: number; } { const orderOfMagnitude = orderOfMagnitudeWorklet(amount); - let minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; + const minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; const significantDecimals = significantDecimalsWorklet(amount); @@ -82,7 +82,7 @@ export function valueBasedDecimalFormatter({ // Format the number to add separators and trim trailing zeros const numberFormatter = new Intl.NumberFormat('en-US', { minimumFractionDigits: minimumDecimalPlaces, - maximumFractionDigits: maximumDecimalPlaces, + maximumFractionDigits: maximumDecimalPlaces || 0, useGrouping: !stripSeparators, }); diff --git a/src/__swaps__/utils/gasUtils.ts b/src/__swaps__/utils/gasUtils.ts index 7e4ff881a7b..5039a93eef1 100644 --- a/src/__swaps__/utils/gasUtils.ts +++ b/src/__swaps__/utils/gasUtils.ts @@ -12,7 +12,7 @@ import { OVM_GAS_PRICE_ORACLE, gasUnits, supportedNativeCurrencies, optimismGasO import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/__swaps__/utils/meteorology'; import { ParsedAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { BlocksToConfirmation, GasFeeLegacyParams, GasFeeParam, GasFeeParams, GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index de0740f4069..c12164ef6d4 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -1,12 +1,11 @@ import { useQuery } from '@tanstack/react-query'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { rainbowMeteorologyGetData } from '@/handlers/gasFees'; import { abs, lessThan, subtract } from '@/helpers/utilities'; import { gweiToWei } from '@/parsers'; import { QueryConfig, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { useCallback } from 'react'; import { MIN_FLASHBOTS_PRIORITY_FEE } from '../screens/Swap/constants'; import { GasSettings } from '../screens/Swap/hooks/useCustomGas'; @@ -72,8 +71,7 @@ type MeteorologyQueryKey = ReturnType; // Query Function async function meteorologyQueryFunction({ queryKey: [{ chainId }] }: QueryFunctionArgs) { - const network = getNetworkFromChainId(chainId); - const parsedResponse = await rainbowMeteorologyGetData(network); + const parsedResponse = await rainbowMeteorologyGetData(chainId); const meteorologyData = parsedResponse.data as MeteorologyResponse | MeteorologyLegacyResponse; return meteorologyData; } diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 33071792138..10f55dae1ad 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -9,7 +9,7 @@ import { SLIDER_WIDTH, STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { chainNameFromChainId, chainNameFromChainIdWorklet } from '@/__swaps__/utils/chains'; import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; import { globalColors } from '@/design-system'; diff --git a/src/__swaps__/utils/userChains.ts b/src/__swaps__/utils/userChains.ts index 0a9a35369c1..cff0ccf7f6e 100644 --- a/src/__swaps__/utils/userChains.ts +++ b/src/__swaps__/utils/userChains.ts @@ -21,7 +21,7 @@ import { sepolia, } from 'viem/chains'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; export const chainIdMap: Record< ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.base | ChainId.bsc | ChainId.zora | ChainId.avalanche, diff --git a/src/analytics/event.ts b/src/analytics/event.ts index ebb6849433e..22c3c8c5e93 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -1,13 +1,12 @@ import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { UnlockableAppIconKey } from '@/appIcons/appIcons'; import { CardType } from '@/components/cards/GenericCard'; import { LearnCategory } from '@/components/cards/utils/types'; import { FiatProviderName } from '@/entities/f2c'; -import { Network } from '@/networks/types'; import { RapSwapActionParameters } from '@/raps/references'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; diff --git a/src/analytics/index.ts b/src/analytics/index.ts index d0800a9cb67..6bbd722edb8 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -19,9 +19,9 @@ export class Analytics { this.client = rudderClient; this.disabled = isTesting || !!device.get(['doNotTrack']); if (isTesting) { - logger.debug('Analytics is disabled for testing'); + logger.debug('[Analytics]: disabled for testing'); } else { - logger.debug('Analytics client initialized'); + logger.debug('[Analytics]: client initialized'); } } @@ -71,7 +71,7 @@ export class Analytics { dataPlaneUrl: RUDDERSTACK_DATA_PLANE_URL, }); } catch (error) { - logger.error(new RainbowError('Unable to initialize Rudderstack'), { error }); + logger.error(new RainbowError('[Analytics]: Unable to initialize Rudderstack'), { error }); } } @@ -80,7 +80,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setDeviceId(deviceId: string) { - logger.debug(`Set deviceId on analytics instance`); + logger.debug(`[Analytics]: Set deviceId on analytics instance`); this.deviceId = deviceId; } @@ -89,7 +89,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setCurrentWalletAddressHash(currentWalletAddressHash: string) { - logger.debug(`Set currentWalletAddressHash on analytics instance`); + logger.debug(`[Analytics]: Set currentWalletAddressHash on analytics instance`); this.currentWalletAddressHash = currentWalletAddressHash; } diff --git a/src/analytics/utils.ts b/src/analytics/utils.ts index 28f474d7faa..450ed5666ff 100644 --- a/src/analytics/utils.ts +++ b/src/analytics/utils.ts @@ -35,7 +35,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { const deviceIdFromStorage = ls.device.get(['id']); if (deviceIdFromStorage) { - logger.debug(`getOrCreateDeviceId using existing deviceId from storage`); + logger.debug(`[getOrCreateDeviceId]: using existing deviceId from storage ${deviceIdFromStorage}`); // if we have a ID in storage, we've already migrated return [deviceIdFromStorage, false]; } else { @@ -47,12 +47,11 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { // set ID ls.device.set(['id'], deviceId); - // log to Sentry if (hasExistingDeviceId) { - logger.info(`getOrCreateDeviceId migrating device ID from keychain to local storage`); + logger.debug(`[getOrCreateDeviceId]: migrating device ID from keychain to local storage`); } - logger.debug(`getOrCreateDeviceId returned new deviceId`); + logger.debug(`[getOrCreateDeviceId]: returned new deviceId ${deviceId}`); // if we had an old device id in keychain, `wasCreated` should be false return [deviceId, !hasExistingDeviceId]; @@ -61,7 +60,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { export function securelyHashWalletAddress(walletAddress: `0x${string}`): string | undefined { if (!SECURE_WALLET_HASH_KEY) { - logger.error(new RainbowError(`Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); + logger.error(new RainbowError(`[securelyHashWalletAddress]: Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); } try { @@ -73,11 +72,11 @@ export function securelyHashWalletAddress(walletAddress: `0x${string}`): string walletAddress ); - logger.debug(`Wallet address securely hashed`); + logger.debug(`[securelyHashWalletAddress]: Wallet address securely hashed`); return hmac; } catch (e) { // could be an invalid hashing key, or trying to hash an ENS - logger.error(new RainbowError(`Wallet address hashing failed`)); + logger.error(new RainbowError(`[securelyHashWalletAddress]: Wallet address hashing failed`)); } } diff --git a/src/appIcons/appIcons.ts b/src/appIcons/appIcons.ts index 025ed695e05..407b688f21b 100644 --- a/src/appIcons/appIcons.ts +++ b/src/appIcons/appIcons.ts @@ -1,5 +1,4 @@ import { EthereumAddress } from '@/entities'; -import { Network } from '@/helpers'; import { ImageSourcePropType } from 'react-native'; import AppIconFiniliar from '@/assets/appIconFiniliar.png'; import AppIconGoldDoge from '@/assets/appIconGoldDoge.png'; @@ -15,6 +14,7 @@ import AppIconPoolboy from '@/assets/appIconPoolboy.png'; import AppIconAdworld from '@/assets/appIconAdworld.png'; import AppIconFarcaster from '@/assets/appIconFarcaster.png'; import { TokenGateCheckerNetwork } from '@/featuresToUnlock/tokenGatedUtils'; +import { Network } from '@/networks/types'; // optimism app icon unlocking NFTs const OPTIMISTIC_EXPLORER_NFT_ADDRESS: EthereumAddress = '0x81b30ff521D1fEB67EDE32db726D95714eb00637'; diff --git a/src/components/AddFundsInterstitial.js b/src/components/AddFundsInterstitial.js index 34312f9026a..23f430fd577 100644 --- a/src/components/AddFundsInterstitial.js +++ b/src/components/AddFundsInterstitial.js @@ -3,7 +3,6 @@ import lang from 'i18n-js'; import React, { Fragment, useCallback } from 'react'; import { Linking, View } from 'react-native'; import networkInfo from '../helpers/networkInfo'; -import networkTypes from '../helpers/networkTypes'; import showWalletErrorAlert from '../helpers/support'; import { useNavigation } from '../navigation/Navigation'; import { useTheme } from '../theme/ThemeContext'; @@ -20,6 +19,7 @@ import styled from '@/styled-thing'; import { padding, position } from '@/styles'; import ShadowStack from '@/react-native-shadow-stack'; import { useRoute } from '@react-navigation/native'; +import { Network } from '@/networks/types'; const ContainerWidth = 261; @@ -213,7 +213,7 @@ const AddFundsInterstitial = ({ network }) => { return ( - {network === networkTypes.mainnet ? ( + {network === Network.mainnet ? ( {ios ? lang.t('add_funds.to_get_started_ios') : lang.t('add_funds.to_get_started_android')} diff --git a/src/components/ChainLogo.js b/src/components/ChainLogo.js index ef03bb368c7..ae21dddd071 100644 --- a/src/components/ChainLogo.js +++ b/src/components/ChainLogo.js @@ -19,9 +19,9 @@ import BaseBadge from '../assets/badges/baseBadge.png'; import BaseBadgeDark from '../assets/badges/baseBadgeDark.png'; import BaseBadgeNoShadow from '../assets/badges/baseBadgeNoShadow.png'; import { Centered } from './layout'; -import networkTypes from '@/helpers/networkTypes'; import styled from '@/styled-thing'; import { position } from '@/styles'; +import { ChainId } from '@/networks/types'; const ChainIcon = styled(FastImage)({ height: ({ size }) => size, @@ -34,25 +34,25 @@ const Content = styled(Centered)(({ size }) => ({ overflow: 'visible', })); -export default function ChainLogo({ network, size = 44, withShadows = true, ...props }) { +export default function ChainLogo({ chainId, size = 44, withShadows = true, ...props }) { const { isDarkMode } = useTheme(); const source = useMemo(() => { let val = null; - if (network === networkTypes.arbitrum) { + if (chainId === ChainId.arbitrum) { val = withShadows ? (isDarkMode ? ArbitrumBadgeDark : ArbitrumBadge) : ArbitrumBadgeNoShadow; - } else if (network === networkTypes.optimism) { + } else if (chainId === ChainId.optimism) { val = withShadows ? (isDarkMode ? OptimismBadgeDark : OptimismBadge) : OptimismBadgeNoShadow; - } else if (network === networkTypes.polygon) { + } else if (chainId === ChainId.polygon) { val = withShadows ? (isDarkMode ? PolygonBadgeDark : PolygonBadge) : PolygonBadgeNoShadow; - } else if (network === networkTypes.bsc) { + } else if (chainId === ChainId.bsc) { val = withShadows ? (isDarkMode ? BscBadgeDark : BscBadge) : BscBadgeNoShadow; - } else if (network === networkTypes.zora) { + } else if (chainId === ChainId.zora) { val = withShadows ? (isDarkMode ? ZoraBadgeDark : ZoraBadge) : ZoraBadgeNoShadow; - } else if (network === networkTypes.base) { + } else if (chainId === ChainId.base) { val = withShadows ? (isDarkMode ? BaseBadgeDark : BaseBadge) : BaseBadgeNoShadow; } return val; - }, [isDarkMode, network, withShadows]); + }, [isDarkMode, chainId, withShadows]); if (!source) return null; diff --git a/src/components/ContactRowInfoButton.js b/src/components/ContactRowInfoButton.js index 60c58c64a07..28ce8a1f159 100644 --- a/src/components/ContactRowInfoButton.js +++ b/src/components/ContactRowInfoButton.js @@ -69,7 +69,7 @@ const ContactRowActions = { const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { @@ -82,7 +82,7 @@ const buildBlockExplorerAction = chainId => { }; }; -const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { +const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { const { setClipboard } = useClipboard(); const handleCopyAddress = useCallback( address => { @@ -93,7 +93,7 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId }))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( { @@ -107,14 +107,14 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { handleCopyAddress(item?.address); } if (idx === 1) { - ethereumUtils.openAddressInBlockExplorer(item?.address, network); + ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); } } ); - }, [item?.network, item?.name, item?.address, handleCopyAddress, network]); + }, [item?.name, item?.address, handleCopyAddress, chainId]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); + const blockExplorerAction = buildBlockExplorerAction(chainId); return { menuItems: [ blockExplorerAction, @@ -125,17 +125,17 @@ const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { ], menuTitle: `${item?.name}`, }; - }, [item]); + }, [chainId, item?.address, item?.name]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContactRowActionsEnum.copyAddress) { handleCopyAddress(item?.address); } else if (actionKey === ContactRowActionsEnum.blockExplorer) { - ethereumUtils.openAddressInBlockExplorer(item?.address); + ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); } }, - [item, handleCopyAddress] + [handleCopyAddress, item?.address, chainId] ); const Container = children ? Centered : InfoButton; diff --git a/src/components/DappBrowser/Dimensions.ts b/src/components/DappBrowser/Dimensions.ts index a367b0967aa..2cddb184f37 100644 --- a/src/components/DappBrowser/Dimensions.ts +++ b/src/components/DappBrowser/Dimensions.ts @@ -2,11 +2,11 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; import { StatusBar } from 'react-native'; -import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; +import { NAVIGATION_BAR_HEIGHT } from '@/utils/deviceUtils'; export const BOTTOM_BAR_HEIGHT = 88; export const TOP_INSET = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight ?? 40; -export const BOTTOM_INSET = IS_ANDROID ? (isUsingButtonNavigation() ? 32 : 12) : BOTTOM_BAR_HEIGHT; +export const BOTTOM_INSET = IS_ANDROID ? NAVIGATION_BAR_HEIGHT - 8 : BOTTOM_BAR_HEIGHT; export const WEBVIEW_HEIGHT = deviceUtils.dimensions.height - TOP_INSET - TAB_BAR_HEIGHT - BOTTOM_INSET; export const COLLAPSED_WEBVIEW_ASPECT_RATIO = 4 / 3; export const COLLAPSED_WEBVIEW_HEIGHT_UNSCALED = Math.min(WEBVIEW_HEIGHT, deviceUtils.dimensions.width * COLLAPSED_WEBVIEW_ASPECT_RATIO); diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index d4fe0a7c014..4053671b59c 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -20,7 +20,7 @@ import { } from '@/design-system'; import { ImgixImage } from '@/components/images'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { IS_ANDROID, IS_IOS } from '@/env'; +import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { opacity } from '@/__swaps__/utils/swaps'; import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; @@ -41,6 +41,10 @@ import { getNameFromFormattedUrl } from './utils'; import { useTrendingDApps } from '@/resources/metadata/trendingDapps'; import { DApp } from '@/graphql/__generated__/metadata'; import { DEFAULT_TAB_URL } from './constants'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { FEATURED_RESULTS, useExperimentalFlag } from '@/config'; +import { FeaturedResultStack } from '../FeaturedResult/FeaturedResultStack'; const HORIZONTAL_PAGE_INSET = 24; const MAX_RECENTS_TO_DISPLAY = 6; @@ -57,7 +61,7 @@ const NUM_CARDS = 2; const CARD_PADDING = 12; const CARD_HEIGHT = 137; const RAW_CARD_WIDTH = (DEVICE_WIDTH - HORIZONTAL_PAGE_INSET * 2 - (NUM_CARDS - 1) * CARD_PADDING) / NUM_CARDS; -const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); +export const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); export const Homepage = ({ tabId }: { tabId: string }) => { const { goToUrl } = useBrowserContext(); @@ -82,6 +86,25 @@ export const Homepage = ({ tabId }: { tabId: string }) => { ); }; +const DappBrowserFeaturedResults = () => { + const { goToUrl } = useBrowserContext(); + const { featured_results } = useRemoteConfig(); + const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; + + const onNavigate = useCallback( + (href: string) => { + goToUrl(href); + }, + [goToUrl] + ); + + if (!featuredResultsEnabled) { + return null; + } + + return ; +}; + const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { const { data } = useTrendingDApps(); @@ -109,6 +132,7 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { > + {data.dApps .filter((dApp): dApp is DApp => dApp !== null) .map((dApp, index) => ( @@ -475,6 +499,57 @@ const Card = memo(function Card({ ); }); +export const DappBrowserFeaturedResultsCard = memo(function Card({ + handlePress, + featuredResult, +}: { + handlePress: () => void; + featuredResult: FeaturedResult; +}) { + const { isDarkMode } = useColorMode(); + + return ( + + + + + + {IS_IOS && ( + + )} + + + ); +}); + const CardBackground = memo(function CardBackgroundOverlay({ imageUrl, isDarkMode, diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index c02f018636d..55de3b4bf14 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -30,7 +30,6 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBalancesAndNames } from '@/hooks'; import { useSyncSharedValue } from '@/hooks/reanimated/useSyncSharedValue'; -import { Network } from '@/networks/types'; import { useBrowserStore } from '@/state/browser/browserStore'; import { colors } from '@/styles'; import { deviceUtils, watchingAlert } from '@/utils'; @@ -41,7 +40,7 @@ import { TOP_INSET } from '../Dimensions'; import { formatUrl } from '../utils'; import { RouteProp, useRoute } from '@react-navigation/native'; import { toHex } from 'viem'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import * as i18n from '@/languages'; import { useDispatch } from 'react-redux'; import store from '@/redux/store'; @@ -63,7 +62,7 @@ import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const PAGES = { HOME: 'home', @@ -102,7 +101,7 @@ export const ControlPanel = () => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null, [hostSessions] @@ -112,7 +111,7 @@ export const ControlPanel = () => { const [currentAddress, setCurrentAddress] = useState( currentSession?.address || hostSessions?.activeSessionAddress || accountAddress ); - const [currentNetwork, setCurrentNetwork] = useState(currentSession?.network || Network.mainnet); + const [currentChainId, setCurrentChainId] = useState(currentSession?.chainId || ChainId.mainnet); // listens to the current active tab and sets the account useEffect(() => { @@ -128,8 +127,8 @@ export const ControlPanel = () => { setCurrentAddress(accountAddress); } - if (currentSession?.network) { - setCurrentNetwork(currentSession?.network); + if (currentSession?.chainId) { + setCurrentChainId(currentSession?.chainId); } } }, [accountAddress, activeTabHost, currentSession]); @@ -142,7 +141,7 @@ export const ControlPanel = () => { const accountBalances: Record = {}; Object.values(walletsWithBalancesAndNames).forEach(wallet => { - wallet.addresses + (wallet.addresses || []) .filter(account => account.visible) .forEach(account => { const balanceText = account.balances ? account.balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); @@ -184,28 +183,27 @@ export const ControlPanel = () => { const { testnetsEnabled } = store.getState().settings; const allNetworkItems = useMemo(() => { - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ networkType, features: { walletconnect } }) => walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => { return { IconComponent: , label: network.name, secondaryLabel: i18n.t( - isConnected && network.value === currentNetwork + isConnected && network.id === currentChainId ? i18n.l.dapp_browser.control_panel.connected : i18n.l.dapp_browser.control_panel.not_connected ), - uniqueId: network.value, - selected: network.value === currentNetwork, - chainId: network.id, + uniqueId: String(network.id), + selected: network.id === currentChainId, }; }); - }, [currentNetwork, isConnected, testnetsEnabled]); + }, [currentChainId, isConnected, testnetsEnabled]); const selectedWallet = allWalletItems.find(item => item.selected); const animatedAccentColor = useSharedValue(selectedWallet?.color || globalColors.blue10); - const selectedNetworkId = useSharedValue(currentNetwork?.toString() || RainbowNetworks[0].value); + const selectedNetworkId = useSharedValue(currentChainId?.toString() || RainbowNetworkObjects[0].value); const selectedWalletId = useSharedValue(selectedWallet?.uniqueId || accountAddress); const handleSwitchWallet = useCallback( @@ -213,21 +211,21 @@ export const ControlPanel = () => { const address = selectedItemId; updateActiveSession({ host: activeTabHost, address: address as `0x${string}` }); if (isConnected) { - updateActiveSessionNetwork({ host: activeTabHost, network: currentNetwork }); + updateActiveSessionNetwork({ host: activeTabHost, chainId: currentChainId }); // need to emit these events to the dapp activeTabRef.current?.injectJavaScript(`window.ethereum.emit('accountsChanged', ['${address}']); true;`); } setCurrentAddress(address); }, - [activeTabHost, activeTabRef, currentNetwork, isConnected, updateActiveSession, updateActiveSessionNetwork] + [activeTabHost, activeTabRef, currentChainId, isConnected, updateActiveSession, updateActiveSessionNetwork] ); const handleNetworkSwitch = useCallback( (selectedItemId: string) => { - updateActiveSessionNetwork({ host: activeTabHost, network: selectedItemId as Network }); - const chainId = RainbowNetworks.find(({ value }) => value === (selectedItemId as Network))?.id as number; + updateActiveSessionNetwork({ host: activeTabHost, chainId: Number(selectedItemId) as ChainId }); + const chainId = RainbowNetworkObjects.find(({ id }) => id === (Number(selectedItemId) as ChainId))?.id as number; activeTabRef.current?.injectJavaScript(`window.ethereum.emit('chainChanged', ${toHex(chainId)}); true;`); - setCurrentNetwork(selectedItemId as Network); + setCurrentChainId(Number(selectedItemId) as ChainId); }, [activeTabHost, activeTabRef, updateActiveSessionNetwork] ); @@ -235,23 +233,21 @@ export const ControlPanel = () => { const handleConnect = useCallback(async () => { const activeTabHost = getDappHost(activeTabUrl || ''); const address = selectedWalletId.value; - const network = selectedNetworkId.value as Network; + const chainId = Number(selectedNetworkId.value); addSession({ host: activeTabHost || '', address: address as `0x${string}`, - network, + chainId, url: activeTabUrl || '', }); - const chainId = ethereumUtils.getChainIdFromNetwork(network); - activeTabRef.current?.injectJavaScript( `window.ethereum.emit('accountsChanged', ['${address}']); window.ethereum.emit('connect', { address: '${address}', chainId: '${toHex(chainId)}' }); true;` ); setIsConnected(true); setCurrentAddress(address); - setCurrentNetwork(network); + setCurrentChainId(chainId); }, [activeTabUrl, selectedWalletId.value, selectedNetworkId.value, addSession, activeTabRef]); const handleDisconnect = useCallback(() => { @@ -273,7 +269,7 @@ export const ControlPanel = () => { ; goToPage: (pageId: string) => void; - selectedNetwork: string; + selectedChainId: ChainId; selectedWallet: ControlPanelMenuItemProps | undefined; allNetworkItems: ControlPanelMenuItemProps[]; isConnected: boolean; @@ -393,8 +389,8 @@ const HomePanel = ({ const walletLabel = selectedWallet?.label || ''; const walletSecondaryLabel = selectedWallet?.secondaryLabel || ''; - const network = allNetworkItems.find(item => item.uniqueId === selectedNetwork); - const networkIcon = ; + const network = allNetworkItems.find(item => item.uniqueId === String(selectedChainId)); + const networkIcon = ; const networkLabel = network?.label || ''; const networkSecondaryLabel = network?.secondaryLabel || ''; @@ -420,7 +416,7 @@ const HomePanel = ({ /> ); - }, [allNetworkItems, animatedAccentColor, goToPage, selectedNetwork, selectedWallet]); + }, [allNetworkItems, animatedAccentColor, goToPage, selectedChainId, selectedWallet]); const runWalletChecksBeforeSwapOrBridge = useCallback(async () => { if (!selectedWallet || !wallets) return false; @@ -460,7 +456,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -488,7 +484,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -737,7 +733,6 @@ interface ControlPanelMenuItemProps { label: string; labelColor?: TextColor; imageUrl?: string; - chainId?: ChainId; color?: string; onPress?: () => void; secondaryLabel?: string; diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index c730eadafdb..d0ce9356952 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -4,20 +4,18 @@ import * as lang from '@/languages'; import { Provider } from '@ethersproject/providers'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { getProviderForNetwork } from '@/handlers/web3'; -import ethereumUtils, { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { RainbowNetworkObjects, RainbowSupportedChainIds } from '@/networks'; +import { getProvider } from '@/handlers/web3'; import { UserRejectedRequestError } from 'viem'; import { convertHexToString } from '@/helpers/utilities'; import { logger } from '@/logger'; import { ActiveSession } from '@rainbow-me/provider/dist/references/appSession'; -import { Network } from '@/helpers'; import { handleDappBrowserConnectionPrompt, handleDappBrowserRequest } from '@/utils/requestNavigationHandlers'; import { Tab } from '@rainbow-me/provider/dist/references/messengers'; import { getDappMetadata } from '@/resources/metadata/dapp'; import { useAppSessionsStore } from '@/state/appSessions'; import { BigNumber } from '@ethersproject/bignumber'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export type ProviderRequestPayload = RequestArguments & { id: number; @@ -123,56 +121,51 @@ const messengerProviderRequestFn = async (messenger: Messenger, request: Provide hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; - // Wait for response from the popup. - let response: unknown | null; - if (request.method === 'eth_requestAccounts') { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - chainId is not defined in the type const chainId = request.params?.[0]?.chainId ? BigNumber.from(request.params?.[0]?.chainId).toNumber() : undefined; - response = await handleDappBrowserConnectionPrompt({ + const response = await handleDappBrowserConnectionPrompt({ dappName: dappData?.appName || request.meta?.sender.title || '', dappUrl: request.meta?.sender.url || '', chainId, address: hostSessions?.activeSessionAddress || undefined, }); + if (!response || response instanceof Error) { + throw new UserRejectedRequestError(Error('User rejected the request.')); + } + useAppSessionsStore.getState().addSession({ host: getDappHost(request.meta?.sender.url) || '', - // @ts-ignore - address: response.address, - // @ts-ignore - network: getNetworkFromChainId(response.chainId), - // @ts-ignore + address: response?.address, + chainId: response.chainId, url: request.meta?.sender.url || '', }); + return response; } else { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); - response = await handleDappBrowserRequest({ + const response = await handleDappBrowserRequest({ dappName: dappData?.appName || request.meta?.sender.title || request.meta?.sender.url || '', imageUrl: dappData?.appLogo || '', address: appSession?.address || '', dappUrl: request.meta?.sender.url || '', payload: request, - chainId: appSession?.network ? ethereumUtils.getChainIdFromNetwork(appSession.network) : ChainId.mainnet, + chainId: appSession?.chainId ? appSession?.chainId : ChainId.mainnet, }); + return response as object; } - - if (!response) { - throw new UserRejectedRequestError(Error('User rejected the request.')); - } - return response; }; const isSupportedChainId = (chainId: number | string) => { const numericChainId = BigNumber.from(chainId).toNumber(); - return !!RainbowNetworks.find(network => Number(network.id) === numericChainId); + return !!RainbowSupportedChainIds.find(chainId => chainId === numericChainId); }; const getActiveSession = ({ host }: { host: string }): ActiveSession => { const hostSessions = useAppSessionsStore.getState().getActiveSession({ host }); @@ -180,26 +173,19 @@ const getActiveSession = ({ host }: { host: string }): ActiveSession => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - network: hostSessions.sessions[hostSessions.activeSessionAddress], + chainId: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; if (!appSession) return null; return { address: appSession?.address || '', - chainId: getChainIdByNetwork(appSession.network), + chainId: appSession.chainId, }; // return null; }; -const getChainIdByNetwork = (network: Network) => getNetworkObj(network).id; - -const getChain = (chainId: number) => RainbowNetworks.find(network => Number(network.id) === chainId); - -const getProvider = ({ chainId }: { chainId?: number | undefined }) => { - const network = getNetworkFromChainId(chainId || 1); - return getProviderForNetwork(network) as unknown as Provider; -}; +const getChain = (chainId: number) => RainbowNetworkObjects.find(network => Number(network.id) === chainId); const checkRateLimitFn = async (host: string) => { // try { @@ -284,13 +270,13 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }): { chainAlreadyAdded: boolean } => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { // TODO - Open add / switch ethereum chain return { chainAlreadyAdded: true }; } else { - logger.info('[DAPPBROWSER]: NOT SUPPORTED CHAIN'); + logger.debug(`[handleProviderRequestApp]: Dapp requested unsupported chain ${chainId}`); return { chainAlreadyAdded: false }; } }; @@ -341,14 +327,14 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }) => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); const supportedChainId = supportedChains.includes(numericChainId); if (supportedChainId) { const host = getDappHost(callbackOptions?.sender.url) || ''; const activeSession = getActiveSession({ host }); if (activeSession) { - useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, network: getNetworkFromChainId(Number(numericChainId)) }); + useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, chainId: Number(numericChainId) }); messenger.send(`chainChanged:${host}`, Number(numericChainId)); } console.warn('PROVIDER TODO: TODO SEND NOTIFICATION'); @@ -364,7 +350,7 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: checkRateLimit, onSwitchEthereumChainNotSupported, onSwitchEthereumChainSupported, - getProvider, + getProvider: chainId => getProvider({ chainId: chainId as number }) as unknown as Provider, getActiveSession, getChain, }); diff --git a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts index 787ba5db987..841ea58aab7 100644 --- a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts +++ b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts @@ -51,7 +51,7 @@ export function useScreenshotAndScrollTriggers() { saveScreenshotToFileSystem(uri, tabId, timestamp, pageUrl); }) .catch(error => { - logger.error(new RainbowError('Failed to capture tab screenshot'), { + logger.error(new RainbowError('[DappBrowser]: Failed to capture tab screenshot'), { error: error.message, }); }); diff --git a/src/components/DappBrowser/screenshots.ts b/src/components/DappBrowser/screenshots.ts index 89a56b359c2..42fe8082aa7 100644 --- a/src/components/DappBrowser/screenshots.ts +++ b/src/components/DappBrowser/screenshots.ts @@ -20,11 +20,11 @@ export const findTabScreenshot = (id: string, url?: string): ScreenshotType | nu if (!Array.isArray(screenshots)) { try { - logger.error(new RainbowError('Screenshot data is malformed — expected array'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — expected array'), { screenshots: JSON.stringify(screenshots, null, 2), }); } catch (e: any) { - logger.error(new RainbowError('Screenshot data is malformed — error stringifying'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — error stringifying'), { message: e.message, }); } @@ -79,7 +79,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] const deletePromises = screenshotsToDelete.map(screenshot => { const filePath = `${RNFS.DocumentDirectoryPath}/${screenshot.uri}`; return RNFS.unlink(filePath).catch(e => { - logger.error(new RainbowError('Error deleting screenshot file'), { + logger.error(new RainbowError('[DappBrowser]: Error deleting screenshot file'), { message: e.message, filePath, screenshot: JSON.stringify(screenshot, null, 2), @@ -88,7 +88,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] }); await Promise.all(deletePromises); } catch (e: any) { - logger.error(new RainbowError('Screenshot file pruning operation failed to complete'), { + logger.error(new RainbowError('[DappBrowser]: Screenshot file pruning operation failed to complete'), { message: e.message, }); } @@ -118,7 +118,7 @@ export const saveScreenshot = async (tempUri: string, tabId: string, timestamp: // Set screenshot for display return screenshotWithRNFSPath; } catch (e: any) { - logger.error(new RainbowError('Error saving tab screenshot to file system'), { + logger.error(new RainbowError('[DappBrowser]: Error saving tab screenshot to file system'), { message: e.message, screenshotData: { tempUri, diff --git a/src/components/DappBrowser/utils.ts b/src/components/DappBrowser/utils.ts index 22c38a979fb..7d1d885311b 100644 --- a/src/components/DappBrowser/utils.ts +++ b/src/components/DappBrowser/utils.ts @@ -120,7 +120,7 @@ export async function handleShareUrl(url: string): Promise { await Share.share({ message: url }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - logger.error(new RainbowError('Error sharing browser URL'), { + logger.error(new RainbowError('[DappBrowser]: Error sharing browser URL'), { message: e.message, url, }); diff --git a/src/components/FeaturedResult/FeaturedResultCard.tsx b/src/components/FeaturedResult/FeaturedResultCard.tsx new file mode 100644 index 00000000000..594449258ea --- /dev/null +++ b/src/components/FeaturedResult/FeaturedResultCard.tsx @@ -0,0 +1,60 @@ +import { FeaturedResultsVariables, useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; +import { getFeaturedResultById } from '@/resources/featuredResults/_selectors/getFeaturedResultById'; +import { useTrackFeaturedResult } from '@/resources/featuredResults/trackFeaturedResult'; +import { TrackFeaturedResultType } from '@/graphql/__generated__/arc'; +import React, { useCallback, useEffect } from 'react'; +import { FeaturedResultStackProps } from './FeaturedResultStack'; +import { logger } from '@/logger'; + +type FeaturedResultCardProps = FeaturedResultStackProps & + FeaturedResultsVariables & { + featuredResultId: string; + }; + +export const FeaturedResultCard = ({ featuredResultId, onNavigate, Card, ...props }: FeaturedResultCardProps) => { + const { data: featuredResult } = useFeaturedResults(props, { + select: data => getFeaturedResultById(data, featuredResultId), + }); + + const { mutateAsync: trackFeaturedResult } = useTrackFeaturedResult(); + + useEffect(() => { + (async () => { + if (!featuredResult) { + return; + } + + await trackFeaturedResult({ + featuredResultCreativeId: featuredResult.advertiserId, + placementId: featuredResult.placementSlug, + impressionId: featuredResult.impressionId, + type: TrackFeaturedResultType.Impression, + }); + })(); + }, [featuredResult, trackFeaturedResult]); + + const handlePress = useCallback(async () => { + try { + if (!featuredResult) return; + + const [cta] = featuredResult.ctas || []; + + await trackFeaturedResult({ + featuredResultCreativeId: featuredResult.advertiserId, + placementId: featuredResult.placementSlug, + impressionId: featuredResult.impressionId, + type: TrackFeaturedResultType.Click, + }); + + onNavigate(cta.href); + } catch (error) { + logger.warn(`[FeaturedResultCard] Error tracking featured result click`, { error }); + } + }, [featuredResult, trackFeaturedResult, onNavigate]); + + if (!featuredResult) { + return null; + } + + return ; +}; diff --git a/src/components/FeaturedResult/FeaturedResultStack.tsx b/src/components/FeaturedResult/FeaturedResultStack.tsx new file mode 100644 index 00000000000..17649921268 --- /dev/null +++ b/src/components/FeaturedResult/FeaturedResultStack.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useAccountSettings } from '@/hooks'; +import { useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; +import { languageLocaleToCountry } from '@/utils/languageLocaleToCountry'; +import { getFeaturedResultsById } from '@/resources/featuredResults/_selectors/getFeaturedResultIds'; +import { useSharedValue } from 'react-native-reanimated'; +import { FeaturedResultCard } from '@/components/FeaturedResult/FeaturedResultCard'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; + +export type FeaturedResultStackProps = { + onNavigate: (url: string) => void; + placementId: string; + Card: React.FC<{ handlePress: () => void; featuredResult: FeaturedResult }>; +}; + +export const FeaturedResultStack = ({ onNavigate, placementId, Card }: FeaturedResultStackProps) => { + const { accountAddress, language } = useAccountSettings(); + const currentIndex = useSharedValue(0); + + // @ts-expect-error - language is type string instead of typeof keyof Language + const country = languageLocaleToCountry(language); + const { data: featuredResultIds } = useFeaturedResults( + { + placementId, + walletAddress: accountAddress, + country, + }, + { + select: getFeaturedResultsById, + } + ); + + const featuredResultId = featuredResultIds?.[currentIndex.value]; + if (!featuredResultId) { + return null; + } + + return ( + + ); +}; diff --git a/src/components/L2Disclaimer.js b/src/components/L2Disclaimer.js index 162084f1dec..aa5361e6970 100644 --- a/src/components/L2Disclaimer.js +++ b/src/components/L2Disclaimer.js @@ -7,14 +7,13 @@ import { Column, Row } from './layout'; import { Text } from './text'; import { padding, position } from '@/styles'; import { darkModeThemeColors } from '@/styles/colors'; -import { getNetworkObj } from '@/networks'; import * as lang from '@/languages'; import { isL2Chain } from '@/handlers/web3'; import { EthCoinIcon } from './coin-icon/EthCoinIcon'; -import { ethereumUtils } from '@/utils'; +import { chainIdToNameMapping } from '@/networks/types'; const L2Disclaimer = ({ - network, + chainId, colors, hideDivider, isNft = false, @@ -37,7 +36,6 @@ const L2Disclaimer = ({ }, }; - const chainId = ethereumUtils.getChainIdFromNetwork(network); const isL2 = isL2Chain({ chainId }); return ( @@ -59,7 +57,7 @@ const L2Disclaimer = ({ ? customText : lang.t(lang.l.expanded_state.asset.l2_disclaimer, { symbol, - network: getNetworkObj(network).name, + network: chainIdToNameMapping[chainId], })} diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index d829d4fa8dc..da1f88d403d 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -2,7 +2,6 @@ import * as lang from '@/languages'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SectionList, StyleSheet, View } from 'react-native'; import sectionListGetItemLayout from 'react-native-section-list-get-item-layout'; -import networkTypes from '../../helpers/networkTypes'; import ActivityIndicator from '../ActivityIndicator'; import Spinner from '../Spinner'; import { ButtonPressAnimation } from '../animations'; @@ -14,6 +13,7 @@ import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { useSectionListScrollToTopContext } from '@/navigation/SectionListScrollToTopContext'; import { safeAreaInsetValues } from '@/utils'; +import { Network } from '@/networks/types'; const sx = StyleSheet.create({ sectionHeader: { @@ -107,7 +107,7 @@ const ActivityList = ({ setScrollToTopRef(ref); }; - if (network === networkTypes.mainnet) { + if (network === Network.mainnet) { return ( remainingItemsLabel && } diff --git a/src/components/animations/animationConfigs.ts b/src/components/animations/animationConfigs.ts index b3505c762d9..1e61b4e6698 100644 --- a/src/components/animations/animationConfigs.ts +++ b/src/components/animations/animationConfigs.ts @@ -1,40 +1,48 @@ +import { IS_TEST } from '@/env'; import { Easing, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'; function createSpringConfigs>(configs: T): T { return configs; } + function createTimingConfigs>(configs: T): T { return configs; } +type AnyConfig = WithSpringConfig | WithTimingConfig; + +export const disableForTestingEnvironment = (config: T): T => { + if (!IS_TEST) return config; + return { + ...config, + duration: 0, + } as T; +}; + // /---- 🍎 Spring Animations 🍎 ----/ // -// const springAnimations = createSpringConfigs({ - browserTabTransition: { dampingRatio: 0.82, duration: 800 }, - keyboardConfig: { damping: 500, mass: 3, stiffness: 1000 }, - sliderConfig: { damping: 40, mass: 1.25, stiffness: 450 }, - slowSpring: { damping: 500, mass: 3, stiffness: 800 }, - snappierSpringConfig: { damping: 42, mass: 0.8, stiffness: 800 }, - snappySpringConfig: { damping: 100, mass: 0.8, stiffness: 275 }, - springConfig: { damping: 100, mass: 1.2, stiffness: 750 }, + browserTabTransition: disableForTestingEnvironment({ dampingRatio: 0.82, duration: 800 }), + keyboardConfig: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 1000 }), + sliderConfig: disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }), + slowSpring: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 800 }), + snappierSpringConfig: disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }), + snappySpringConfig: disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }), + springConfig: disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }), }); export const SPRING_CONFIGS: Record = springAnimations; -// // /---- END ----/ // // /---- ⏱️ Timing Animations ⏱️ ----/ // -// const timingAnimations = createTimingConfigs({ - buttonPressConfig: { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }, - fadeConfig: { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - fastFadeConfig: { duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowFadeConfig: { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowerFadeConfig: { duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - slowestFadeConfig: { duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }, - tabPressConfig: { duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + buttonPressConfig: disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), + fadeConfig: disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + fastFadeConfig: disableForTestingEnvironment({ duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowFadeConfig: disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowerFadeConfig: disableForTestingEnvironment({ duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + slowestFadeConfig: disableForTestingEnvironment({ duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + tabPressConfig: disableForTestingEnvironment({ duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }), }); export const TIMING_CONFIGS: Record = timingAnimations; -// // /---- END ----/ // diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 30b8915ed54..f6a4885e514 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -12,6 +12,8 @@ import Routes from '@/navigation/routesNames'; import { borders, colors, padding, shadow } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; +import { NativeCurrencyKey } from '@/entities'; +import { ChainId } from '@/networks/types'; interface CoinCheckButtonProps { isHidden: boolean; @@ -55,7 +57,7 @@ const formatPercentageString = (percentString?: string) => (percentString ? perc interface MemoizedBalanceCoinRowProps { uniqueId: string; - nativeCurrency: string; + nativeCurrency: NativeCurrencyKey; theme: any; navigate: any; nativeCurrencySymbol: string; @@ -65,7 +67,7 @@ interface MemoizedBalanceCoinRowProps { const MemoizedBalanceCoinRow = React.memo( ({ uniqueId, nativeCurrency, theme, navigate, nativeCurrencySymbol, isHidden, maybeCallback }: MemoizedBalanceCoinRowProps) => { - const item = useAccountAsset(uniqueId, nativeCurrency) as any; + const item = useAccountAsset(uniqueId, nativeCurrency); const handlePress = useCallback(() => { if (maybeCallback.current) { @@ -80,7 +82,7 @@ const MemoizedBalanceCoinRow = React.memo( } }, [navigate, item, maybeCallback]); - const percentChange = item?.native?.change; + const percentChange = item?.native?.change || undefined; const percentageChangeDisplay = formatPercentageString(percentChange); const isPositive = percentChange && percentageChangeDisplay.charAt(0) !== '-'; @@ -91,10 +93,10 @@ const MemoizedBalanceCoinRow = React.memo( const valueColor = nativeDisplay ? theme.colors.dark : theme.colors.blueGreyLight; - const chainId = ethereumUtils.getChainIdFromNetwork(item?.network); + const chainId = item?.chainId || ChainId.mainnet; return ( - + @@ -102,7 +104,7 @@ const MemoizedBalanceCoinRow = React.memo( size={40} icon={item?.icon_url} chainId={chainId} - symbol={item?.symbol} + symbol={item?.symbol || ''} theme={theme} colors={item?.colors} /> diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx index 40262984048..01092ef5462 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx @@ -18,7 +18,7 @@ import BlastBadge from '@/assets/badges/blastBadge.png'; import BlastBadgeDark from '@/assets/badges/blastBadgeDark.png'; import DegenBadge from '@/assets/badges/degenBadge.png'; import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; interface FastChainBadgeProps { chainId: ChainId; diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 15923fff565..0da7a18422e 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -13,7 +13,7 @@ import { colors, fonts, fontWithWidth, getFontSize } from '@/styles'; import { deviceUtils } from '@/utils'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const SafeRadialGradient = (IS_TESTING === 'true' ? View : RadialGradient) as typeof RadialGradient; diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index a23be055a48..26f131b8638 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -13,7 +13,6 @@ import { StyleSheet } from 'react-native'; import { LIGHT_SEPARATOR_COLOR, SEPARATOR_COLOR } from '@/__swaps__/screens/Swap/constants'; import { analyticsV2 } from '@/analytics'; import { convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; -import { ethereumUtils } from '@/utils'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; type LaunchFeaturedMintButtonProps = { @@ -32,8 +31,7 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); } }, [featuredMint]); diff --git a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx index 821f6be0487..1bf25cbc586 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx @@ -42,7 +42,7 @@ const ExternalScrollViewWithRef = React.forwardRef ); diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index 3f063c6a23d..f1ce574bb24 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -13,7 +13,7 @@ import rowRenderer from './RowRenderer'; import { BaseCellType, CellTypes, RecyclerListViewRef } from './ViewTypes'; import getLayoutProvider from './getLayoutProvider'; import useLayoutItemAnimator from './useLayoutItemAnimator'; -import { UniqueAsset } from '@/entities'; +import { NativeCurrencyKey, UniqueAsset } from '@/entities'; import { useRecyclerListViewScrollToTopContext } from '@/navigation/RecyclerListViewScrollToTopContext'; import { useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks'; import { useNavigation } from '@/navigation'; @@ -29,7 +29,7 @@ const dataProvider = new DataProvider((r1, r2) => { export type ExtendedState = { theme: any; nativeCurrencySymbol: string; - nativeCurrency: string; + nativeCurrency: NativeCurrencyKey; navigate: any; isCoinListEdited: boolean; hiddenCoins: BooleanMap; diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index 2378b7c6f5d..9dd8a5ba71c 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -20,7 +20,7 @@ import { useAccountAccentColor } from '@/hooks/useAccountAccentColor'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const ProfileActionButtonsRowHeight = 80; @@ -191,7 +191,7 @@ function SwapButton() { android && delayNext(); - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, accountAddress); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: accountAddress }); navigate(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { diff --git a/src/components/avatar-builder/EmojiContent.tsx b/src/components/avatar-builder/EmojiContent.tsx index 0e7d75f1aa8..da1cafba6de 100644 --- a/src/components/avatar-builder/EmojiContent.tsx +++ b/src/components/avatar-builder/EmojiContent.tsx @@ -18,10 +18,10 @@ const EmojiContent = ({ data, columns, onEmojiSelect, cellSize, fontSize }: Prop const { colors } = useTheme(); const categoryEmojis = useMemo(() => { - let categoryEmojis = []; + const categoryEmojis = []; for (let i = 0; i < data.length; i += columns) { - let rowContent = []; - let touchableNet = []; + const rowContent = []; + const touchableNet = []; for (let j = 0; j < columns; j++) { if (i + j < data.length) { rowContent.push(charFromEmojiObject(data[i + j].emoji)); diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupChooseProviderStep.tsx index d18dbeb68f5..38325639704 100644 --- a/src/components/backup/BackupChooseProviderStep.tsx +++ b/src/components/backup/BackupChooseProviderStep.tsx @@ -55,8 +55,10 @@ export default function BackupSheetSectionNoProvider() { } }); } catch (e) { + logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), { + error: e, + }); Alert.alert(lang.t(lang.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -86,7 +88,9 @@ export default function BackupSheetSectionNoProvider() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey + ? (selectedWallet.addresses || [])[0].label + : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/BackupManuallyStep.tsx b/src/components/backup/BackupManuallyStep.tsx index 211eda2d196..da18d73806a 100644 --- a/src/components/backup/BackupManuallyStep.tsx +++ b/src/components/backup/BackupManuallyStep.tsx @@ -20,7 +20,9 @@ export default function BackupManuallyStep() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey + ? (selectedWallet.addresses || [])[0].label + : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/CloudBackupProvider.tsx b/src/components/backup/CloudBackupProvider.tsx index 4819f22bf6a..377e9d13a83 100644 --- a/src/components/backup/CloudBackupProvider.tsx +++ b/src/components/backup/CloudBackupProvider.tsx @@ -32,7 +32,7 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { setIsFetching(true); const isAvailable = await isCloudBackupAvailable(); if (!isAvailable) { - logger.log('Cloud backup is not available'); + logger.debug('[CloudBackupProvider]: Cloud backup is not available'); setIsFetching(false); return; } @@ -44,20 +44,20 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { } } - logger.log('Syncing with cloud'); + logger.debug('[CloudBackupProvider]: Syncing with cloud'); await syncCloud(); - logger.log('Fetching user data'); + logger.debug('[CloudBackupProvider]: Fetching user data'); const userData = await fetchUserDataFromCloud(); setUserData(userData); - logger.log('Fetching all backups'); + logger.debug('[CloudBackupProvider]: Fetching all backups'); const backups = await fetchAllBackups(); - logger.log(`Retrieved ${backups.files.length} backup files`); + logger.debug(`[CloudBackupProvider]: Retrieved ${backups.files.length} backup files`); setBackups(backups); } catch (e) { - logger.error(new RainbowError('Failed to fetch all backups'), { + logger.error(new RainbowError('[CloudBackupProvider]: Failed to fetch all backups'), { error: e, }); } diff --git a/src/components/backup/RestoreCloudStep.tsx b/src/components/backup/RestoreCloudStep.tsx index cfbeea0072e..e8bd83aa7a3 100644 --- a/src/components/backup/RestoreCloudStep.tsx +++ b/src/components/backup/RestoreCloudStep.tsx @@ -151,14 +151,15 @@ export default function RestoreCloudStep() { filename = normalizeAndroidBackupFilename(filename); } - logger.info('Done updating backup state'); + logger.debug('[RestoreCloudStep]: Done updating backup state'); // NOTE: Marking the restored wallets as backed up // @ts-expect-error TypeScript doesn't play nicely with Redux types here const walletIdsToUpdate = Object.keys(newWalletsState || {}).filter(walletId => !(prevWalletsState || {})[walletId]); - logger.log('updating backup state of wallets with ids', { + + logger.debug('[RestoreCloudStep]: Updating backup state of wallets with ids', { walletIds: JSON.stringify(walletIdsToUpdate), }); - logger.log('backupSelected.name', { + logger.debug('[RestoreCloudStep]: Selected backup name', { fileName: selectedBackup.name, }); @@ -182,7 +183,7 @@ export default function RestoreCloudStep() { const walletKeys = Object.keys(newWalletsState || {}); // @ts-expect-error TypeScript doesn't play nicely with Redux types here const firstWallet = walletKeys.length > 0 ? (newWalletsState || {})[walletKeys[0]] : undefined; - const firstAddress = firstWallet ? firstWallet.addresses[0].address : undefined; + const firstAddress = firstWallet ? (firstWallet.addresses || [])[0].address : undefined; const p1 = dispatch(walletsSetSelected(firstWallet)); const p2 = dispatch(addressSetSelected(firstAddress)); await Promise.all([p1, p2]); diff --git a/src/components/cards/EthCard.tsx b/src/components/cards/EthCard.tsx index 0e11d03b334..693ff97b0f8 100644 --- a/src/components/cards/EthCard.tsx +++ b/src/components/cards/EthCard.tsx @@ -22,10 +22,9 @@ import * as i18n from '@/languages'; import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import assetTypes from '@/entities/assetTypes'; -import { Network } from '@/networks/types'; +import { Network, ChainId } from '@/networks/types'; import { getUniqueId } from '@/utils/ethereumUtils'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; export const ETH_CARD_HEIGHT = 284.3; @@ -45,6 +44,7 @@ export const EthCard = () => { ...externalEthAsset, address: ETH_ADDRESS, network: Network.mainnet, + chainId: ChainId.mainnet, uniqueId: getUniqueId(ETH_ADDRESS, ChainId.mainnet), }), [externalEthAsset] diff --git a/src/components/cards/FeaturedMintCard.tsx b/src/components/cards/FeaturedMintCard.tsx index 1b6e2e1cf12..b2738022f5c 100644 --- a/src/components/cards/FeaturedMintCard.tsx +++ b/src/components/cards/FeaturedMintCard.tsx @@ -27,7 +27,6 @@ import { Media } from '../Media'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; -import { ethereumUtils } from '@/utils'; const IMAGE_SIZE = 111; @@ -74,8 +73,7 @@ export function FeaturedMintCard() { mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); } }, [featuredMint]); diff --git a/src/components/cards/MintsCard/CollectionCell.tsx b/src/components/cards/MintsCard/CollectionCell.tsx index 01053b1a852..072057e5ce3 100644 --- a/src/components/cards/MintsCard/CollectionCell.tsx +++ b/src/components/cards/MintsCard/CollectionCell.tsx @@ -6,7 +6,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { useTheme } from '@/theme'; import { View } from 'react-native'; import { MintableCollection } from '@/graphql/__generated__/arc'; -import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { IS_IOS } from '@/env'; @@ -58,8 +58,7 @@ export function CollectionCell({ collection }: { collection: MintableCollection priceInEth: amount, }); - const network = ethereumUtils.getNetworkFromChainId(collection.chainId); - navigateToMintCollection(collection.contract, collection.mintStatus.price, network); + navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); }, [amount, collection.chainId, collection.contract, collection.contractAddress, collection.mintStatus.price]); return ( diff --git a/src/components/cards/NFTOffersCard/Offer.tsx b/src/components/cards/NFTOffersCard/Offer.tsx index 42bde292b39..b20da3382f8 100644 --- a/src/components/cards/NFTOffersCard/Offer.tsx +++ b/src/components/cards/NFTOffersCard/Offer.tsx @@ -23,6 +23,7 @@ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useAccountSettings } from '@/hooks'; import { Network } from '@/networks/types'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; const TWO_HOURS_MS = 2 * 60 * 60 * 1000; export const CELL_HORIZONTAL_PADDING = 7; @@ -69,7 +70,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { const { nativeCurrency } = useAccountSettings(); const offerChainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address, + address: offer.paymentToken.address as AddressOrEth, chainId: offerChainId, currency: nativeCurrency, }); @@ -140,7 +141,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { default: secondaryTextColor = 'labelTertiary'; secondaryText = ''; - logger.error(new RainbowError('NFTOffersCard: invalid sort criterion')); + logger.error(new RainbowError('[NFTOffersCard]: invalid sort criterion')); break; } diff --git a/src/components/cards/OpRewardsCard.tsx b/src/components/cards/OpRewardsCard.tsx index d53a421d187..89a307ff16e 100644 --- a/src/components/cards/OpRewardsCard.tsx +++ b/src/components/cards/OpRewardsCard.tsx @@ -8,6 +8,7 @@ import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; +import { ChainId } from '@/networks/types'; const GRADIENT: Gradient = { colors: ['#520907', '#B22824'], @@ -23,7 +24,7 @@ export const OpRewardsCard: React.FC = () => { }; return ( - + { const wallet = allWallets[key]; - const filteredAccounts = wallet.addresses.filter((account: any) => account.visible); + const filteredAccounts = (wallet.addresses || []).filter((account: any) => account.visible); filteredAccounts.forEach((account: any) => { const row = { ...account, @@ -138,7 +138,7 @@ export default function WalletList({ isReadOnly: wallet.type === WalletTypes.readOnly, isLedger: wallet.type === WalletTypes.bluetooth, isSelected: accountAddress === account.address && (watchOnly || wallet?.id === currentWallet?.id), - label: network !== networkTypes.mainnet && account.ens === account.label ? address(account.address, 6, 4) : account.label, + label: network !== Network.mainnet && account.ens === account.label ? address(account.address, 6, 4) : account.label, onPress: () => onChangeAccount(wallet?.id, account.address), rowType: RowTypes.ADDRESS, walletId: wallet?.id, diff --git a/src/components/coin-icon/ChainBadge.js b/src/components/coin-icon/ChainBadge.js index 19cce6700b8..b5d25a2f67f 100644 --- a/src/components/coin-icon/ChainBadge.js +++ b/src/components/coin-icon/ChainBadge.js @@ -40,8 +40,7 @@ import { Centered } from '../layout'; import styled from '@/styled-thing'; import { position as positions } from '@/styles'; import { ChainBadgeSizeConfigs } from '@/components/coin-icon/ChainBadgeSizeConfigs'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ChainIcon = styled(FastImage)({ height: ({ containerSize }) => containerSize, diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx index 844b96a2d18..1b2184ceb56 100644 --- a/src/components/coin-icon/ChainImage.tsx +++ b/src/components/coin-icon/ChainImage.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import ArbitrumBadge from '@/assets/badges/arbitrum.png'; import BaseBadge from '@/assets/badges/base.png'; @@ -12,7 +12,6 @@ import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; import FastImage, { Source } from 'react-native-fast-image'; -import { ChainId } from '@/__swaps__/types/chains'; export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) { const source = useMemo(() => { diff --git a/src/components/coin-icon/EthCoinIcon.tsx b/src/components/coin-icon/EthCoinIcon.tsx index 4207a9e6333..53346a7afd0 100644 --- a/src/components/coin-icon/EthCoinIcon.tsx +++ b/src/components/coin-icon/EthCoinIcon.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@/theme'; import { useNativeAsset } from '@/utils/ethereumUtils'; import RainbowCoinIcon from './RainbowCoinIcon'; import { ETH_SYMBOL } from '@/references'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type EthCoinIconProps = { size?: number; diff --git a/src/components/coin-icon/RainbowCoinIcon.tsx b/src/components/coin-icon/RainbowCoinIcon.tsx index bcd086c3f39..d01e0e78b42 100644 --- a/src/components/coin-icon/RainbowCoinIcon.tsx +++ b/src/components/coin-icon/RainbowCoinIcon.tsx @@ -7,7 +7,7 @@ import { FallbackIcon as CoinIconTextFallback } from '@/utils'; import { FastFallbackCoinIconImage } from '../asset-list/RecyclerAssetList2/FastComponents/FastFallbackCoinIconImage'; import { FastChainBadge } from '../asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const fallbackTextStyles = { fontFamily: fonts.family.SFProRounded, diff --git a/src/components/coin-icon/RequestVendorLogoIcon.js b/src/components/coin-icon/RequestVendorLogoIcon.js index d858f7522dd..cedf992c177 100644 --- a/src/components/coin-icon/RequestVendorLogoIcon.js +++ b/src/components/coin-icon/RequestVendorLogoIcon.js @@ -1,7 +1,7 @@ import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { useTheme } from '../../theme/ThemeContext'; -import { ethereumUtils, initials } from '../../utils'; +import { initials } from '../../utils'; import ChainBadge from './ChainBadge'; import { Centered } from '../layout'; import { Text } from '../text'; @@ -33,7 +33,7 @@ export default function RequestVendorLogoIcon({ shouldPrioritizeImageLoading, showLargeShadow, size = CoinIconSize, - network, + chainId, ...props }) { const [error, setError] = useState(null); @@ -71,7 +71,7 @@ export default function RequestVendorLogoIcon({ )} - + ); } diff --git a/src/components/coin-icon/TwoCoinsIcon.tsx b/src/components/coin-icon/TwoCoinsIcon.tsx index a6537bb131c..d6827ad1942 100644 --- a/src/components/coin-icon/TwoCoinsIcon.tsx +++ b/src/components/coin-icon/TwoCoinsIcon.tsx @@ -4,7 +4,7 @@ import { ParsedAddressAsset } from '@/entities'; import { useTheme } from '@/theme'; import ChainBadge from './ChainBadge'; import RainbowCoinIcon from './RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function TwoCoinsIcon({ size = 45, diff --git a/src/components/coin-row/CoinRowInfoButton.js b/src/components/coin-row/CoinRowInfoButton.js index 63a58a9aa5b..59ad34022ad 100644 --- a/src/components/coin-row/CoinRowInfoButton.js +++ b/src/components/coin-row/CoinRowInfoButton.js @@ -93,7 +93,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.chainId))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -115,7 +115,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) }, [item, handleCopyContractAddress]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); + const blockExplorerAction = buildBlockExplorerAction(item?.chainId); return { menuItems: [ blockExplorerAction, @@ -126,7 +126,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ], menuTitle: `${item?.name} (${item?.symbol})`, }; - }, [item?.address, item?.name, item?.network, item?.symbol]); + }, [item?.address, item?.chainId, item?.name, item?.symbol]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index 9ee6cb3bb10..5adaba45b02 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -10,7 +10,7 @@ import Routes from '@rainbow-me/routes'; import { ImgixImage } from '../images'; import { CardSize } from '../unique-token/CardSize'; import { ChainBadge } from '../coin-icon'; -import { Network } from '@/networks/types'; +import { ChainId } from '@/networks/types'; import { address } from '@/utils/abbreviations'; import { TransactionType } from '@/resources/transactions/types'; import { @@ -26,7 +26,6 @@ import Spinner from '../Spinner'; import * as lang from '@/languages'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { checkForPendingSwap } from '@/screens/transaction-details/helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; export const getApprovalLabel = ({ approvalAmount, asset, type }: Pick) => { if (!approvalAmount || !asset) return; @@ -408,7 +407,7 @@ export default React.memo(function TransactionCoinRow({ const [topValue] = activityValues(item, nativeCurrency) ?? []; return ( - + diff --git a/src/components/coin-row/SendCoinRow.js b/src/components/coin-row/SendCoinRow.js index 4795baafa14..8a0515f2422 100644 --- a/src/components/coin-row/SendCoinRow.js +++ b/src/components/coin-row/SendCoinRow.js @@ -3,7 +3,7 @@ import { TouchableWithoutFeedback } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { buildAssetUniqueIdentifier } from '../../helpers/assets'; import { useTheme } from '../../theme/ThemeContext'; -import { deviceUtils, ethereumUtils } from '../../utils'; +import { deviceUtils } from '../../utils'; import { ButtonPressAnimation } from '../animations'; import { Text } from '../text'; import CoinName from './CoinName'; @@ -105,9 +105,7 @@ const SendCoinRow = ({ const Wrapper = disablePressAnimation ? TouchableWithoutFeedback : ButtonPressAnimation; - const isL2 = useMemo(() => { - return isL2Chain({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }); - }, [item?.network]); + const isL2 = useMemo(() => isL2Chain({ chainId: item?.chainId }), [item?.chainId]); const containerSelectedStyles = { height: selectedHeight, diff --git a/src/components/contacts/ContactRow.js b/src/components/contacts/ContactRow.js index b213d33dd23..e52b5f8251e 100644 --- a/src/components/contacts/ContactRow.js +++ b/src/components/contacts/ContactRow.js @@ -58,7 +58,7 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const { width: deviceWidth } = useDimensions(); const { onAddOrUpdateContacts } = useContacts(); const { colors } = useTheme(); - const { accountType, balances, ens, image, label, network, onPress, showcaseItem, testID } = props; + const { accountType, balances, ens, image, label, onPress, showcaseItem, testID } = props; const balanceText = balances ? balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); @@ -80,12 +80,12 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const name = await fetchReverseRecord(address); if (name !== ensName) { setENSName(name); - onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, network, name); + onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, name); } }; fetchENSName(); } - }, [accountType, onAddOrUpdateContacts, address, color, ensName, network, nickname, profilesEnabled, setENSName]); + }, [accountType, onAddOrUpdateContacts, address, color, ensName, nickname, profilesEnabled, setENSName]); let cleanedUpLabel = null; if (label) { diff --git a/src/components/context-menu-buttons/ChainContextMenu.tsx b/src/components/context-menu-buttons/ChainContextMenu.tsx index 09828c27eb0..5f79b99c36e 100644 --- a/src/components/context-menu-buttons/ChainContextMenu.tsx +++ b/src/components/context-menu-buttons/ChainContextMenu.tsx @@ -3,7 +3,7 @@ import { ChainImage } from '@/components/coin-icon/ChainImage'; import { ContextMenuButton } from '@/components/context-menu'; import { Bleed, Box, Inline, Text, TextProps } from '@/design-system'; import * as i18n from '@/languages'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; import { showActionSheetWithOptions } from '@/utils'; import { userAssetsStore } from '@/state/assets/userAssets'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; diff --git a/src/components/discover/DiscoverSearchInput.js b/src/components/discover/DiscoverSearchInput.js index b7284d3c7f2..99913ebfef1 100644 --- a/src/components/discover/DiscoverSearchInput.js +++ b/src/components/discover/DiscoverSearchInput.js @@ -11,9 +11,9 @@ import { analytics } from '@/analytics'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { margin, padding } from '@/styles'; -import { deviceUtils, ethereumUtils } from '@/utils'; +import { deviceUtils } from '@/utils'; import DiscoverSheetContext from '@/screens/discover/DiscoverScreenContext'; -import { getNetworkObj } from '@/networks'; +import { chainIdToNameMapping } from '@/networks/types'; export const ExchangeSearchHeight = 40; const ExchangeSearchWidth = deviceUtils.dimensions.width - 30; @@ -131,9 +131,8 @@ const ExchangeSearch = ( const placeholder = useMemo(() => { if (!currentChainId) return placeholderText; - const network = getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)); return lang.t('button.exchange_search_placeholder_network', { - network: network.name, + network: chainIdToNameMapping[currentChainId], }); }, [currentChainId, placeholderText]); diff --git a/src/components/ens-profile/ActionButtons/ActionButtons.tsx b/src/components/ens-profile/ActionButtons/ActionButtons.tsx index bf10fe77235..87ae91aabac 100644 --- a/src/components/ens-profile/ActionButtons/ActionButtons.tsx +++ b/src/components/ens-profile/ActionButtons/ActionButtons.tsx @@ -19,7 +19,7 @@ export default function ActionButtons({ const isOwner = useMemo(() => { return Object.values(wallets || {}).some( - (wallet: any) => wallet.type !== 'readOnly' && wallet.addresses.some(({ address }: any) => address === primaryAddress) + (wallet: any) => wallet.type !== 'readOnly' && (wallet.addresses || []).some(({ address }: any) => address === primaryAddress) ); }, [primaryAddress, wallets]); diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index d8bd9130d39..dd695ea4e9e 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -12,7 +12,7 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils } from '@/utils'; import { formatAddressForDisplay } from '@/utils/abbreviations'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -34,9 +34,9 @@ export default function MoreButton({ address, ensName }: { address?: string; ens params: { setIsSearchModeEnabled }, } = useRoute(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); + const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); - return visibleWallet.address.toLowerCase() === address?.toLowerCase(); + return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; @@ -112,7 +112,7 @@ export default function MoreButton({ address, ensName }: { address?: string; ens setClipboard(address!); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); + ethereumUtils.openAddressInBlockExplorer({ address, chainId: ChainId.mainnet }); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/components/error-boundary/Fallback.tsx b/src/components/error-boundary/Fallback.tsx index ad57442ebc3..0ca291fddeb 100644 --- a/src/components/error-boundary/Fallback.tsx +++ b/src/components/error-boundary/Fallback.tsx @@ -36,7 +36,7 @@ export default function Fallback({ resetError: () => void; }) { const handleRestart = () => { - logger.error(new RainbowError('RainbowAppRestartFromErrorBoundary'), { + logger.error(new RainbowError('[ErrorBoundary]: RainbowAppRestartFromErrorBoundary'), { data: { error: error.toString(), componentStack, diff --git a/src/components/exchange/ConfirmExchangeButton.js b/src/components/exchange/ConfirmExchangeButton.js index 05b438e0acd..455517866ee 100644 --- a/src/components/exchange/ConfirmExchangeButton.js +++ b/src/components/exchange/ConfirmExchangeButton.js @@ -14,12 +14,12 @@ import Routes from '@/navigation/routesNames'; import { lightModeThemeColors } from '@/styles'; import { useTheme } from '@/theme'; import handleSwapErrorCodes from '@/utils/exchangeErrorCodes'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; const NOOP = () => null; export default function ConfirmExchangeButton({ - currentNetwork, + chainId, disabled, loading, isHighPriceImpact, @@ -95,7 +95,7 @@ export default function ConfirmExchangeButton({ label = lang.t('button.confirm_exchange.insufficient_funds'); } else if (isSufficientGas != null && !isSufficientGas) { label = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: getNetworkObj(currentNetwork).nativeCurrency.symbol, + tokenName: getNetworkObject({ chainId }).nativeCurrency.symbol, }); } else if (!isValidGas && isGasReady) { label = lang.t('button.confirm_exchange.invalid_fee'); diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/exchange/ExchangeAssetList.tsx index 4fd91c50b27..ff7da7c343e 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/exchange/ExchangeAssetList.tsx @@ -201,7 +201,6 @@ const ExchangeAssetList: ForwardRefRenderFunction items.map(({ data, ...item }) => ({ diff --git a/src/components/exchange/ExchangeField.tsx b/src/components/exchange/ExchangeField.tsx index a31dc8e749b..f72ed6fe138 100644 --- a/src/components/exchange/ExchangeField.tsx +++ b/src/components/exchange/ExchangeField.tsx @@ -4,14 +4,13 @@ import { TokenSelectionButton } from '../buttons'; import { ChainBadge, CoinIconSize } from '../coin-icon'; import { EnDash } from '../text'; import ExchangeInput from './ExchangeInput'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import styled from '@/styled-thing'; import { borders } from '@/styles'; import { useTheme } from '@/theme'; import { AccentColorProvider, Box, Space } from '@/design-system'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; const ExchangeFieldHeight = android ? 64 : 38; const ExchangeFieldPadding: Space = android ? '15px (Deprecated)' : '19px (Deprecated)'; diff --git a/src/components/exchange/ExchangeInputField.tsx b/src/components/exchange/ExchangeInputField.tsx index 490cc202a04..438955417c8 100644 --- a/src/components/exchange/ExchangeInputField.tsx +++ b/src/components/exchange/ExchangeInputField.tsx @@ -4,10 +4,9 @@ import { ColumnWithMargins, Row } from '../layout'; import ExchangeField from './ExchangeField'; import ExchangeMaxButton from './ExchangeMaxButton'; import ExchangeNativeField from './ExchangeNativeField'; -import { Network } from '@/helpers'; +import { ChainId } from '@/networks/types'; import styled from '@/styled-thing'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; const Container = styled(ColumnWithMargins).attrs({ margin: 5 })({ paddingTop: android ? 0 : 6, diff --git a/src/components/exchange/ExchangeOutputField.tsx b/src/components/exchange/ExchangeOutputField.tsx index 479862c16f6..0ada42075e3 100644 --- a/src/components/exchange/ExchangeOutputField.tsx +++ b/src/components/exchange/ExchangeOutputField.tsx @@ -3,7 +3,7 @@ import { TextInput } from 'react-native'; import ExchangeField from './ExchangeField'; import { Box } from '@rainbow-me/design-system'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; interface ExchangeOutputFieldProps { color: string; diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index b1280c79fcc..5a1f9254bf5 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -9,7 +9,8 @@ import { IS_IOS } from '@/env'; import { FavStar, Info } from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; import { View } from 'react-native'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { ParsedAddressAsset } from '@/entities'; interface ExchangeTokenRowProps { item: any; @@ -48,7 +49,6 @@ export default React.memo(function ExchangeTokenRow({ {name ?? item?.name} - {showBalance && item?.balance?.display && ( + {showBalance && (item as ParsedAddressAsset)?.balance?.display && ( - {item?.balance?.display ?? ''} + {(item as ParsedAddressAsset)?.balance?.display ?? ''} )} {!showBalance && ( @@ -92,7 +92,7 @@ export default React.memo(function ExchangeTokenRow({ {showBalance && ( - {item?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} + {(item as ParsedAddressAsset)?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} )} diff --git a/src/components/exchange/NetworkSwitcher.js b/src/components/exchange/NetworkSwitcher.js index 44870fb6286..e9b3a1812d7 100644 --- a/src/components/exchange/NetworkSwitcher.js +++ b/src/components/exchange/NetworkSwitcher.js @@ -8,11 +8,12 @@ import { Column, Row } from '../layout'; import { Text } from '../text'; import { padding, position } from '@/styles'; import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { chainIdToNameMapping } from '@/networks/types'; const networkMenuItems = () => { - return RainbowNetworks.filter(network => network.features.swaps).map(network => ({ + return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => ({ actionKey: network.value, actionTitle: network.name, icon: { @@ -22,7 +23,7 @@ const networkMenuItems = () => { })); }; const androidNetworkMenuItems = () => { - return RainbowNetworks.filter(network => network.features.swaps).map(network => network.name); + return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => network.name); }; const NetworkSwitcherv1 = ({ @@ -93,7 +94,7 @@ const NetworkSwitcherv1 = ({ weight={prominent ? 'heavy' : 'bold'} > {lang.t('expanded_state.swap.network_switcher', { - network: getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)).name, + network: chainIdToNameMapping[currentChainId], })} diff --git a/src/components/exchange/NetworkSwitcherv2.tsx b/src/components/exchange/NetworkSwitcherv2.tsx index 2ee33c53529..a16693c7cbb 100644 --- a/src/components/exchange/NetworkSwitcherv2.tsx +++ b/src/components/exchange/NetworkSwitcherv2.tsx @@ -4,11 +4,11 @@ import RadialGradient from 'react-native-radial-gradient'; import { ButtonPressAnimation } from '../animations'; import ChainBadge from '../coin-icon/ChainBadge'; import { Bleed, Box, Columns, Inline, Text } from '@/design-system'; -import { Network } from '@/helpers'; import { position } from '@rainbow-me/styles'; import { useTheme } from '@/theme'; import { sortNetworks } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId } from '@/networks/types'; const NetworkSwitcherv2 = ({ currentChainId, @@ -31,10 +31,10 @@ const NetworkSwitcherv2 = ({ })); }, []); - const radialGradientProps = (network: Network) => { + const radialGradientProps = (chainId: ChainId) => { return { center: [0, 1], - colors: [colors.alpha(colors.networkColors[network], 0.1), colors.alpha(colors.networkColors[network], 0.02)], + colors: [colors.alpha(colors.networkColors[chainId], 0.1), colors.alpha(colors.networkColors[chainId], 0.02)], pointerEvents: 'none', style: { ...position.coverAsObject, @@ -55,7 +55,7 @@ const NetworkSwitcherv2 = ({ testID={'network-switcher-scroll-view'} > - {networkMenuItems.map(({ chainId, title, network }) => { + {networkMenuItems.map(({ chainId, title }) => { const isSelected = currentChainId === chainId; return ( setCurrentChainId(chainId)} padding="8px" - testID={`${testID}-${network}`} + testID={`${testID}-${chainId}`} > {isSelected && ( )} - {network === Network.mainnet ? ( + {chainId === ChainId.mainnet ? ( ) : ( )} {title} diff --git a/src/components/exchange/exchangeAssetRowContextMenuProps.ts b/src/components/exchange/exchangeAssetRowContextMenuProps.ts index 57d0c2dc12f..e1de9563887 100644 --- a/src/components/exchange/exchangeAssetRowContextMenuProps.ts +++ b/src/components/exchange/exchangeAssetRowContextMenuProps.ts @@ -3,11 +3,11 @@ import { startCase } from 'lodash'; import { NativeSyntheticEvent } from 'react-native'; import { setClipboard } from '../../hooks/useClipboard'; import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const buildBlockExplorerAction = (chainId: ChainId) => { const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { actionKey: CoinRowActionsEnum.blockExplorer, @@ -43,7 +43,7 @@ export default function contextMenuProps(item: any, onCopySwapDetailsText: (addr }; const onPressAndroid = () => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( diff --git a/src/components/expanded-state/AvailableNetworks.js b/src/components/expanded-state/AvailableNetworks.js index 72cba5b9315..f6e96c439ab 100644 --- a/src/components/expanded-state/AvailableNetworks.js +++ b/src/components/expanded-state/AvailableNetworks.js @@ -3,11 +3,9 @@ import React from 'react'; import { Linking } from 'react-native'; import RadialGradient from 'react-native-radial-gradient'; import { Box } from '@/design-system'; -import networkInfo from '@/helpers/networkInfo'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { padding, position } from '@/styles'; -import { ethereumUtils } from '@/utils'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import { Column, Row } from '../layout'; @@ -15,11 +13,11 @@ import { ChainBadge } from '../coin-icon'; import Divider from '../Divider'; import { Text } from '../text'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, marginHorizontal = 19, prominent }) => { const { colors } = useTheme(); const { navigate } = useNavigation(); - const radialGradientProps = { center: [0, 1], colors: colors.gradients.lightGreyWhite, @@ -30,9 +28,7 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, }, }; - const availableNetworks = Object.keys(networks).map(network => { - return ethereumUtils.getNetworkFromChainId(Number(network)); - }); + const availableChainIds = Object.keys(networks).map(network => Number(network)); const linkToHop = useCallback(() => { Linking.openURL('https://app.hop.exchange/#/send'); @@ -40,12 +36,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, const handleAvailableNetworksPress = useCallback(() => { navigate(Routes.EXPLAIN_SHEET, { - networks: availableNetworks, + chainIds: availableChainIds, onClose: linkToHop, tokenSymbol: asset.symbol, type: 'availableNetworks', }); - }, [navigate, availableNetworks, linkToHop, asset.symbol]); + }, [navigate, availableChainIds, linkToHop, asset.symbol]); return ( <> @@ -53,12 +49,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, - {availableNetworks?.map((network, index) => { + {availableChainIds?.map((chainId, index) => { return ( - {network !== 'mainnet' ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( )} @@ -85,12 +81,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, size="smedium" weight={prominent ? 'heavy' : 'bold'} > - {availableNetworks?.length > 1 + {availableChainIds?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableNetworks?.length, + availableNetworks: availableChainIds?.length, }) : lang.t('expanded_state.asset.available_network', { - availableNetwork: networkInfo[availableNetworks?.[0]]?.name, + availableNetwork: chainIdToNameMapping[availableChainIds[0]]?.name, })} diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index 496a8bacd3a..b1345505800 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -8,14 +8,14 @@ import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { position } from '@/styles'; import { ethereumUtils, watchingAlert } from '@/utils'; -import { CurrencySelectionTypes, ExchangeModalTypes, Network } from '@/helpers'; +import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; import { useSwapCurrencyHandlers, useWallets } from '@/hooks'; import { RainbowToken } from '@/entities'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { implementation } from '@/entities/dispersion'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { SWAPS_V2, enableActionsOnReadOnlyWallet, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; @@ -25,7 +25,8 @@ import { AddressOrEth, AssetType } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; +import { getUniqueId } from '@/utils/ethereumUtils'; const NOOP = () => null; @@ -56,11 +57,11 @@ const AvailableNetworksv2 = ({ }, }; - const availableNetworks = useMemo(() => { + const availableChainIds = useMemo(() => { // we dont want to show mainnet return Object.keys(networks) - .map(network => ethereumUtils.getNetworkFromChainId(Number(network))) - .filter(network => network !== Network.mainnet); + .filter(chainId => Number(chainId) !== ChainId.mainnet) + .map(chainId => Number(chainId)); }, [networks]); const { updateInputCurrency } = useSwapCurrencyHandlers({ @@ -68,7 +69,7 @@ const AvailableNetworksv2 = ({ type: ExchangeModalTypes.swap, }); const convertAssetAndNavigate = useCallback( - (chosenNetwork: Network) => { + (chainId: ChainId) => { if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return; @@ -77,15 +78,14 @@ const AvailableNetworksv2 = ({ const newAsset = asset; // we need to convert the mainnet asset to the selected network's - newAsset.mainnet_address = networks?.[ethereumUtils.getChainIdFromNetwork(Network.mainnet)]?.address ?? asset.address; - newAsset.address = networks?.[ethereumUtils.getChainIdFromNetwork(chosenNetwork)].address; - newAsset.network = chosenNetwork; + newAsset.mainnet_address = networks?.[ChainId.mainnet]?.address ?? asset.address; + newAsset.address = networks?.[chainId].address; + newAsset.chainId = chainId; goBack(); if (swapsV2Enabled || swaps_v2) { - const chainId = ethereumUtils.getChainIdFromNetwork(newAsset.network); - const uniqueId = `${newAsset.address}_${chainId}`; + const uniqueId = `${newAsset.address}_${asset.chainId}`; const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); const parsedAsset = parseSearchAsset({ @@ -94,16 +94,16 @@ const AvailableNetworksv2 = ({ uniqueId, address: newAsset.address as AddressOrEth, type: newAsset.type as AssetType, - chainId, - chainName: chainNameFromChainId(chainId), + chainId: asset.chainId, + chainName: chainNameFromChainId(asset.chainId), isNativeAsset: false, native: {}, }, searchAsset: { ...newAsset, uniqueId, - chainId, - chainName: chainNameFromChainId(chainId), + chainId: asset.chainId, + chainName: chainNameFromChainId(asset.chainId), address: newAsset.address as AddressOrEth, highLiquidity: newAsset.highLiquidity ?? false, isRainbowCurated: newAsset.isRainbowCurated ?? false, @@ -118,7 +118,7 @@ const AvailableNetworksv2 = ({ const largestBalanceSameChainUserAsset = userAssetsStore .getState() .getUserAssets() - .find(userAsset => userAsset.chainId === chainId && userAsset.address !== newAsset.address); + .find(userAsset => userAsset.chainId === asset.chainId && userAsset.address !== newAsset.address); if (largestBalanceSameChainUserAsset) { swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); } else { @@ -133,8 +133,8 @@ const AvailableNetworksv2 = ({ return; } - newAsset.uniqueId = `${asset.address}_${chosenNetwork}`; - newAsset.type = chosenNetwork; + newAsset.uniqueId = getUniqueId(asset.address, chainId); + newAsset.type = ethereumUtils.getNetworkFromChainId(chainId); navigate(Routes.EXCHANGE_MODAL, { params: { @@ -151,37 +151,35 @@ const AvailableNetworksv2 = ({ screen: Routes.CURRENCY_SELECT_SCREEN, }); }, - [asset, goBack, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] + [asset, goBack, isReadOnlyWallet, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] ); const handlePressContextMenu = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly - ({ nativeEvent: { actionKey: network } }) => { - convertAssetAndNavigate(network); + ({ nativeEvent: { actionKey: chainId } }) => { + convertAssetAndNavigate(chainId); }, [convertAssetAndNavigate] ); const handlePressButton = useCallback(() => { - convertAssetAndNavigate(availableNetworks[0]); - }, [availableNetworks, convertAssetAndNavigate]); + convertAssetAndNavigate(availableChainIds[0]); + }, [availableChainIds, convertAssetAndNavigate]); const networkMenuItems = useMemo(() => { - return RainbowNetworks.filter(({ features, value, id }) => features.swaps && value !== Network.mainnet && !!networks[id]).map( - network => ({ - actionKey: network.value, - actionTitle: network.name, - icon: { - iconType: 'ASSET', - iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, - }, - }) - ); + return RainbowNetworkObjects.filter(({ features, id }) => features.swaps && id !== ChainId.mainnet && !!networks[id]).map(network => ({ + actionKey: `${network.id}`, + actionTitle: network.name, + icon: { + iconType: 'ASSET', + iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, + }, + })); }, [networks]); - const MenuWrapper = availableNetworks.length > 1 ? ContextMenuButton : Box; + const MenuWrapper = availableChainIds.length > 1 ? ContextMenuButton : Box; - if (availableNetworks.length === 0) return null; + if (availableChainIds.length === 0) return null; return ( <> @@ -208,16 +206,15 @@ const AvailableNetworksv2 = ({ - {availableNetworks?.map((network, index) => { - const chainId = ethereumUtils.getChainIdFromNetwork(network); + {availableChainIds?.map((chainId, index) => { return ( @@ -233,18 +230,18 @@ const AvailableNetworksv2 = ({ - {availableNetworks?.length > 1 + {availableChainIds?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableNetworks?.length, + availableNetworks: availableChainIds?.length, }) : lang.t('expanded_state.asset.available_networkv2', { - availableNetwork: getNetworkObj(availableNetworks?.[0])?.name, + availableNetwork: chainIdToNameMapping[availableChainIds[0]], })} - {availableNetworks?.length > 1 ? '􀁱' : '􀯻'} + {availableChainIds?.length > 1 ? '􀁱' : '􀯻'} diff --git a/src/components/expanded-state/ContactProfileState.js b/src/components/expanded-state/ContactProfileState.js index b6bde10d0de..9a895e6dedc 100644 --- a/src/components/expanded-state/ContactProfileState.js +++ b/src/components/expanded-state/ContactProfileState.js @@ -7,7 +7,7 @@ import { magicMemo } from '../../utils'; import ProfileModal from './profile/ProfileModal'; import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; -import { useAccountSettings, useContacts, useENSAvatar } from '@/hooks'; +import { useContacts, useENSAvatar } from '@/hooks'; import { addressHashedColorIndex, addressHashedEmoji } from '@/utils/profileUtils'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; @@ -24,16 +24,14 @@ const ContactProfileState = ({ address, color, contact, ens, nickname }) => { const colorIndex = useMemo(() => color || addressHashedColorIndex(address) || 0, [address, color]); - const { network } = useAccountSettings(); - const handleAddContact = useCallback(() => { const nickname = profilesEnabled ? value : (emoji ? `${emoji} ${value}` : value).trim(); if (value?.length > 0) { - onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], network, ens); + onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], ens); goBack(); } android && Keyboard.dismiss(); - }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, network, onAddOrUpdateContacts, profilesEnabled, value]); + }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, onAddOrUpdateContacts, profilesEnabled, value]); const handleCancel = useCallback(() => { goBack(); diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index eed7e54ba95..d19af128e10 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -40,7 +40,7 @@ export default function CustomGasState({ asset }) { const { height: deviceHeight } = useDimensions(); const keyboardHeight = useKeyboardHeight(); const colorForAsset = useColorForAsset(asset || {}, fallbackColor, false, true); - const { selectedGasFee, currentBlockParams, txNetwork } = useGas(); + const { selectedGasFee, currentBlockParams, chainId } = useGas(); const [canGoBack, setCanGoBack] = useState(true); const { tradeDetails } = useSelector(state => state.swap); @@ -93,7 +93,7 @@ export default function CustomGasState({ asset }) { { - switch (network) { - case Network.mainnet: +const getIsSupportedOnRainbowWeb = (chainId: ChainId) => { + switch (chainId) { + case ChainId.mainnet: return true; default: return false; @@ -251,7 +251,7 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE [offer] ); - const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.network); + const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.chainId); const [isRefreshMetadataToastActive, setIsRefreshMetadataToastActive] = useState(false); const [isReportSpamToastActive, setIsReportSpamToastActive] = useState(false); @@ -548,10 +548,10 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE /> )} - {asset.network !== Network.mainnet ? ( + {asset.chainId !== ChainId.mainnet ? ( // @ts-expect-error JavaScript component isL2Chain({ chainId: assetChainId }), [assetChainId]); - const isTestnet = isTestnetNetwork(currentNetwork); + const isL2 = useMemo(() => isL2Chain({ chainId: asset?.chainId }), [asset?.chainId]); + const isTestnet = isTestnetChain({ chainId: currentChainId }); const { data, isLoading: additionalAssetDataLoading } = useAdditionalAssetData({ address: asset?.address, - network: asset?.network, + chainId: asset?.chainId, currency: nativeCurrency, }); @@ -235,12 +235,13 @@ export default function ChartExpandedState({ asset }) { const { colors } = useTheme(); const crosschainEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); + const AvailableNetworks = !crosschainEnabled ? AvailableNetworksv1 : AvailableNetworksv2; - const assetNetwork = assetWithPrice.network; + const assetChainId = assetWithPrice.chainId; const { swagg_enabled, f2c_enabled } = useRemoteConfig(); - const swapEnabled = swagg_enabled && getNetworkObj(assetNetwork).features.swaps; + const swapEnabled = swagg_enabled && getNetworkObject({ chainId: assetChainId }).features.swaps; const addCashEnabled = f2c_enabled; const format = useCallback( @@ -314,7 +315,7 @@ export default function ChartExpandedState({ asset }) { ) : null} {!data?.networks && isL2 && ( - + )} {data?.networks && !hasBalance && ( @@ -375,7 +376,7 @@ export default function ChartExpandedState({ asset }) { isNativeAsset={assetWithPrice?.isNativeAsset} links={data?.links} marginTop={!delayedDescriptions && 19} - chainId={ethereumUtils.getChainIdFromNetwork(asset?.network)} + chainId={asset?.chainId} /> diff --git a/src/components/expanded-state/asset/SocialLinks.js b/src/components/expanded-state/asset/SocialLinks.js index 79d2706a75a..f5199f6480d 100644 --- a/src/components/expanded-state/asset/SocialLinks.js +++ b/src/components/expanded-state/asset/SocialLinks.js @@ -28,8 +28,8 @@ const CommunityLink = styled(Link).attrs({ }); export default function SocialLinks({ address, color, isNativeAsset, links, marginTop, chainId }) { - const etherscanURL = ethereumUtils.getEtherscanHostForNetwork(chainId); - const blockExplorerName = ethereumUtils.getBlockExplorer(chainId); + const etherscanURL = ethereumUtils.getEtherscanHostForNetwork({ chainId }); + const blockExplorerName = ethereumUtils.getBlockExplorer({ chainId }); return ( <> diff --git a/src/components/expanded-state/chart/ChartContextButton.js b/src/components/expanded-state/chart/ChartContextButton.js index 8ce84794e84..19044bc6fe1 100644 --- a/src/components/expanded-state/chart/ChartContextButton.js +++ b/src/components/expanded-state/chart/ChartContextButton.js @@ -46,12 +46,12 @@ export default function ChartContextButton({ asset, color }) { ? [] : [ `🔍 ${emojiSpacing}${lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), })}`, ]), ...(ios ? [lang.t('wallet.action.cancel')] : []), ], - [asset?.isNativeAsset, asset?.network, currentAction] + [asset?.chainId, asset?.isNativeAsset, currentAction] ); return ; diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 624255db57e..2d23e3ca37c 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -11,7 +11,6 @@ import { useAccountSettings, useBooleanState } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ethereumUtils } from '@/utils'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -52,9 +51,6 @@ export default function ChartExpandedStateHeader({ }) { const theme = useTheme(); const color = givenColors || theme.colors.dark; - const tokens = useMemo(() => { - return isPool ? asset.tokens : [asset]; - }, [asset, isPool]); const { nativeCurrency } = useAccountSettings(); const tabularNums = useTabularNumsWhileScrubbing(); @@ -114,7 +110,7 @@ export default function ChartExpandedStateHeader({ (null); const [maxPriorityFeeError, setMaxPriorityFeeError] = useState(null); @@ -248,12 +248,12 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack ); const addMinerTip = useCallback(() => { - updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, txNetwork)); - }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); + updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, chainId)); + }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); const substMinerTip = useCallback(() => { - updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, txNetwork)); - }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); + updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, chainId)); + }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); const addMaxFee = useCallback(() => { updateFeePerGas(isL2 ? GAS_FEE_L2_INCREMENT : GAS_FEE_INCREMENT); diff --git a/src/components/expanded-state/swap-details/CurrencyTile.js b/src/components/expanded-state/swap-details/CurrencyTile.js index dee048e8fe9..62072cc8930 100644 --- a/src/components/expanded-state/swap-details/CurrencyTile.js +++ b/src/components/expanded-state/swap-details/CurrencyTile.js @@ -78,7 +78,7 @@ export default function CurrencyTile({ { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }); return { actionKey: ContractActionsEnum.blockExplorer, @@ -105,7 +105,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const [menuVisible, setMenuVisible] = useState(false); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(asset?.network)); + const blockExplorerAction = buildBlockExplorerAction(asset?.chainId); return { menuItems: [ blockExplorerAction, @@ -116,14 +116,14 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . ], menuTitle: `${asset?.name} (${asset?.symbol})`, }; - }, [asset?.address, asset?.name, asset?.symbol, asset?.network]); + }, [asset?.chainId, asset?.address, asset?.name, asset?.symbol]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContractActionsEnum.copyAddress) { handleCopyContractAddress(asset?.address); } else if (actionKey === ContractActionsEnum.blockExplorer) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); } }, [asset, handleCopyContractAddress] @@ -131,7 +131,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const onPressAndroid = useCallback(() => { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), }); const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -146,7 +146,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . handleCopyContractAddress(asset?.address); } if (idx === 1) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); } } ); diff --git a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js index fb9a5852652..67efaa8db35 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js @@ -1,7 +1,6 @@ import lang from 'i18n-js'; import { capitalize } from 'lodash'; import React, { Fragment, useMemo } from 'react'; -import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { convertAmountToPercentageDisplay } from '../../../helpers/utilities'; import Pill from '../../Pill'; import { ButtonPressAnimation } from '../../animations'; @@ -11,10 +10,10 @@ import { usePrevious, useStepper } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { getExchangeIconUrl, magicMemo } from '@/utils'; import { SocketBridges } from '@/references/swap/bridges'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; const parseExchangeName = name => { - const networks = RainbowNetworks.map(network => network.name.toLowerCase()); + const networks = RainbowNetworkObjects.map(network => network.name.toLowerCase()); const removeNetworks = name => networks.some(network => name.toLowerCase().includes(network)) ? name.slice(name.indexOf('_') + 1, name.length) : name; diff --git a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx index 657c529d1a5..2dd81818208 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx +++ b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx @@ -9,7 +9,7 @@ import { ChainBadge } from '@/components/coin-icon'; import { getNetworkObject } from '@/networks'; import { useTheme } from '@/theme'; import * as i18n from '@/languages'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export function SwapDetailsRewardRow({ reward }: { reward: Reward }) { const { navigate } = useNavigation(); diff --git a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx index 98f5ceeb63d..d2413fed5f1 100644 --- a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx +++ b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx @@ -6,13 +6,13 @@ import { ButtonPressAnimation } from '../../animations'; import { Icon } from '../../icons'; import StepButtonInput from './StepButtonInput'; import { AccentColorProvider, Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; -import { Network } from '@/helpers'; import { add, convertNumberToString, greaterThan } from '@/helpers/utilities'; import { useMagicAutofocus, useSwapSettings } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; import { ethereumUtils } from '@/utils'; +import { ChainId } from '@/networks/types'; const convertBipsToPercent = (bips: number) => (bips / 100).toString(); const convertPercentToBips = (percent: number) => (percent * 100).toString(); @@ -20,133 +20,131 @@ const convertPercentToBips = (percent: number) => (percent * 100).toString(); const SLIPPAGE_INCREMENT = 0.1; // eslint-disable-next-line react/display-name -export const MaxToleranceInput = forwardRef( - ({ colorForAsset, currentNetwork }: { colorForAsset: string; currentNetwork: Network }, ref) => { - const { slippageInBips, updateSwapSlippage } = useSwapSettings(); - const { navigate } = useNavigation(); - - const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); - - const slippageRef = useRef(null); - - const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); - - const { hasPriceImpact, priceImpactColor } = useMemo(() => { - const hasPriceImpact = Number(slippageValue) >= 3; - const priceImpactColor = hasPriceImpact ? colors.orange : null; - return { hasPriceImpact, priceImpactColor }; - }, [slippageValue]); - - useImperativeHandle(ref, () => ({ - blur: () => { - slippageRef?.current?.blur(); - }, - reset: () => { - const slippage = getDefaultSlippageFromConfig(ethereumUtils.getChainIdFromNetwork(currentNetwork)) as unknown as number; - onSlippageChange(convertBipsToPercent(slippage)); - }, - })); - - const updateSlippage = useCallback( - (increment: any) => { - const newSlippage = add(slippageValue, increment); - const newSlippageValue = convertNumberToString(newSlippage); - if (greaterThan(0, newSlippageValue)) return; - - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); - setSlippageValue(newSlippageValue); - }, - [slippageValue, updateSwapSlippage] - ); - - const addSlippage = useCallback(() => { - updateSlippage(SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const minusSlippage = useCallback(() => { - updateSlippage(-SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const onSlippageChange = useCallback( - (value: any) => { - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(value)); - setSlippageValue(value); - }, - [updateSwapSlippage, setSlippageValue] - ); - - const openSlippageExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'slippage', - }); - }; - - return ( - - - - - - - {`${lang.t('exchange.slippage_tolerance')} `} - {!hasPriceImpact && ( - - {' 􀅵'} - - )} - - {hasPriceImpact && ( - - - +export const MaxToleranceInput = forwardRef(({ colorForAsset, chainId }: { colorForAsset: string; chainId: ChainId }, ref) => { + const { slippageInBips, updateSwapSlippage } = useSwapSettings(); + const { navigate } = useNavigation(); + + const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); + + const slippageRef = useRef(null); + + const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); + + const { hasPriceImpact, priceImpactColor } = useMemo(() => { + const hasPriceImpact = Number(slippageValue) >= 3; + const priceImpactColor = hasPriceImpact ? colors.orange : null; + return { hasPriceImpact, priceImpactColor }; + }, [slippageValue]); + + useImperativeHandle(ref, () => ({ + blur: () => { + slippageRef?.current?.blur(); + }, + reset: () => { + const slippage = getDefaultSlippageFromConfig(chainId) as unknown as number; + onSlippageChange(convertBipsToPercent(slippage)); + }, + })); + + const updateSlippage = useCallback( + (increment: any) => { + const newSlippage = add(slippageValue, increment); + const newSlippageValue = convertNumberToString(newSlippage); + if (greaterThan(0, newSlippageValue)) return; + + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); + setSlippageValue(newSlippageValue); + }, + [slippageValue, updateSwapSlippage] + ); + + const addSlippage = useCallback(() => { + updateSlippage(SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const minusSlippage = useCallback(() => { + updateSlippage(-SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const onSlippageChange = useCallback( + (value: any) => { + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(value)); + setSlippageValue(value); + }, + [updateSwapSlippage, setSlippageValue] + ); + + const openSlippageExplainer = () => { + Keyboard.dismiss(); + navigate(Routes.EXPLAIN_SHEET, { + type: 'slippage', + }); + }; + + return ( + + + + + + + {`${lang.t('exchange.slippage_tolerance')} `} + {!hasPriceImpact && ( + + {' 􀅵'} + )} - + + {hasPriceImpact && ( + + + + )} + + + {hasPriceImpact && ( + + + + + {lang.t('exchange.high')} + + + {` · ${lang.t('exchange.price_impact.label')}`} + - {hasPriceImpact && ( - - - - - {lang.t('exchange.high')} - - - {` · ${lang.t('exchange.price_impact.label')}`} - - - )} - - - - - - - ); - } -); + )} + + + + + + + ); +}); diff --git a/src/components/expanded-state/swap-settings/SwapSettingsState.js b/src/components/expanded-state/swap-settings/SwapSettingsState.js index 13d2ae8899e..7432ebece10 100644 --- a/src/components/expanded-state/swap-settings/SwapSettingsState.js +++ b/src/components/expanded-state/swap-settings/SwapSettingsState.js @@ -32,7 +32,7 @@ function useAndroidDisableGesturesOnFocus() { export default function SwapSettingsState({ asset }) { const { flashbotsEnabled, settingsChangeFlashbotsEnabled } = useAccountSettings(); const { - params: { swapSupportsFlashbots = false, network }, + params: { swapSupportsFlashbots = false, chainId }, } = useRoute(); const { colors } = useTheme(); const { setParams, goBack } = useNavigation(); @@ -151,7 +151,7 @@ export default function SwapSettingsState({ asset }) { )} - + diff --git a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx index 21f6e2cd2f5..8247be503b0 100644 --- a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx +++ b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx @@ -54,7 +54,7 @@ export default function NFTBriefTokenInfoRow({ asset }: { asset: UniqueAsset }) const { data: listing } = useNFTListing({ contractAddress: asset?.asset_contract?.address ?? '', tokenId: asset?.id, - network: asset?.network, + chainId: asset?.chainId, }); const listingValue = listing && convertRawAmountToRoundedDecimal(listing?.price, listing?.payment_token?.decimals, 3); diff --git a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx index 08fb711b91e..205b88c4bff 100644 --- a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx +++ b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx @@ -9,7 +9,6 @@ import saveToCameraRoll from './saveToCameraRoll'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Bleed, Column, Columns, Heading, Inline, Inset, Space, Stack, Text } from '@/design-system'; import { UniqueAsset } from '@/entities'; -import { Network } from '@/helpers'; import { useClipboard, useDimensions, useHiddenTokens, useShowcaseTokens } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { useNavigation } from '@/navigation/Navigation'; @@ -23,6 +22,7 @@ import { refreshNFTContractMetadata, reportNFT } from '@/resources/nfts/simpleha import { ContextCircleButton } from '@/components/context-menu'; import { IS_ANDROID, IS_IOS } from '@/env'; import { MenuActionConfig, MenuConfig } from 'react-native-ios-context-menu'; +import { ChainId } from '@/networks/types'; const AssetActionsEnum = { copyTokenID: 'copyTokenID', @@ -36,7 +36,7 @@ const AssetActionsEnum = { report: 'report', } as const; -const getAssetActions = (network: Network) => +const getAssetActions = ({ chainId }: { chainId: ChainId }) => ({ [AssetActionsEnum.copyTokenID]: { actionKey: AssetActionsEnum.copyTokenID, @@ -57,7 +57,7 @@ const getAssetActions = (network: Network) => [AssetActionsEnum.etherscan]: { actionKey: AssetActionsEnum.etherscan, actionTitle: lang.t('expanded_state.unique_expanded.view_on_block_explorer', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(network))), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), }), icon: { iconType: 'SYSTEM', @@ -263,7 +263,7 @@ const UniqueTokenExpandedStateHeader = ({ const isPhotoDownloadAvailable = !isSVG && !isENS; const assetMenuConfig: MenuConfig = useMemo(() => { - const AssetActions = getAssetActions(asset.network); + const AssetActions = getAssetActions({ chainId: asset.chainId }); return { menuItems: [ @@ -309,7 +309,7 @@ const UniqueTokenExpandedStateHeader = ({ { ...AssetActions[AssetActionsEnum.etherscan], }, - ...(asset.network === Network.mainnet + ...(asset.chainId === ChainId.mainnet ? [ { menuTitle: lang.t('expanded_state.unique_expanded.view_on_marketplace'), @@ -320,7 +320,7 @@ const UniqueTokenExpandedStateHeader = ({ ], menuTitle: '', }; - }, [asset.id, asset?.network, isPhotoDownloadAvailable, isHiddenAsset, isModificationActionsEnabled, isSupportedOnRainbowWeb]); + }, [asset.id, asset.chainId, isModificationActionsEnabled, isHiddenAsset, isPhotoDownloadAvailable, isSupportedOnRainbowWeb]); const handlePressFamilyMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly diff --git a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js index 3b74a5f5a1d..b81a35f88c0 100644 --- a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js +++ b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js @@ -186,8 +186,8 @@ export const ZoomableWrapper = ({ let targetScale = Math.min(scale.value, MAX_IMAGE_SCALE); // determine whether to snap to screen edges - let breakingScaleX = deviceWidth / fullSizeWidth; - let breakingScaleY = deviceHeight / fullSizeHeight; + const breakingScaleX = deviceWidth / fullSizeWidth; + const breakingScaleY = deviceHeight / fullSizeHeight; const maxDisplacementX = (deviceWidth * (Math.max(1, targetScale / breakingScaleX) - 1)) / 2 / zooming; const maxDisplacementY = (deviceHeight * (Math.max(1, targetScale / breakingScaleY) - 1)) / 2 / zooming; diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 8ba909abe83..a8851d7d387 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -28,7 +28,7 @@ import { getNetworkObject } from '@/networks'; import { IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const { GAS_EMOJIS, GAS_ICONS, GasSpeedOrder, CUSTOM, URGENT, NORMAL, FAST, getGasLabel } = gasUtils; @@ -310,9 +310,9 @@ const GasSpeedButton = ({ type: 'crossChainGas', }); } else { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId }); navigate(Routes.EXPLAIN_SHEET, { - network: ethereumUtils.getNetworkFromChainId(chainId), + chainId, type: 'gas', nativeAsset, }); diff --git a/src/components/investment-cards/PoolValue.js b/src/components/investment-cards/PoolValue.js index b382a707989..cf1e6397ee8 100644 --- a/src/components/investment-cards/PoolValue.js +++ b/src/components/investment-cards/PoolValue.js @@ -29,7 +29,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { const { nativeCurrency } = useAccountSettings(); if (type === 'annualized_fees') { - let percent = parseFloat(value); + const percent = parseFloat(value); if (!percent || percent === 0) { formattedValue = '0%'; } @@ -42,7 +42,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { formattedValue = '< 0.0001%'; } - let fixedPercent = percent.toFixed(2); + const fixedPercent = percent.toFixed(2); if (fixedPercent === '0.00') { formattedValue = '0%'; } diff --git a/src/components/list/NoResults.tsx b/src/components/list/NoResults.tsx index 5086163a346..ef610f7b36e 100644 --- a/src/components/list/NoResults.tsx +++ b/src/components/list/NoResults.tsx @@ -41,7 +41,7 @@ export const NoResults = ({ onL2, type }: { onL2?: boolean; type: NoResultsType break; default: title = lang.t('exchange.no_results.nothing_found'); - logger.warn('NoResults: unknown type, falling back to default message'); + logger.warn('[NoResults]: unknown type, falling back to default message'); break; } diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx index 366e9c3577d..c1faa16a794 100644 --- a/src/components/positions/PositionsCard.tsx +++ b/src/components/positions/PositionsCard.tsx @@ -18,6 +18,7 @@ import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useAccountSettings } from '@/hooks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; type PositionCardProps = { position: RainbowPosition; @@ -33,7 +34,7 @@ function CoinIconForStack({ token }: { token: CoinStackToken }) { const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const chainId = ethereumUtils.getChainIdFromNetwork(token.network); - const { data: externalAsset } = useExternalToken({ address: token.address, chainId, currency: nativeCurrency }); + const { data: externalAsset } = useExternalToken({ address: token.address as AddressOrEth, chainId, currency: nativeCurrency }); return ( = async ({ assetAddress, network }) => { - const { accountAddress, nativeCurrency } = store.getState().settings; - - const assets = await fetchUserAssets({ - address: accountAddress, - currency: nativeCurrency, - connectedToHardhat: false, - }); - if (!assets || Object.keys(assets).length === 0) return false; - - const desiredAsset = Object.values(assets).find(asset => { - if (!network) { - return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase(); - } - - return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase() && asset.network === network; - }); - if (!desiredAsset) return false; - - return Number(desiredAsset.balance?.amount) > 0; -}; diff --git a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts index b6133f9f4af..0c7fe5d7fa2 100644 --- a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts +++ b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts @@ -1,5 +1,5 @@ import type { EthereumAddress, RainbowTransaction } from '@/entities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import store from '@/redux/store'; import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; @@ -13,7 +13,7 @@ const isSwapTx = (tx: RainbowTransaction): boolean => tx.to?.toLowerCase() === R export const hasSwapTxn = async (): Promise => { const { accountAddress, nativeCurrency } = store.getState().settings; - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, diff --git a/src/components/remote-promo-sheet/check-fns/index.ts b/src/components/remote-promo-sheet/check-fns/index.ts index 94ec08d36ec..b3fdb065edb 100644 --- a/src/components/remote-promo-sheet/check-fns/index.ts +++ b/src/components/remote-promo-sheet/check-fns/index.ts @@ -1,5 +1,4 @@ export * from './hasNftOffers'; -export * from './hasNonZeroAssetBalance'; export * from './hasNonZeroTotalBalance'; export * from './hasSwapTxn'; export * from './isAfterCampaignLaunch'; diff --git a/src/components/remote-promo-sheet/checkForCampaign.ts b/src/components/remote-promo-sheet/checkForCampaign.ts index 47b5bf34447..da6a7794a25 100644 --- a/src/components/remote-promo-sheet/checkForCampaign.ts +++ b/src/components/remote-promo-sheet/checkForCampaign.ts @@ -31,21 +31,21 @@ const timeBetweenPromoSheets = () => { }; export const checkForCampaign = async () => { - logger.debug('Campaigns: Running Checks'); + logger.debug('[checkForCampaign]: Running Checks'); if (timeBetweenPromoSheets() < TIMEOUT_BETWEEN_PROMOS) { - logger.debug('Campaigns: Time between promos has not exceeded timeout'); + logger.debug('[checkForCampaign]: Time between promos has not exceeded timeout'); return; } const isShown = remotePromoSheetsStore.getState().isShown; if (isShown) { - logger.debug('Campaigns: Another remote sheet is currently shown'); + logger.debug('[checkForCampaign]: Another remote sheet is currently shown'); return; } const isReturningUser = device.get(['isReturningUser']); if (!isReturningUser) { - logger.debug('Campaigns: First launch, not showing promo sheet'); + logger.debug('[checkForCampaign]: First launch, not showing promo sheet'); return; } @@ -55,10 +55,10 @@ export const checkForCampaign = async () => { for (const promo of promoSheetCollection?.items || []) { if (!promo) continue; - logger.debug(`Campaigns: Checking ${promo.sys.id}`); + logger.debug(`[checkForCampaign]: Checking ${promo.sys.id}`); const result = await shouldPromptCampaign(promo as PromoSheet); - logger.debug(`Campaigns: ${promo.sys.id} will show: ${result}`); + logger.debug(`[checkForCampaign]: ${promo.sys.id} will show: ${result}`); if (result) { const isShown = remotePromoSheetsStore.getState().isShown; if (!isShown) { @@ -69,7 +69,7 @@ export const checkForCampaign = async () => { }; export const triggerCampaign = async ({ campaignKey, sys: { id: campaignId } }: PromoSheet) => { - logger.debug(`Campaigns: Showing ${campaignKey} Promo`); + logger.debug(`[checkForCampaign]: Showing ${campaignKey} Promo`); setTimeout(() => { remotePromoSheetsStore.getState().showSheet(campaignId); @@ -93,14 +93,14 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise action.fn === 'isPreviewing'); const hasShown = remotePromoSheetsStore.getState().getSheet(id)?.hasBeenShown; // If the campaign has been viewed already or it's the first app launch, exit early if (hasShown && !isPreviewing) { - logger.debug(`Campaigns: User has already been shown ${campaignKey}`); + logger.debug(`[checkForCampaign]: User has already been shown ${campaignKey}`); return false; } @@ -113,9 +113,9 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise ${result === outcome}`); + logger.debug(`[checkForCampaign]: [${fn}] matches desired outcome: => ${result === outcome}`); if (result !== outcome) { shouldPrompt = false; diff --git a/src/components/remote-promo-sheet/localCampaignChecks.ts b/src/components/remote-promo-sheet/localCampaignChecks.ts index 9577f3f1bef..f91c7909c7b 100644 --- a/src/components/remote-promo-sheet/localCampaignChecks.ts +++ b/src/components/remote-promo-sheet/localCampaignChecks.ts @@ -31,7 +31,7 @@ export interface Campaign { export const activeCampaigns: Campaign[] = [NotificationsPromoCampaign]; export const runLocalCampaignChecks = async (): Promise => { - logger.debug('Campaigns: Running Checks'); + logger.debug('[runLocalCampaignChecks]: Running Checks'); for (const campaign of activeCampaigns) { InteractionManager.runAfterInteractions(async () => { const response = await campaign.check(); diff --git a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts index 00aa7b65c77..465ea2dddef 100644 --- a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts +++ b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts @@ -10,12 +10,12 @@ import { STORAGE_IDS } from '@/model/mmkv'; const mmkv = new MMKV(); export const notificationsCampaignAction = async () => { - logger.debug('Notifications promo: showing notifications promo'); + logger.debug('[notificationsCampaignAction]: showing notifications promo'); mmkv.set(CampaignKey.notificationsLaunch, true); setTimeout(() => { - logger.debug(`Notifications promo: triggering notifications promo action`); + logger.debug('[notificationsCampaignAction]: triggering notifications promo action'); Navigation.handleAction(Routes.NOTIFICATIONS_PROMO_SHEET, {}); }, 1000); @@ -25,7 +25,7 @@ export const notificationsCampaignCheck = async (): Promise { const runChecks = useCallback(() => { InteractionManager.runAfterInteractions(async () => { if (IS_TEST || !remotePromoSheets) { - logger.debug('Campaigns: remote promo sheets is disabled'); + logger.debug('[useRunChecks]: remote promo sheets is disabled'); return; } diff --git a/src/components/secret-display/SecretDisplaySection.tsx b/src/components/secret-display/SecretDisplaySection.tsx index eaa38f59627..0ef93ba05e6 100644 --- a/src/components/secret-display/SecretDisplaySection.tsx +++ b/src/components/secret-display/SecretDisplaySection.tsx @@ -106,11 +106,10 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: onSecretLoaded?.(!!seedPhrase); } catch (error) { const message = (error as Error)?.message; - logger.error(new RainbowError('Error while trying to reveal secret'), { + logger.error(new RainbowError('[SecretDisplaySection]: Error while trying to reveal secret'), { error: message, }); setSectionState(message === createdWithBiometricError ? SecretDisplayStates.securedWithBiometrics : SecretDisplayStates.noSeed); - captureException(error); onSecretLoaded?.(false); } }, [onSecretLoaded, privateKeyAddress, onWalletTypeIdentified, walletId]); @@ -234,7 +233,7 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: ); default: - logger.error(new RainbowError('Secret display section tries to present an unknown state')); + logger.error(new RainbowError(`[SecretDisplaySection]: Secret display section state unknown ${sectionState}`)); return null; } } diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx index 810bb9e7890..af2a29de48b 100644 --- a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx +++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx @@ -85,7 +85,7 @@ function SwapActionButton({ asset, color: givenColor, inputType, label, fromDisc if (inputType === assetInputTypes.in) { swapsStore.setState({ inputAsset: userAsset || parsedAsset }); - const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork({ chainId }); if (nativeAssetForChain && !isSameAsset({ address: nativeAssetForChain.address as AddressOrEth, chainId }, parsedAsset)) { const userOutputAsset = userAssetsStore.getState().getUserAsset(`${nativeAssetForChain.address}_${chainId}`); diff --git a/src/components/svg/SvgImage.js b/src/components/svg/SvgImage.js index 1960a973590..e55bd0be0b1 100644 --- a/src/components/svg/SvgImage.js +++ b/src/components/svg/SvgImage.js @@ -4,7 +4,7 @@ import { WebView } from 'react-native-webview'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { CardSize } from '../unique-token/CardSize'; const ImageTile = styled(ImgixImage)({ @@ -97,7 +97,7 @@ class SvgImage extends Component { } doFetch = async props => { - let uri = props.source && props.source.uri; + const uri = props.source && props.source.uri; if (uri) { props.onLoadStart && props.onLoadStart(); if (uri.match(/^data:image\/svg/)) { @@ -110,17 +110,17 @@ class SvgImage extends Component { if (text.toLowerCase().indexOf('/)) { - logger.log('foreignObject tag not supported', { text, uri }); + logger.debug('[SvgImage]: foreignObject tag not supported', { text, uri }); // return w/o error so we can fallback to png return; } this.mounted && this.setState({ fetchingUrl: uri, svgContent: text }); } else { - logger.log('invalid svg', { text, uri }); + logger.debug('[SvgImage]: invalid svg', { text, uri }); this.mounted && props.onError && props.onError('invalid svg'); } } catch (err) { - logger.log('error loading remote svg image', err); + logger.debug('[SvgImage]: error loading remote svg image', err); this.mounted && props.onError && props.onError('error loading remote svg image'); } } diff --git a/src/components/toasts/OfflineToast.js b/src/components/toasts/OfflineToast.js index defab4a11ee..ec4565c57e9 100644 --- a/src/components/toasts/OfflineToast.js +++ b/src/components/toasts/OfflineToast.js @@ -1,15 +1,15 @@ import lang from 'i18n-js'; import React from 'react'; import { web3Provider } from '../../handlers/web3'; -import networkTypes from '../../helpers/networkTypes'; import Toast from './Toast'; import { useAccountSettings, useInternetStatus } from '@/hooks'; +import { Network } from '@/networks/types'; const OfflineToast = () => { const isConnected = useInternetStatus(); const { network } = useAccountSettings(); const providerUrl = web3Provider?.connection?.url; - const isMainnet = network === networkTypes.mainnet && !providerUrl?.startsWith('http://'); + const isMainnet = network === Network.mainnet && !providerUrl?.startsWith('http://'); return ; }; diff --git a/src/components/toasts/TestnetToast.js b/src/components/toasts/TestnetToast.js index ec370b3e51b..acbf9afb4ab 100644 --- a/src/components/toasts/TestnetToast.js +++ b/src/components/toasts/TestnetToast.js @@ -1,22 +1,22 @@ import React, { useEffect, useState } from 'react'; -import networkTypes from '../../helpers/networkTypes'; import { Icon } from '../icons'; import { Nbsp, Text } from '../text'; import Toast from './Toast'; -import { isHardHat } from '@/handlers/web3'; import { useInternetStatus } from '@/hooks'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -const TestnetToast = ({ network, web3Provider }) => { +const TestnetToast = ({ chainId }) => { + const { connectedToHardhat } = useConnectedToHardhatStore(); const isConnected = useInternetStatus(); - const providerUrl = web3Provider?.connection?.url; - const { name, colors: networkColors } = getNetworkObj(network); - const [visible, setVisible] = useState(!network === networkTypes.mainnet); + const { name, colors: networkColors } = getNetworkObject({ chainId }); + const [visible, setVisible] = useState(chainId !== ChainId.mainnet); const [networkName, setNetworkName] = useState(name); useEffect(() => { - if (network === networkTypes.mainnet) { - if (isHardHat(providerUrl)) { + if (chainId === ChainId.mainnet) { + if (connectedToHardhat) { setVisible(true); setNetworkName('Hardhat'); } else { @@ -26,7 +26,7 @@ const TestnetToast = ({ network, web3Provider }) => { setVisible(true); setNetworkName(name + (isConnected ? '' : ' (offline)')); } - }, [name, network, providerUrl, isConnected]); + }, [name, isConnected, chainId, connectedToHardhat]); const { colors, isDarkMode } = useTheme(); diff --git a/src/components/token-info/TokenInfoBalanceValue.js b/src/components/token-info/TokenInfoBalanceValue.js index e69519c8811..f9e42c44412 100644 --- a/src/components/token-info/TokenInfoBalanceValue.js +++ b/src/components/token-info/TokenInfoBalanceValue.js @@ -3,7 +3,7 @@ import { RowWithMargins } from '../layout'; import TokenInfoValue from './TokenInfoValue'; import { useColorForAsset } from '@/hooks'; import styled from '@/styled-thing'; -import { ethereumUtils, magicMemo } from '@/utils'; +import { magicMemo } from '@/utils'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; import { View } from 'react-native'; @@ -28,7 +28,7 @@ const TokenInfoBalanceValue = ({ align, asset, ...props }) => { { - const networkObj = getNetworkObj(ethereumUtils.getNetworkFromChainId(Number(chainId))); + const networkObj = getNetworkObject({ chainId }); return { chainId, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, @@ -195,7 +195,7 @@ export default function WalletConnectListItem({ account, chainId, dappIcon, dapp > - + parseInt(chain.split(':')[1]))?.filter(isSupportedChain) ?? []) as ChainId[]; if (!address) { - const e = new RainbowError(`WalletConnectV2ListItem: could not parse address`); + const e = new RainbowError(`[WalletConnectV2ListItem]: could not parse address`); logger.error(e); // defensive, just for types, should never happen diff --git a/src/config/experimental.ts b/src/config/experimental.ts index 77c87bd58a4..b380ec0b0ac 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -28,6 +28,7 @@ export const SWAPS_V2 = 'SwapsV2'; export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; +export const FEATURED_RESULTS = 'Featured Results'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -61,9 +62,10 @@ export const defaultConfig: Record = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: false }, + [SWAPS_V2]: { settings: true, value: !!IS_TEST }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, + [FEATURED_RESULTS]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/debugging/network.js b/src/debugging/network.js index 716faef9a23..989aa3365b2 100644 --- a/src/debugging/network.js +++ b/src/debugging/network.js @@ -1,5 +1,5 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; let internalCounter = 0; @@ -21,10 +21,10 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses }; const emptyLine = () => { - logger.log(''); + logger.debug(''); }; const separator = () => { - logger.log(`----------------------------------------`); + logger.debug(`----------------------------------------`); }; if (showNetworkRequests) { @@ -35,21 +35,21 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.log(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); + logger.debug(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); emptyLine(); if (data) { emptyLine(); - logger.log(' PARAMETERS: '); + logger.debug(' PARAMETERS: '); emptyLine(); try { const dataObj = JSON.parse(data); - logger.log(' {'); + logger.debug(' {'); Object.keys(dataObj).forEach(key => { - logger.log(` ${key} : `, dataObj[key]); + logger.debug(` ${key} : `, dataObj[key]); }); - logger.log(' }'); + logger.debug(' }'); } catch (e) { - logger.log(data); + logger.error(new RainbowError(`Error parsing data: ${e}`), { data }); } } emptyLine(); @@ -72,33 +72,33 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.log(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); + logger.debug(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); emptyLine(); if (timeout && status > 400) { - logger.log(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); + logger.debug(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); } if (status) { - logger.log(` Status: ${status}`); + logger.debug(` Status: ${status}`); } if (time) { - logger.log(` Completed in: ${time / 1000} s`); + logger.debug(` Completed in: ${time / 1000} s`); } if (response) { emptyLine(); - logger.log(' RESPONSE: '); + logger.debug(' RESPONSE: '); emptyLine(); try { const responseObj = JSON.parse(response); - logger.log(' {'); + logger.debug(' {'); Object.keys(responseObj).forEach(key => { - logger.log(` ${key} : `, responseObj[key]); + logger.debug(` ${key} : `, responseObj[key]); }); - logger.log(' }'); + logger.debug(' }'); } catch (e) { - logger.log(response); + logger.error(new RainbowError(`Error parsing response: ${e}`), { data: response }); } } emptyLine(); diff --git a/src/debugging/useDependencyDebugger.ts b/src/debugging/useDependencyDebugger.ts index 4080b3bffc5..b3966e3c1da 100644 --- a/src/debugging/useDependencyDebugger.ts +++ b/src/debugging/useDependencyDebugger.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { logger } from '../utils'; +import { logger } from '@/logger'; const usePrevious = (value: T): T => { const ref = useRef(value); @@ -50,6 +50,6 @@ export const useDependencyDebugger = (dependencies: unknown[] | Record Component instanceof _C) ? View : Component; + const isView = ComponentToUse === View || ComponentToUse === Animated.View; const shadowStylesExist = !!styleProp && @@ -273,7 +278,7 @@ export const Box = forwardRef(function Box( {({ backgroundColor, backgroundStyle }) => ( - + {children} {borderColor || borderWidth ? ( ) : null} - + )} ) : ( - + {children} {borderColor || borderWidth ? ( ) : null} - + ); }) as PolymorphicBox; diff --git a/src/ens-avatar/src/utils.ts b/src/ens-avatar/src/utils.ts index 3e5a7a77b06..1b765ea0c8a 100644 --- a/src/ens-avatar/src/utils.ts +++ b/src/ens-avatar/src/utils.ts @@ -39,7 +39,7 @@ export function isCID(hash: any) { } } -export function parseNFT(uri: string, seperator: string = '/') { +export function parseNFT(uri: string, seperator = '/') { // parse valid nft spec (CAIP-22/CAIP-29) // @see: https://github.com/ChainAgnostic/CAIPs/tree/master/CAIPs try { diff --git a/src/entities/tokens.ts b/src/entities/tokens.ts index 3de13f36507..75fab380118 100644 --- a/src/entities/tokens.ts +++ b/src/entities/tokens.ts @@ -1,8 +1,7 @@ import { EthereumAddress } from '.'; import { Chain } from '@wagmi/chains'; -import { Network } from '@/networks/types'; +import { Network, ChainId } from '@/networks/types'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/__swaps__/types/chains'; export interface ZerionAssetPrice { value: number; @@ -54,7 +53,7 @@ export interface ParsedAddressAsset extends Asset, Partial { - const p = await getProviderForNetwork(network); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + const p = await getProvider({ chainId }); const contractInstance = new Contract(TOKEN_GATE_CHECKER_ADDRESS[network], tokenGateCheckerAbi, p); diff --git a/src/featuresToUnlock/unlockableAppIconCheck.ts b/src/featuresToUnlock/unlockableAppIconCheck.ts index f2c9b72ea29..b2d87d261ae 100644 --- a/src/featuresToUnlock/unlockableAppIconCheck.ts +++ b/src/featuresToUnlock/unlockableAppIconCheck.ts @@ -22,7 +22,7 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w const handled = unlockableAppIconStorage.getBoolean(appIconKey); - logger.debug(`${appIconKey} was handled? ${handled}`); + logger.debug(`[unlockableAppIconCheck]: ${appIconKey} was handled? ${handled}`); if (handled) return false; @@ -32,13 +32,13 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w (Object.keys(appIcon.unlockingNFTs) as TokenGateCheckerNetwork[]).map(async network => { const nfts = appIcon.unlockingNFTs[network]; if (!nfts) return; - logger.debug(`Checking ${appIconKey} on network ${network}`); + logger.debug(`[unlockableAppIconCheck]: Checking ${appIconKey} on network ${network}`); return await checkIfWalletsOwnNft(nfts, network, walletsToCheck); }) ) ).some(result => !!result); - logger.debug(`${appIconKey} check result: ${found}`); + logger.debug(`[unlockableAppIconCheck]: ${appIconKey} check result: ${found}`); // We open the sheet with a setTimeout 1 sec later to make sure we can return first // so we can abort early if we're showing a sheet to prevent 2+ sheets showing at the same time @@ -46,14 +46,14 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w setTimeout(() => { if (found) { unlockableAppIconStorage.set(appIconKey, true); - logger.debug(`Feature check ${appIconKey} set to true. Wont show up anymore!`); + logger.debug(`[unlockableAppIconCheck]: Feature check ${appIconKey} set to true. Wont show up anymore!`); Navigation.handleAction(Routes.APP_ICON_UNLOCK_SHEET, { appIconKey }); return true; } }, 1000); return found; } catch (e) { - logger.error(new RainbowError('UnlockableAppIconCheck blew up'), { e }); + logger.error(new RainbowError('[unlockableAppIconCheck]: UnlockableAppIconCheck blew up'), { e }); } return false; }; diff --git a/src/graphql/index.ts b/src/graphql/index.ts index b3ababa3bbd..2675fa9a603 100644 --- a/src/graphql/index.ts +++ b/src/graphql/index.ts @@ -15,7 +15,25 @@ export const ensClient = getEnsSdk(getFetchRequester(config.ens)); export const metadataClient = getMetadataSdk(metadataRequester); export const metadataPOSTClient = getMetadataSdk(getFetchRequester(config.metadataPOST)); export const arcClient = IS_PROD ? getArcSdk(getFetchRequester(config.arc)) : getArcDevSdk(getFetchRequester(config.arcDev)); - +export const arcPOSTClient = IS_PROD + ? getArcSdk( + getFetchRequester({ + ...config.arc, + schema: { + ...config.arc.schema, + method: 'POST', + }, + }) + ) + : getArcDevSdk( + getFetchRequester({ + ...config.arcDev, + schema: { + ...config.arcDev.schema, + method: 'POST', + }, + }) + ); export const requestMetadata = (q: string, options?: Pick) => metadataRequester( gql` diff --git a/src/graphql/queries/arc.graphql b/src/graphql/queries/arc.graphql index 4b8d55ebb58..69648355a7c 100644 --- a/src/graphql/queries/arc.graphql +++ b/src/graphql/queries/arc.graphql @@ -473,3 +473,43 @@ query getNFTs($walletAddress: String!, $sortBy: NFTCollectionSortCriterion, $sor } } } + +query getFeaturedResults($placementId: String!, $walletAddress: String!, $country: String, $limit: Int) { + featuredResults(placementId: $placementId, walletAddress: $walletAddress, country: $country, limit: $limit) { + items { + id + type + impressionId + advertiserId + placementSlug + title + context { + text + } + description + imageUrl + category + imageAltText + ctas { + title + href + } + } + } +} + +mutation trackFeaturedResult( + $type: TrackFeaturedResultType! + $placementId: String! + $impressionId: String! + $featuredResultCreativeId: String! +) { + trackFeaturedResult( + type: $type + placementId: $placementId + impressionId: $impressionId + featuredResultCreativeId: $featuredResultCreativeId + ) { + message + } +} diff --git a/src/graphql/queries/metadata.graphql b/src/graphql/queries/metadata.graphql index e398cbdd2ea..0b38946e07f 100644 --- a/src/graphql/queries/metadata.graphql +++ b/src/graphql/queries/metadata.graphql @@ -571,8 +571,17 @@ query tokenMetadata($address: String!, $chainId: Int!, $currency: String) { } } -query priceChart($chainId: Int!, $address: String!, $day: Boolean!, $hour: Boolean!, $week: Boolean!, $month: Boolean!, $year: Boolean!) { - token(chainID: $chainId, address: $address) { +query priceChart( + $chainId: Int! + $address: String! + $currency: String + $day: Boolean! + $hour: Boolean! + $week: Boolean! + $month: Boolean! + $year: Boolean! +) { + token(chainID: $chainId, address: $address, currency: $currency) { priceCharts { day @include(if: $day) { points diff --git a/src/handlers/LedgerSigner.ts b/src/handlers/LedgerSigner.ts index 7e86770d4d4..af5a511c81d 100644 --- a/src/handlers/LedgerSigner.ts +++ b/src/handlers/LedgerSigner.ts @@ -57,14 +57,14 @@ export class LedgerSigner extends Signer { return new Promise(async (resolve, reject) => { if (timeout && timeout > 0) { setTimeout(() => { - logger.debug('Ledger: Signer timeout', {}, logger.DebugContext.ledger); + logger.debug('[LedgerSigner]: Signer timeout', {}, logger.DebugContext.ledger); return reject(new RainbowError('Ledger: Signer timeout')); }, timeout); } const eth = await this._eth; if (!eth) { - logger.debug('Ledger: Eth app not open', {}, logger.DebugContext.ledger); + logger.debug('[LedgerSigner]: Eth app not open', {}, logger.DebugContext.ledger); return reject(new Error('Ledger: Eth app not open')); } @@ -74,7 +74,7 @@ export class LedgerSigner extends Signer { const result = await callback(eth); return resolve(result); } catch (error: any) { - logger.error(new RainbowError('Ledger: Transport error'), error); + logger.error(new RainbowError('[LedgerSigner]: Transport error'), error); // blind signing isnt enabled if (error.name === 'EthAppPleaseEnableContractData') diff --git a/src/handlers/__mocks__/web3.ts b/src/handlers/__mocks__/web3.ts index dbd6cbf1714..4b6ff17ecf0 100644 --- a/src/handlers/__mocks__/web3.ts +++ b/src/handlers/__mocks__/web3.ts @@ -1,3 +1,3 @@ import { jest } from '@jest/globals'; -export const getProviderForNetwork = jest.fn(); +export const getProvider = jest.fn(); diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 48044fac0a0..164ae1c436a 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -2,22 +2,16 @@ import { Contract } from '@ethersproject/contracts'; import { erc20ABI } from '@/references'; import { convertAmountToBalanceDisplay, convertRawAmountToDecimalFormat } from '@/helpers/utilities'; -import { getNetworkObj, getNetworkObject } from '@/networks'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; -import { ethereumUtils } from '@/utils'; - -export function isL2Asset(network: Network) { - return getNetworkObj(network).networkType === 'layer2'; -} +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; export function isNativeAsset(address: string, chainId: ChainId) { return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } -export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, network: any, provider: any) { +export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: any, provider: any) { // Check if it's the native chain asset - if (isNativeAsset(address, ethereumUtils.getChainIdFromNetwork(network))) { + if (isNativeAsset(address, chainId)) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); } return getOnchainTokenBalance({ address, decimals, symbol }, userAddress, provider); diff --git a/src/handlers/authentication.ts b/src/handlers/authentication.ts index df12fa72059..75ddcb3f0ae 100644 --- a/src/handlers/authentication.ts +++ b/src/handlers/authentication.ts @@ -17,7 +17,9 @@ export async function getExistingPIN(): Promise { return userPIN as string; } } catch (error) { - logger.error(new RainbowError('Error while trying to get existing PIN code.'), { message: (error as Error).message }); + logger.error(new RainbowError('[getExistingPIN]: Error while trying to get existing PIN code.'), { + message: (error as Error).message, + }); } return; } @@ -39,7 +41,7 @@ export async function savePIN(pin: string | undefined) { await keychain.saveString(pinKey, encryptedPin); } } catch (error) { - logger.error(new RainbowError('savePin error'), { + logger.error(new RainbowError('[savePIN]: savePin error'), { message: (error as Error).message, }); } diff --git a/src/handlers/cloudBackup.ts b/src/handlers/cloudBackup.ts index 0f500a4bb46..1eb3f5be795 100644 --- a/src/handlers/cloudBackup.ts +++ b/src/handlers/cloudBackup.ts @@ -115,16 +115,14 @@ export async function encryptAndSaveDataToCloud(data: any, password: any, filena ); if (!exists) { - logger.info('Backup doesnt exist after completion'); const error = new Error(CLOUD_BACKUP_ERRORS.INTEGRITY_CHECK_FAILED); - logger.error(new RainbowError(error.message)); throw error; } await RNFS.unlink(path); return filename; } catch (e: any) { - logger.error(new RainbowError('Error during encryptAndSaveDataToCloud'), { + logger.error(new RainbowError('[cloudBackup]: Error during encryptAndSaveDataToCloud'), { message: e.message, }); throw new Error(CLOUD_BACKUP_ERRORS.GENERAL_ERROR); @@ -173,7 +171,7 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n } if (!document) { - logger.error(new RainbowError('No backup found with that name!'), { + logger.error(new RainbowError('[cloudBackup]: No backup found with that name!'), { filename, }); const error = new Error(CLOUD_BACKUP_ERRORS.SPECIFIC_BACKUP_NOT_FOUND); @@ -186,19 +184,19 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n const encryptedData = ios ? await getICloudDocument(filename) : await getGoogleDriveDocument(document.id); if (encryptedData) { - logger.info('Got cloud document ', { filename }); + logger.debug(`[cloudBackup]: Got cloud document ${filename}`); const backedUpDataStringified = await encryptor.decrypt(backupPassword, encryptedData); if (backedUpDataStringified) { const backedUpData = JSON.parse(backedUpDataStringified); return backedUpData; } else { - logger.error(new RainbowError('We couldnt decrypt the data')); + logger.error(new RainbowError('[cloudBackup]: We couldnt decrypt the data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA); throw error; } } - logger.error(new RainbowError('We couldnt get the encrypted data')); + logger.error(new RainbowError('[cloudBackup]: We couldnt get the encrypted data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_GETTING_ENCRYPTED_DATA); throw error; } diff --git a/src/handlers/cloudinary.ts b/src/handlers/cloudinary.ts index 244a4b2575b..5a3521a7d52 100644 --- a/src/handlers/cloudinary.ts +++ b/src/handlers/cloudinary.ts @@ -13,7 +13,7 @@ type CloudinaryConfig = { const PixelRatios = [1, 1.5, 2, 2.625, 2.75, 3, 3.5]; // popular ratios. const IconsSizes = [40, 36]; // Remove 36 with TopMover const allowedIconSizes = PixelRatios.reduce((acc, ratio) => { - for (let size of IconsSizes) { + for (const size of IconsSizes) { acc.push(size * ratio); } return acc; diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index 7c2f95be5fd..c8914e691bd 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -28,7 +28,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; export default async function handleDeeplink(url: string, initialRoute: any = null) { if (!url) { - logger.warn(`handleDeeplink: No url provided`); + logger.warn(`[handleDeeplink]: No url provided`); return; } @@ -36,13 +36,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * We need to wait till the wallet is ready to handle any deeplink */ while (!store.getState().appState.walletReady) { - logger.info(`handleDeeplink: Waiting for wallet to be ready`); + logger.debug(`[handleDeeplink]: Waiting for wallet to be ready`); await delay(50); } const { protocol, host, pathname, query } = new URL(url, true); - logger.info(`handleDeeplink: handling url`, { + logger.debug(`[handleDeeplink]: handling url`, { url, protocol, host, @@ -54,13 +54,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * Handling send deep links */ - logger.info(`handleDeeplink: ethereum:// protocol`); + logger.debug(`[handleDeeplink]: ethereum:// protocol`); ethereumUtils.parseEthereumUrl(url); } else if (protocol === 'https:' || protocol === 'rainbow:') { /** * Any native iOS deep link OR universal links via HTTPS */ - logger.info(`handleDeeplink: https:// or rainbow:// protocol`); + logger.debug(`[handleDeeplink]: https:// or rainbow:// protocol`); /** * The first path following the host (universal link) or protocol @@ -75,7 +75,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * tap "Rainbow" in Web3Modal and it hits this handler */ case 'wc': { - logger.info(`handleDeeplink: wc`); + logger.debug(`[handleDeeplink]: wc`); handleWalletConnect(query.uri, query.connector); break; } @@ -84,7 +84,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Links from website to an individual token */ case 'token': { - logger.info(`handleDeeplink: token`); + logger.debug(`[handleDeeplink]: token`); const { addr } = query; const address = (addr as string)?.toLowerCase() ?? ''; @@ -120,12 +120,12 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * should contain metadata about the transaction, if we have it. */ case 'f2c': { - logger.info(`handleDeeplink: f2c`); + logger.debug(`[handleDeeplink]: f2c`); const { provider, sessionId } = query; if (!provider || !sessionId) { - logger.warn('Received FWC deeplink with invalid params', { + logger.warn(`[handleDeeplink]: Received FWC deeplink with invalid params`, { url, query, }); @@ -166,11 +166,12 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Ratio's onramp SDK. */ case 'plaid': { - logger.log('handleDeeplink: handling Plaid redirect', { url }); + logger.debug(`[handleDeeplink]: handling Plaid redirect`, { url }); break; } case 'poap': { + logger.debug(`[handleDeeplink]: handling POAP`, { url }); const secretWordOrHash = pathname?.split('/')?.[1]; await getPoapAndOpenSheetWithSecretWord(secretWordOrHash, false); await getPoapAndOpenSheetWithQRHash(secretWordOrHash, false); @@ -178,6 +179,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } case 'points': { + logger.debug(`[handleDeeplink]: handling points`, { url }); const referralCode = query?.ref; if (referralCode) { analyticsV2.track(analyticsV2.event.pointsReferralCodeDeeplinkOpened); @@ -191,6 +193,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu case 'dapp': { const { url } = query; + logger.debug(`[handleDeeplink]: handling dapp`, { url }); if (url) { Navigation.handleAction(Routes.DAPP_BROWSER_SCREEN, { url }); } @@ -198,6 +201,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } default: { + logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; /** * This handles ENS profile links on mobile i.e. @@ -215,7 +219,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu fromRoute: 'Deeplink', }); } else { - logger.warn(`handleDeeplink: invalid address or ENS provided`, { + logger.warn(`[handleDeeplink]: invalid address or ENS provided`, { url, protocol, host, @@ -228,7 +232,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * This is a catch-all for any other deep links that we don't handle */ - logger.warn(`handleDeeplink: invalid or unknown deeplink`, { + logger.warn(`[handleDeeplink]: invalid or unknown deeplink`, { url, protocol, host, @@ -240,7 +244,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } // Android uses normal deeplinks } else if (protocol === 'wc:') { - logger.info(`handleDeeplink: wc:// protocol`); + logger.debug(`[handleDeeplink]: wc:// protocol`); handleWalletConnect(url, query.connector); } } @@ -267,21 +271,21 @@ const walletConnectURICache = new Set(); function handleWalletConnect(uri?: string, connector?: string) { if (!uri) { - logger.debug(`handleWalletConnect: skipping uri empty`, {}); + logger.debug(`[handleWalletConnect]: skipping uri empty`); return; } const cacheKey = JSON.stringify({ uri }); if (walletConnectURICache.has(cacheKey)) { - logger.debug(`handleWalletConnect: skipping duplicate event`, {}); + logger.debug(`[handleWalletConnect]: skipping duplicate event`); return; } const { query } = new URL(uri); const parsedUri = uri ? parseUri(uri) : null; - logger.debug(`handleWalletConnect: handling event`, { + logger.debug(`[handleWalletConnect]: handling event`, { uri, query, parsedUri, @@ -295,7 +299,7 @@ function handleWalletConnect(uri?: string, connector?: string) { store.dispatch(walletConnectSetPendingRedirect()); store.dispatch( walletConnectOnSessionRequest(uri, connector, (status: any, dappScheme: any) => { - logger.debug(`walletConnectOnSessionRequest callback`, { + logger.debug(`[walletConnectOnSessionRequest] callback`, { status, dappScheme, }); @@ -304,12 +308,12 @@ function handleWalletConnect(uri?: string, connector?: string) { }) ); } else if (parsedUri.version === 2) { - logger.debug(`handleWalletConnect: handling v2`, { uri }); + logger.debug(`[handleWalletConnect]: handling v2`, { uri }); setHasPendingDeeplinkPendingRedirect(true); pairWalletConnect({ uri, connector }); } } else { - logger.debug(`handleWalletConnect: handling fallback`, { uri }); + logger.debug(`[handleWalletConnect]: handling fallback`, { uri }); // This is when we get focused by WC due to a signing request // Don't add this URI to cache setHasPendingDeeplinkPendingRedirect(true); diff --git a/src/handlers/dispersion.ts b/src/handlers/dispersion.ts index c2281dadf71..f2f1686f794 100644 --- a/src/handlers/dispersion.ts +++ b/src/handlers/dispersion.ts @@ -1,7 +1,7 @@ import { RainbowFetchClient } from '../rainbow-fetch'; import { EthereumAddress, IndexToken, RainbowToken } from '@/entities'; import UniswapAssetsCache from '@/utils/uniswapAssetsCache'; -import { logger } from '@/logger'; +import { logger, RainbowError } from '@/logger'; const dispersionApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -25,7 +25,7 @@ export const getUniswapV2Tokens = async (addresses: EthereumAddress[]): Promise< return res?.data?.tokens ?? null; } } catch (e: any) { - logger.warn(`dispersionApi: error fetching uniswap v2 tokens`, { + logger.error(new RainbowError(`[getUniswapV2Tokens]: error fetching uniswap v2 tokens`), { message: e.message, }); } @@ -37,7 +37,7 @@ export const getTrendingAddresses = async (): Promise const res = await dispersionApi.get('/dispersion/v1/trending'); return res?.data?.data?.trending ?? null; } catch (e: any) { - logger.warn(`dispersionApi: error fetching trending addresses`, { + logger.error(new RainbowError(`[getTrendingAddresses]: error fetching trending addresses`), { message: e.message, }); return null; @@ -49,7 +49,7 @@ export const getAdditionalAssetData = async (address: EthereumAddress, chainId = const res = await dispersionApi.get(`/dispersion/v1/expanded/${chainId}/${address}`); return res?.data?.data ?? null; } catch (e: any) { - logger.warn(`dispersionApi: error fetching additional asset data`, { + logger.error(new RainbowError(`[getAdditionalAssetData]: error fetching additional asset data`), { message: e.message, }); return null; diff --git a/src/handlers/ens.ts b/src/handlers/ens.ts index e29076a878f..aa039d13a05 100644 --- a/src/handlers/ens.ts +++ b/src/handlers/ens.ts @@ -1,33 +1,30 @@ import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder'; import { getAddress } from '@ethersproject/address'; import { Resolver } from '@ethersproject/providers'; -import { captureException } from '@sentry/react-native'; import { Duration, sub } from 'date-fns'; import { isValidAddress, isZeroAddress } from 'ethereumjs-util'; import { BigNumber } from '@ethersproject/bignumber'; import { debounce, isEmpty, sortBy } from 'lodash'; -import { fetchENSAvatar, prefetchENSAvatar } from '../hooks/useENSAvatar'; +import { fetchENSAvatar } from '../hooks/useENSAvatar'; import { prefetchENSCover } from '../hooks/useENSCover'; import { prefetchENSRecords } from '../hooks/useENSRecords'; import { ENSActionParameters, ENSRapActionType } from '@/raps/common'; import { getENSData, getNameFromLabelhash, saveENSData } from './localstorage/ens'; -import { estimateGasWithPadding, getProviderForNetwork, TokenStandard } from './web3'; +import { estimateGasWithPadding, getProvider, TokenStandard } from './web3'; import { ENSRegistrationRecords, Records, UniqueAsset } from '@/entities'; -import { Network } from '@/helpers'; import { ENS_DOMAIN, ENS_RECORDS, ENSRegistrationTransactionType, generateSalt, getENSExecutionDetails, getNameOwner } from '@/helpers/ens'; import { add } from '@/helpers/utilities'; import { ImgixImage } from '@/components/images'; import { ENS_NFT_CONTRACT_ADDRESS, ethUnits } from '@/references'; -import { labelhash, logger, profileUtils } from '@/utils'; +import { labelhash, profileUtils } from '@/utils'; import { AvatarResolver } from '@/ens-avatar/src'; import { ensClient } from '@/graphql'; import { prefetchFirstTransactionTimestamp } from '@/resources/transactions/firstTransactionTimestampQuery'; import { prefetchENSAddress } from '@/resources/ens/ensAddressQuery'; -import { ENS_MARQUEE_QUERY_KEY } from '@/resources/metadata/ensMarqueeQuery'; -import { queryClient } from '@/react-query'; -import { EnsMarqueeAccount } from '@/graphql/__generated__/metadata'; import { MimeType, handleNFTImages } from '@/utils/handleNFTImages'; import store from '@/redux/store'; +import { logger, RainbowError } from '@/logger'; +import { ChainId, Network } from '@/networks/types'; const DUMMY_RECORDS = { description: 'description', @@ -54,6 +51,7 @@ const buildEnsToken = ({ }); return { acquisition_date: undefined, + chainId: ChainId.mainnet, animation_url: null, asset_contract: { address: contractAddress, @@ -137,8 +135,9 @@ export const fetchMetadata = async ({ const image_url = `https://metadata.ens.domains/mainnet/${contractAddress}/${tokenId}/image`; return { image_url, name }; } catch (error) { - logger.sentry('ENS: Error getting ENS metadata', error); - captureException(new Error('ENS: Error getting ENS metadata')); + logger.error(new RainbowError(`[ENS]: fetchMetadata failed`), { + error, + }); throw error; } }; @@ -175,8 +174,9 @@ export const fetchEnsTokens = async ({ .filter((token: TToken | null | undefined): token is TToken => !!token) || [] ); } catch (error) { - logger.sentry('ENS: Error getting ENS unique tokens', error); - captureException(new Error('ENS: Error getting ENS unique tokens')); + logger.error(new RainbowError(`[ENS]: fetchEnsTokens failed`), { + error, + }); return []; } }; @@ -322,7 +322,7 @@ export const fetchAccountDomains = async (address: string) => { export const fetchImage = async (imageType: 'avatar' | 'header', ensName: string) => { let imageUrl; - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); try { const avatarResolver = new AvatarResolver(provider); imageUrl = await avatarResolver.getImage(ensName, { @@ -347,7 +347,7 @@ export const fetchRecords = async (ensName: string, { supportedOnly = true }: { const data = response.domains[0] || {}; const rawRecordKeys = data.resolver?.texts || []; - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const supportedRecords = Object.values(ENS_RECORDS); const recordKeys = (rawRecordKeys as ENS_RECORDS[]).filter(key => (supportedOnly ? supportedRecords.includes(key) : true)); @@ -369,7 +369,7 @@ export const fetchCoinAddresses = async ( const response = await ensClient.getCoinTypesByName({ name: ensName }); const data = response.domains[0] || {}; const supportedRecords = Object.values(ENS_RECORDS); - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const rawCoinTypes: number[] = data.resolver?.coinTypes || []; const rawCoinTypesNames: string[] = rawCoinTypes.map(type => formatsByCoinType[type].name); @@ -402,7 +402,7 @@ export const fetchCoinAddresses = async ( }; export const fetchContenthash = async (ensName: string) => { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); const contenthash = await resolver?.getContentHash(); return contenthash; @@ -449,7 +449,7 @@ export const fetchRegistration = async (ensName: string) => { }; export const fetchPrimary = async (ensName: string) => { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const address = await provider.resolveName(ensName); return { address, @@ -888,7 +888,7 @@ export const getRapActionTypeForTxType = (txType: ENSRegistrationTransactionType export const fetchReverseRecord = async (address: string) => { try { const checksumAddress = getAddress(address); - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const reverseRecord = await provider.lookupAddress(checksumAddress); return reverseRecord ?? ''; } catch (e) { @@ -898,7 +898,7 @@ export const fetchReverseRecord = async (address: string) => { export const fetchResolver = async (ensName: string) => { try { - const provider = await getProviderForNetwork(); + const provider = await getProvider({ chainId: ChainId.mainnet }); const resolver = await provider.getResolver(ensName); return resolver ?? ({} as Resolver); } catch (e) { diff --git a/src/handlers/gasFees.ts b/src/handlers/gasFees.ts index aeffb8cddac..6d85e0d1b59 100644 --- a/src/handlers/gasFees.ts +++ b/src/handlers/gasFees.ts @@ -1,5 +1,5 @@ -import { Network } from '@/helpers'; import { RainbowFetchClient } from '../rainbow-fetch'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const rainbowMeteorologyApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -10,4 +10,5 @@ const rainbowMeteorologyApi = new RainbowFetchClient({ timeout: 30000, // 30 secs }); -export const rainbowMeteorologyGetData = (network: Network) => rainbowMeteorologyApi.get(`/meteorology/v1/gas/${network}`, {}); +export const rainbowMeteorologyGetData = (chainId: ChainId) => + rainbowMeteorologyApi.get(`/meteorology/v1/gas/${chainIdToNameMapping[chainId]}`, {}); diff --git a/src/handlers/imgix.ts b/src/handlers/imgix.ts index 5df0d639337..d07591839a8 100644 --- a/src/handlers/imgix.ts +++ b/src/handlers/imgix.ts @@ -6,7 +6,6 @@ import { Source } from 'react-native-fast-image'; import parse from 'url-parse'; import { isCloudinaryStorageIconLink, signCloudinaryIconUrl } from '@/handlers/cloudinary'; import { logger, RainbowError } from '@/logger'; -import { GOOGLE_USER_CONTENT_URL } from '@/utils/getFullResUrl'; const shouldCreateImgixClient = (): ImgixClient | null => { if (typeof domain === 'string' && !!domain.length && typeof secureURLToken === 'string' && !!secureURLToken.length) { @@ -32,7 +31,7 @@ const staticImgixClient = shouldCreateImgixClient(); // This might be conditional based upon either the runtime // hardware or the number of unique tokens a user may have. const capacity = 1024; -export let staticSignatureLRU: LRUCache = new LRUCache(capacity); +export const staticSignatureLRU: LRUCache = new LRUCache(capacity); interface ImgOptions { w?: number; @@ -117,11 +116,7 @@ const isPossibleToSignUri = (externalImageUri: string | undefined): boolean => { return false; }; -export const maybeSignUri = ( - externalImageUri: string | undefined, - options?: ImgOptions, - skipCaching: boolean = false -): string | undefined => { +export const maybeSignUri = (externalImageUri: string | undefined, options?: ImgOptions, skipCaching = false): string | undefined => { // If the image has already been signed, return this quickly. const signature = `${externalImageUri}-${options?.w}-${options?.fm}`; if (typeof externalImageUri === 'string' && staticSignatureLRU.has(signature as string) && !skipCaching) { diff --git a/src/handlers/localstorage/common.ts b/src/handlers/localstorage/common.ts index 12aa8a41735..591b9e47d36 100644 --- a/src/handlers/localstorage/common.ts +++ b/src/handlers/localstorage/common.ts @@ -1,4 +1,4 @@ -/*global storage*/ +/* global storage*/ import { legacy } from '@/storage/legacy'; import { logger, RainbowError } from '@/logger'; @@ -17,7 +17,7 @@ export const saveLocal = (key = '', data = {}) => { try { legacy.set([key], data); } catch (error) { - logger.error(new RainbowError('Legacy Storage: saveLocal error')); + logger.error(new RainbowError('[localstorage/common]: saveLocal error')); } }; @@ -51,7 +51,7 @@ export const deprecatedSaveLocal = async (key = '', data = {}, version = default key, }); } catch (error) { - logger.error(new RainbowError('Storage: deprecatedSaveLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedSaveLocal error')); } }; @@ -90,7 +90,7 @@ export const deprecatedGetLocal = async (key = '', version = defaultVersion) => case 'ExpiredError': break; default: - logger.error(new RainbowError('Storage: deprecatedGetLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedGetLocal error')); } return null; @@ -109,7 +109,7 @@ export const deprecatedRemoveLocal = (key = '') => { // @ts-expect-error ts-migrate(2552) FIXME: Cannot find name 'storage'. Did you mean 'Storage'... Remove this comment to see the full error message storage.remove({ key }); } catch (error) { - logger.error(new RainbowError('Storage: deprecatedRemoveLocal error')); + logger.error(new RainbowError('[localstorage/common]: deprecatedRemoveLocal error')); } }; diff --git a/src/handlers/localstorage/globalSettings.ts b/src/handlers/localstorage/globalSettings.ts index a9e3769bfbf..c54370a7cd1 100644 --- a/src/handlers/localstorage/globalSettings.ts +++ b/src/handlers/localstorage/globalSettings.ts @@ -1,6 +1,6 @@ +import { ChainId } from '@/networks/types'; import { getGlobal, saveGlobal } from './common'; import { NativeCurrencyKeys } from '@/entities'; -import networkTypes from '@/helpers/networkTypes'; import { Language } from '@/languages'; export const IMAGE_METADATA = 'imageMetadata'; @@ -8,7 +8,7 @@ const KEYBOARD_HEIGHT = 'keyboardHeight'; const APP_ICON = 'appIcon'; const LANGUAGE = 'language'; const NATIVE_CURRENCY = 'nativeCurrency'; -const NETWORK = 'network'; +const CHAIN_ID = 'chainId'; const KEYCHAIN_INTEGRITY_STATE = 'keychainIntegrityState'; const AUTH_TIMELOCK = 'authTimelock'; const PIN_AUTH_ATTEMPTS_LEFT = 'pinAuthAttemptsLeft'; @@ -32,9 +32,9 @@ export const getLanguage = () => getGlobal(LANGUAGE, Language.EN_US); export const saveLanguage = (language: any) => saveGlobal(LANGUAGE, language); -export const getNetwork = () => getGlobal(NETWORK, networkTypes.mainnet); +export const getChainId = () => getGlobal(CHAIN_ID, ChainId.mainnet); -export const saveNetwork = (network: any) => saveGlobal(NETWORK, network); +export const saveChainId = (chainId: ChainId) => saveGlobal(CHAIN_ID, chainId); export const getKeyboardHeight = () => getGlobal(KEYBOARD_HEIGHT, null); diff --git a/src/handlers/localstorage/removeWallet.ts b/src/handlers/localstorage/removeWallet.ts index fe450683a61..1a2934127be 100644 --- a/src/handlers/localstorage/removeWallet.ts +++ b/src/handlers/localstorage/removeWallet.ts @@ -1,23 +1,25 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { keys } from 'lodash'; -import NetworkTypes from '../../helpers/networkTypes'; import { accountLocalKeys } from './accountLocal'; import { getKey } from './common'; import { walletConnectAccountLocalKeys } from './walletconnectRequests'; import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet } from '@/notifications/settings'; +import { Network } from '@/networks/types'; export const removeWalletData = async (accountAddress: any) => { - logger.debug('[remove wallet]', { accountAddress }); + logger.debug('[localstorage/removeWallet]: removing wallet data', { accountAddress }); const allPrefixes = accountLocalKeys.concat(walletConnectAccountLocalKeys); - logger.debug('[remove wallet] - all prefixes', { allPrefixes }); - const networks = keys(NetworkTypes); + logger.debug('[localstorage/removeWallet]: all prefixes', { allPrefixes }); + const networks = keys(Network); const allKeysWithNetworks = allPrefixes.map(prefix => networks.map(network => getKey(prefix, accountAddress, network))); const allKeys = allKeysWithNetworks.flat(); try { await AsyncStorage.multiRemove(allKeys); } catch (error) { - logger.error(new RainbowError('Error removing wallet data from storage')); + logger.error(new RainbowError('[localstorage/removeWallet]: Error removing wallet data from storage'), { + error, + }); } removeNotificationSettingsForWallet(accountAddress); }; diff --git a/src/handlers/localstorage/theme.ts b/src/handlers/localstorage/theme.ts index 32011a99809..a18f805da04 100644 --- a/src/handlers/localstorage/theme.ts +++ b/src/handlers/localstorage/theme.ts @@ -2,7 +2,7 @@ import { IS_ANDROID } from '@/env'; import { getGlobal, saveGlobal } from './common'; import { NativeModules } from 'react-native'; import { colors } from '@/styles'; -import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; +import { isUsingButtonNavigation } from '@/utils/deviceUtils'; import { Themes, ThemesType } from '@/theme'; const { NavigationBar } = NativeModules; diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index 55a9dd87b5a..d68138be447 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -15,13 +15,14 @@ import { Contract } from '@ethersproject/contracts'; import { MaxUint256 } from '@ethersproject/constants'; import { IS_TESTING } from 'react-native-dotenv'; import { Token } from '../entities/tokens'; -import { estimateGasWithPadding, getProviderForNetwork, toHexNoLeadingZeros } from './web3'; +import { estimateGasWithPadding, getProvider, toHexNoLeadingZeros } from './web3'; import { getRemoteConfig } from '@/model/remoteConfig'; import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; import { erc20ABI, ethUnits } from '@/references'; -import { ethereumUtils, logger } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ethereumUtils } from '@/utils'; +import { logger, RainbowError } from '@/logger'; +import { ChainId } from '@/networks/types'; export enum Field { INPUT = 'INPUT', @@ -137,7 +138,9 @@ export const getStateDiff = async (provider: StaticJsonRpcProvider, tradeDetails return formattedStateDiff; } } - logger.log('Couldnt get stateDiff...', JSON.stringify(trace, null, 2)); + logger.debug('[swap]: Couldnt get stateDiff...', { + trace, + }); }; export const getSwapGasLimitWithFakeApproval = async ( @@ -168,20 +171,26 @@ export const getSwapGasLimitWithFakeApproval = async ( try { await provider.send('eth_call', [...callParams, stateDiff]); - logger.log(`Estimate worked with gasLimit: `, gas); + logger.debug('[swap]: Estimate worked with gasLimit', { + gas, + }); return true; } catch (e) { - logger.log(`Estimate failed with gasLimit ${gas}. Trying with different amounts...`); + logger.debug('[swap]: Estimate failed with gasLimit', { + gas, + }); return false; } }); if (gasLimit && gasLimit >= ethUnits.basic_swap) { return gasLimit; } else { - logger.log('Could not find a gas estimate'); + logger.debug('[swap]: Could not find a gas estimate'); } } catch (e) { - logger.log(`Blew up trying to get state diff. Falling back to defaults`, e); + logger.error(new RainbowError('[swap]: Blew up trying to get state diff. Falling back to defaults'), { + error: e, + }); } return getDefaultGasLimitForTrade(tradeDetails, chainId); }; @@ -196,7 +205,7 @@ export const isUnwrapNative = ({ buyTokenAddress: string; }) => { return ( - sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() && + sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() && buyTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() ); }; @@ -212,7 +221,7 @@ export const isWrapNative = ({ }) => { return ( sellTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() && - buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() + buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() ); }; @@ -225,8 +234,7 @@ export const estimateSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: Quote | null; }): Promise => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = await getProviderForNetwork(network); + const provider = await getProvider({ chainId }); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } @@ -271,10 +279,14 @@ export const estimateSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); + logger.debug('[swap]: Got gasLimitWithFakeApproval!', { + gasLimitWithFakeApproval, + }); return gasLimitWithFakeApproval; } catch (e) { - logger.debug('Error estimating swap gas limit with approval', e); + logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { + error: e, + }); } } @@ -298,8 +310,7 @@ export const estimateCrosschainSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: CrosschainQuote; }): Promise => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = await getProviderForNetwork(network); + const provider = await getProvider({ chainId }); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } @@ -308,10 +319,14 @@ export const estimateCrosschainSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); + logger.debug('[swap]: Got gasLimitWithFakeApproval!', { + gasLimitWithFakeApproval, + }); return gasLimitWithFakeApproval; } catch (e) { - logger.debug('Error estimating swap gas limit with approval', e); + logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { + error: e, + }); } } diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index 023e7100617..00a54669b06 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -52,7 +52,7 @@ export const swapSearch = async (searchParams: { const tokenSearch = await tokenSearchApi.get(url); return { ...tokenSearch.data?.data, chainId: searchParams.chainId }; } catch (e: any) { - logger.error(new RainbowError(`An error occurred while searching for query`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -93,17 +93,18 @@ export const tokenSearch = async (searchParams: { return tokenSearch.data.data.map(token => { const networkKeys = Object.keys(token.networks); - const network = ethereumUtils.getNetworkFromChainId(Number(networkKeys[0])); + const chainId = Number(networkKeys[0]); + const network = ethereumUtils.getNetworkFromChainId(chainId); return { ...token, - address: token.networks['1']?.address || token.networks[Number(networkKeys[0])]?.address, + chainId, + address: token.networks['1']?.address || token.networks[chainId]?.address, network, - chainId: searchParams.chainId, mainnet_address: token.networks['1']?.address, }; }); } catch (e: any) { - logger.error(new RainbowError(`An error occurred while searching for query`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -121,7 +122,7 @@ export const walletFilter = async (params: { addresses: EthereumAddress[]; fromC }); return filteredAddresses?.data?.data || []; } catch (e: any) { - logger.error(new RainbowError(`An error occurred while filter wallet addresses`), { + logger.error(new RainbowError(`[tokenSearch]: An error occurred while filter wallet addresses`), { toChainId: params.toChainId, fromChainId: params.fromChainId, message: e.message, diff --git a/src/handlers/transactions.ts b/src/handlers/transactions.ts index 949e03a5990..f2cf11f164e 100644 --- a/src/handlers/transactions.ts +++ b/src/handlers/transactions.ts @@ -128,12 +128,12 @@ export const getTransactionSocketStatus = async (pendingTransaction: RainbowTran pending = false; } } else if (socketResponse.error) { - logger.warn('getTransactionSocketStatus transaction check failed', socketResponse.error); + logger.warn('[getTransactionSocketStatus]: transaction check failed', socketResponse.error); status = TransactionStatus.failed; pending = false; } } catch (e) { - logger.error(new RainbowError('getTransactionSocketStatus transaction check caught')); + logger.error(new RainbowError('[getTransactionSocketStatus]: transaction check caught')); if (IS_TEST) { status = swap?.isBridge ? TransactionStatus.bridged : TransactionStatus.swapped; pending = false; diff --git a/src/handlers/walletReadyEvents.ts b/src/handlers/walletReadyEvents.ts index 9f02a762c36..1cfa62be144 100644 --- a/src/handlers/walletReadyEvents.ts +++ b/src/handlers/walletReadyEvents.ts @@ -53,10 +53,10 @@ export const runWalletBackupStatusChecks = () => { if (!rainbowWalletsNotBackedUp.length) return; - logger.debug('there is a rainbow wallet not backed up'); + logger.debug('[walletReadyEvents]: there is a rainbow wallet not backed up'); const hasSelectedWallet = rainbowWalletsNotBackedUp.find(notBackedUpWallet => notBackedUpWallet.id === selected!.id); - logger.debug('rainbow wallet not backed up that is selected?', { + logger.debug('[walletReadyEvents]: rainbow wallet not backed up that is selected?', { hasSelectedWallet, }); @@ -70,7 +70,7 @@ export const runWalletBackupStatusChecks = () => { } setTimeout(() => { - logger.debug(`showing ${stepType} backup sheet for selected wallet`); + logger.debug(`[walletReadyEvents]: showing ${stepType} backup sheet for selected wallet`); triggerOnSwipeLayout(() => Navigation.handleAction(Routes.BACKUP_SHEET, { step: stepType, @@ -99,11 +99,11 @@ export const runFeatureUnlockChecks = async (): Promise => { } }); - logger.debug('WALLETS TO CHECK', { walletsToCheck }); + logger.debug('[walletReadyEvents]: WALLETS TO CHECK', { walletsToCheck }); if (!walletsToCheck.length) return false; - logger.debug('Feature Unlocks: Running Checks'); + logger.debug('[walletReadyEvents]: Feature Unlocks: Running Checks'); // short circuits once the first feature is unlocked for (const featureUnlockCheck of featureUnlockChecks) { diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 95822333253..13416a8aceb 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -10,7 +10,6 @@ import { startsWith } from 'lodash'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AssetType, NewTransaction, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers/networkTypes'; import { isUnstoppableAddressFormat } from '@/helpers/validators'; import { ARBITRUM_ETH_ADDRESS, @@ -36,17 +35,16 @@ import { import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; import { IS_IOS, RPC_PROXY_API_KEY, RPC_PROXY_BASE_URL } from '@/env'; -import { getNetworkObj, getNetworkObject } from '@/networks'; +import { getNetworkObject } from '@/networks'; import store from '@/redux/store'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum TokenStandard { ERC1155 = 'ERC1155', ERC721 = 'ERC721', } -export const networkProviders = new Map(); +export const chainsProviders = new Map(); /** * Creates an rpc endpoint for a given chain id using the Rainbow rpc proxy. @@ -73,30 +71,29 @@ export const proxyRpcEndpoint = (chainId: number, customEndpoint?: string) => { }`; } else { if (customEndpoint) return customEndpoint; - const network = ethereumUtils.getNetworkFromChainId(chainId); - switch (network) { - case Network.arbitrum: + switch (chainId) { + case ChainId.arbitrum: return arbitrum_mainnet_rpc; - case Network.goerli: + case ChainId.goerli: return ethereum_goerli_rpc; - case Network.optimism: + case ChainId.optimism: return optimism_mainnet_rpc; - case Network.polygon: + case ChainId.polygon: return polygon_mainnet_rpc; - case Network.base: + case ChainId.base: return base_mainnet_rpc; - case Network.bsc: + case ChainId.bsc: return bsc_mainnet_rpc; - case Network.zora: + case ChainId.zora: return zora_mainnet_rpc; - case Network.avalanche: + case ChainId.avalanche: return avalanche_mainnet_rpc; - case Network.blast: + case ChainId.blast: return blast_mainnet_rpc; - case Network.degen: + case ChainId.degen: return degen_mainnet_rpc; - case Network.gnosis: - case Network.mainnet: + case ChainId.gnosis: + case ChainId.mainnet: default: return ethereum_mainnet_rpc; } @@ -119,7 +116,7 @@ type GasParamsInput = { gasPrice: BigNumberish } & { /** * The input data provied to `getTxDetails`. */ -type TransactionDetailsInput = Pick & +type TransactionDetailsInput = Pick & Pick & GasParamsInput; @@ -130,7 +127,7 @@ type TransactionDetailsReturned = { data?: TransactionRequest['data']; from?: TransactionRequest['from']; gasLimit?: string; - network?: Network | string; + chainId?: ChainId | string; to?: TransactionRequest['to']; value?: TransactionRequest['value']; nonce?: TransactionRequest['nonce']; @@ -150,23 +147,13 @@ type NewTransactionNonNullable = { */ export let web3Provider: StaticJsonRpcProvider = null as unknown as StaticJsonRpcProvider; -/** - * @desc Checks whether or not a `Network | string` union type should be - * treated as a `Network` based on its prefix, as opposed to a `string` type. - * @param network The network to check. - * @return A type predicate of `network is Network`. - */ -const isNetworkEnum = (network: Network | string): network is Network => { - return !network.startsWith('http://'); -}; - /** * @desc Sets a different web3 provider. * @param network The network to set. * @return A promise that resolves with an Ethers Network when the provider is ready. */ -export const web3SetHttpProvider = async (network: Network | string): Promise => { - web3Provider = await getProviderForNetwork(network); +export const web3SetHttpProvider = async (chainId: ChainId): Promise => { + web3Provider = await getProvider({ chainId }); return web3Provider.ready; }; @@ -179,92 +166,42 @@ export const isL2Chain = ({ chainId }: { chainId: ChainId }): boolean => { return getNetworkObject({ chainId }).networkType === 'layer2'; }; -/** - * @desc Checks whether a provider is HardHat. - * @param providerUrl The provider URL. - * @return Whether or not the provider is HardHat. - */ -export const isHardHat = (providerUrl: string): boolean => { - return providerUrl?.startsWith('http://') && providerUrl?.endsWith('8545'); -}; - /** * @desc Checks if the given network is a testnet. * @param network The network to check. * @return Whether or not the network is a testnet. */ -export const isTestnetNetwork = (network: Network): boolean => { - return getNetworkObj(network as Network).networkType === 'testnet'; +export const isTestnetChain = ({ chainId }: { chainId: ChainId }): boolean => { + return getNetworkObject({ chainId }).networkType === 'testnet'; }; // shoudl figure out better way to include this in networks export const getFlashbotsProvider = async () => { return new StaticJsonRpcProvider( proxyRpcEndpoint( - 1, + ChainId.mainnet, 'https://rpc.flashbots.net/?hint=hash&builder=flashbots&builder=f1b.io&builder=rsync&builder=beaverbuild.org&builder=builder0x69&builder=titan&builder=eigenphi&builder=boba-builder' - ), - Network.mainnet + ) ); }; -export const getCachedProviderForNetwork = (network: Network = Network.mainnet): StaticJsonRpcProvider | undefined => { - return networkProviders.get(network); -}; - -/** - * @desc Gets or constructs a web3 provider for the specified network. - * @param network The network as a `Network` or string. - * @return The provider for the network. - */ -export const getProviderForNetwork = (network: Network | string = Network.mainnet): StaticJsonRpcProvider => { - const isSupportedNetwork = isNetworkEnum(network); - const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; - - if (isSupportedNetwork && cachedProvider) { - return cachedProvider; - } - - if (!isSupportedNetwork) { - const provider = new StaticJsonRpcProvider(network, Network.mainnet); - networkProviders.set(Network.mainnet, provider); - return provider; - } else { - const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); - networkProviders.set(network, provider); - return provider; - } +export const getCachedProviderForNetwork = (chainId: ChainId = ChainId.mainnet): StaticJsonRpcProvider | undefined => { + return chainsProviders.get(chainId); }; export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { - const network = getNetworkFromChainId(chainId); - const isSupportedNetwork = isNetworkEnum(network); - const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; + const cachedProvider = chainsProviders.get(chainId); - if (isSupportedNetwork && cachedProvider) { + const networkObject = getNetworkObject({ chainId }); + + if (cachedProvider && cachedProvider?.connection.url === networkObject.rpc()) { return cachedProvider; } - if (!isSupportedNetwork) { - const provider = new StaticJsonRpcProvider(network, Network.mainnet); - networkProviders.set(Network.mainnet, provider); - return provider; - } else { - const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); - networkProviders.set(network, provider); - return provider; - } -}; + const provider = new StaticJsonRpcProvider(networkObject.rpc(), networkObject.id); + chainsProviders.set(chainId, provider); -/** - * @desc Checks if the active network is Hardhat. - * @returns boolean: `true` if connected to Hardhat. - */ -export const getIsHardhatConnected = (): boolean => { - const currentNetwork = store.getState().settings.network; - const currentProviderUrl = getCachedProviderForNetwork(currentNetwork)?.connection?.url; - const connectedToHardhat = !!currentProviderUrl && isHardHat(currentProviderUrl); - return connectedToHardhat; + return provider; }; /** @@ -414,16 +351,16 @@ export async function estimateGasWithPadding( const code = to ? await p.getCode(to) : undefined; // 2 - if it's not a contract AND it doesn't have any data use the default gas limit if ((!contractCallEstimateGas && !to) || (to && !data && (!code || code === '0x'))) { - logger.debug('⛽ Skipping estimates, using default', { + logger.debug('[web3]: ⛽ Skipping estimates, using default', { ethUnits: ethUnits.basic_tx.toString(), }); return ethUnits.basic_tx.toString(); } - logger.debug('⛽ Calculating safer gas limit for last block'); + logger.debug('[web3]: ⛽ Calculating safer gas limit for last block'); // 3 - If it is a contract, call the RPC method `estimateGas` with a safe value const saferGasLimit = fraction(gasLimit.toString(), 19, 20); - logger.debug('⛽ safer gas limit for last block is', { saferGasLimit }); + logger.debug('[web3]: ⛽ safer gas limit for last block is', { saferGasLimit }); txPayloadToEstimate[contractCallEstimateGas ? 'gasLimit' : 'gas'] = toHex(saferGasLimit); @@ -435,7 +372,7 @@ export async function estimateGasWithPadding( const lastBlockGasLimit = addBuffer(gasLimit.toString(), 0.9); const paddedGas = addBuffer(estimatedGas.toString(), paddingFactor.toString()); - logger.debug('⛽ GAS CALCULATIONS!', { + logger.debug('[web3]: ⛽ GAS CALCULATIONS!', { estimatedGas: estimatedGas.toString(), gasLimit: gasLimit.toString(), lastBlockGasLimit: lastBlockGasLimit, @@ -444,24 +381,24 @@ export async function estimateGasWithPadding( // If the safe estimation is above the last block gas limit, use it if (greaterThan(estimatedGas.toString(), lastBlockGasLimit)) { - logger.debug('⛽ returning orginal gas estimation', { + logger.debug('[web3]: ⛽ returning orginal gas estimation', { esimatedGas: estimatedGas.toString(), }); return estimatedGas.toString(); } // If the estimation is below the last block gas limit, use the padded estimate if (greaterThan(lastBlockGasLimit, paddedGas)) { - logger.debug('⛽ returning padded gas estimation', { paddedGas }); + logger.debug('[web3]: ⛽ returning padded gas estimation', { paddedGas }); return paddedGas; } // otherwise default to the last block gas limit - logger.debug('⛽ returning last block gas limit', { lastBlockGasLimit }); + logger.debug('[web3]: ⛽ returning last block gas limit', { lastBlockGasLimit }); return lastBlockGasLimit; } catch (e) { /* * Reported ~400x per day, but if it's not actionable it might as well be a warning. */ - logger.warn('Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); + logger.warn('[web3]: Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); return null; } } @@ -496,8 +433,8 @@ export const getTransactionCount = async (address: string): Promise & GasParamsInput): GasParamsReturned => { - return getNetworkObj(transaction.network).gas.gasType === 'legacy' +export const getTransactionGasParams = (transaction: Pick & GasParamsInput): GasParamsReturned => { + return getNetworkObject({ chainId: transaction.chainId }).gas.gasType === 'legacy' ? { gasPrice: toHex(transaction.gasPrice), } @@ -552,7 +489,7 @@ export const resolveUnstoppableDomain = async (domain: string): Promise { - logger.error(new RainbowError(`resolveUnstoppableDomain error`), { + logger.error(new RainbowError(`[web3]: resolveUnstoppableDomain error`), { message: error.message, }); return null; @@ -573,7 +510,7 @@ export const resolveNameOrAddress = async (nameOrAddress: string): Promise ): Promise => { const recipient = await resolveNameOrAddress(transaction.to); @@ -608,7 +545,7 @@ export const getTransferNftTransaction = async ( data, from, gasLimit: transaction.gasLimit?.toString(), - network: transaction.network, + chainId: transaction.chainId, nonce, to: contractAddress, ...gasParams, @@ -624,7 +561,7 @@ export const getTransferNftTransaction = async ( export const getTransferTokenTransaction = async ( transaction: Pick< NewTransactionNonNullable, - 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'network' | 'maxFeePerGas' | 'maxPriorityFeePerGas' + 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'chainId' | 'maxFeePerGas' | 'maxPriorityFeePerGas' > ): Promise => { const value = convertAmountToRawAmount(transaction.amount, transaction.asset.decimals); @@ -635,7 +572,7 @@ export const getTransferTokenTransaction = async ( data, from: transaction.from, gasLimit: transaction.gasLimit?.toString(), - network: transaction.network, + chainId: transaction.chainId, to: transaction.asset.address, ...gasParams, }; @@ -708,10 +645,10 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd const lowercasedContractAddress = asset.asset_contract.address.toLowerCase(); const standard = asset.asset_contract?.schema_name; let data: string | undefined; - if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.network === Network.mainnet) { + if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { const transferMethod = smartContractMethods.token_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); - } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.network === Network.mainnet) { + } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { const transferMethod = smartContractMethods.punk_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); } else if (standard === TokenStandard.ERC1155) { @@ -740,7 +677,7 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd * @param [{address, amount, asset, gasLimit, recipient}] The transaction * initialization details. * @param provider The RCP provider to use. - * @param network The network for the transaction + * @param chainId The chainId for the transaction * @return The transaction request. */ export const buildTransaction = async ( @@ -758,7 +695,7 @@ export const buildTransaction = async ( gasLimit?: string; }, provider: StaticJsonRpcProvider | null, - network: Network + chainId: ChainId ): Promise => { const _amount = amount && Number(amount) ? convertAmountToRawAmount(amount, asset.decimals) : estimateAssetBalancePortion(asset); const value = _amount.toString(); @@ -777,7 +714,7 @@ export const buildTransaction = async ( from: address, to: contractAddress, }; - } else if (!isNativeAsset(asset.address, ethereumUtils.getChainIdFromNetwork(network))) { + } else if (!isNativeAsset(asset.address, chainId)) { const transferData = getDataForTokenTransfer(value, _recipient); txData = { data: transferData, @@ -797,7 +734,7 @@ export const buildTransaction = async ( * to `false`. * @param provider If provided, a provider to use instead of the default * cached `web3Provider`. - * @param network The network to use, defaulting to `Network.mainnet`. + * @param chainId The chainId to use, defaulting to `ChainId.mainnet`. * @returns The estimated gas limit. */ export const estimateGasLimit = async ( @@ -814,9 +751,9 @@ export const estimateGasLimit = async ( }, addPadding = false, provider: StaticJsonRpcProvider | null = null, - network: Network = Network.mainnet + chainId: ChainId = ChainId.mainnet ): Promise => { - const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, network); + const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, chainId); if (addPadding) { return estimateGasWithPadding(estimateGasData, null, null, provider); diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index 055210d6fd8..c0734b50313 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -3,31 +3,18 @@ import { MMKV } from 'react-native-mmkv'; import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; -import { - showReloadButton, - showSwitchModeButton, - // @ts-ignore - showConnectToHardhatButton, -} from '../config/debug'; +import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; import { defaultConfig } from '../config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; import { STORAGE_IDS } from '@/model/mmkv'; -import { - // @ts-ignore - HARDHAT_URL_ANDROID, - // @ts-ignore - HARDHAT_URL_IOS, - // @ts-ignore - IS_TESTING, -} from 'react-native-dotenv'; -import { web3SetHttpProvider } from '@/handlers/web3'; +import { IS_TESTING } from 'react-native-dotenv'; import { logger, RainbowError } from '@/logger'; -import networkTypes from '@/helpers/networkTypes'; import { explorerInit } from '@/redux/explorer'; import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const RainbowContext = createContext({}); const storageKey = 'config'; @@ -40,6 +27,7 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { // This value is hold here to prevent JS VM from shutting down // on unmounting all shared values. useSharedValue(0); + const { setConnectedToHardhat } = useConnectedToHardhatStore(); const [config, setConfig] = useState>( Object.entries(defaultConfig).reduce((acc, [key, { value }]) => ({ ...acc, [key]: value }), {}) ); @@ -75,17 +63,17 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { const connectToHardhat = useCallback(async () => { try { - const ready = await web3SetHttpProvider('http://127.0.0.1:8545'); - logger.debug('connected to hardhat', { ready }); + setConnectedToHardhat(true); + logger.debug('connected to hardhat'); } catch (e: any) { - await web3SetHttpProvider(networkTypes.mainnet); + setConnectedToHardhat(false); logger.error(new RainbowError('error connecting to hardhat'), { message: e.message, }); } dispatch(explorerInit()); Navigation.handleAction(Routes.WALLET_SCREEN, {}); - }, [dispatch]); + }, [dispatch, setConnectedToHardhat]); return ( diff --git a/src/helpers/accountInfo.ts b/src/helpers/accountInfo.ts index a3005dde802..d694523cb71 100644 --- a/src/helpers/accountInfo.ts +++ b/src/helpers/accountInfo.ts @@ -17,7 +17,7 @@ export function getAccountProfileInfo(selectedWallet: any, walletNames: any, acc const accountENS = walletNames?.[accountAddress]; - const selectedAccount = selectedWallet.addresses.find((account: any) => account.address === accountAddress); + const selectedAccount = selectedWallet.addresses?.find((account: any) => account.address === accountAddress); if (!selectedAccount) { return {}; diff --git a/src/helpers/ens.ts b/src/helpers/ens.ts index 8a1e81f8e7d..f6be6fdf771 100644 --- a/src/helpers/ens.ts +++ b/src/helpers/ens.ts @@ -8,7 +8,7 @@ import { atom } from 'recoil'; import { InlineFieldProps } from '../components/inputs/InlineField'; import { add, addBuffer, convertAmountAndPriceToNativeDisplay, divide, fromWei, handleSignificantDecimals, multiply } from './utilities'; import { ENSRegistrationRecords, EthereumAddress } from '@/entities'; -import { getProviderForNetwork, toHex } from '@/handlers/web3'; +import { getProvider, toHex } from '@/handlers/web3'; import { gweiToWei } from '@/parsers'; import { ENSBaseRegistrarImplementationABI, @@ -25,6 +25,7 @@ import { import { colors } from '@/styles'; import { labelhash } from '@/utils'; import { encodeContenthash, isValidContenthash } from '@/utils/contenthash'; +import { ChainId } from '@/networks/types'; export const ENS_SECONDS_WAIT = 60; export const ENS_SECONDS_PADDING = 5; @@ -367,27 +368,27 @@ export const deprecatedTextRecordFields = { export const ENS_DOMAIN = '.eth'; const getENSRegistrarControllerContract = async (wallet?: Signer, registrarAddress?: string) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(registrarAddress || ensETHRegistrarControllerAddress, ENSETHRegistrarControllerABI, signerOrProvider); }; const getENSPublicResolverContract = async (wallet?: Signer, resolverAddress?: EthereumAddress) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(resolverAddress || ensPublicResolverAddress, ENSPublicResolverABI, signerOrProvider); }; const getENSReverseRegistrarContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensReverseRegistrarAddress, ENSReverseRegistrarABI, signerOrProvider); }; const getENSBaseRegistrarImplementationContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProviderForNetwork()); + const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensBaseRegistrarImplementationAddress, ENSBaseRegistrarImplementationABI, signerOrProvider); }; const getENSRegistryContract = async (wallet?: Signer) => { - const signerOrProvider = wallet ?? (await getProviderForNetwork()); + const signerOrProvider = wallet ?? (await getProvider({ chainId: ChainId.mainnet })); return new Contract(ensRegistryAddress, ENSRegistryWithFallbackABI, signerOrProvider); }; diff --git a/src/helpers/findWalletWithAccount.ts b/src/helpers/findWalletWithAccount.ts index 85b06a2bd8a..109371d2b82 100644 --- a/src/helpers/findWalletWithAccount.ts +++ b/src/helpers/findWalletWithAccount.ts @@ -5,7 +5,7 @@ export function findWalletWithAccount(wallets: { [key: string]: RainbowWallet }, let walletWithAccount: RainbowWallet | undefined; sortedKeys.forEach(key => { const wallet = wallets[key]; - const found = wallet.addresses.find((account: any) => account.address === accountAddress); + const found = wallet.addresses?.find((account: any) => account.address === accountAddress); if (found) { walletWithAccount = wallet; } diff --git a/src/helpers/gas.ts b/src/helpers/gas.ts index 540cf9752ad..69b7a786578 100644 --- a/src/helpers/gas.ts +++ b/src/helpers/gas.ts @@ -1,7 +1,7 @@ -import { Network } from '@/networks/types'; import { memoFn } from '../utils/memoFn'; import { gasUtils } from '@/utils'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; +import { ChainId } from '@/networks/types'; const { GasTrends } = gasUtils; const { FALLING, NO_TREND, RISING, STABLE, SURGING } = GasTrends; @@ -25,8 +25,8 @@ export const getTrendKey = memoFn((trend: number) => { return NO_TREND; }); -export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { - const networkObject = getNetworkObj(txNetwork); +export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { + const networkObject = getNetworkObject({ chainId }); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; @@ -38,8 +38,8 @@ export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, tx } }); -export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { - const networkObject = getNetworkObj(txNetwork); +export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { + const networkObject = getNetworkObject({ chainId }); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index eb9b7a803b2..4a38d19659d 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -5,6 +5,5 @@ export { default as CurrencySelectionTypes } from './currencySelectionTypes'; export { default as ExchangeModalTypes } from './exchangeModalTypes'; export { default as isKeyboardOpen } from './isKeyboardOpen'; export { default as isReanimatedAvailable } from './isReanimatedAvailable'; -export { default as NetworkTypes, Network } from './networkTypes'; export { default as TokenSectionTypes } from './tokenSectionTypes'; export { StatusBarHelper }; diff --git a/src/helpers/networkInfo.ts b/src/helpers/networkInfo.ts index 2bda8506ad8..be81a8bad48 100644 --- a/src/helpers/networkInfo.ts +++ b/src/helpers/networkInfo.ts @@ -1,8 +1,8 @@ -import networkTypes from './networkTypes'; +import { Network } from '@/networks/types'; // TODO: networkInfo is DEPRECATED after the new network support changes const networkInfo = { - [`${networkTypes.mainnet}`]: { + [`${Network.mainnet}`]: { balance_checker_contract_address: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', color: '#0E76FD', disabled: false, @@ -10,9 +10,9 @@ const networkInfo = { faucet_url: null, name: 'Ethereum', gasToken: 'ETH', - value: networkTypes.mainnet, + value: Network.mainnet, }, - [`${networkTypes.goerli}`]: { + [`${Network.goerli}`]: { balance_checker_contract_address: '0xf3352813b612a2d198e437691557069316b84ebe', color: '#f6c343', disabled: false, @@ -21,9 +21,9 @@ const networkInfo = { name: 'Goerli', gasToken: 'ETH', testnet: true, - value: networkTypes.goerli, + value: Network.goerli, }, - [`${networkTypes.arbitrum}`]: { + [`${Network.arbitrum}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#2D374B', disabled: false, @@ -32,9 +32,9 @@ const networkInfo = { layer2: true, name: 'Arbitrum', gasToken: 'ETH', - value: networkTypes.arbitrum, + value: Network.arbitrum, }, - [`${networkTypes.optimism}`]: { + [`${Network.optimism}`]: { balance_checker_contract_address: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', color: '#FF4040', disabled: false, @@ -43,9 +43,9 @@ const networkInfo = { layer2: true, name: 'Optimism', gasToken: 'ETH', - value: networkTypes.optimism, + value: Network.optimism, }, - [`${networkTypes.polygon}`]: { + [`${Network.polygon}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#8247E5', disabled: false, @@ -55,9 +55,9 @@ const networkInfo = { longName: 'Polygon (Matic)', name: 'Polygon', gasToken: 'MATIC', - value: networkTypes.polygon, + value: Network.polygon, }, - [`${networkTypes.bsc}`]: { + [`${Network.bsc}`]: { balance_checker_contract_address: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', color: '#F0B90B', disabled: false, @@ -67,7 +67,7 @@ const networkInfo = { longName: 'Binance Smart Chain', name: 'BSC', gasToken: 'BNB', - value: networkTypes.bsc, + value: Network.bsc, }, }; diff --git a/src/helpers/networkTypes.ts b/src/helpers/networkTypes.ts deleted file mode 100644 index 62e425cda73..00000000000 --- a/src/helpers/networkTypes.ts +++ /dev/null @@ -1,31 +0,0 @@ -export enum Network { - arbitrum = 'arbitrum', - goerli = 'goerli', - mainnet = 'mainnet', - optimism = 'optimism', - polygon = 'polygon', - base = 'base', - bsc = 'bsc', - zora = 'zora', - gnosis = 'gnosis', - avalanche = 'avalanche', - blast = 'blast', - degen = 'degen', -} - -// We need to keep this one until -// we have typescript everywhere -export default { - arbitrum: 'arbitrum' as Network, - goerli: 'goerli' as Network, - mainnet: 'mainnet' as Network, - optimism: 'optimism' as Network, - polygon: 'polygon' as Network, - base: 'base' as Network, - bsc: 'bsc' as Network, - zora: 'zora' as Network, - gnosis: 'gnosis' as Network, - avalanche: 'avalanche' as Network, - blast: 'blast' as Network, - degen: 'degen' as Network, -}; diff --git a/src/helpers/signingWallet.ts b/src/helpers/signingWallet.ts index 5722ea9c0ee..7d41fc0e074 100644 --- a/src/helpers/signingWallet.ts +++ b/src/helpers/signingWallet.ts @@ -8,9 +8,8 @@ import { signingWalletAddress, signingWallet as signingWalletKeychain } from '.. import { EthereumAddress } from '@/entities'; import AesEncryptor from '@/handlers/aesEncryption'; import { addHexPrefix } from '@/handlers/web3'; -import { logger } from '@/utils'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger as Logger, RainbowError } from '@/logger'; +import { logger, RainbowError } from '@/logger'; export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): Promise { let alreadyExistingWallet = await loadString(signingWalletAddress); @@ -20,7 +19,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P const { wallet, address } = await deriveAccountFromWalletInput(walletSeed); if (!wallet || !address) { - Logger.error(new RainbowError('signingWallet - wallet or address undefined')); + logger.error(new RainbowError('[signingWallet]: wallet or address undefined')); // @ts-ignore need to handle types in case wallet or address are null return null; } @@ -35,7 +34,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P await saveString(signingWalletAddress, address, publicAccessControlOptions); alreadyExistingWallet = address; } - logger.log('Signing wallet already existing'); + logger.debug('[signingWallet]: Signing wallet already existing'); return alreadyExistingWallet; } @@ -48,7 +47,7 @@ export async function getSignatureForSigningWalletAndCreateSignatureIfNeeded(add if (address === verifyMessage(publicKeyForTheSigningWallet, decryptedSignature)) { return decryptedSignature; } else { - logger.log('Signature does not match. Creating a new one.'); + logger.debug('[signingWallet]: Signature does not match. Creating a new one.'); alreadyExistingEncodedSignature = null; return createSignature(address); } @@ -61,14 +60,14 @@ export async function signWithSigningWallet(messageToSign: string): Promise { StatusBar.setTranslucent(translucent); @@ -30,7 +29,3 @@ export const setDarkContent = (isAnimated = true) => { barStyle: 'dark-content', }); }; - -export const isUsingButtonNavigation = () => { - return getSoftMenuBarHeight() > 95; -}; diff --git a/src/helpers/validators.ts b/src/helpers/validators.ts index 72aceda6c26..3016e5c7950 100644 --- a/src/helpers/validators.ts +++ b/src/helpers/validators.ts @@ -1,8 +1,8 @@ import { isValidAddress } from 'ethereumjs-util'; import { memoFn } from '../utils/memoFn'; -import { Network } from './networkTypes'; -import { getProviderForNetwork, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; +import { getProvider, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; import { sanitizeSeedPhrase } from '@/utils/formatters'; +import { ChainId } from '@/networks/types'; // Currently supported Top Level Domains from Unstoppable Domains const supportedUnstoppableDomains = ['888', 'bitcoin', 'blockchain', 'coin', 'crypto', 'dao', 'nft', 'wallet', 'x', 'zil']; @@ -68,7 +68,7 @@ export const checkIsValidAddressOrDomainFormat = (address: any) => { * @return {Boolean} */ export const checkIsValidAddressOrDomain = async (address: any) => { - const provider = getProviderForNetwork(Network.mainnet); + const provider = getProvider({ chainId: ChainId.mainnet }); if (isENSAddressFormat(address)) { try { const resolvedAddress = await provider.resolveName(address); diff --git a/src/helpers/walletConnectNetworks.ts b/src/helpers/walletConnectNetworks.ts index 326399fd897..cb02ab79f2c 100644 --- a/src/helpers/walletConnectNetworks.ts +++ b/src/helpers/walletConnectNetworks.ts @@ -1,12 +1,12 @@ -import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { Network } from '@/networks/types'; +import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; import store from '@/redux/store'; import { showActionSheetWithOptions } from '@/utils'; import * as i18n from '@/languages'; +import { ChainId } from '@/networks/types'; const androidNetworkActions = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => network.name); }; @@ -15,7 +15,7 @@ export const NETWORK_MENU_ACTION_KEY_FILTER = 'switch-to-network-'; export const networksMenuItems = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworks.filter( + return RainbowNetworkObjects.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => ({ actionKey: `${NETWORK_MENU_ACTION_KEY_FILTER}${network.value}`, @@ -76,7 +76,8 @@ export const androidShowNetworksActionSheet = (callback: any) => { (idx: any) => { if (idx !== undefined) { const networkActions = androidNetworkActions(); - const networkObj = RainbowNetworks.find(network => network.name === networkActions[idx]) || getNetworkObj(Network.mainnet); + const networkObj = + RainbowNetworkObjects.find(network => network.name === networkActions[idx]) || getNetworkObject({ chainId: ChainId.mainnet }); callback({ chainId: networkObj.id, network: networkObj.value }); } } diff --git a/src/hooks/charts/useChartInfo.ts b/src/hooks/charts/useChartInfo.ts index 05efb97f142..e2402eb2e9f 100644 --- a/src/hooks/charts/useChartInfo.ts +++ b/src/hooks/charts/useChartInfo.ts @@ -1,18 +1,11 @@ import { useNavigation, useRoute } from '@react-navigation/native'; -import { isEmpty } from 'lodash'; -import { useCallback, useEffect, useState } from 'react'; -import isEqual from 'react-fast-compare'; -import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import { useCallbackOne } from 'use-memo-one'; -import { disableCharts } from '../../config/debug'; +import { useCallback } from 'react'; import { DEFAULT_CHART_TYPE } from '../../redux/charts'; import { metadataClient } from '@/graphql'; import { useQuery } from '@tanstack/react-query'; import { createQueryKey } from '@/react-query'; -import { getNetworkObj } from '@/networks'; -import { NetworkProperties } from '@/networks/types'; -import { Network } from '@/helpers'; +import { SupportedCurrencyKey } from '@/references'; +import { ChainId } from '@/networks/types'; const chartTimes = ['hour', 'day', 'week', 'month', 'year'] as const; type ChartTime = (typeof chartTimes)[number]; @@ -23,9 +16,19 @@ const getChartTimeArg = (selected: ChartTime) => export type ChartData = { x: number; y: number }; -const fetchPriceChart = async (time: ChartTime, chainId: NetworkProperties['id'], address: string) => { +const fetchPriceChart = async ({ + address, + chainId, + currency, + time, +}: { + address: string; + chainId: ChainId; + currency: SupportedCurrencyKey; + time: ChartTime; +}) => { const priceChart = await metadataClient - .priceChart({ address, chainId, ...getChartTimeArg(time) }) + .priceChart({ address, chainId, currency, ...getChartTimeArg(time) }) .then(d => d.token?.priceCharts[time] as PriceChartTimeData); return priceChart?.points?.reduce((result, point) => { result.push({ x: point[0], y: point[1] }); @@ -33,7 +36,17 @@ const fetchPriceChart = async (time: ChartTime, chainId: NetworkProperties['id'] }, [] as ChartData[]); }; -export const usePriceChart = ({ mainnetAddress, address, network }: { mainnetAddress?: string; address: string; network: Network }) => { +export const usePriceChart = ({ + mainnetAddress, + address, + currency, + chainId, +}: { + mainnetAddress?: string; + address: string; + currency: SupportedCurrencyKey; + chainId: ChainId; +}) => { const { setParams } = useNavigation(); const updateChartType = useCallback( (type: ChartTime) => { @@ -47,12 +60,11 @@ export const usePriceChart = ({ mainnetAddress, address, network }: { mainnetAdd params: any; }>(); const chartType = params?.chartType ?? DEFAULT_CHART_TYPE; - const chainId = getNetworkObj(network).id; - const mainnetChainId = getNetworkObj(Network.mainnet).id; const query = useQuery({ queryFn: async () => { - const chart = await fetchPriceChart(chartType, chainId, address); - if (!chart && mainnetAddress) return fetchPriceChart(chartType, mainnetChainId, mainnetAddress); + const chart = await fetchPriceChart({ address, chainId, currency, time: chartType }); + if (!chart && mainnetAddress) + return fetchPriceChart({ address: mainnetAddress, chainId: ChainId.mainnet, currency, time: chartType }); return chart || null; }, queryKey: createQueryKey('price chart', { address, chainId, chartType }), diff --git a/src/hooks/charts/useChartThrottledPoints.ts b/src/hooks/charts/useChartThrottledPoints.ts index a88dc3b111c..3586efe49c6 100644 --- a/src/hooks/charts/useChartThrottledPoints.ts +++ b/src/hooks/charts/useChartThrottledPoints.ts @@ -98,8 +98,9 @@ export default function useChartThrottledPoints({ updateChartType, } = usePriceChart({ address: asset.address, - network: asset.network, + chainId: asset.chainId, mainnetAddress: asset?.mainnet_address || asset?.mainnetAddress, + currency: nativeCurrency, }); const [throttledPoints, setThrottledPoints] = useState(() => traverseData({ nativePoints: [], points: [] }, chart)); diff --git a/src/hooks/useAccountAsset.ts b/src/hooks/useAccountAsset.ts index 146e6412ff9..2d2bccc2699 100644 --- a/src/hooks/useAccountAsset.ts +++ b/src/hooks/useAccountAsset.ts @@ -1,9 +1,10 @@ +import { NativeCurrencyKey } from '@/entities'; import useAccountSettings from './useAccountSettings'; import { parseAssetNative } from '@/parsers'; import { useUserAsset } from '@/resources/assets/useUserAsset'; // this is meant to be used for assets contained in the current wallet -export default function useAccountAsset(uniqueId: string, nativeCurrency: string | undefined = undefined) { +export default function useAccountAsset(uniqueId: string, nativeCurrency: NativeCurrencyKey | undefined = undefined) { const { data: accountAsset } = useUserAsset(uniqueId); // this is temporary for FastBalanceCoinRow to make a tiny bit faster diff --git a/src/hooks/useAccountTransactions.ts b/src/hooks/useAccountTransactions.ts index f7dc48cdac1..9fab4b41ae9 100644 --- a/src/hooks/useAccountTransactions.ts +++ b/src/hooks/useAccountTransactions.ts @@ -8,9 +8,9 @@ import { useTheme } from '@/theme'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import { RainbowTransaction } from '@/entities'; import { pendingTransactionsStore, usePendingTransactionsStore } from '@/state/pendingTransactions'; -import { RainbowNetworks } from '@/networks'; -import { Network } from '@/networks/types'; +import { RainbowNetworkObjects } from '@/networks'; import { nonceStore } from '@/state/nonces'; +import { ChainId } from '@/networks/types'; export const NOE_PAGE = 30; @@ -34,16 +34,16 @@ export default function useAccountTransactions() { .filter(t => t.from?.toLowerCase() === accountAddress?.toLowerCase()) .reduce( (latestTxMap, currentTx) => { - const currentNetwork = currentTx?.network; - if (currentNetwork) { - const latestTx = latestTxMap.get(currentNetwork); + const currentChainId = currentTx?.chainId; + if (currentChainId) { + const latestTx = latestTxMap.get(currentChainId); if (!latestTx) { - latestTxMap.set(currentNetwork, currentTx); + latestTxMap.set(currentChainId, currentTx); } } return latestTxMap; }, - new Map(RainbowNetworks.map(chain => [chain.value, null as RainbowTransaction | null])) + new Map(RainbowNetworkObjects.map(chain => [chain.id, null as RainbowTransaction | null])) ); watchForPendingTransactionsReportedByRainbowBackend({ currentAddress: accountAddress, @@ -56,17 +56,17 @@ export default function useAccountTransactions() { latestTransactions, }: { currentAddress: string; - latestTransactions: Map; + latestTransactions: Map; }) { const { setNonce } = nonceStore.getState(); const { setPendingTransactions, pendingTransactions: storePendingTransactions } = pendingTransactionsStore.getState(); const pendingTransactions = storePendingTransactions[currentAddress] || []; - const networks = RainbowNetworks.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); + const networks = RainbowNetworkObjects.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); for (const network of networks) { - const latestTxConfirmedByBackend = latestTransactions.get(network.value); + const latestTxConfirmedByBackend = latestTransactions.get(network.id); if (latestTxConfirmedByBackend) { const latestNonceConfirmedByBackend = latestTxConfirmedByBackend.nonce || 0; - const [latestPendingTx] = pendingTransactions.filter(tx => tx?.network === network.value); + const [latestPendingTx] = pendingTransactions.filter(tx => tx?.chainId === network.id); let currentNonce; if (latestPendingTx) { @@ -79,7 +79,7 @@ export default function useAccountTransactions() { setNonce({ address: currentAddress, - network: network.value, + chainId: network.id, currentNonce, latestConfirmedNonce: latestNonceConfirmedByBackend, }); @@ -88,7 +88,7 @@ export default function useAccountTransactions() { const updatedPendingTransactions = pendingTransactions?.filter(tx => { const txNonce = tx.nonce || 0; - const latestTx = latestTransactions.get(tx.network); + const latestTx = latestTransactions.get(tx.chainId); const latestTxNonce = latestTx?.nonce || 0; // still pending or backend is not returning confirmation yet // if !latestTx means that is the first tx of the wallet diff --git a/src/hooks/useAdditionalAssetData.ts b/src/hooks/useAdditionalAssetData.ts index cf8b8569890..df16a1dcd74 100644 --- a/src/hooks/useAdditionalAssetData.ts +++ b/src/hooks/useAdditionalAssetData.ts @@ -1,9 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { NativeCurrencyKey } from '@/entities'; -import { Network } from '@/networks/types'; import { metadataClient } from '@/graphql'; -import { ethereumUtils } from '@/utils'; import { Token } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/networks/types'; // Types type TokenMetadata = Pick< @@ -14,21 +13,20 @@ type TokenMetadata = Pick< // Types for the query arguments type AdditionalAssetDataArgs = { address: string; - network: Network; + chainId: ChainId; currency: NativeCurrencyKey; }; // Query Key function -const createAdditionalAssetDataQueryKey = ({ address, network, currency }: AdditionalAssetDataArgs) => [ +const createAdditionalAssetDataQueryKey = ({ address, chainId, currency }: AdditionalAssetDataArgs) => [ 'additionalAssetData', address, - network, + chainId, currency, ]; // Refactor the getAdditionalAssetData function to accept the new parameters -async function getAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs): Promise { - const chainId = ethereumUtils.getChainIdFromNetwork(network); +async function getAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs): Promise { const data = await metadataClient.tokenMetadata({ address, chainId, @@ -42,12 +40,12 @@ async function getAdditionalAssetData({ address, network, currency }: Additional } // Usage of the useQuery hook -export default function useAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs) { +export default function useAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs) { return useQuery( - createAdditionalAssetDataQueryKey({ address, network, currency }), - () => getAdditionalAssetData({ address, network, currency }), + createAdditionalAssetDataQueryKey({ address, chainId, currency }), + () => getAdditionalAssetData({ address, chainId, currency }), { - enabled: !!address && !!network && !!currency, // Ensure all parameters are provided + enabled: !!address && !!chainId && !!currency, // Ensure all parameters are provided } ); } diff --git a/src/hooks/useAppVersion.ts b/src/hooks/useAppVersion.ts index b6a660c970c..c0ce735f36c 100644 --- a/src/hooks/useAppVersion.ts +++ b/src/hooks/useAppVersion.ts @@ -1,7 +1,7 @@ import VersionNumber from 'react-native-version-number'; import { useState } from 'react'; function formatAppVersion(appVersion = VersionNumber.appVersion) { - let version = `${appVersion} (${VersionNumber.buildVersion})`; + const version = `${appVersion} (${VersionNumber.buildVersion})`; return version; } diff --git a/src/hooks/useAsset.ts b/src/hooks/useAsset.ts index 9a5e3180397..91a77b25563 100644 --- a/src/hooks/useAsset.ts +++ b/src/hooks/useAsset.ts @@ -4,11 +4,12 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useSelector } from 'react-redux'; import { AppState } from '@/redux/store'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { Address } from 'viem'; // To fetch an asset from account assets, // generic assets, and uniqueTokens -export default function useAsset({ address, chainId }: { address: string; chainId: ChainId }) { +export default function useAsset({ address, chainId }: { address: Address; chainId: ChainId }) { const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); const uniqueId = getUniqueId(address, chainId); const accountAsset = useAccountAsset(uniqueId); diff --git a/src/hooks/useCloudBackups.ts b/src/hooks/useCloudBackups.ts index a9146462af5..506e669c682 100644 --- a/src/hooks/useCloudBackups.ts +++ b/src/hooks/useCloudBackups.ts @@ -26,31 +26,31 @@ export default function useCloudBackups() { setIsFetching(true); const isAvailable = isCloudBackupAvailable(); if (!isAvailable) { - logger.log('Cloud backup is not available'); + logger.debug('[useCloudBackups]: Cloud backup is not available'); setIsFetching(false); setStep(CloudBackupStep.IDLE); return; } setStep(CloudBackupStep.SYNCING); - logger.log('Syncing with cloud'); + logger.debug('[useCloudBackups]: Syncing with cloud'); await syncCloud(); setStep(CloudBackupStep.FETCHING_USER_DATA); - logger.log('Fetching user data'); + logger.debug('[useCloudBackups]: Fetching user data'); const userData = await fetchUserDataFromCloud(); setUserData(userData); setStep(CloudBackupStep.FETCHING_ALL_BACKUPS); - logger.log('Fetching all backups'); + logger.debug('[useCloudBackups]: Fetching all backups'); const backups = await fetchAllBackups(); - logger.log(`Retrieved ${backups.files.length} backup files`); + logger.debug(`[useCloudBackups]: Retrieved ${backups.files.length} backup files`); setBackups(backups); setStep(CloudBackupStep.IDLE); } catch (e) { setStep(CloudBackupStep.FAILED); - logger.error(new RainbowError('Failed to fetch all backups'), { + logger.error(new RainbowError('[useCloudBackups]: Failed to fetch all backups'), { error: e, }); } diff --git a/src/hooks/useContacts.ts b/src/hooks/useContacts.ts index 277a5f77e1c..73aaac697f7 100644 --- a/src/hooks/useContacts.ts +++ b/src/hooks/useContacts.ts @@ -2,7 +2,6 @@ import { sortBy, values } from 'lodash'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createSelector } from 'reselect'; -import networkTypes from '../helpers/networkTypes'; import { contactsAddOrUpdate, removeContact } from '../redux/contacts'; import { AppState } from '@/redux/store'; @@ -16,9 +15,6 @@ const contactsSelector = createSelector( export default function useContacts() { const dispatch = useDispatch(); - const { network } = useSelector(({ settings: { network } }: AppState) => ({ - network, - })); const { contacts, sortedContacts } = useSelector(contactsSelector); const onAddOrUpdateContacts = useCallback( @@ -29,13 +25,9 @@ export default function useContacts() { const onRemoveContact = useCallback((data: string) => dispatch(removeContact(data)), [dispatch]); - const filteredContacts = sortedContacts.filter(contact => - contact.network === network || (!contact.network && network === networkTypes.mainnet) ? contact : false - ); - return { contacts, - filteredContacts, + filteredContacts: sortedContacts, onAddOrUpdateContacts, onRemoveContact, sortedContacts, diff --git a/src/hooks/useDeleteWallet.ts b/src/hooks/useDeleteWallet.ts index 4613034e530..bbe54e5b587 100644 --- a/src/hooks/useDeleteWallet.ts +++ b/src/hooks/useDeleteWallet.ts @@ -13,7 +13,7 @@ export default function useDeleteWallet({ address: primaryAddress }: { address?: const [watchingWalletId] = useMemo(() => { return ( Object.entries(wallets || {}).find(([_, wallet]: [string, RainbowWallet]) => - wallet.addresses.some(({ address }: RainbowAccount) => address === primaryAddress) + (wallet.addresses || []).some(({ address }: RainbowAccount) => address === primaryAddress) ) || ['', ''] ); }, [primaryAddress, wallets]); diff --git a/src/hooks/useENSRegistrationActionHandler.ts b/src/hooks/useENSRegistrationActionHandler.ts index 65c94689835..6216645ff4c 100644 --- a/src/hooks/useENSRegistrationActionHandler.ts +++ b/src/hooks/useENSRegistrationActionHandler.ts @@ -12,19 +12,20 @@ import { Records, RegistrationParameters } from '@/entities'; import { fetchResolver } from '@/handlers/ens'; import { saveNameFromLabelhash } from '@/handlers/localstorage/ens'; import { uploadImage } from '@/handlers/pinata'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { ENS_DOMAIN, generateSalt, getRentPrice, REGISTRATION_STEPS } from '@/helpers/ens'; import { loadWallet } from '@/model/wallet'; import { timeUnits } from '@/references'; import Routes from '@/navigation/routesNames'; -import { labelhash, logger } from '@/utils'; +import { labelhash } from '@/utils'; import { getNextNonce } from '@/state/nonces'; -import { Network } from '@/networks/types'; import { Hex } from 'viem'; import { executeENSRap } from '@/raps/actions/ens'; import store from '@/redux/store'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { noop } from 'lodash'; +import { logger, RainbowError } from '@/logger'; +import { ChainId } from '@/networks/types'; // Generic type for action functions type ActionFunction

= (...params: P) => Promise; @@ -114,7 +115,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step }; (() => { - provider = getProviderForNetwork(); + provider = getProvider({ chainId: ChainId.mainnet }); provider.on('block', updateAvatars); })(); return () => { @@ -127,7 +128,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { updateAvatarsOnNextBlock.current = true; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -138,7 +139,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const salt = generateSalt(); const [nonce, rentPrice] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), getRentPrice(registrationParameters.name.replace(ENS_DOMAIN, ''), duration), ]); @@ -185,7 +186,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name, duration } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -195,7 +196,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, rentPrice, changedRecords] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), getRentPrice(name.replace(ENS_DOMAIN, ''), duration), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, @@ -224,7 +225,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -233,7 +234,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const rentPrice = await getRentPrice(name.replace(ENS_DOMAIN, ''), duration); const registerEnsRegistrationParameters: ENSActionParameters = { @@ -252,7 +253,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -261,7 +262,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const registerEnsRegistrationParameters: ENSActionParameters = { ...formatENSActionParams(registrationParameters), @@ -277,7 +278,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const setRecordsAction: ActionTypes[typeof REGISTRATION_STEPS.EDIT] = useCallback( async (callback = noop) => { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -287,7 +288,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, changedRecords, resolver] = await Promise.all([ - getNextNonce({ network: Network.mainnet, address: accountAddress }), + getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, header: coverMetadata, @@ -315,7 +316,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async ({ clearRecords, records, name, setAddress, toAddress, transferControl, wallet: walletOverride }, callback = noop) => { let wallet = walletOverride; if (!wallet) { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -325,7 +326,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); const transferEnsParameters: ENSActionParameters = { ...formatENSActionParams({ @@ -379,7 +380,9 @@ async function uploadRecordImages(records: Partial | undefined, imageMe }); return url; } catch (error) { - logger.sentry('[uploadRecordImages] Failed to upload image.', error); + logger.error(new RainbowError('[useENSRegistrationActionHandler]: Failed to upload image.'), { + error, + }); return undefined; } } diff --git a/src/hooks/useENSRegistrationCosts.ts b/src/hooks/useENSRegistrationCosts.ts index d1143975b3e..75fc975a023 100644 --- a/src/hooks/useENSRegistrationCosts.ts +++ b/src/hooks/useENSRegistrationCosts.ts @@ -24,11 +24,10 @@ import { REGISTRATION_MODES, REGISTRATION_STEPS, } from '@/helpers/ens'; -import { Network } from '@/helpers/networkTypes'; import { add, addBuffer, addDisplay, fromWei, greaterThanOrEqualTo, multiply } from '@/helpers/utilities'; import { ethUnits, timeUnits } from '@/references'; import { ethereumUtils, gasUtils } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; enum QUERY_KEYS { GET_COMMIT_GAS_LIMIT = 'GET_COMMIT_GAS_LIMIT', @@ -93,7 +92,7 @@ export default function useENSRegistrationCosts({ const rentPriceInWei = rentPrice?.wei?.toString(); const checkIfSufficientEth = useCallback((wei: string) => { - const nativeAsset = ethereumUtils.getNetworkNativeAsset(ChainId.mainnet); + const nativeAsset = ethereumUtils.getNetworkNativeAsset({ chainId: ChainId.mainnet }); const balanceAmount = nativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(wei); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); @@ -248,7 +247,7 @@ export default function useENSRegistrationCosts({ ); const estimatedFee = useMemo(() => { - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); const { gasFeeParamsBySpeed, currentBaseFee } = gasFeeParams; let estimatedGasLimit = ''; @@ -334,7 +333,7 @@ export default function useENSRegistrationCosts({ const data = useMemo(() => { const rentPricePerYearInWei = rentPrice?.perYear?.wei?.toString(); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); if (rentPricePerYearInWei) { const rentPriceInWei = multiply(rentPricePerYearInWei, yearsDuration); diff --git a/src/hooks/useENSRegistrationStepHandler.tsx b/src/hooks/useENSRegistrationStepHandler.tsx index 77653dca970..f53c8ece33a 100644 --- a/src/hooks/useENSRegistrationStepHandler.tsx +++ b/src/hooks/useENSRegistrationStepHandler.tsx @@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'; import usePrevious from './usePrevious'; import { useENSRegistration, useInterval } from '.'; import { RegistrationParameters } from '@/entities'; -import { getProviderForNetwork, isHardHat, web3Provider } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { ENS_SECONDS_PADDING, ENS_SECONDS_WAIT, @@ -14,18 +14,20 @@ import { REGISTRATION_STEPS, } from '@/helpers/ens'; import { updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const checkRegisterBlockTimestamp = async ({ registrationParameters, secondsSinceCommitConfirmed, - isTestingHardhat, + connectedToHardhat, }: { registrationParameters: RegistrationParameters; secondsSinceCommitConfirmed: number; - isTestingHardhat: boolean; + connectedToHardhat: boolean; }) => { try { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const block = await provider.getBlock('latest'); const msBlockTimestamp = getBlockMsTimestamp(block); const secs = differenceInSeconds(msBlockTimestamp, registrationParameters?.commitTransactionConfirmedAt || msBlockTimestamp); @@ -33,7 +35,7 @@ const checkRegisterBlockTimestamp = async ({ (secs > ENS_SECONDS_WAIT_WITH_PADDING && secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_WITH_PADDING) || // sometimes the provider.getBlock('latest) takes a long time to update to newest block secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_PROVIDER_PADDING || - isTestingHardhat + connectedToHardhat ) { return true; } @@ -60,12 +62,12 @@ export default function useENSRegistrationStepHandler(observer = true) { -1 ); - const isTestingHardhat = useMemo(() => isHardHat(web3Provider.connection.url), []); + const { connectedToHardhat } = useConnectedToHardhatStore(); const [readyToRegister, setReadyToRegister] = useState(secondsSinceCommitConfirmed > ENS_SECONDS_WAIT); // flag to wait 10 secs before we get the tx block, to be able to simulate not confirmed tx when testing - const shouldLoopForConfirmation = useRef(isTestingHardhat); + const shouldLoopForConfirmation = useRef(connectedToHardhat); const registrationStep = useMemo(() => { if (mode === REGISTRATION_MODES.EDIT) return REGISTRATION_STEPS.EDIT; @@ -90,7 +92,7 @@ export default function useENSRegistrationStepHandler(observer = true) { const watchCommitTransaction = useCallback(async () => { if (observer) return; - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); let confirmed = false; const tx = await provider.getTransaction(commitTransactionHash || ''); if (!tx?.blockHash) return confirmed; @@ -99,7 +101,7 @@ export default function useENSRegistrationStepHandler(observer = true) { const now = Date.now(); const msBlockTimestamp = getBlockMsTimestamp(block); // hardhat block timestamp is behind - const timeDifference = isTestingHardhat ? now - msBlockTimestamp : 0; + const timeDifference = connectedToHardhat ? now - msBlockTimestamp : 0; const commitTransactionConfirmedAt = msBlockTimestamp + timeDifference; const secs = differenceInSeconds(now, commitTransactionConfirmedAt); setSecondsSinceCommitConfirmed(secondsSinceCommitConfirmed < 0 ? 0 : secs); @@ -113,7 +115,7 @@ export default function useENSRegistrationStepHandler(observer = true) { shouldLoopForConfirmation.current = false; } return confirmed; - }, [observer, commitTransactionHash, isTestingHardhat, secondsSinceCommitConfirmed, dispatch]); + }, [observer, commitTransactionHash, connectedToHardhat, secondsSinceCommitConfirmed, dispatch]); const startPollingWatchCommitTransaction = useCallback(async () => { if (observer) return; @@ -166,7 +168,7 @@ export default function useENSRegistrationStepHandler(observer = true) { if (!observer && secondsSinceCommitConfirmed % 2 === 0 && secondsSinceCommitConfirmed >= ENS_SECONDS_WAIT && !readyToRegister) { const checkIfReadyToRegister = async () => { const readyToRegister = await checkRegisterBlockTimestamp({ - isTestingHardhat, + connectedToHardhat, registrationParameters, secondsSinceCommitConfirmed, }); @@ -174,7 +176,7 @@ export default function useENSRegistrationStepHandler(observer = true) { }; checkIfReadyToRegister(); } - }, [isTestingHardhat, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]); + }, [connectedToHardhat, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]); useEffect( () => () => { diff --git a/src/hooks/useENSSearch.ts b/src/hooks/useENSSearch.ts index b99378a9f0c..b492ad0749f 100644 --- a/src/hooks/useENSSearch.ts +++ b/src/hooks/useENSSearch.ts @@ -4,11 +4,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useAccountSettings, useENSLocalTransactions } from '.'; import { fetchRegistrationDate } from '@/handlers/ens'; import { ENS_DOMAIN, formatRentPrice, getAvailable, getENSRegistrarControllerContract, getNameExpires, getRentPrice } from '@/helpers/ens'; -import { Network } from '@/helpers/networkTypes'; import { timeUnits } from '@/references'; import { ethereumUtils, validateENS } from '@/utils'; +import { ChainId } from '@/networks/types'; -const formatTime = (timestamp: string, abbreviated: boolean = true) => { +const formatTime = (timestamp: string, abbreviated = true) => { const style = abbreviated ? 'MMM d, y' : 'MMMM d, y'; return format(new Date(Number(timestamp) * 1000), style); }; @@ -51,7 +51,7 @@ export default function useENSSearch({ yearsDuration = 1, name: inputName }: { y } const [isAvailable, rentPrice] = await Promise.all([getAvailable(name, contract), getRentPrice(name, duration, contract)]); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); const formattedRentPrice = formatRentPrice(rentPrice, yearsDuration, nativeCurrency, nativeAssetPrice); if (isAvailable) { diff --git a/src/hooks/useEffectDebugger.ts b/src/hooks/useEffectDebugger.ts index 85e742c5f57..7294d1b1e5d 100644 --- a/src/hooks/useEffectDebugger.ts +++ b/src/hooks/useEffectDebugger.ts @@ -1,12 +1,12 @@ -import logger from '@/utils/logger'; +import { logger } from '@/logger'; const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { // Edge-case: different array lengths if (oldInputs.length !== newInputs.length) { // Not helpful to compare item by item, so just output the whole array - logger.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs); - logger.log('Old inputs:', oldInputs); - logger.log('New inputs:', newInputs); + logger.debug(`[useEffectDebugger]: ${prefix} - Inputs have a different length`, oldInputs, newInputs); + logger.debug(`[useEffectDebugger]: Old inputs:`, oldInputs); + logger.debug(`[useEffectDebugger]: New inputs:`, newInputs); return; } @@ -14,9 +14,9 @@ const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { oldInputs.forEach((oldInput: any, index: any) => { const newInput = newInputs[index]; if (oldInput !== newInput) { - logger.log(`${prefix} - The input changed in position ${index}`); - logger.log('Old value:', oldInput); - logger.log('New value:', newInput); + logger.debug(`[useEffectDebugger]: ${prefix} - The input changed in position ${index}`); + logger.debug(`[useEffectDebugger]: Old value:`, oldInput); + logger.debug(`[useEffectDebugger]: New value:`, newInput); } }); }; diff --git a/src/hooks/useGas.ts b/src/hooks/useGas.ts index 224e25ebc8e..4c6c7f3c2ce 100644 --- a/src/hooks/useGas.ts +++ b/src/hooks/useGas.ts @@ -13,7 +13,6 @@ import { ParsedAddressAsset, SelectedGasFee, } from '@/entities'; -import networkTypes, { Network } from '@/helpers/networkTypes'; import { fromWei, greaterThan, greaterThanOrEqualTo } from '@/helpers/utilities'; import { gasPricesStartPolling, @@ -24,25 +23,24 @@ import { gasUpdateTxFee, } from '@/redux/gas'; import { ethereumUtils } from '@/utils'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { BNB_MAINNET_ADDRESS, ETH_ADDRESS, MATIC_MAINNET_ADDRESS } from '@/references'; import useAccountSettings from './useAccountSettings'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; -const checkSufficientGas = (txFee: LegacyGasFee | GasFee, network: Network, nativeAsset?: ParsedAddressAsset) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkSufficientGas = (txFee: LegacyGasFee | GasFee, chainId: ChainId, nativeAsset?: ParsedAddressAsset) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; - const chainId = ethereumUtils.getChainIdFromNetwork(network); - const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset(chainId); + const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset({ chainId }); const balanceAmount = networkNativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(txFeeValue?.value?.amount); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); return isSufficientGas; }; -const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -50,8 +48,8 @@ const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, net return isValidGas; }; -const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { - const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; +const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -88,7 +86,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit: string; selectedGasFee: SelectedGasFee; selectedGasFeeOption: string; - txNetwork: Network; + chainId: ChainId; l1GasFeeOptimism: string; } = useSelector( ({ @@ -100,7 +98,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit, l1GasFeeOptimism, selectedGasFee, - txNetwork, + chainId, }, }: AppState) => ({ currentBlockParams, @@ -111,29 +109,29 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss l1GasFeeOptimism, selectedGasFee, selectedGasFeeOption: selectedGasFee.option, - txNetwork, + chainId, }) ); const prevSelectedGasFee = usePrevious(gasData?.selectedGasFee); const isSufficientGas = useMemo( - () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset), - [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset] + () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset), + [gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset] ); const isValidGas = useMemo( - () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), - [gasData?.selectedGasFee, gasData?.txNetwork] + () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), + [gasData?.selectedGasFee, gasData?.chainId] ); const isGasReady = useMemo( - () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), - [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork] + () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), + [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId] ); const startPollingGasFees = useCallback( - (network = networkTypes.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(network, flashbots)), + (chainId = ChainId.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(chainId, flashbots)), [dispatch] ); const stopPollingGasFees = useCallback(() => dispatch(gasPricesStopPolling()), [dispatch]); @@ -153,12 +151,12 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss const getTotalGasPrice = useCallback(() => { const txFee = gasData?.selectedGasFee?.gasFee; - const isLegacyGasNetwork = getNetworkObj(gasData?.txNetwork).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObject({ chainId: gasData?.chainId }).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; const txFeeAmount = fromWei(txFeeValue?.value?.amount); return txFeeAmount; - }, [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork]); + }, [gasData?.selectedGasFee?.gasFee, gasData?.chainId]); return { isGasReady, diff --git a/src/hooks/useHideSplashScreen.ts b/src/hooks/useHideSplashScreen.ts index d9383a7b482..aff7dd0c43a 100644 --- a/src/hooks/useHideSplashScreen.ts +++ b/src/hooks/useHideSplashScreen.ts @@ -55,12 +55,12 @@ export default function useHideSplashScreen() { if (appIcon === 'poolboy') { const sound = new Sound(require('../assets/sounds/RainbowSega.mp3'), (error: any) => { if (error) { - logger.error(new RainbowError('Error playing poolboy sound')); + logger.error(new RainbowError('[useHideSplashScreen]: Error playing poolboy sound')); return; } sound.play((success: any) => { - logger.debug('playing poolboy sound'); + logger.debug('[useHideSplashScreen]: playing poolboy sound'); }); }); } diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 0e8c5368f5f..1523bae55c4 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -17,7 +17,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { PROFILES, useExperimentalFlag } from '@/config'; import { fetchReverseRecord } from '@/handlers/ens'; -import { getProviderForNetwork, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; +import { getProvider, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; import { isENSAddressFormat, isUnstoppableAddressFormat, isValidWallet } from '@/helpers/validators'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { walletInit } from '@/model/wallet'; @@ -25,13 +25,13 @@ import { Navigation, useNavigation } from '@/navigation'; import { walletsLoadState } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; import { sanitizeSeedPhrase } from '@/utils'; -import logger from '@/utils/logger'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger as Logger, RainbowError } from '@/logger'; +import { logger, RainbowError } from '@/logger'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils'; import walletBackupTypes from '@/helpers/walletBackupTypes'; +import { ChainId } from '@/networks/types'; export default function useImportingWallet({ showImportModal = true } = {}) { const { accountAddress } = useAccountSettings(); @@ -46,8 +46,8 @@ export default function useImportingWallet({ showImportModal = true } = {}) { const [name, setName] = useState(null); const [image, setImage] = useState(null); const [busy, setBusy] = useState(false); - const [checkedWallet, setCheckedWallet] = useState(null); - const [resolvedAddress, setResolvedAddress] = useState(null); + const [checkedWallet, setCheckedWallet] = useState> | null>(null); + const [resolvedAddress, setResolvedAddress] = useState(null); const wasImporting = usePrevious(isImporting); const { updateWalletENSAvatars } = useWalletENSAvatar(); const profilesEnabled = useExperimentalFlag(PROFILES); @@ -123,9 +123,9 @@ export default function useImportingWallet({ showImportModal = true } = {}) { // Validate ENS if (isENSAddressFormat(input)) { try { - const web3Provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const [address, avatar] = await Promise.all([ - web3Provider.resolveName(input), + provider.resolveName(input), !avatarUrl && profilesEnabled && fetchENSAvatar(input, { swallowError: true }), ]); if (!address) { @@ -133,7 +133,6 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_ens_name')); return; } - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; avatarUrl = avatarUrl || avatar?.imageUrl; @@ -157,7 +156,6 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_unstoppable_name')); return; } - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; setBusy(false); @@ -187,7 +185,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { input, }); } catch (e) { - logger.log(`Error resolving ENS during wallet import`, e); + logger.error(new RainbowError(`[useImportingWallet]: Error resolving ENS during wallet import: ${e}`)); } setBusy(false); // @ts-expect-error ts-migrate(2554) FIXME: Expected 4 arguments, but got 3. @@ -196,10 +194,9 @@ export default function useImportingWallet({ showImportModal = true } = {}) { try { setTimeout(async () => { const walletResult = await deriveAccountFromWalletInput(input); - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ address: string; isHDWallet: b... Remove this comment to see the full error message setCheckedWallet(walletResult); if (!walletResult.address) { - Logger.error(new RainbowError('useImportingWallet - walletResult address is undefined')); + logger.error(new RainbowError('[useImportingWallet]: walletResult address is undefined')); return null; } const ens = await fetchReverseRecord(walletResult.address); @@ -220,7 +217,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { }); }, 100); } catch (error) { - logger.log('Error looking up ENS for imported HD type wallet', error); + logger.error(new RainbowError(`[useImportingWallet]: Error looking up ENS for imported HD type wallet: ${error}`)); setBusy(false); } } @@ -337,7 +334,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { .catch(error => { handleSetImporting(false); android && handleSetImporting(false); - logger.error('error importing seed phrase: ', error); + logger.error(new RainbowError(`[useImportingWallet]: Error importing seed phrase: ${error}`)); setTimeout(() => { inputRef.current?.focus(); // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 0. diff --git a/src/hooks/useInitializeAccountData.ts b/src/hooks/useInitializeAccountData.ts index 0ac4db6d733..205d8fa42fa 100644 --- a/src/hooks/useInitializeAccountData.ts +++ b/src/hooks/useInitializeAccountData.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { InteractionManager } from 'react-native'; import { useDispatch } from 'react-redux'; import { explorerInit } from '../redux/explorer'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; export default function useInitializeAccountData() { const dispatch = useDispatch(); @@ -11,11 +11,11 @@ export default function useInitializeAccountData() { const initializeAccountData = useCallback(async () => { try { InteractionManager.runAfterInteractions(() => { - logger.sentry('Initialize account data'); + logger.debug('[useInitializeAccountData]: Initialize account data'); dispatch(explorerInit()); }); } catch (error) { - logger.sentry('Error initializing account data'); + logger.error(new RainbowError(`[useInitializeAccountData]: Error initializing account data: ${error}`)); captureException(error); } }, [dispatch]); diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts index c702ac8d61a..4ece4a1d2d5 100644 --- a/src/hooks/useInitializeWallet.ts +++ b/src/hooks/useInitializeWallet.ts @@ -60,19 +60,19 @@ export default function useInitializeWallet() { ) => { try { PerformanceTracking.startMeasuring(PerformanceMetrics.useInitializeWallet); - logger.debug('Start wallet setup'); + logger.debug('[useInitializeWallet]: Start wallet setup'); await resetAccountState(); - logger.debug('resetAccountState ran ok'); + logger.debug('[useInitializeWallet]: resetAccountState ran ok'); const isImporting = !!seedPhrase; - logger.debug('isImporting? ' + isImporting); + logger.debug(`[useInitializeWallet]: isImporting? ${isImporting}`); if (shouldRunMigrations && !seedPhrase) { - logger.debug('shouldRunMigrations && !seedPhrase? => true'); + logger.debug('[useInitializeWallet]: shouldRunMigrations && !seedPhrase? => true'); await dispatch(walletsLoadState(profilesEnabled)); - logger.debug('walletsLoadState call #1'); + logger.debug('[useInitializeWallet]: walletsLoadState call #1'); await runMigrations(); - logger.debug('done with migrations'); + logger.debug('[useInitializeWallet]: done with migrations'); } setIsSmallBalancesOpen(false); @@ -82,7 +82,7 @@ export default function useInitializeWallet() { const { isNew, walletAddress } = await walletInit(seedPhrase, color, name, overwrite, checkedWallet, network, image, silent); - logger.debug('walletInit returned', { + logger.debug('[useInitializeWallet]: walletInit returned', { isNew, walletAddress, }); @@ -94,12 +94,12 @@ export default function useInitializeWallet() { } if (seedPhrase || isNew) { - logger.debug('walletLoadState call #2'); + logger.debug('[useInitializeWallet]: walletsLoadState call #2'); await dispatch(walletsLoadState(profilesEnabled)); } if (isNil(walletAddress)) { - logger.debug('walletAddress is nil'); + logger.debug('[useInitializeWallet]: walletAddress is nil'); Alert.alert(lang.t('wallet.import_failed_invalid_private_key')); if (!isImporting) { dispatch(appStateUpdate({ walletReady: true })); @@ -109,18 +109,18 @@ export default function useInitializeWallet() { if (!(isNew || isImporting)) { await loadGlobalEarlyData(); - logger.debug('loaded global data...'); + logger.debug('[useInitializeWallet]: loaded global data...'); } await dispatch(settingsUpdateAccountAddress(walletAddress)); - logger.debug('updated settings address', { + logger.debug('[useInitializeWallet]: updated settings address', { walletAddress, }); // Newly created / imported accounts have no data in localstorage if (!(isNew || isImporting)) { await loadAccountData(); - logger.debug('loaded account data', { + logger.debug('[useInitializeWallet]: loaded account data', { network, }); } @@ -128,7 +128,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('Error while hiding splash screen'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { error: err, }); } @@ -136,7 +136,7 @@ export default function useInitializeWallet() { initializeAccountData(); dispatch(appStateUpdate({ walletReady: true })); - logger.debug('💰 Wallet initialized'); + logger.debug('[useInitializeWallet]: 💰 Wallet initialized'); PerformanceTracking.finishMeasuring(PerformanceMetrics.useInitializeWallet, { walletStatus: getWalletStatusForPerformanceMetrics(isNew, isImporting), @@ -145,7 +145,7 @@ export default function useInitializeWallet() { return walletAddress; } catch (error) { PerformanceTracking.clearMeasure(PerformanceMetrics.useInitializeWallet); - logger.error(new RainbowError('Error while initializing wallet'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while initializing wallet'), { error, }); // TODO specify error states more granular @@ -156,7 +156,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('Error while hiding splash screen'), { + logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { error: err, }); } diff --git a/src/hooks/useLedgerConnect.ts b/src/hooks/useLedgerConnect.ts index 4ac4888839d..103370bacb0 100644 --- a/src/hooks/useLedgerConnect.ts +++ b/src/hooks/useLedgerConnect.ts @@ -33,13 +33,13 @@ export function useLedgerConnect({ if (isReady) return; if (errorType === LEDGER_ERROR_CODES.DISCONNECTED) { setReadyForPolling(false); - logger.info('[LedgerConnect] - Device Disconnected - Attempting Reconnect', {}); + logger.debug('[useLedgerConnect]: Device Disconnected - Attempting Reconnect', {}); transport.current = undefined; try { transport.current = await TransportBLE.open(deviceId); setReadyForPolling(true); } catch (e) { - logger.error(new RainbowError('[LedgerConnect] - Reconnect Error'), { + logger.error(new RainbowError('[useLedgerConnect]: Reconnect Error'), { error: (e as Error).message, }); // temp removing this to see if it fixes an issue @@ -67,7 +67,7 @@ export function useLedgerConnect({ const pollerCleanup = (poller: NodeJS.Timer | undefined) => { try { if (poller) { - logger.debug('[LedgerConnect] - polling tear down', {}); + logger.debug('[useLedgerConnect]: polling tear down', {}); clearInterval(poller); poller?.unref(); timer.current = undefined; @@ -78,7 +78,7 @@ export function useLedgerConnect({ }; useEffect(() => { if (readyForPolling && (!timer.current || triggerPollerCleanup)) { - logger.debug('[LedgerConnect] - init device polling', {}); + logger.debug('[useLedgerConnect]: init device polling', {}); setTriggerPollerCleanup(false); timer.current = setInterval(async () => { if (transport.current) { diff --git a/src/hooks/useLedgerImport.ts b/src/hooks/useLedgerImport.ts index 611e39f0c99..074e4319a96 100644 --- a/src/hooks/useLedgerImport.ts +++ b/src/hooks/useLedgerImport.ts @@ -1,9 +1,8 @@ import TransportBLE from '@ledgerhq/react-native-hw-transport-ble'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { DebugContext } from '@/logger/debugContext'; import { logger, RainbowError } from '@/logger'; import { Subscription } from '@ledgerhq/hw-transport'; -import { Alert } from 'react-native'; import { checkAndRequestAndroidBluetooth, showBluetoothPermissionsAlert, showBluetoothPoweredOffAlert } from '@/utils/bluetoothPermissions'; import { IS_ANDROID, IS_IOS } from '@/env'; import { ledgerErrorHandler, LEDGER_ERROR_CODES } from '@/utils/ledger'; @@ -30,7 +29,7 @@ export function useLedgerImport({ */ const handlePairError = useCallback( (error: Error) => { - logger.error(new RainbowError('[LedgerImport] - Pairing Error'), { + logger.error(new RainbowError('[useLedgerImport]: Pairing Error'), { error, }); errorCallback?.(ledgerErrorHandler(error)); @@ -43,7 +42,7 @@ export function useLedgerImport({ */ const handlePairSuccess = useCallback( (deviceId: string) => { - logger.debug('[LedgerImport] - Pairing Success', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Pairing Success', {}, DebugContext.ledger); successCallback?.(deviceId); handleCleanUp(); }, @@ -59,15 +58,15 @@ export function useLedgerImport({ const newObserver = TransportBLE.observeState({ // havnt seen complete or error fire yet but its in the docs so keeping for reporting purposes complete: (e: any) => { - logger.debug('[LedgerImport] Observer complete', { e }, DebugContext.ledger); + logger.debug('[useLedgerImport]: Observer complete', { e }, DebugContext.ledger); }, error: (e: any) => { - logger.debug('[LedgerImport] Observer error ', { e }, DebugContext.ledger); + logger.debug('[useLedgerImport]: Observer error ', { e }, DebugContext.ledger); }, next: async (e: any) => { // App is not authorized to use Bluetooth if (e.type === 'Unauthorized') { - logger.debug('[LedgerImport] - Bluetooth Unauthorized', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Bluetooth Unauthorized', {}, DebugContext.ledger); if (IS_IOS) { await showBluetoothPermissionsAlert(); } else { @@ -76,14 +75,14 @@ export function useLedgerImport({ } // Bluetooth is turned off if (e.type === 'PoweredOff') { - logger.debug('[LedgerImport] - Bluetooth Powered Off', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: Bluetooth Powered Off', {}, DebugContext.ledger); await showBluetoothPoweredOffAlert(); } if (e.available) { const newListener = TransportBLE.listen({ complete: () => {}, error: error => { - logger.error(new RainbowError('[Ledger Import] - Error Pairing'), { errorMessage: (error as Error).message }); + logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (error as Error).message }); }, next: async e => { if (e.type === 'add') { @@ -119,10 +118,10 @@ export function useLedgerImport({ useEffect(() => { const asyncFn = async () => { - logger.debug('[LedgerImport] - init device polling', {}, DebugContext.ledger); + logger.debug('[useLedgerImport]: init device polling', {}, DebugContext.ledger); const isBluetoothEnabled = IS_ANDROID ? await checkAndRequestAndroidBluetooth() : true; - logger.debug('[LedgerImport] - bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); + logger.debug('[useLedgerImport]: bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); if (isBluetoothEnabled) { searchAndPair(); diff --git a/src/hooks/useLoadAccountData.ts b/src/hooks/useLoadAccountData.ts index ecfd441cf48..bb74891d0c7 100644 --- a/src/hooks/useLoadAccountData.ts +++ b/src/hooks/useLoadAccountData.ts @@ -5,12 +5,12 @@ import { requestsLoadState } from '../redux/requests'; import { showcaseTokensLoadState } from '../redux/showcaseTokens'; import { walletConnectLoadState } from '../redux/walletconnect'; import { promiseUtils } from '../utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; export default function useLoadAccountData() { const dispatch = useDispatch(); const loadAccountData = useCallback(async () => { - logger.sentry('Load wallet account data'); + logger.debug('[useLoadAccountData]: Load wallet account data'); await dispatch(showcaseTokensLoadState()); await dispatch(hiddenTokensLoadState()); const promises = []; diff --git a/src/hooks/useLoadAccountLateData.ts b/src/hooks/useLoadAccountLateData.ts index 68d84bd34f2..e438b081072 100644 --- a/src/hooks/useLoadAccountLateData.ts +++ b/src/hooks/useLoadAccountLateData.ts @@ -3,7 +3,7 @@ import { promiseUtils } from '../utils'; import { prefetchAccountENSDomains } from './useAccountENSDomains'; import useAccountSettings from './useAccountSettings'; import useWallets from './useWallets'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { ensRegistrationsLoadState } from '@/redux/ensRegistration'; import { useDispatch } from 'react-redux'; import { showcaseTokensUpdateStateFromWeb } from '@/redux/showcaseTokens'; @@ -15,7 +15,7 @@ export default function useLoadAccountLateData() { const dispatch = useDispatch(); const loadAccountLateData = useCallback(async () => { - logger.sentry('Load wallet account late data'); + logger.debug('[useLoadAccountLateData]: Load wallet account late data'); const promises = []; diff --git a/src/hooks/useLoadGlobalEarlyData.ts b/src/hooks/useLoadGlobalEarlyData.ts index 8427c958ef0..fab0b4b3d0c 100644 --- a/src/hooks/useLoadGlobalEarlyData.ts +++ b/src/hooks/useLoadGlobalEarlyData.ts @@ -3,13 +3,13 @@ import { useDispatch } from 'react-redux'; import { settingsLoadLanguage, settingsLoadState } from '@/redux/settings'; import { promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; export default function useLoadGlobalEarlyData() { const dispatch = useDispatch(); const loadGlobalData = useCallback(async () => { - logger.sentry('Load wallet global early data'); + logger.debug('[useLoadGlobalEarlyData]: Load wallet global early data'); const promises = []; // native currency, app icon, testnetsEnabled, flashbotsEnabled diff --git a/src/hooks/useLoadGlobalLateData.ts b/src/hooks/useLoadGlobalLateData.ts index c4d9d41fcfa..7ad42460955 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -6,7 +6,7 @@ import { keyboardHeightsLoadState } from '@/redux/keyboardHeight'; import { AppState } from '@/redux/store'; import { transactionSignaturesLoadState } from '@/redux/transactionSignatures'; import { promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -21,7 +21,7 @@ export default function useLoadGlobalLateData() { if (!walletReady) { return false; } - logger.sentry('Load wallet global late data'); + logger.debug('[useLoadGlobalLateData]: Load wallet global late data'); const promises = []; // mainnet eth balances for all wallets diff --git a/src/hooks/useManageCloudBackups.ts b/src/hooks/useManageCloudBackups.ts index 0d39ca141d5..141f26b7f4e 100644 --- a/src/hooks/useManageCloudBackups.ts +++ b/src/hooks/useManageCloudBackups.ts @@ -20,7 +20,7 @@ export default function useManageCloudBackups() { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`Error Fetching google account data for Backups Section`), { + logger.error(new RainbowError(`[useManageCloudBackups]: Error Fetching google account data for Backups Section`), { error: (error as Error).message, }); }); @@ -54,7 +54,7 @@ export default function useManageCloudBackups() { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`Logging into Google Drive failed.`), { + logger.error(new RainbowError(`[useManageCloudBackups]: Logging into Google Drive failed.`), { error: (error as Error).message, }); } diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index 4c7323bcf24..bda2d6f20f5 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -1,5 +1,5 @@ import lang from 'i18n-js'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback } from 'react'; import { Linking } from 'react-native'; import { ImageOrVideo } from 'react-native-image-crop-picker'; import { useDispatch } from 'react-redux'; @@ -139,7 +139,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { const isReadOnly = isReadOnlyWallet && !enableActionsOnReadOnlyWallet; const isENSProfile = profilesEnabled && profileEnabled && isOwner; - const isZeroETH = isZero(accountAsset?.balance?.amount); + const isZeroETH = isZero(accountAsset?.balance?.amount || 0); const callback = useCallback( async (buttonIndex: number) => { diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts index f6789c38b76..b077d816a9b 100644 --- a/src/hooks/usePriceImpactDetails.ts +++ b/src/hooks/usePriceImpactDetails.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import { SwappableAsset } from '@/entities'; -import { Network } from '@/helpers'; import { useTheme } from '@/theme'; import { @@ -14,9 +13,9 @@ import { } from '@/helpers/utilities'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; -import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; +import { useNativeAsset } from '@/utils/ethereumUtils'; import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum SwapPriceImpactType { none = 'none', diff --git a/src/hooks/useRainbowFee.js b/src/hooks/useRainbowFee.js index b80d489a6be..6cc0d416bef 100644 --- a/src/hooks/useRainbowFee.js +++ b/src/hooks/useRainbowFee.js @@ -47,7 +47,7 @@ export default function useRainbowFee({ tradeDetails, chainId }) { useEffect(() => { const getNativeAsset = async () => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); setNativeAsset(nativeAsset); }; !nativeAsset && getNativeAsset(); diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index 8b9a5f1d839..67897f81319 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,13 +1,11 @@ -import { captureException } from '@sentry/react-native'; import delay from 'delay'; import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { getIsHardhatConnected } from '@/handlers/web3'; import { walletConnectLoadState } from '../redux/walletconnect'; import { fetchWalletENSAvatars, fetchWalletNames } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; import { PROFILES, useExperimentalFlag } from '@/config'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; @@ -15,26 +13,24 @@ import { invalidateAddressNftsQueries } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { Address } from 'viem'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; -import { useNftSort } from './useNFTsSortBy'; import useWallets from './useWallets'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export default function useRefreshAccountData() { const dispatch = useDispatch(); const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); - const { nftSort, nftSortDirection } = useNftSort(); + const { connectedToHardhat } = useConnectedToHardhatStore(); const { wallets } = useWallets(); const allAddresses = useMemo( - () => Object.values(wallets || {}).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), + () => Object.values(wallets || {}).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), [wallets] ); const fetchAccountData = useCallback(async () => { - const connectedToHardhat = getIsHardhatConnected(); - invalidateAddressNftsQueries(accountAddress); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); @@ -54,11 +50,10 @@ export default function useRefreshAccountData() { wc, ]); } catch (error) { - logger.log('Error refreshing data', error); - captureException(error); + logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`)); throw error; } - }, [accountAddress, allAddresses, dispatch, nativeCurrency, nftSort, nftSortDirection, profilesEnabled]); + }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, profilesEnabled]); const refresh = useCallback(async () => { if (isRefreshing) return; @@ -67,8 +62,8 @@ export default function useRefreshAccountData() { try { await fetchAccountData(); - } catch (e) { - logger.error(e); + } catch (error) { + logger.error(new RainbowError(`[useRefreshAccountData]: Error calling fetchAccountData: ${error}`)); } finally { setIsRefreshing(false); } diff --git a/src/hooks/useSafeImageUri.ts b/src/hooks/useSafeImageUri.ts index a08ef6153b9..f76b99e0264 100644 --- a/src/hooks/useSafeImageUri.ts +++ b/src/hooks/useSafeImageUri.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { maybeSignUri } from '../handlers/imgix'; -export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching: boolean = false): string | undefined { +export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching = false): string | undefined { return useMemo(() => { return maybeSignUri(maybeUnsafeUri, {}, skipCaching); }, [maybeUnsafeUri, skipCaching]); diff --git a/src/hooks/useScanner.ts b/src/hooks/useScanner.ts index 02406c9bc24..7a50df6b8f8 100644 --- a/src/hooks/useScanner.ts +++ b/src/hooks/useScanner.ts @@ -16,7 +16,7 @@ import { Navigation } from '@/navigation'; import { POAP_BASE_URL, RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { addressUtils, ethereumUtils, haptics } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { checkPushNotificationPermissions } from '@/notifications/permissions'; import { pair as pairWalletConnect } from '@/walletConnect'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; @@ -28,12 +28,12 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { const enabledVar = useRef(); const enableScanning = useCallback(() => { - logger.log('📠✅ Enabling QR Code Scanner'); + logger.debug('[useScanner]: 📠✅ Enabling QR Code Scanner'); enabledVar.current = true; }, [enabledVar]); const disableScanning = useCallback(() => { - logger.log('📠🚫 Disabling QR Code Scanner'); + logger.debug('[useScanner]: 📠🚫 Disabling QR Code Scanner'); enabledVar.current = false; }, [enabledVar]); @@ -113,8 +113,8 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { } else if (version === 2) { await pairWalletConnect({ uri, connector }); } - } catch (e) { - logger.log('walletConnectOnSessionRequest exception', e); + } catch (error) { + logger.error(new RainbowError(`[useScanner]: Error handling WalletConnect QR code: ${error}`)); } }, [goBack, onSuccess, walletConnectOnSessionRequest] @@ -190,14 +190,14 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { if (lowerCaseData.startsWith(`${RAINBOW_PROFILES_BASE_URL}/poap`)) { const secretWordOrQrHash = lowerCaseData.split(`${RAINBOW_PROFILES_BASE_URL}/poap/`)?.[1]; - logger.log('onScan: handling poap scan', { secretWordOrQrHash }); + logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } if (lowerCaseData.startsWith(`rainbow://poap`)) { const secretWordOrQrHash = lowerCaseData.split(`rainbow://poap/`)?.[1]; - logger.log('onScan: handling poap scan', { secretWordOrQrHash }); + logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 554fe14cfb6..16c8bf2149d 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -11,13 +11,14 @@ import { tokenSearch } from '@/handlers/tokenSearch'; import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; +import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; +import { logger } from '@/logger'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type swapCurrencyListType = | 'verifiedAssets' @@ -85,7 +86,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); const [verifiedAssets, setVerifiedAssets] = useState([]); - const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ [ChainId.mainnet]: [], @@ -200,8 +200,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } as RainbowToken, ]; } catch (e) { - logger.log('error getting token data'); - logger.log(e); + logger.warn('[useSearchCurrencyList]: error getting token data', { error: (e as Error).message }); return null; } } @@ -387,7 +386,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -430,7 +429,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), diff --git a/src/hooks/useSendableUniqueTokens.ts b/src/hooks/useSendableUniqueTokens.ts index 331f6b2042f..7e9341676df 100644 --- a/src/hooks/useSendableUniqueTokens.ts +++ b/src/hooks/useSendableUniqueTokens.ts @@ -11,7 +11,7 @@ export default function useSendableUniqueTokens() { const sendableUniqueTokens = uniqueTokens?.filter((uniqueToken: any) => uniqueToken.isSendable); const grouped = groupBy(sendableUniqueTokens, token => token.familyName); const families = Object.keys(grouped).sort(); - let sendableTokens = []; + const sendableTokens = []; for (let i = 0; i < families.length; i++) { let newObject = {}; newObject = { diff --git a/src/hooks/useStepper.ts b/src/hooks/useStepper.ts index 672e8e237f5..6ac07f73523 100644 --- a/src/hooks/useStepper.ts +++ b/src/hooks/useStepper.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -export default function useStepper(max: number, initialIndex: number = 0) { +export default function useStepper(max: number, initialIndex = 0) { const [step, setStep] = useState(initialIndex); const nextStep = useCallback(() => setStep(p => (p + 1) % max), [max]); return [step, nextStep, setStep]; diff --git a/src/hooks/useSwapCurrencyHandlers.ts b/src/hooks/useSwapCurrencyHandlers.ts index 3fce074a3cb..9b835ff7a2d 100644 --- a/src/hooks/useSwapCurrencyHandlers.ts +++ b/src/hooks/useSwapCurrencyHandlers.ts @@ -119,8 +119,6 @@ export default function useSwapCurrencyHandlers({ } : null; - // prefetchExternalToken({address: newInputCurrency.address, network: newInputCurrency.network, currency: nativeCurrency}) - dispatch(updateSwapInputCurrency(newInputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newInputCurrency); @@ -136,7 +134,6 @@ export default function useSwapCurrencyHandlers({ } : null; - // prefetchExternalToken({address: newOutputCurrency.address, network: newOutputCurrency.network, currency: nativeCurrency}) dispatch(updateSwapOutputCurrency(newOutputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newOutputCurrency); diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index a987758763e..02bd432f880 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -6,21 +6,20 @@ import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; -import { RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; +import { RainbowToken, TokenSearchTokenListId } from '@/entities'; import { swapSearch } from '@/handlers/tokenSearch'; -import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; +import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; +import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; -import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { logger } from '@/logger'; +import { ChainId, Network } from '@/networks/types'; -const MAINNET_CHAINID = 1; type swapCurrencyListType = | 'verifiedAssets' | 'highLiquidityAssets' @@ -29,13 +28,10 @@ type swapCurrencyListType = | 'curatedAssets' | 'importedAssets'; -type CrosschainVerifiedAssets = { - [Network.mainnet]: RT[]; - [Network.optimism]: RT[]; - [Network.polygon]: RT[]; - [Network.bsc]: RT[]; - [Network.arbitrum]: RT[]; -}; +type CrosschainVerifiedAssets = Record< + ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.bsc | ChainId.arbitrum, + RainbowToken[] +>; const abcSort = (list: any[], key?: string) => { return list.sort((a, b) => { @@ -46,16 +42,16 @@ const abcSort = (list: any[], key?: string) => { const searchCurrencyList = async (searchParams: { chainId: number; fromChainId?: number | ''; - searchList: RT[] | TokenSearchTokenListId; + searchList: RainbowToken[] | TokenSearchTokenListId; query: string; }) => { const { searchList, query, chainId, fromChainId } = searchParams; const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - const keys: (keyof RT)[] = isAddress ? ['address'] : ['symbol', 'name']; + const keys: (keyof RainbowToken)[] = isAddress ? ['address'] : ['symbol', 'name']; const formattedQuery = isAddress ? addHexPrefix(query).toLowerCase() : query; if (typeof searchList === 'string') { const threshold = isAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'; - if (chainId === MAINNET_CHAINID && !formattedQuery && searchList !== 'verifiedAssets') { + if (chainId === ChainId.mainnet && !formattedQuery && searchList !== 'verifiedAssets') { return []; } return swapSearch({ @@ -73,10 +69,10 @@ const searchCurrencyList = async (searchParams: { } }; -const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINID, isDiscover = false) => { +const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainnet, isDiscover = false) => { const previousChainId = usePrevious(searchChainId); - const searching = useMemo(() => searchQuery !== '' || MAINNET_CHAINID !== searchChainId, [searchChainId, searchQuery]); + const searching = useMemo(() => searchQuery !== '' || ChainId.mainnet !== searchChainId, [searchChainId, searchQuery]); const { favorites: favoriteAddresses, favoritesMetadata: favoriteMap } = useFavorites(); @@ -84,24 +80,24 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const unfilteredFavorites = Object.values(favoriteMap); const [loading, setLoading] = useState(true); - const [favoriteAssets, setFavoriteAssets] = useState([]); - const [importedAssets, setImportedAssets] = useState([]); - const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); - const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); - const [verifiedAssets, setVerifiedAssets] = useState([]); + const [favoriteAssets, setFavoriteAssets] = useState([]); + const [importedAssets, setImportedAssets] = useState([]); + const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); + const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); + const [verifiedAssets, setVerifiedAssets] = useState([]); const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ - [Network.mainnet]: [], - [Network.optimism]: [], - [Network.polygon]: [], - [Network.bsc]: [], - [Network.arbitrum]: [], + [ChainId.mainnet]: [], + [ChainId.optimism]: [], + [ChainId.polygon]: [], + [ChainId.bsc]: [], + [ChainId.arbitrum]: [], }); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyNetwork = usePrevious(inputCurrency?.network); - const inputChainId = useMemo(() => ethereumUtils.getChainIdFromNetwork(inputCurrency?.network), [inputCurrency?.network]); + const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); + const inputChainId = inputCurrency?.chainId; const isCrosschainSearch = useMemo(() => { if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { return true; @@ -113,17 +109,16 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI [favoriteAddresses] ); const handleSearchResponse = useCallback( - (tokens: RT[], crosschainNetwork?: Network) => { + (tokens: RainbowToken[], chainId?: ChainId) => { // These transformations are necessary for L2 tokens to match our spec - const activeChainId = crosschainNetwork ? ethereumUtils.getChainIdFromNetwork(crosschainNetwork) : searchChainId; + const activeChainId = chainId ? chainId : searchChainId; return (tokens || []) .map(token => { token.address = token.networks?.[activeChainId]?.address || token.address; - const network = crosschainNetwork || ethereumUtils.getNetworkFromChainId(searchChainId); - token.network = network; - if (token.networks[MAINNET_CHAINID]) { - token.mainnet_address = token.networks[MAINNET_CHAINID].address; + token.chainId = activeChainId; + if (token.networks[ChainId.mainnet]) { + token.mainnet_address = token.networks[ChainId.mainnet].address; } token.uniqueId = getUniqueId(token.address, activeChainId); @@ -158,6 +153,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI return { ...token, network: Network.mainnet, + chainId: ChainId.mainnet, uniqueId: getUniqueId(token.address, ChainId.mainnet), }; }); @@ -174,15 +170,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }, [searchChainId, searchQuery, searching, unfilteredFavorites]); const getImportedAsset = useCallback( - async (searchQuery: string, chainId: number): Promise => { + async (searchQuery: string, chainId: number): Promise => { if (searching) { if (isAddress(searchQuery)) { const tokenListEntry = rainbowTokenList.RAINBOW_TOKEN_LIST[searchQuery.toLowerCase()]; if (tokenListEntry) { return [tokenListEntry]; } - const network = ethereumUtils.getNetworkFromChainId(chainId); - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); const tokenContract = new Contract(searchQuery, erc20ABI, provider); try { const [name, symbol, decimals, address] = await Promise.all([ @@ -191,10 +186,11 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI tokenContract.decimals(), getAddress(searchQuery), ]); - const uniqueId = `${address}_${network}`; + const uniqueId = getUniqueId(address, chainId); return [ { address, + chainId, decimals, favorite: false, highLiquidity: false, @@ -208,13 +204,12 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }, }, symbol, - network, + network: ethereumUtils.getNetworkFromChainId(chainId), uniqueId, } as RainbowToken, ]; } catch (e) { - logger.log('error getting token data'); - logger.log(e); + logger.warn('[useSwapCurrencyList]: error getting token data', { error: (e as Error).message }); return null; } } @@ -225,18 +220,17 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI ); const getCrosschainVerifiedAssetsForNetwork = useCallback( - async (network: Network) => { - const crosschainId = ethereumUtils.getChainIdFromNetwork(network); - const fromChainId = inputChainId !== crosschainId ? inputChainId : ''; + async (chainId: ChainId) => { + const fromChainId = inputChainId !== chainId ? inputChainId : ''; const results = await searchCurrencyList({ searchList: 'verifiedAssets', query: '', - chainId: crosschainId, + chainId, fromChainId, }); setCrosschainVerifiedAssets(state => ({ ...state, - [network]: handleSearchResponse(results, network), + [chainId]: handleSearchResponse(results, chainId), })); }, [handleSearchResponse, inputChainId] @@ -244,8 +238,8 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const getCrosschainVerifiedAssets = useCallback(async () => { const crosschainAssetRequests: Promise[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(network as Network)); + Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { + crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(Number(chainIdKey))); }); await Promise.all(crosschainAssetRequests); }, [crosschainVerifiedAssets, getCrosschainVerifiedAssetsForNetwork]); @@ -306,7 +300,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI const search = useCallback(async () => { const categories: swapCurrencyListType[] = - searchChainId === MAINNET_CHAINID + searchChainId === ChainId.mainnet ? ['favoriteAssets', 'highLiquidityAssets', 'verifiedAssets', 'importedAssets'] : ['verifiedAssets', 'importedAssets']; setLoading(true); @@ -351,9 +345,9 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI (searching && !wasSearching) || (searching && previousSearchQuery !== searchQuery) || searchChainId !== previousChainId || - inputCurrency?.network !== previousInputCurrencyNetwork + inputCurrency?.chainId !== previousInputCurrencyChainId ) { - if (searchChainId === MAINNET_CHAINID) { + if (searchChainId === ChainId.mainnet) { search(); slowSearch(); } else { @@ -368,14 +362,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }; doSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.network]); + }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); const { colors } = useTheme(); const currencyList = useMemo(() => { const list = []; let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.network !== inputCurrency?.network) + ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) : null; if (searching) { const importedAsset = importedAssets?.[0]; @@ -400,14 +394,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), }); } } - if (favoriteAssets?.length && searchChainId === MAINNET_CHAINID) { + if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { list.push({ color: colors.yellowFavorite, data: abcSort(favoriteAssets, 'name'), @@ -438,12 +432,12 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI }); } } else { - const curatedAssets = searchChainId === MAINNET_CHAINID && getCurated(); + const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.network], + color: colors.networkColors[bridgeAsset.chainId], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -469,39 +463,39 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINI } return list; }, [ + isCrosschainSearch, + verifiedAssets, searching, + inputCurrency?.name, + inputCurrency?.chainId, importedAssets, - favoriteAssets, - verifiedAssets, highLiquidityAssets, lowLiquidityAssets, - colors.yellowFavorite, - unfilteredFavorites, - searchChainId, - getCurated, isFavorite, - inputCurrency?.name, + favoriteAssets, + searchChainId, colors.networkColors, - isCrosschainSearch, - inputCurrency?.network, + colors.yellowFavorite, + getCurated, + unfilteredFavorites, ]); const crosschainExactMatches = useMemo(() => { if (currencyList.length) return []; if (!searchQuery) return []; - const exactMatches: RT[] = []; - Object.keys(crosschainVerifiedAssets).forEach(network => { - const currentNetworkChainId = ethereumUtils.getChainIdFromNetwork(network as Network); - if (currentNetworkChainId !== searchChainId) { + const exactMatches: RainbowToken[] = []; + Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { + const chainId = Number(chainIdKey); + if (chainId !== searchChainId) { // including goerli in our networks type is causing this type issue // @ts-ignore - const exactMatch = crosschainVerifiedAssets[network as Network].find((asset: RT) => { + const exactMatch = crosschainVerifiedAssets[chainId].find((asset: RainbowToken) => { const symbolMatch = isLowerCaseMatch(asset?.symbol, searchQuery); const nameMatch = isLowerCaseMatch(asset?.name, searchQuery); return symbolMatch || nameMatch; }); if (exactMatch) { - exactMatches.push({ ...exactMatch, network }); + exactMatches.push({ ...exactMatch, chainId }); } } }); diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index fa8db8c1b63..dfcd744ea3c 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -65,20 +65,20 @@ const getInputAmount = async ( if (!inputToken || !outputAmount || isZero(outputAmount) || !outputToken) return null; try { - const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken?.network); + const outputChainId = outputToken.chainId; - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken?.network); + const inputChainId = inputToken.chainId; - const inputTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; + const inputTokenAddress = isNativeAsset(inputToken.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken.address; - const outputTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; + const outputTokenAddress = isNativeAsset(outputToken.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken.address; const isCrosschainSwap = inputChainId !== outputChainId; if (isCrosschainSwap) return null; const buyAmount = convertAmountToRawAmount(convertNumberToString(outputAmount), outputToken.decimals); - logger.info(`[getInputAmount]: `, { + logger.debug('[useSwapDerivedOutputs]: ', { outputToken, outputChainId, outputNetwork: outputToken?.network, @@ -105,7 +105,7 @@ const getInputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[getInputAmount]: Getting quote', { rand, quoteParams }); + logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote @@ -115,7 +115,7 @@ const getInputAmount = async ( if (!quote || (quote as QuoteError).error || !(quote as Quote).sellAmount) { if ((quote as QuoteError).error) { const quoteError = quote as unknown as QuoteError; - logger.error(new RainbowError('[getInputAmount]: Quote error'), { + logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -164,24 +164,22 @@ const getOutputAmount = async ( if (!inputAmount || isZero(inputAmount) || !outputToken) return null; try { - const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken.network); + const outputChainId = outputToken.chainId; const buyTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken.network); + const inputChainId = inputToken.chainId; const sellTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); const isCrosschainSwap = outputChainId !== inputChainId; - // logger.info(`[getOutputAmount]: `, { - // outputToken, - // outputChainId, - // outputNetwork, - // inputToken, - // inputChainId, - // inputNetwork, - // isCrosschainSwap, - // }); + logger.debug(`[useSwapDerivedOutputs]: `, { + outputToken, + outputChainId, + inputToken, + inputChainId, + isCrosschainSwap, + }); const quoteSource = getSource(source); const quoteParams: QuoteParams = { @@ -200,16 +198,16 @@ const getOutputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[getOutputAmount]: Getting quote', { rand, quoteParams }); + logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote const quote: Quote | CrosschainQuote | QuoteError | null = await (isCrosschainSwap ? getCrosschainQuote : getQuote)(quoteParams); - logger.debug('[getOutputAmount]: Got quote', { rand, quote }); + logger.debug('[useSwapDerivedOutputs]: Got quote', { rand, quote }); if (!quote || (quote as QuoteError)?.error || !(quote as Quote)?.buyAmount) { const quoteError = quote as QuoteError; if (quoteError.error) { - logger.error(new RainbowError('[getOutputAmount]: Quote error'), { + logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -329,7 +327,7 @@ export default function useSwapDerivedOutputs(type: string) { }; } - logger.debug('[getTradeDetails]: Getting trade details', { + logger.debug('[useSwapDerivedOutputs]: Getting trade details', { independentField, independentValue, inputCurrency, @@ -450,7 +448,7 @@ export default function useSwapDerivedOutputs(type: string) { tradeDetails, }; - logger.debug('[getTradeDetails]: Got trade details', { + logger.debug('[useSwapDerivedOutputs]: Got trade details', { data, }); diff --git a/src/hooks/useSwapInputHandlers.ts b/src/hooks/useSwapInputHandlers.ts index b6c4478bae2..ac7684de5c2 100644 --- a/src/hooks/useSwapInputHandlers.ts +++ b/src/hooks/useSwapInputHandlers.ts @@ -19,12 +19,12 @@ export default function useSwapInputHandlers() { const updateMaxInputAmount = useCallback(() => { const inputCurrencyAddress = inputCurrency?.address; const inputCurrencyUniqueId = inputCurrency?.uniqueId; - const inputCurrencyNetwork = inputCurrency?.network; + const inputCurrencyChainId = inputCurrency?.chainId; const accountAsset = ethereumUtils.getAccountAsset(inputCurrencyUniqueId); const oldAmount = accountAsset?.balance?.amount ?? '0'; let newAmount = oldAmount; - if (isNativeAsset(inputCurrencyAddress, ethereumUtils.getChainIdFromNetwork(inputCurrencyNetwork)) && accountAsset) { + if (isNativeAsset(inputCurrencyAddress, inputCurrencyChainId) && accountAsset) { // this subtracts gas from the balance of the asset newAmount = toFixedDecimals(ethereumUtils.getBalanceAmount(selectedGasFee, accountAsset, l1GasFeeOptimism), 6); @@ -39,7 +39,7 @@ export default function useSwapInputHandlers() { } } dispatch(updateSwapInputAmount(newAmount, true)); - }, [dispatch, inputCurrency?.address, inputCurrency?.network, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); + }, [dispatch, inputCurrency?.address, inputCurrency?.chainId, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); const updateInputAmount = useCallback( (value: string | null) => { diff --git a/src/hooks/useSwapRefuel.ts b/src/hooks/useSwapRefuel.ts index 488edca8bdc..d2455ee51ee 100644 --- a/src/hooks/useSwapRefuel.ts +++ b/src/hooks/useSwapRefuel.ts @@ -7,8 +7,8 @@ import { useEffect, useMemo, useState } from 'react'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { useAccountSettings, useGas } from '.'; import { isNativeAsset } from '@/handlers/assets'; -import { NetworkTypes } from '@/helpers'; import { toWei } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; export enum RefuelState { 'Add' = 'Add', @@ -32,22 +32,17 @@ export default function useSwapRefuel({ const [outputNativeAsset, setOutputNativeAsset] = useState(); const [inputNativeAsset, setInputNativeAsset] = useState(); - const { inputNetwork, outputNetwork, chainId, toChainId, isCrosschainSwap } = useMemo(() => { - const inputNetwork = inputCurrency.network; - const outputNetwork = outputCurrency.network; - const chainId = ethereumUtils.getChainIdFromNetwork(inputNetwork); - - const toChainId = ethereumUtils.getChainIdFromNetwork(outputNetwork); - const isCrosschainSwap = crosschainSwapsEnabled && inputNetwork !== outputNetwork; + const { chainId, toChainId, isCrosschainSwap } = useMemo(() => { + const chainId = inputCurrency.chainId; + const toChainId = outputCurrency.chainId; + const isCrosschainSwap = crosschainSwapsEnabled && chainId !== toChainId; return { - inputNetwork, - outputNetwork, chainId, toChainId, isCrosschainSwap, }; - }, [crosschainSwapsEnabled, inputCurrency.network, outputCurrency.network]); + }, [crosschainSwapsEnabled, inputCurrency.chainId, outputCurrency.chainId]); const { data: minRefuelAmount } = useMinRefuelAmount( { @@ -59,14 +54,14 @@ export default function useSwapRefuel({ useEffect(() => { const getNativeInputOutputAssets = async () => { - if (!outputNetwork || !inputNetwork || !accountAddress) return; - const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(toChainId, accountAddress); - const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); + if (!chainId || !toChainId || !accountAddress) return; + const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId: toChainId, address: accountAddress }); + const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); setOutputNativeAsset(outputNativeAsset); setInputNativeAsset(inputNativeAsset); }; getNativeInputOutputAssets(); - }, [outputNetwork, inputNetwork, accountAddress, toChainId, chainId]); + }, [accountAddress, toChainId, chainId]); const { showRefuelSheet, refuelState } = useMemo(() => { const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, toChainId); @@ -79,7 +74,7 @@ export default function useSwapRefuel({ return { showRefuelSheet: false, refuelState: null }; } // If we are swapping to mainnet then ignore - if (outputNetwork === NetworkTypes.mainnet) return { showRefuelSheet: false, refuelState: null }; + if (toChainId === ChainId.mainnet) return { showRefuelSheet: false, refuelState: null }; // Does the user have an existing balance on the output native asset const hasZeroOutputNativeAssetBalance = isZero(outputNativeAsset?.balance?.amount || 0); @@ -130,7 +125,6 @@ export default function useSwapRefuel({ minRefuelAmount, outputCurrency?.address, outputNativeAsset?.balance?.amount, - outputNetwork, selectedGasFee?.gasFee?.estimatedFee?.value?.amount, toChainId, tradeDetails?.sellAmount, diff --git a/src/hooks/useSwappableUserAssets.ts b/src/hooks/useSwappableUserAssets.ts index f83282af7bd..7e4338f042d 100644 --- a/src/hooks/useSwappableUserAssets.ts +++ b/src/hooks/useSwappableUserAssets.ts @@ -1,13 +1,13 @@ import { SwappableAsset } from '@/entities'; import { walletFilter } from '@/handlers/tokenSearch'; -import { Network } from '@/helpers'; import { useCoinListEditOptions } from '@/hooks'; import { ETH_ADDRESS } from '@/references'; import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import { EthereumAddress, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS } from '@rainbow-me/swaps'; import { ethereumUtils } from '@/utils'; import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { RainbowNetworks, getNetworkObj, getSwappableNetworks } from '@/networks'; +import { RainbowNetworkObjects, getNetworkObject, getSwappableNetworks } from '@/networks'; +import { Network } from '@/networks/types'; type SwappableAddresses = Record; @@ -29,8 +29,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset if (hiddenCoinsObj[asset.uniqueId]) return true; // filter out networks where swaps are not enabled - const assetNetwork = asset.network; - if (getNetworkObj(assetNetwork).features.swaps) return true; + if (getNetworkObject({ chainId: asset.chainId }).features.swaps) return true; return false; }); @@ -60,7 +59,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset ); const getSwappableAddressesInWallet = useCallback(async () => { - const networks = RainbowNetworks.filter(({ features }) => features.swaps).map(({ value }) => value); + const networks = RainbowNetworkObjects.filter(({ features }) => features.swaps).map(({ value }) => value); const walletFilterRequests: Promise[] = []; networks.forEach(network => { diff --git a/src/hooks/useUserAccounts.ts b/src/hooks/useUserAccounts.ts index 3c999e3e1d5..eafa2508ee9 100644 --- a/src/hooks/useUserAccounts.ts +++ b/src/hooks/useUserAccounts.ts @@ -1,43 +1,37 @@ import { values } from 'lodash'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; import walletTypes from '@/helpers/walletTypes'; -import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; import { useMemo } from 'react'; import { RainbowAccount } from '@/model/wallet'; -import { Network } from '@/helpers'; export default function useUserAccounts() { const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); - const network = useSelector((state: AppState) => state.settings.network); const userAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type !== walletTypes.readOnly); - const addresses: (RainbowAccount & { network: Network })[] = []; + const addresses: RainbowAccount[] = []; filteredWallets.forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addresses.push({ ...account, - network, }); }); }); return addresses; - }, [network, walletsWithBalancesAndNames]); + }, [walletsWithBalancesAndNames]); const watchedAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type === walletTypes.readOnly); - const addresses: (RainbowAccount & { network: Network })[] = []; + const addresses: RainbowAccount[] = []; filteredWallets.forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addresses.push({ ...account, - network, }); }); }); return addresses; - }, [network, walletsWithBalancesAndNames]); + }, [walletsWithBalancesAndNames]); return { userAccounts, diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 71ff139cc6b..5e52dbf1aa2 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -32,7 +32,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); const allAddresses = useMemo( - () => Object.values(wallets).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), + () => Object.values(wallets).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), [wallets] ); @@ -79,7 +79,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { } return result; - }, [allAddresses, summaryData, nativeCurrency]); + }, [isLoading, allAddresses, summaryData?.data?.addresses, nativeCurrency]); return { balances, diff --git a/src/hooks/useWalletCloudBackup.ts b/src/hooks/useWalletCloudBackup.ts index f0efed48182..57b9caac681 100644 --- a/src/hooks/useWalletCloudBackup.ts +++ b/src/hooks/useWalletCloudBackup.ts @@ -12,7 +12,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { CLOUD_BACKUP_ERRORS, isCloudBackupAvailable } from '@/handlers/cloudBackup'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { getSupportedBiometryType } from '@/keychain'; import { IS_ANDROID } from '@/env'; import { authenticateWithPIN } from '@/handlers/authentication'; @@ -98,19 +98,19 @@ export default function useWalletCloudBackup() { } // We have the password and we need to add it to an existing backup - logger.log('password fetched correctly'); + logger.debug('[useWalletCloudBackup]: password fetched correctly'); let updatedBackupFile = null; try { if (!latestBackup) { - logger.log(`backing up to ${cloudPlatform}`, wallets![walletId]); + logger.debug(`[useWalletCloudBackup]: backing up to ${cloudPlatform}: ${wallets![walletId]}`); updatedBackupFile = await backupWalletToCloud({ password, wallet: wallets![walletId], userPIN, }); } else { - logger.log(`adding wallet to ${cloudPlatform} backup`, wallets![walletId]); + logger.debug(`[useWalletCloudBackup]: adding wallet to ${cloudPlatform} backup: ${wallets![walletId]}`); updatedBackupFile = await addWalletToCloudBackup({ password, wallet: wallets![walletId], @@ -121,8 +121,7 @@ export default function useWalletCloudBackup() { } catch (e: any) { const userError = getUserError(e); !!onError && onError(userError); - logger.sentry(`error while trying to backup wallet to ${cloudPlatform}`); - captureException(e); + logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to backup wallet to ${cloudPlatform}: ${e}`)); analytics.track(`Error during ${cloudPlatform} Backup`, { category: 'backup', error: userError, @@ -132,14 +131,13 @@ export default function useWalletCloudBackup() { } try { - logger.log('backup completed!'); + logger.debug('[useWalletCloudBackup]: backup completed!'); await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.cloud, updatedBackupFile)); - logger.log('backup saved everywhere!'); + logger.debug('[useWalletCloudBackup]: backup saved everywhere!'); !!onSuccess && onSuccess(); return true; } catch (e) { - logger.sentry('error while trying to save wallet backup state'); - captureException(e); + logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to save wallet backup state: ${e}`)); const userError = getUserError(new Error(CLOUD_BACKUP_ERRORS.WALLET_BACKUP_STATUS_UPDATE_FAILED)); !!onError && onError(userError); analytics.track('Error updating Backup status', { diff --git a/src/hooks/useWalletManualBackup.ts b/src/hooks/useWalletManualBackup.ts index 1a5ba71404b..daa83ed1554 100644 --- a/src/hooks/useWalletManualBackup.ts +++ b/src/hooks/useWalletManualBackup.ts @@ -1,9 +1,8 @@ -import { captureException } from '@sentry/react-native'; import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { setWalletBackedUp } from '../redux/wallets'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; export default function useWalletManualBackup() { const dispatch = useDispatch(); @@ -13,8 +12,9 @@ export default function useWalletManualBackup() { try { await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.manual)); } catch (e) { - logger.sentry(`error while trying to set walletId ${walletId} as manually backed up`); - captureException(e); + logger.error( + new RainbowError(`[useWalletManualBackup]: error while trying to set walletId ${walletId} as manually backed up: ${e}`) + ); } }, [dispatch] diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index dcf48245d98..51ec2a03b12 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -11,7 +11,7 @@ export default function useWalletsWithBalancesAndNames() { const walletsWithBalancesAndNames = useMemo( () => mapValues(wallets, wallet => { - const updatedAccounts = (wallet.addresses ?? []).map(account => ({ + const updatedAccounts = (wallet.addresses || []).map(account => ({ ...account, balances: balances[account.address.toLowerCase() as Address], ens: walletNames[account.address], diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index c065a77d255..d81780db3b2 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,26 +1,30 @@ import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; -import { MinedTransaction, RainbowTransaction } from '@/entities/transactions/transaction'; -import { getTransactionFlashbotStatus } from '@/handlers/transactions'; -import { getIsHardhatConnected, getProviderForNetwork } from '@/handlers/web3'; +import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { RainbowError, logger } from '@/logger'; -import { RainbowNetworks } from '@/networks'; -import { Network } from '@/networks/types'; +import { getProvider } from '@/handlers/web3'; +import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; +import { RainbowNetworkObjects } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; +import { getTransactionFlashbotStatus } from '@/handlers/transactions'; +import { ChainId } from '@/networks/types'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { invalidateAddressNftsQueries } from '@/resources/nfts'; -import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; -import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { useNonceStore } from '@/state/nonces'; import { usePendingTransactionsStore } from '@/state/pendingTransactions'; import { useCallback, useMemo } from 'react'; import { Address } from 'viem'; +import { staleBalancesStore } from '@/state/staleBalances'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; import useAccountSettings from './useAccountSettings'; +import { RainbowTransaction } from '@/entities'; +import { MinedTransaction } from '@/entities/transactions/transaction'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ storePendingTransactions: state.pendingTransactions, setPendingTransactions: state.setPendingTransactions, })); + const { connectedToHardhat } = useConnectedToHardhatStore(); const setNonce = useNonceStore(state => state.setNonce); @@ -31,7 +35,6 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const refreshAssets = useCallback( (_: RainbowTransaction) => { // NOTE: We have two user assets stores right now, so let's invalidate both queries and trigger a refetch - const connectedToHardhat = getIsHardhatConnected(); queryClient.invalidateQueries( userAssetsQueryKey({ address, @@ -48,7 +51,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); invalidateAddressNftsQueries(address); }, - [address, nativeCurrency] + [address, connectedToHardhat, nativeCurrency] ); const processFlashbotsTransaction = useCallback(async (tx: RainbowTransaction): Promise => { @@ -71,7 +74,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { const transaction = await transactionFetchQuery({ hash: tx.hash!, - network: tx.network, + chainId: tx.chainId, address, currency: nativeCurrency, }); @@ -88,7 +91,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { let updatedTransaction: RainbowTransaction = { ...tx }; try { - if (tx.network && tx.hash && address) { + if (tx.chainId && tx.hash && address) { updatedTransaction = await processSupportedNetworkTransaction(updatedTransaction); // if flashbots tx and no blockNumber, check if it failed if (!(tx as any).blockNumber && tx.flashbots) { @@ -97,10 +100,9 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => } else { throw new Error('Pending transaction missing chain id'); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(new RainbowError(`useWatchPendingTransaction: Failed to watch transaction`), { - message: e.message, + } catch (e) { + logger.error(new RainbowError(`[useWatchPendingTransaction]: Failed to watch transaction`), { + message: (e as Error)?.message || 'Unknown error', }); } @@ -115,46 +117,46 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const processNonces = useCallback( (txs: RainbowTransaction[]) => { const userTxs = txs.filter(tx => address?.toLowerCase() === tx.from?.toLowerCase()); - const networks = [ + const chainIds = [ ...new Set( userTxs.reduce((acc, tx) => { - acc.add(tx.network); + acc.add(tx.chainId); return acc; - }, new Set()) + }, new Set()) ), ]; let flashbotsTxFailed = false; const highestNoncePerChainId = userTxs.reduce((acc, tx) => { // if tx is not on mainnet, we don't care about the nonce - if (tx.network !== Network.mainnet) { - acc.set(tx.network, tx.nonce); + if (tx.chainId !== ChainId.mainnet) { + acc.set(tx.chainId, tx.nonce); return acc; } // if tx is flashbots and failed, we want to use the lowest nonce if (tx.flashbots && (tx as any)?.flashbotsStatus === 'FAILED' && tx?.nonce) { // if we already have a failed flashbots tx, we want to use the lowest nonce - if (flashbotsTxFailed && tx.nonce < acc.get(tx.network)) { - acc.set(tx.network, tx.nonce); + if (flashbotsTxFailed && tx.nonce < acc.get(tx.chainId)) { + acc.set(tx.chainId, tx.nonce); } else { - acc.set(tx.network, tx.nonce); + acc.set(tx.chainId, tx.nonce); flashbotsTxFailed = true; } // if tx succeeded, we want to use the highest nonce - } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.network)) { - acc.set(tx.network, tx.nonce); + } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.chainId)) { + acc.set(tx.chainId, tx.nonce); } return acc; }, new Map()); - networks.map(async network => { - const provider = getProviderForNetwork(network); + chainIds.map(async chainId => { + const provider = getProvider({ chainId }); const providerTransactionCount = await provider.getTransactionCount(address, 'latest'); const currentProviderNonce = providerTransactionCount - 1; - const currentNonceForChainId = highestNoncePerChainId.get(network) - 1; + const currentNonceForChainId = highestNoncePerChainId.get(chainId) - 1; setNonce({ address, - network: network, + chainId, currentNonce: currentProviderNonce > currentNonceForChainId ? currentProviderNonce : currentNonceForChainId, latestConfirmedNonce: currentProviderNonce, }); @@ -187,7 +189,23 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); if (minedTransactions.length) { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(networkObject => networkObject.enabled && networkObject.networkType !== 'testnet').map( + networkObject => networkObject.id + ); + minedTransactions.forEach(tx => { + if (tx.changes?.length) { + tx.changes?.forEach(change => { + processStaleAsset({ asset: change?.asset, address, transactionHash: tx?.hash }); + }); + } else if (tx.asset) { + processStaleAsset({ address, asset: tx.asset, transactionHash: tx?.hash }); + } + }); + + queryClient.refetchQueries({ + queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }), + }); + await queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ address, @@ -211,7 +229,31 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => address, pendingTransactions: newPendingTransactions, }); - }, [address, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); + }, [address, connectedToHardhat, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); return { watchPendingTransactions }; }; + +function processStaleAsset({ + asset, + address, + transactionHash, +}: { + asset: RainbowTransaction['asset']; + address: string; + transactionHash: string; +}) { + const { addStaleBalance } = staleBalancesStore.getState(); + const chainId = asset?.chainId; + if (asset && typeof chainId === 'number') { + const changedAssetAddress = asset?.address as Address; + addStaleBalance({ + address, + chainId, + info: { + address: changedAssetAddress, + transactionHash, + }, + }); + } +} diff --git a/src/hooks/useWatchWallet.ts b/src/hooks/useWatchWallet.ts index 96aae19410f..19703a3b344 100644 --- a/src/hooks/useWatchWallet.ts +++ b/src/hooks/useWatchWallet.ts @@ -6,7 +6,8 @@ import { useAccountProfile, useDeleteWallet, useImportingWallet, useInitializeWa import { cleanUpWalletKeys, RainbowWallet } from '@/model/wallet'; import { addressSetSelected, walletsSetSelected } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import { doesWalletsContainAddress, logger } from '@/utils'; +import { doesWalletsContainAddress } from '@/utils'; +import { RainbowError, logger } from '@/logger'; export default function useWatchWallet({ address: primaryAddress, @@ -24,7 +25,9 @@ export default function useWatchWallet({ const { wallets } = useWallets(); const watchingWallet = useMemo(() => { - return Object.values(wallets || {}).find(wallet => wallet.addresses.some(({ address }) => address === primaryAddress)); + return Object.values(wallets || {}).find(wallet => + (wallet.addresses || []).some(({ address }) => address === primaryAddress) + ); }, [primaryAddress, wallets]); const isWatching = useMemo(() => Boolean(watchingWallet), [watchingWallet]); @@ -42,7 +45,9 @@ export default function useWatchWallet({ // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 7. initializeWallet(null, null, null, false, false, null, true); } catch (e) { - logger.log('error while switching account', e); + logger.error(new RainbowError(`[useWatchWallet]: error while switching account`), { + error: (e as Error)?.message || 'Unknown error', + }); } }, [dispatch, initializeWallet, wallets] @@ -61,7 +66,7 @@ export default function useWatchWallet({ // it's deletable const isLastAvailableWallet = Object.keys(wallets!).find(key => { const someWallet = wallets![key]; - const otherAccount = someWallet.addresses.find((account: any) => account.visible && account.address !== accountAddress); + const otherAccount = someWallet.addresses?.find((account: any) => account.visible && account.address !== accountAddress); if (otherAccount) { return true; } diff --git a/src/hooks/useWebData.ts b/src/hooks/useWebData.ts index daa5a80b5ad..f3efe8e9585 100644 --- a/src/hooks/useWebData.ts +++ b/src/hooks/useWebData.ts @@ -10,7 +10,7 @@ import { containsEmoji } from '@/helpers/strings'; import WalletTypes from '@/helpers/walletTypes'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { AppState } from '@/redux/store'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { useTheme } from '@/theme'; const getAccountSymbol = (name: string) => { @@ -98,7 +98,7 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(assetIds); - logger.log('showcase initialized!'); + logger.debug('[useWebData]: showcase initialized!'); return; } @@ -112,7 +112,7 @@ export default function useWebData() { const response = await getPreference('hidden', accountAddress); if (!response || !response.hidden.ids.length) { await setPreference(PreferenceActionType.init, 'hidden', accountAddress, assetIds); - logger.log('hidden initialized!'); + logger.debug('[useWebData]: hidden initialized!'); return; } @@ -130,15 +130,15 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(showcaseTokens); - logger.log('showcase initialized!'); + logger.debug('[useWebData]: showcase initialized!'); return; } - logger.log('showcase already initialized. skipping'); + logger.debug('[useWebData]: showcase already initialized. skipping'); } } } catch (e) { - logger.log('Error trying to initiailze showcase'); + logger.error(new RainbowError(`[useWebData]: error while trying to initialize showcase: ${e}`)); } }, [accountAddress, initWebData, showcaseTokens, webDataEnabled]); diff --git a/src/keychain/index.ts b/src/keychain/index.ts index 9479712c1d9..0ef3d78771a 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -67,11 +67,11 @@ export const publicAccessControlOptions: Options = { * decrypt the data. */ export async function get(key: string, options: KeychainOptions = {}): Promise> { - logger.debug(`keychain: get`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: get`, { key }, logger.DebugContext.keychain); async function _get(attempts = 0): Promise> { if (attempts > 0) { - logger.debug(`keychain: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); } let data = cache.getString(key); @@ -93,24 +93,24 @@ export async function get(key: string, options: KeychainOptions = {}): Promise 2) { return { @@ -170,7 +170,7 @@ export async function get(key: string, options: KeychainOptions = {}): Promise { - logger.debug(`keychain: set`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: set`, { key }, logger.DebugContext.keychain); // only save public data to mmkv // private data has accessControl if (!options.accessControl) { cache.set(key, value); } else if (options.accessControl && IS_ANDROID && !(await getSupportedBiometryType())) { - logger.debug(`keychain: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); + logger.debug(`[keychain]: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); const pin = options.androidEncryptionPin || (await authenticateWithPINAndCreateIfNeeded()); const encryptedValue = await encryptor.encrypt(pin, value); @@ -216,7 +216,7 @@ export async function set(key: string, value: string, options: KeychainOptions = if (encryptedValue) { value = encryptedValue; } else { - throw new Error(`keychain: failed to encrypt value`); + throw new Error(`[keychain]: failed to encrypt value`); } } @@ -231,7 +231,7 @@ export async function getObject = Record> { - logger.debug(`keychain: getObject`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: getObject`, { key }, logger.DebugContext.keychain); const { value, error } = await get(key, options); @@ -250,7 +250,7 @@ export async function getObject = Record, options: KeychainOptions = {}): Promise { - logger.debug(`keychain: setObject`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: setObject`, { key }, logger.DebugContext.keychain); await set(key, JSON.stringify(value), options); } @@ -259,7 +259,7 @@ export async function setObject(key: string, value: Record, options * Check if a value exists on the keychain. */ export async function has(key: string): Promise { - logger.debug(`keychain: has`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: has`, { key }, logger.DebugContext.keychain); return Boolean(await hasInternetCredentials(key)); } @@ -267,7 +267,7 @@ export async function has(key: string): Promise { * Remove a value from the keychain. */ export async function remove(key: string) { - logger.debug(`keychain: remove`, { key }, logger.DebugContext.keychain); + logger.debug(`[keychain]: remove`, { key }, logger.DebugContext.keychain); cache.delete(key); await resetInternetCredentials(key); @@ -281,11 +281,11 @@ export async function remove(key: string) { */ export async function getAllKeys(): Promise { try { - logger.debug(`keychain: getAllKeys`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getAllKeys`, {}, logger.DebugContext.keychain); const res = await getAllInternetCredentials(); return res ? res.results : []; } catch (e: any) { - logger.error(new RainbowError(`keychain: getAllKeys() failed`), { + logger.error(new RainbowError(`[keychain]: getAllKeys() failed`), { message: e.toString(), }); return undefined; @@ -299,7 +299,7 @@ export async function getAllKeys(): Promise { * `getAllKeys`. */ export async function clear() { - logger.debug(`keychain: clear`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: clear`, {}, logger.DebugContext.keychain); cache.clearAll(); @@ -314,7 +314,7 @@ export async function clear() { * Wrapper around the underlying library's method by the same name. */ export async function getSupportedBiometryType(): Promise { - logger.debug(`keychain: getSupportedBiometryType`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getSupportedBiometryType`, {}, logger.DebugContext.keychain); return (await originalGetSupportedBiometryType()) || undefined; } @@ -323,7 +323,7 @@ export async function getSupportedBiometryType(): Promise> { - logger.debug(`keychain: getSharedWebCredentials`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getSharedWebCredentials`, {}, logger.DebugContext.keychain); let data = undefined; @@ -352,7 +352,7 @@ export async function getSharedWebCredentials(): Promise { - logger.debug(`keychain: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); + logger.debug(`[keychain]: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); const isSimulator = IS_DEV && (await DeviceInfo.isEmulator()); diff --git a/src/languages/ar_AR.json b/src/languages/ar_AR.json index 529ecaacb6a..2ca00d05971 100644 --- a/src/languages/ar_AR.json +++ b/src/languages/ar_AR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "أوتو", - "degen_mode": "وضع ديجين", - "degen_mode_description": "تجاوز ورقة المراجعة للتبديل بشكل أسرع", + "degen_mode": "وضع Degen", + "degen_mode_description": "تخطي خطوة المراجعة للتبديل بشكل أسرع", "maximum_sold": "الحد الأقصى المباع", "minimum_received": "الحد الأدنى المستلم", "preferred_network": "الشبكة المفضلة", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "بيع", "sort": { + "ABC": "أبج", "abc": "أبج", + "MOST_RECENT": "الأخير", "most_recent": "الأحدث", + "FLOOR_PRICE": "سعر الأرضية", "floor_price": "سعر الأرضية" - } + }, + "empty": "القطع التذكارية", + "collect_now": "اجمع الآن", + "will_appear_here": "سوف تظهر مقتنياتك هنا" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "الأخير", + "popular": "شائع في Rainbow", "favorites": "المفضلة", "bridge": "جسر", "verified": "تم التحقق منه", @@ -2496,7 +2503,7 @@ "balance_title": "رصيد", "buy": "شراء", "change_wallet": { - "no_balance": "لا يوجد رصيد", + "loading_balance": "جاري تحميل الرصيد...", "balance_eth": "%{balanceEth} ETH", "watching": "مشاهدة", "ledger": "ليدجر" diff --git a/src/languages/en_US.json b/src/languages/en_US.json index c336b050e56..b2c1194593f 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -487,6 +487,7 @@ "clear_local_storage": "Clear local storage", "clear_mmkv_storage": "Clear MMKV storage", "connect_to_hardhat": "Connect to hardhat", + "disconnect_to_hardhat": "Disconnect from hardhat", "crash_app_render_error": "Crash app (render error)", "enable_testnets": "Enable Testnets", "installing_update": "Installing update", diff --git a/src/languages/es_419.json b/src/languages/es_419.json index 43216bd6eb9..5a5759b5ab8 100644 --- a/src/languages/es_419.json +++ b/src/languages/es_419.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Omitir la hoja de revisión para intercambiar más rápido", + "degen_mode_description": "Saltar el paso de revisión para intercambiar más rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Mínimo Recibido", "preferred_network": "Red preferida", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vendiendo", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Reciente", "most_recent": "Más Reciente", + "FLOOR_PRICE": "Precio Base", "floor_price": "Precio Base" - } + }, + "empty": "Coleccionables", + "collect_now": "Colecciona Ahora", + "will_appear_here": "Tus coleccionables aparecerán aquí" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Reciente", + "popular": "Popular en Rainbow", "favorites": "Favoritos", "bridge": "Puente", "verified": "Verificado", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "no_balance": "Sin Balance", + "loading_balance": "Cargando saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/fr_FR.json b/src/languages/fr_FR.json index 8866fc35723..18e626c7058 100644 --- a/src/languages/fr_FR.json +++ b/src/languages/fr_FR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Auto", "degen_mode": "Mode Degen", - "degen_mode_description": "Passez la feuille de révision pour un échange plus rapide", + "degen_mode_description": "Passer l'étape de l'examen pour échanger plus rapidement", "maximum_sold": "Vendu au maximum", "minimum_received": "Montant minimum reçu", "preferred_network": "Réseau Préféré", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vente", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Récent", "most_recent": "Le plus récent", + "FLOOR_PRICE": "Prix Plancher", "floor_price": "Prix Plancher" - } + }, + "empty": "Objets de collection", + "collect_now": "Collect Now", + "will_appear_here": "Vos objets de collection apparaîtront ici" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Récent", + "popular": "Populaire sur Rainbow", "favorites": "Favoris", "bridge": "Pont", "verified": "Vérifié", @@ -2496,7 +2503,7 @@ "balance_title": "Solde", "buy": "Acheter", "change_wallet": { - "no_balance": "Pas de solde", + "loading_balance": "Chargement du solde...", "balance_eth": "%{balanceEth} ETH", "watching": "Regarder", "ledger": "Ledger" diff --git a/src/languages/hi_IN.json b/src/languages/hi_IN.json index 69f9d44673b..1a92d75d7f0 100644 --- a/src/languages/hi_IN.json +++ b/src/languages/hi_IN.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "ऑटो", - "degen_mode": "डिजन मोड", - "degen_mode_description": "तेज स्वैप करने के लिए समीक्षा शीट को छोड़ें", + "degen_mode": "Degen मोड", + "degen_mode_description": "फ़ास्ट स्वैप के लिए समीक्षा चरण को छोड़ें", "maximum_sold": "अधिकतम बेचा गया", "minimum_received": "न्यूनतम प्राप्त", "preferred_network": "पसंदीदा नेटवर्क", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "बिक्री", "sort": { + "ABC": "एबीसी", "abc": "एबीसी", + "MOST_RECENT": "हाल का", "most_recent": "सबसे हालिया", + "FLOOR_PRICE": "मंजिल मूल्य", "floor_price": "मंजिल मूल्य" - } + }, + "empty": "संग्रह", + "collect_now": "अभी इकट्ठा करें", + "will_appear_here": "आपके संग्रह योग्य सामग्री यहाँ दिखाई देंगे" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "हाल का", + "popular": "रेनबो में लोकप्रिय", "favorites": "पसंदीदा", "bridge": "ब्रिज", "verified": "सत्यापित", @@ -2496,7 +2503,7 @@ "balance_title": "बैलेंस", "buy": "खरीदें", "change_wallet": { - "no_balance": "कोई बैलेंस नहीं", + "loading_balance": "शेष लोड हो रहा है...", "balance_eth": "%{balanceEth} ETH", "watching": "देख रहा है", "ledger": "लेजर" diff --git a/src/languages/id_ID.json b/src/languages/id_ID.json index 312d0d854ac..55e9437efae 100644 --- a/src/languages/id_ID.json +++ b/src/languages/id_ID.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Otomatis", "degen_mode": "Mode Degen", - "degen_mode_description": "Lewati lembar tinjauan untuk swap lebih cepat", + "degen_mode_description": "Lewati langkah ulasan untuk bertukar lebih cepat", "maximum_sold": "Maksimum Dijual", "minimum_received": "Minimum yang diterima", "preferred_network": "Jaringan yang Disukai", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Menjual", "sort": { + "ABC": "Abjad", "abc": "Abjad", + "MOST_RECENT": "Baru-baru ini", "most_recent": "Terbaru", + "FLOOR_PRICE": "Harga Dasar", "floor_price": "Harga Dasar" - } + }, + "empty": "Koleksi", + "collect_now": "Koleksi Sekarang", + "will_appear_here": "Koleksi Anda Akan Muncul di Sini" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Baru-baru ini", + "popular": "Populer di Rainbow", "favorites": "Favorit", "bridge": "Jembatan", "verified": "Terverifikasi", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Beli", "change_wallet": { - "no_balance": "Tidak Ada Saldo", + "loading_balance": "Memuat Saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Menonton", "ledger": "Ledger" diff --git a/src/languages/ja_JP.json b/src/languages/ja_JP.json index a3a1f552f82..ee48137fd7a 100644 --- a/src/languages/ja_JP.json +++ b/src/languages/ja_JP.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "自動", - "degen_mode": "デゲンモード", - "degen_mode_description": "レビューページをスキップして速くスワップする", + "degen_mode": "Degenモード", + "degen_mode_description": "レビューステップをスキップしてより早くスワップ", "maximum_sold": "最大売却数量", "minimum_received": "受け取る最小額", "preferred_network": "優先ネットワーク", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "販売", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "最近の", "most_recent": "最新順", + "FLOOR_PRICE": "フロア価格", "floor_price": "フロア価格" - } + }, + "empty": "コレクタブル", + "collect_now": "今すぐコレクト", + "will_appear_here": "あなたのコレクティブルはこちらに表示されます" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "最近の", + "popular": "Rainbowの人気", "favorites": "お気に入り", "bridge": "ブリッジ", "verified": "確認済み", @@ -2496,7 +2503,7 @@ "balance_title": "残高", "buy": "購入", "change_wallet": { - "no_balance": "残高なし", + "loading_balance": "残高を読み込み中...", "balance_eth": "%{balanceEth} ETH", "watching": "ウォッチ中", "ledger": "Ledger" diff --git a/src/languages/ko_KR.json b/src/languages/ko_KR.json index ee720cf8789..87f8d541567 100644 --- a/src/languages/ko_KR.json +++ b/src/languages/ko_KR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "자동", - "degen_mode": "디겐 모드", - "degen_mode_description": "검토 시트를 건너뛰어 더 빠르게 스왑", + "degen_mode": "Degen모드", + "degen_mode_description": "리뷰 단계를 건너뛰어 더 빠르게 교환", "maximum_sold": "최대 판매", "minimum_received": "최소 수령량", "preferred_network": "선호 네트워크", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "판매", "sort": { + "ABC": "가나다", "abc": "가나다", + "MOST_RECENT": "최근", "most_recent": "최신순", + "FLOOR_PRICE": "최저 가격", "floor_price": "최저 가격" - } + }, + "empty": "수집품", + "collect_now": "지금 수집", + "will_appear_here": "여기에 수집품이 나타납니다" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "최근", + "popular": "레인보우에서 인기", "favorites": "즐겨찾기", "bridge": "브릿지", "verified": "인증됨", @@ -2496,7 +2503,7 @@ "balance_title": "균형", "buy": "구매", "change_wallet": { - "no_balance": "잔액 없음", + "loading_balance": "잔액 로딩 중...", "balance_eth": "%{balanceEth} 이더리움", "watching": "시청 중", "ledger": "렛저" diff --git a/src/languages/pt_BR.json b/src/languages/pt_BR.json index 37c17e65acc..503cb630c1b 100644 --- a/src/languages/pt_BR.json +++ b/src/languages/pt_BR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Pule a revisão para trocar mais rápido", + "degen_mode_description": "Pule a etapa de revisão para trocar mais rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Valor Mínimo Recebido", "preferred_network": "Rede Preferida", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Vendendo", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Recente", "most_recent": "Mais Recente", + "FLOOR_PRICE": "Preço do Piso", "floor_price": "Preço do Piso" - } + }, + "empty": "Colecionáveis", + "collect_now": "Colecione Agora", + "will_appear_here": "Suas Colecionáveis Aparecerão Aqui" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Recente", + "popular": "Popular no Rainbow", "favorites": "Favoritos", "bridge": "Ponte", "verified": "Verificado", @@ -2496,7 +2503,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "no_balance": "Sem Saldo", + "loading_balance": "Carregando saldo...", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/ru_RU.json b/src/languages/ru_RU.json index 7bbc404f7f8..d6868c93fc0 100644 --- a/src/languages/ru_RU.json +++ b/src/languages/ru_RU.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Авто", "degen_mode": "Режим Degen", - "degen_mode_description": "Пропустите лист проверки для ускорения обмена", + "degen_mode_description": "Пропустить этап обзора, чтобы ускорить обмен", "maximum_sold": "Максимум продано", "minimum_received": "Минимальная получаемая", "preferred_network": "Предпочитаемая сеть", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Продажа", "sort": { + "ABC": "АБВ", "abc": "АБВ", + "MOST_RECENT": "Последние", "most_recent": "Самые последние", + "FLOOR_PRICE": "Начальная цена", "floor_price": "Начальная цена" - } + }, + "empty": "Коллекционные предметы", + "collect_now": "Соберите сейчас", + "will_appear_here": "Ваши коллекции появятся здесь" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Последние", + "popular": "Популярное в Rainbow", "favorites": "Избранное", "bridge": "Мост", "verified": "Проверенные", @@ -2496,7 +2503,7 @@ "balance_title": "Баланс", "buy": "Купить", "change_wallet": { - "no_balance": "Нет Баланса", + "loading_balance": "Загрузка баланса...", "balance_eth": "%{balanceEth} ETH", "watching": "Наблюдение", "ledger": "Ledger" diff --git a/src/languages/th_TH.json b/src/languages/th_TH.json index 9c82f0bfb88..d2fb43308fd 100644 --- a/src/languages/th_TH.json +++ b/src/languages/th_TH.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "อัตโนมัติ", - "degen_mode": "โหมดดีเจ็น", - "degen_mode_description": "ข้ามหน้าตรวจสอบเพื่อแลกเปลี่ยนเร็วขึ้น", + "degen_mode": "Degenโหมด", + "degen_mode_description": "ข้ามขั้นตอนการตรวจสอบเพื่อการสลับที่เร็วขึ้น", "maximum_sold": "ขายสูงสุด", "minimum_received": "รับต่ำสุด", "preferred_network": "เครือข่ายที่คุณต้องการ", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "ขาย", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "ล่าสุด", "most_recent": "ล่าสุด", + "FLOOR_PRICE": "ราคาพื้น", "floor_price": "ราคาพื้น" - } + }, + "empty": "สะสม", + "collect_now": "เก็บสะสมทันที", + "will_appear_here": "ของสะสมจะปรากฏที่นี่" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "ล่าสุด", + "popular": "เป็นที่นิยมใน Rainbow", "favorites": "รายการโปรด", "bridge": "สะพาน", "verified": "ได้รับการตรวจสอบ", @@ -2496,7 +2503,7 @@ "balance_title": "ยอดคงเหลือ", "buy": "ซื้อ", "change_wallet": { - "no_balance": "ไม่มีคงเหลือ", + "loading_balance": "กำลังโหลดยอดคงเหลือ...", "balance_eth": "%{balanceEth} ETH", "watching": "กำลังรับชม", "ledger": "เลเจอร์" diff --git a/src/languages/tr_TR.json b/src/languages/tr_TR.json index 0ccec26d073..05d5df71b1e 100644 --- a/src/languages/tr_TR.json +++ b/src/languages/tr_TR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Oto", "degen_mode": "Degen Modu", - "degen_mode_description": "Hızlı Takas İçin İnceleme Sayfasını Atlayın", + "degen_mode_description": "İnceleme adımını atlayarak daha hızlı takas yapın", "maximum_sold": "Azami Satılan", "minimum_received": "Asgari Alınan", "preferred_network": "Tercih Edilen Ağ", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "Satış", "sort": { + "ABC": "Abc", "abc": "Abc", + "MOST_RECENT": "Son", "most_recent": "En Yeni", + "FLOOR_PRICE": "Taban Fiyatı", "floor_price": "Taban Fiyatı" - } + }, + "empty": "Koleksiyonlar", + "collect_now": "Şimdi Koleksiyon Yap", + "will_appear_here": "Koleksiyonlarınız Burada Görünecek" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "Son", + "popular": "Rainbow'da Popüler", "favorites": "Favoriler", "bridge": "Köprü", "verified": "Doğrulanmış", @@ -2496,7 +2503,7 @@ "balance_title": "Bakiye", "buy": "Satın Al", "change_wallet": { - "no_balance": "Bakiye Yok", + "loading_balance": "Bakiye Yükleniyor...", "balance_eth": "%{balanceEth} ETH", "watching": "İzliyor", "ledger": "Ledger" diff --git a/src/languages/zh_CN.json b/src/languages/zh_CN.json index a347d97c12e..809425396e8 100644 --- a/src/languages/zh_CN.json +++ b/src/languages/zh_CN.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "自动", "degen_mode": "Degen 模式", - "degen_mode_description": "跳过审核表以更快地交换", + "degen_mode_description": "跳过审查步骤以更快交换", "maximum_sold": "最大出售量", "minimum_received": "最低接收金额", "preferred_network": "首选网络", @@ -1321,10 +1321,16 @@ "nfts": { "selling": "出售", "sort": { + "ABC": "字母顺序", "abc": "字母顺序", + "MOST_RECENT": "最近", "most_recent": "最新的", + "FLOOR_PRICE": "底价", "floor_price": "底价" - } + }, + "empty": "收藏品", + "collect_now": "立即收藏", + "will_appear_here": "您的收藏品将会出现在这里" }, "nft_offers": { "card": { @@ -2068,6 +2074,7 @@ "token_search": { "section_header": { "recent": "最近", + "popular": "在 Rainbow 中流行", "favorites": "收藏夹", "bridge": "桥接", "verified": "已验证", @@ -2496,7 +2503,7 @@ "balance_title": "余额", "buy": "购买", "change_wallet": { - "no_balance": "无余额", + "loading_balance": "正在加载余额...", "balance_eth": "%{balanceEth} ETH", "watching": "正在关注", "ledger": "分类账" diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index 53d5160db83..fd44de21771 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -21,7 +21,7 @@ export const defaultOptions: Sentry.ReactNativeOptions = { export function initSentry() { if (IS_TEST) { - logger.debug(`Sentry is disabled for test environment`); + logger.debug(`[sentry]: disabled for test environment`); return; } try { @@ -34,8 +34,8 @@ export function initSentry() { release, // MUST BE A STRING or Sentry will break in native code }); - logger.debug(`Sentry initialized`); + logger.debug(`[sentry]: Successfully initialized`); } catch (e) { - logger.error(new RainbowError(`Sentry initialization failed`)); + logger.error(new RainbowError(`[sentry]: initialization failed`)); } } diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 8abb7f47509..1f2fe83f900 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -55,7 +55,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { * should exit early */ if (debug && env.IS_PROD) { - logger.error(new RainbowError(`Migration is in debug mode`), { + logger.error(new RainbowError(`[migrations]: is in debug mode`), { migration: name, }); return; @@ -66,7 +66,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { if (handler) { try { logger.debug( - `Migrating`, + `[migrations]: Migrating`, { migration: name, }, @@ -75,19 +75,19 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { await handler(); if (!debug) storage.set([name], new Date().toUTCString()); logger.debug( - `Migrating complete`, + `[migrations]: Migrating complete`, { migration: name, }, MIGRATIONS_DEBUG_CONTEXT ); } catch (e) { - logger.error(new RainbowError(`Migration failed`), { + logger.error(new RainbowError(`[migrations]: Migration failed`), { migration: name, }); } } else { - logger.error(new RainbowError(`Migration had no handler`), { + logger.error(new RainbowError(`[migrations]: Migration had no handler`), { migration: name, }); } @@ -112,11 +112,11 @@ export async function runMigrations(migrations: Migration[]) { ranMigrations.push(migration.name); } else { - logger.debug(`Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); + logger.debug(`[migrations]: Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); } } - logger.info(`Ran or scheduled migrations`, { + logger.debug(`[migrations]: Ran or scheduled migrations`, { migrations: ranMigrations, }); } diff --git a/src/migrations/migrations/migrateFavorites.ts b/src/migrations/migrations/migrateFavorites.ts index 46845e56225..4ffa4c52259 100644 --- a/src/migrations/migrations/migrateFavorites.ts +++ b/src/migrations/migrations/migrateFavorites.ts @@ -3,7 +3,6 @@ import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { EthereumAddress, RainbowToken } from '@/entities'; import { createQueryKey, persistOptions, queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; -import { ethereumUtils } from '@/utils'; import { persistQueryClientRestore, persistQueryClientSave } from '@tanstack/react-query-persist-client'; import { Migration, MigrationName } from '../types'; @@ -25,7 +24,7 @@ export function migrateFavoritesV2(): Migration { for (const favorite of Object.values(v1Data)) { const uniqueId = getStandardizedUniqueIdWorklet({ address: favorite.address as AddressOrEth, - chainId: ethereumUtils.getChainIdFromNetwork(favorite.network), + chainId: favorite.chainId, }); favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network migratedFavorites[uniqueId] = favorite; diff --git a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts index df4e1f41baa..4284569d94b 100644 --- a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts +++ b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts @@ -27,7 +27,7 @@ export function migrateRemotePromoSheetsToZustand(): Migration { }); } } catch (error) { - logger.error(new RainbowError(`Failed to migrate remote promo sheets to zustand`), { + logger.error(new RainbowError(`[migrations]: Failed to migrate remote promo sheets to zustand`), { data: error, }); } diff --git a/src/model/backup.ts b/src/model/backup.ts index b5d02bdedac..2eb50a7c297 100644 --- a/src/model/backup.ts +++ b/src/model/backup.ts @@ -11,7 +11,6 @@ import * as keychain from '@/model/keychain'; import * as kc from '@/keychain'; import { AllRainbowWallets, allWalletsVersion, createWallet, RainbowWallet } from './wallet'; import { analytics } from '@/analytics'; -import oldLogger from '@/utils/logger'; import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_DEV } from '@/env'; import AesEncryptor from '../handlers/aesEncryption'; @@ -152,7 +151,7 @@ export async function backupAllWalletsToCloud({ ); const now = Date.now(); - logger.debug(`Creating backup with all wallets to ${cloudPlatform}`, { + logger.debug(`[backup]: Creating backup with all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -204,7 +203,7 @@ export async function backupAllWalletsToCloud({ const walletIdsToUpdate = Object.keys(wallets); await dispatch(setAllWalletsWithIdsAsBackedUp(walletIdsToUpdate, WalletBackupTypes.cloud, updatedBackupFile)); - logger.debug(`Successfully backed up all wallets to ${cloudPlatform}`, { + logger.debug(`[backup]: Successfully backed up all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -430,7 +429,7 @@ export async function restoreCloudBackup({ if (message === CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA) { return RestoreCloudBackupResultStates.incorrectPassword; } - logger.error(new RainbowError('Error while restoring back up'), { + logger.error(new RainbowError(`[backup]: Error while restoring back up`), { message, }); return RestoreCloudBackupResultStates.failedWhenRestoring; @@ -521,8 +520,7 @@ async function restoreSpecificBackupIntoKeychain(backedUpData: BackedUpData, use } return true; } catch (e) { - oldLogger.sentry('error in restoreSpecificBackupIntoKeychain'); - captureException(e); + logger.error(new RainbowError(`[backup]: Error restoring specific backup into keychain: ${e}`)); return false; } } @@ -590,8 +588,7 @@ async function restoreCurrentBackupIntoKeychain(backedUpData: BackedUpData, newP return true; } catch (e) { - oldLogger.sentry('error in restoreBackupIntoKeychain'); - captureException(e); + logger.error(new RainbowError(`[backup]: Error restoring current backup into keychain: ${e}`)); return false; } } @@ -620,7 +617,7 @@ async function decryptSecretFromBackupPin({ secret, backupPIN }: { secret?: stri } processedSecret = decryptedSecretToUse; } else { - logger.error(new RainbowError('Failed to decrypt backed up seed phrase using backup PIN.')); + logger.error(new RainbowError(`[backup]: Failed to decrypt backed up seed phrase using backup PIN.`)); return processedSecret; } } @@ -673,8 +670,7 @@ export async function fetchBackupPassword(): Promise { } return null; } catch (e) { - oldLogger.sentry('Error while fetching backup password', e); - captureException(e); + logger.error(new RainbowError(`[backup]: Error while fetching backup password: ${e}`)); return null; } } @@ -687,7 +683,7 @@ export async function getDeviceUUID(): Promise { return new Promise(resolve => { DeviceUUID.getUUID((error: unknown, uuid: string[]) => { if (error) { - logger.error(new RainbowError('Received error when trying to get uuid from Native side'), { + logger.error(new RainbowError(`[backup]: Received error when trying to get uuid from Native side`), { error, }); resolve(null); @@ -739,7 +735,7 @@ export async function checkIdentifierOnLaunch() { if (currentIdentifier.error) { switch (currentIdentifier.error) { case kc.ErrorType.Unavailable: { - logger.debug('Value for current identifier not found, setting it to new UUID...', { + logger.debug(`[backup]: Value for current identifier not found, setting it to new UUID...`, { uuid, error: currentIdentifier.error, }); @@ -748,7 +744,7 @@ export async function checkIdentifierOnLaunch() { } default: - logger.error(new RainbowError('Error while checking identifier on launch'), { + logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { error: currentIdentifier.error, }); break; @@ -798,10 +794,8 @@ export async function checkIdentifierOnLaunch() { }); }); } catch (error) { - logger.error(new RainbowError('Error while checking identifier on launch'), { - extra: { - error, - }, + logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { + error, }); } diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 5026391f774..561500aa58a 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -32,7 +32,7 @@ import { returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { ethereumUtils, profileUtils } from '@/utils'; import { review } from '@/storage'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; @@ -52,13 +52,11 @@ export default async function runMigrations() { * using the updated Keychain settings (THIS_DEVICE_ONLY) */ const v0 = async () => { - logger.sentry('Start migration v0'); const walletAddress = await loadAddress(); if (walletAddress) { - logger.sentry('v0 migration - Save loaded address'); + logger.debug('[runMigrations]: v0 migration - Save loaded address'); await saveAddress(walletAddress); } - logger.sentry('Complete migration v0'); }; migrations.push(v0); @@ -70,14 +68,13 @@ export default async function runMigrations() { * that were created / imported before we launched this feature */ const v1 = async () => { - logger.sentry('Start migration v1'); const { selected } = store.getState().wallets; if (!selected) { // Read from the old wallet data const address = await loadAddress(); if (address) { - logger.sentry('v1 migration - address found'); + logger.debug('[runMigrations]: v1 migration - address found'); const id = `wallet_${Date.now()}`; const currentWallet = { addresses: [ @@ -100,12 +97,11 @@ export default async function runMigrations() { const wallets = { [id]: currentWallet }; - logger.sentry('v1 migration - update wallets and selected wallet'); + logger.debug('[runMigrations]: v1 migration - update wallets and selected wallet'); await store.dispatch(walletsUpdate(wallets)); await store.dispatch(walletsSetSelected(currentWallet)); } } - logger.sentry('Complete migration v1'); }; migrations.push(v1); @@ -116,11 +112,10 @@ export default async function runMigrations() { * which are the only wallets allowed to create new accounts under it */ const v2 = async () => { - logger.sentry('Start migration v2'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.sentry('Complete migration v2 early'); + logger.debug('[runMigrations]: Complete migration v2 early'); return; } @@ -131,7 +126,7 @@ export default async function runMigrations() { // if there's a wallet with seed phrase that wasn't imported // and set it as primary if (!primaryWallet) { - logger.sentry('v2 migration - primary wallet not found'); + logger.debug('[runMigrations]: v2 migration - primary wallet not found'); let primaryWalletKey = null; Object.keys(wallets).some(key => { const wallet = wallets[key]; @@ -161,7 +156,7 @@ export default async function runMigrations() { ...updatedWallets[primaryWalletKey], primary: true, }; - logger.sentry('v2 migration - update wallets'); + logger.debug('[runMigrations]: v2 migration - update wallets'); await store.dispatch(walletsUpdate(updatedWallets)); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too @@ -171,7 +166,6 @@ export default async function runMigrations() { } } } - logger.sentry('Complete migration v2'); }; migrations.push(v2); @@ -182,7 +176,7 @@ export default async function runMigrations() { */ const v3 = async () => { - logger.sentry('Ignoring migration v3'); + logger.debug('[runMigrations]: Ignoring migration v3'); return true; }; @@ -194,7 +188,7 @@ export default async function runMigrations() { */ const v4 = async () => { - logger.sentry('Ignoring migration v4'); + logger.debug('[runMigrations]: Ignoring migration v4'); return true; }; @@ -206,43 +200,41 @@ export default async function runMigrations() { * incorrectly by the keychain integrity checks */ const v5 = async () => { - logger.sentry('Start migration v5'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.sentry('Complete migration v5 early'); + logger.debug('[runMigrations]: Complete migration v5 early'); return; } const hasMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (!hasMigratedFlag) { - logger.sentry('Migration flag not set'); + logger.debug('[runMigrations]: Migration flag not set'); const hasOldSeedphraseKey = await hasKey(seedPhraseKey); if (hasOldSeedphraseKey) { - logger.sentry('Old seedphrase is still there'); + logger.debug('[runMigrations]: Old seedphrase is still there'); let incorrectDamagedWalletId = null; const updatedWallets = { ...wallets }; keys(updatedWallets).forEach(walletId => { if (updatedWallets[walletId].damaged && !updatedWallets[walletId].imported) { - logger.sentry('found incorrect damaged wallet', walletId); + logger.debug(`[runMigrations]: found incorrect damaged wallet ${walletId}`); delete updatedWallets[walletId].damaged; incorrectDamagedWalletId = walletId; } }); - logger.sentry('updating all wallets'); + logger.debug('[runMigrations]: updating all wallets'); await store.dispatch(walletsUpdate(updatedWallets)); - logger.sentry('done updating all wallets'); + logger.debug('[runMigrations]: done updating all wallets'); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too if (selected!.id === incorrectDamagedWalletId) { - logger.sentry('need to update the selected wallet'); + logger.debug('[runMigrations]: need to update the selected wallet'); const updatedSelectedWallet = updatedWallets[incorrectDamagedWalletId]; await store.dispatch(walletsSetSelected(updatedSelectedWallet)); - logger.sentry('selected wallet updated'); + logger.debug('[runMigrations]: selected wallet updated'); } } } - logger.sentry('Complete migration v5'); }; migrations.push(v5); @@ -252,6 +244,7 @@ export default async function runMigrations() { */ /* Fix dollars => stablecoins */ const v6 = async () => { + logger.debug('[runMigrations]: Ignoring migration v6'); // try { // const userLists = await getUserLists(); // const newLists = userLists.map((list: { id: string }) => { @@ -280,9 +273,9 @@ export default async function runMigrations() { const wallet = wallets[walletKeys[i]]; if (wallet.type !== WalletTypes.readOnly) { // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < wallet.addresses.length; x++) { - const { address } = wallet.addresses[x]; - logger.log('setting web profiles for address', address); + for (let x = 0; x < (wallet.addresses || []).length; x++) { + const { address } = (wallet.addresses || [])[x]; + logger.debug(`[runMigrations]: setting web profiles for address ${address}`); await store.dispatch(updateWebDataEnabled(true, address)); } } @@ -292,7 +285,7 @@ export default async function runMigrations() { migrations.push(v7); const v8 = async () => { - logger.log('wiping old metadata'); + logger.debug('[runMigrations]: wiping old metadata'); await deprecatedRemoveLocal(IMAGE_METADATA); }; @@ -305,7 +298,6 @@ export default async function runMigrations() { * same for contacts */ const v9 = async () => { - logger.log('Start migration v9'); // map from old color index to closest new color's index const newColorIndexes = [0, 4, 12, 21, 1, 20, 4, 9, 10]; try { @@ -316,7 +308,7 @@ export default async function runMigrations() { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; - const newAddresses = wallet.addresses.map((account: RainbowAccount) => { + const newAddresses = (wallet.addresses || []).map((account: RainbowAccount) => { const accountEmoji = returnStringFirstEmoji(account?.label); return { ...account, @@ -329,12 +321,12 @@ export default async function runMigrations() { const newWallet = { ...wallet, addresses: newAddresses }; updatedWallets[walletKeys[i]] = newWallet; } - logger.log('update wallets in store to index new colors'); + logger.debug('[runMigrations]: update wallets in store to index new colors'); await store.dispatch(walletsUpdate(updatedWallets)); const selectedWalletId = selected?.id; if (selectedWalletId) { - logger.log('update selected wallet to index new color'); + logger.debug('[runMigrations]: update selected wallet to index new color'); await store.dispatch(walletsSetSelected(updatedWallets[selectedWalletId])); } @@ -355,12 +347,10 @@ export default async function runMigrations() { : getRandomColor(), }; } - logger.log('update contacts to index new colors'); + logger.debug('[runMigrations]: update contacts to index new colors'); await saveContacts(updatedContacts); } catch (error) { - logger.sentry('Migration v9 failed: ', error); - const migrationError = new Error('Migration 9 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v9 failed: ${error}`)); } }; @@ -371,7 +361,6 @@ export default async function runMigrations() { * This step makes sure all contacts have an emoji set based on the address */ const v10 = async () => { - logger.log('Start migration v10'); try { // migrate contacts to corresponding emoji const contacts = await getContacts(); @@ -403,12 +392,10 @@ export default async function runMigrations() { } } } - logger.log('update contacts to add emojis / colors'); + logger.debug('[runMigrations]: update contacts to add emojis / colors'); await saveContacts(updatedContacts); } catch (error) { - logger.sentry('Migration v10 failed: ', error); - const migrationError = new Error('Migration 10 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v10 failed: ${error}`)); } }; @@ -419,9 +406,9 @@ export default async function runMigrations() { * This step resets review timers if we havnt asked in the last 2 weeks prior to running this */ const v11 = async () => { - logger.log('Start migration v11'); const hasReviewed = review.get(['hasReviewed']); if (hasReviewed) { + logger.debug('[runMigrations]: Migration v11: exiting early - already reviewed'); return; } @@ -430,10 +417,12 @@ export default async function runMigrations() { const TWO_MONTHS = 2 * 30 * 24 * 60 * 60 * 1000; if (Number(reviewAsked) > Date.now() - TWO_WEEKS) { + logger.debug('[runMigrations]: Migration v11: exiting early - not reviewed in the last 2 weeks'); return; } review.set(['timeOfLastPrompt'], Date.now() - TWO_MONTHS); + logger.debug('[runMigrations]: Migration v11: updated review timeOfLastPrompt'); }; migrations.push(v11); @@ -451,15 +440,15 @@ export default async function runMigrations() { for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < wallet.addresses.length; x++) { - const { address } = wallet.addresses[x]; + for (let x = 0; x < (wallet.addresses || []).length; x++) { + const { address } = (wallet.addresses || [])[x]; const assets = await getAssets(address, network); const hiddenCoins = await getHiddenCoins(address, network); const pinnedCoins = await getPinnedCoins(address, network); - logger.log(JSON.stringify({ pinnedCoins }, null, 2)); - logger.log(JSON.stringify({ hiddenCoins }, null, 2)); + logger.debug(`[runMigrations]: pinnedCoins: ${JSON.stringify({ pinnedCoins }, null, 2)}`); + logger.debug(`[runMigrations]: hiddenCoins: ${JSON.stringify({ hiddenCoins }, null, 2)}`); const pinnedCoinsMigrated = pinnedCoins.map((address: string) => { const asset = assets?.find((asset: any) => asset.address === address.toLowerCase()); @@ -471,8 +460,8 @@ export default async function runMigrations() { return getUniqueIdNetwork(asset?.address, network); }); - logger.log(JSON.stringify({ pinnedCoinsMigrated }, null, 2)); - logger.log(JSON.stringify({ hiddenCoinsMigrated }, null, 2)); + logger.debug(`[runMigrations]: pinnedCoinsMigrated: ${JSON.stringify({ pinnedCoinsMigrated }, null, 2)}`); + logger.debug(`[runMigrations]: hiddenCoinsMigrated: ${JSON.stringify({ hiddenCoinsMigrated }, null, 2)}`); await savePinnedCoins(uniq(pinnedCoinsMigrated), address, network); await saveHiddenCoins(uniq(hiddenCoinsMigrated), address, network); @@ -514,17 +503,14 @@ export default async function runMigrations() { const value = await loadString(key); if (typeof value === 'string') { await saveString(key, value, publicAccessControlOptions); - logger.debug('key migrated', key); + logger.debug(`[runMigrations]: key migrated: ${key}`); } } catch (error) { - logger.sentry('Error migration 13 :: key ', key); - logger.sentry('reason', error); + logger.error(new RainbowError(`[runMigrations]: Error migration 13 :: key ${key}: ${error}`)); } } } catch (error) { - logger.sentry('Migration v13 failed: ', error); - const migrationError = new Error('Migration 13 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v13 failed: ${error}`)); } }; @@ -557,6 +543,7 @@ export default async function runMigrations() { Ignored */ const v15 = async () => { + logger.debug('[runMigrations]: Ignoring migration v15'); return true; }; @@ -577,9 +564,7 @@ export default async function runMigrations() { // we don't care if it fails }); } catch (error: any) { - logger.sentry('Migration v16 failed: ', error); - const migrationError = new Error('Migration 16 failed'); - captureException(migrationError); + logger.error(new RainbowError(`[runMigrations]: Migration v16 failed: ${error}`)); } }; @@ -641,9 +626,19 @@ export default async function runMigrations() { /** *************** Migration v19 ****************** - * Migrates dapp browser favorites store from createStore to createRainbowStore + * Deleted migration */ const v19 = async () => { + return; + }; + + migrations.push(v19); + + /** + *************** Migration v20 ****************** + * Migrates dapp browser favorites store from createStore to createRainbowStore + */ + const v20 = async () => { const initializeLegacyStore = () => { return new Promise(resolve => { // Give the async legacy store a moment to initialize @@ -666,20 +661,20 @@ export default async function runMigrations() { } }; - migrations.push(v19); + migrations.push(v20); - logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`); + logger.debug(`[runMigrations]: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); if (migrations.length === currentVersion) { - logger.sentry(`Migrations: Nothing to run`); + logger.debug(`[runMigrations]: Nothing to run`); return; } for (let i = currentVersion; i < migrations.length; i++) { - logger.sentry(`Migrations: Running migration v${i}`); + logger.debug(`[runMigrations]: Running migration v${i}`); // @ts-expect-error await migrations[i].apply(null); - logger.sentry(`Migrations: Migration ${i} completed succesfully`); + logger.debug(`[runMigrations]: Migration ${i} completed succesfully`); await setMigrationVersion(i + 1); } } diff --git a/src/model/preferences.ts b/src/model/preferences.ts index 226424912e2..b74867ed820 100644 --- a/src/model/preferences.ts +++ b/src/model/preferences.ts @@ -88,13 +88,13 @@ export async function setPreference( }; const message = JSON.stringify(objToSign); const signature2 = await signWithSigningWallet(message); - logger.debug('☁️ SENDING ', { message }); + logger.debug(`[preferences]: ☁️ SENDING `, { message }); const { data } = await preferencesAPI.post>(`${PREFS_ENDPOINT}/${key}`, { message, signature, signature2, }); - logger.debug('☁️ RESPONSE', { + logger.debug(`[preferences]: ☁️ RESPONSE`, { reason: data?.reason, success: data?.success, }); @@ -105,7 +105,7 @@ export async function setPreference( return data?.success; } catch (e) { - logger.warn(`Preferences API failed to set preference`, { + logger.warn(`[preferences]: Preferences API failed to set preference`, { preferenceKey: key, }); return false; @@ -120,7 +120,7 @@ export async function getPreference( const { data } = await preferencesAPI.get>(`${PREFS_ENDPOINT}/${key}`, { params: { address }, }); - logger.debug('☁️ RESPONSE', { + logger.debug(`[preferences]: ☁️ RESPONSE`, { reason: data?.reason, success: data?.success, }); @@ -131,8 +131,9 @@ export async function getPreference( return data.data; } catch (e) { - logger.warn(`Preferences API failed to get preference`, { + logger.warn(`[preferences]: Preferences API failed to get preference`, { preferenceKey: key, + error: e, }); return null; } diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index 2f16922be6b..bf79c65490d 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -1,4 +1,4 @@ -import { getNetwork, saveNetwork } from '@/handlers/localstorage/globalSettings'; +import { getChainId, saveChainId } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; import { createQueryKey, queryClient } from '@/react-query'; @@ -91,6 +91,7 @@ export interface RainbowConfig extends Record rewards_enabled: boolean; degen_mode: boolean; + featured_results: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -173,13 +174,14 @@ export const DEFAULT_CONFIG: RainbowConfig = { rewards_enabled: true, degen_mode: false, + featured_results: false, }; export async function fetchRemoteConfig(): Promise { const config: RainbowConfig = { ...DEFAULT_CONFIG }; try { await remoteConfig().fetchAndActivate(); - logger.debug('Remote config fetched successfully'); + logger.debug(`[remoteConfig]: Remote config fetched successfully`); const parameters = remoteConfig().getAll(); Object.entries(parameters).forEach($ => { const [key, entry] = $; @@ -227,7 +229,8 @@ export async function fetchRemoteConfig(): Promise { key === 'swaps_v2' || key === 'idfa_check_enabled' || key === 'rewards_enabled' || - key === 'degen_mode' + key === 'degen_mode' || + key === 'featured_results' ) { config[key] = entry.asBoolean(); } else { @@ -236,15 +239,15 @@ export async function fetchRemoteConfig(): Promise { }); return config; } catch (e) { - logger.error(new RainbowError('Failed to fetch remote config'), { + logger.error(new RainbowError(`[remoteConfig]: Failed to fetch remote config`), { error: e, }); throw e; } finally { - logger.debug(`Current remote config:\n${JSON.stringify(config, null, 2)}`); - const currentNetwork = await getNetwork(); - web3SetHttpProvider(currentNetwork); - saveNetwork(currentNetwork); + logger.debug(`[remoteConfig]: Current remote config:\n${JSON.stringify(config, null, 2)}`); + const currentChainId = await getChainId(); + web3SetHttpProvider(currentChainId); + saveChainId(currentChainId); } } diff --git a/src/model/wallet.ts b/src/model/wallet.ts index a79e52b0d23..d35c72f8b48 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -56,8 +56,8 @@ import { IS_ANDROID } from '@/env'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { Signer } from '@ethersproject/abstract-signer'; import { sanitizeTypedData } from '@/utils/signingUtils'; -import { Network } from '@/helpers'; import { ExecuteFnParamsWithoutFn, performanceTracking, Screen } from '@/state/performance/performance'; +import { Network } from '@/networks/types'; export type EthereumPrivateKey = string; type EthereumMnemonic = string; @@ -317,7 +317,7 @@ export const sendTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.info('wallet: sending transaction', { transaction }); + logger.debug('[wallet]: sending transaction', { transaction }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -330,10 +330,10 @@ export const sendTransaction = async ({ if (!wallet) return null; try { const result = await wallet.sendTransaction(transaction); - logger.debug('send - tx result', { result }, DebugContext.wallet); + logger.debug(`[wallet]: send - tx result`, { result }, DebugContext.wallet); return { result }; } catch (error) { - logger.error(new RainbowError('Failed to send transaction'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to send transaction`), { error }); if (isHardwareWallet) { setHardwareTXError(true); } else { @@ -348,7 +348,7 @@ export const sendTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError('Failed to send transaction due to auth'), { + logger.error(new RainbowError(`[wallet]: Failed to send transaction due to auth`), { error, }); return null; @@ -365,7 +365,7 @@ export const signTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing transaction'); + logger.debug('[wallet]: signing transaction', {}, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -385,7 +385,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError('Failed to sign transaction'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign transaction`), { error }); return { error }; } } catch (error) { @@ -394,7 +394,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign transaction due to auth'), { + logger.error(new RainbowError(`[wallet]: Failed to sign transaction due to auth`), { error, }); return null; @@ -411,7 +411,7 @@ export const signPersonalMessage = async ( }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing personal message', { message }); + logger.debug('[wallet]: signing personal message', { message }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -433,7 +433,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError('Failed to sign personal message'), { + logger.error(new RainbowError(`[wallet]: Failed to sign personal message`), { error, }); return { error }; @@ -444,7 +444,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign personal message due to auth'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign personal message due to auth`), { error }); return null; } }; @@ -459,7 +459,7 @@ export const signTypedDataMessage = async ( }> => { let isHardwareWallet = false; try { - logger.info('wallet: signing typed data message', { message }); + logger.debug('[wallet]: signing typed data message', { message }, DebugContext.wallet); const wallet = existingWallet || (await loadWallet({ @@ -511,7 +511,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError('Failed to sign typed data message'), { + logger.error(new RainbowError(`[wallet]: Failed to sign typed data message`), { error, }); return { error }; @@ -522,7 +522,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError('Failed to sign typed data message due to auth'), { error }); + logger.error(new RainbowError(`[wallet]: Failed to sign typed data message due to auth`), { error }); return null; } }; @@ -558,7 +558,7 @@ export const loadPrivateKey = async (address: EthereumAddress, hardware: boolean return privateKey; } catch (error) { - logger.error(new RainbowError('Error loading private key'), { error }); + logger.error(new RainbowError(`[wallet]: Error loading private key`), { error }); return null; } }; @@ -617,15 +617,10 @@ export const createWallet = async ({ callbackAfterSeeds = null; } const isImported = !!seed; - logger.info('Importing new wallet'); - if (!seed) { - logger.info('Creating new wallet'); - } + logger.debug(`[wallet]: ${isImported ? 'Importing new wallet' : 'Creating new wallet'}`, {}, DebugContext.wallet); const walletSeed = seed || generateMnemonic(); const addresses: RainbowAccount[] = []; try { - const { dispatch } = store; - const { isHDWallet, type, @@ -645,19 +640,19 @@ export const createWallet = async ({ // hardware pkey format is ${bluetooth device id}/${index} pkey = `${seed}/0`; } - logger.debug('[createWallet] - getWallet from seed', {}, DebugContext.wallet); + logger.debug('[wallet]: getWallet from seed', {}, DebugContext.wallet); // Get all wallets const allWalletsResult = await getAllWallets(); - logger.debug('[createWallet] - getAllWallets', {}, DebugContext.wallet); + logger.debug('[wallet]: getAllWallets', {}, DebugContext.wallet); const allWallets: AllRainbowWallets = allWalletsResult?.wallets ?? {}; let existingWalletId = null; if (isImported) { // Checking if the generated account already exists and is visible - logger.debug('[createWallet] - checking if account already exists', {}, DebugContext.wallet); + logger.debug('[wallet]: checking if account already exists', {}, DebugContext.wallet); const alreadyExistingWallet = Object.values(allWallets).find((someWallet: RainbowWallet) => { - return !!someWallet.addresses.find( + return !!someWallet.addresses?.find( account => toChecksumAddress(account.address) === toChecksumAddress(walletAddress) && account.visible ); }); @@ -673,13 +668,13 @@ export const createWallet = async ({ if (!isRestoring) { setTimeout(() => Alert.alert(lang.t('wallet.new.alert.oops'), lang.t('wallet.new.alert.looks_like_already_imported')), 1); } - logger.debug('[createWallet] - already imported this wallet', {}, DebugContext.wallet); + logger.debug('[wallet]: already imported this wallet', {}, DebugContext.wallet); return null; } } const id = existingWalletId || `wallet_${Date.now()}`; - logger.debug('[createWallet] - wallet ID', { id }, DebugContext.wallet); + logger.debug('[wallet]: wallet ID', { id }, DebugContext.wallet); // load this up front and pass to other keychain setters to avoid multiple // auth requests @@ -688,17 +683,17 @@ export const createWallet = async ({ await saveSeedPhrase(walletSeed, id, { androidEncryptionPin }); - logger.debug('[createWallet] - saved seed phrase', {}, DebugContext.wallet); + logger.debug('[wallet]: saved seed phrase', {}, DebugContext.wallet); // Save address await saveAddress(walletAddress); - logger.debug('[createWallet] - saved address', {}, DebugContext.wallet); + logger.debug('[wallet]: saved address', {}, DebugContext.wallet); // Save private key await saveKeyForWallet(walletAddress, pkey, isHardwareWallet, { androidEncryptionPin, }); - logger.debug('[createWallet] - saved private key', {}, DebugContext.wallet); + logger.debug('[wallet]: saved private key', {}, DebugContext.wallet); const colorIndexForWallet = color !== null ? color : addressHashedColorIndex(walletAddress) || 0; @@ -715,10 +710,10 @@ export const createWallet = async ({ }); if (type !== EthereumWalletType.readOnly && type !== EthereumWalletType.bluetooth) { // Creating signature for this wallet - logger.debug(`[createWallet] - generating signature`, {}, DebugContext.wallet); + logger.debug(`[wallet]: generating signature`, {}, DebugContext.wallet); await createSignature(walletAddress, pkey); // Enable web profile - logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); store.dispatch(updateWebDataEnabled(true, walletAddress)); // Save the color setPreference(PreferenceActionType.init, 'profile', address, { @@ -730,7 +725,7 @@ export const createWallet = async ({ // Initiate auto account discovery for imported wallets via seedphrase // or for hardware wallets if ((isHDWallet && root && isImported) || (isHardwareWallet && seed)) { - logger.debug('[createWallet] - initializing account auto discovery', {}, DebugContext.wallet); + logger.debug('[wallet]: initializing account auto discovery', {}, DebugContext.wallet); let index = 1; let lookup = 0; // Starting on index 1, we check the tx history @@ -757,14 +752,14 @@ export const createWallet = async ({ try { hasTxHistory = await ethereumUtils.hasPreviousTransactions(nextWallet.address); } catch (error) { - logger.error(new RainbowError('[createWallet] - Error getting txn history for address'), { error }); + logger.error(new RainbowError('[wallet]: Error getting txn history for address'), { error }); } let discoveredAccount: RainbowAccount | undefined; let discoveredWalletId: RainbowWallet['id'] | undefined; Object.values(allWallets).forEach(someWallet => { - const existingAccount = someWallet.addresses.find( + const existingAccount = someWallet.addresses?.find( account => toChecksumAddress(account.address) === toChecksumAddress(nextWallet.address) ); if (existingAccount) { @@ -792,7 +787,7 @@ export const createWallet = async ({ if (hasTxHistory) { // Save private key await saveKeyForWallet(nextWallet.address, nextWallet.privateKey, isHardwareWallet, { androidEncryptionPin }); - logger.debug(`[createWallet] - saved private key for wallet index: ${index}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: saved private key for wallet index: ${index}`, {}, DebugContext.wallet); addresses.push({ address: nextWallet.address, @@ -806,7 +801,7 @@ export const createWallet = async ({ if (!isHardwareWallet) { // Creating signature for this wallet - logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); await createSignature(nextWallet.address, nextWallet.privateKey); // Enable web profile store.dispatch(updateWebDataEnabled(true, nextWallet.address)); @@ -870,11 +865,11 @@ export const createWallet = async ({ } if (!silent) { - logger.debug('[createWallet] - setting selected wallet', {}, DebugContext.wallet); + logger.debug('[wallet]: setting selected wallet', {}, DebugContext.wallet); await setSelectedWallet(allWallets[id]); } - logger.debug('[createWallet] - saving all wallets', {}, DebugContext.wallet); + logger.debug('[wallet]: saving all wallets', {}, DebugContext.wallet); await saveAllWallets(allWallets); if (walletResult && walletAddress) { @@ -885,7 +880,7 @@ export const createWallet = async ({ } return null; } catch (error) { - logger.error(new RainbowError('Error in createWallet'), { error }); + logger.error(new RainbowError('[wallet]: Error in createWallet'), { error }); return null; } }; @@ -996,7 +991,7 @@ export const getPrivateKey = async (address: EthereumAddress): Promise => } return null; } catch (error) { - logger.error(new RainbowError('Error in getAllWallets'), { error }); + logger.error(new RainbowError('[wallet]: Error in getAllWallets'), { error }); return null; } }; @@ -1159,26 +1154,26 @@ export const generateAccount = async (id: RainbowWallet['id'], index: number): P return newAccount; } catch (error) { - logger.error(new RainbowError('[generateAccount] - Error generating account for keychain'), { error }); + logger.error(new RainbowError('[wallet]: Error generating account for keychain'), { error }); return null; } }; const migrateSecrets = async (): Promise => { try { - logger.info('Migrating wallet secrets'); + logger.debug('[wallet]: Migrating wallet secrets', {}, DebugContext.wallet); const seedphrase = await oldLoadSeedPhrase(); if (!seedphrase) { - logger.debug('[migrateSecrets] - old seed doesnt exist!', {}, DebugContext.wallet); + logger.debug('[wallet]: old seed doesnt exist!', {}, DebugContext.wallet); // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[migrateSecrets] - marking secrets as migrated', {}, DebugContext.wallet); + logger.debug('[wallet]: marking secrets as migrated', {}, DebugContext.wallet); return null; } const type = identifyWalletType(seedphrase); - logger.debug(`[migrateSecrets] - wallet type: ${type}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: wallet type: ${type}`, {}, DebugContext.wallet); let hdnode: undefined | HDNode, node: undefined | HDNode, existingAccount: undefined | Wallet; switch (type) { case EthereumWalletType.privateKey: @@ -1200,10 +1195,10 @@ const migrateSecrets = async (): Promise => { } if (!existingAccount && hdnode) { - logger.debug('[migrateSecrets] - No existing account, so we have to derive it', {}, DebugContext.wallet); + logger.debug('[wallet]: No existing account, so we have to derive it', {}, DebugContext.wallet); node = hdnode.derivePath(getHdPath({ type: WalletLibraryType.ethers, index: 0 })); existingAccount = new Wallet(node.privateKey); - logger.debug('[migrateSecrets] - Got existing account', {}, DebugContext.wallet); + logger.debug('[wallet]: Got existing account', {}, DebugContext.wallet); } if (!existingAccount) { @@ -1213,10 +1208,10 @@ const migrateSecrets = async (): Promise => { // Check that wasn't migrated already! const pkeyExists = await keychain.hasKey(`${existingAccount.address}_${privateKeyKey}`); if (!pkeyExists) { - logger.debug('[migrateSecrets] - new pkey didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[wallet]: new pkey didnt exist so we should save it', {}, DebugContext.wallet); // Save the private key in the new format await saveKeyForWallet(existingAccount.address, existingAccount.privateKey, false); - logger.debug('[migrateSecrets] - new pkey saved', {}, DebugContext.wallet); + logger.debug('[wallet]: new pkey saved', {}, DebugContext.wallet); } const selectedWalletData = await getSelectedWallet(); @@ -1228,13 +1223,13 @@ const migrateSecrets = async (): Promise => { // Save the seedphrase in the new format const seedExists = await keychain.hasKey(`${wallet.id}_${seedPhraseKey}`); if (!seedExists) { - logger.debug('[migrateSecrets] - new seed didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[wallet]: new seed didnt exist so we should save it', {}, DebugContext.wallet); await saveSeedPhrase(seedphrase, wallet.id); - logger.debug('[migrateSecrets] - new seed saved', {}, DebugContext.wallet); + logger.debug('[wallet]: new seed saved', {}, DebugContext.wallet); } // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[migrateSecrets] - saved migrated key', {}, DebugContext.wallet); + logger.debug('[wallet]: saved migrated key', {}, DebugContext.wallet); return { hdnode, privateKey: existingAccount.privateKey, @@ -1242,7 +1237,7 @@ const migrateSecrets = async (): Promise => { type, }; } catch (error) { - logger.error(new RainbowError('[migrateSecrets] - Error while migrating secrets'), { error }); + logger.error(new RainbowError('[wallet]: Error while migrating secrets'), { error }); return null; } }; @@ -1257,7 +1252,7 @@ export const cleanUpWalletKeys = async (): Promise => { keychain.remove(key); } catch (error) { // key might not exists - logger.warn('[cleanUpWalletKeys] - failure to delete key', { + logger.warn('[wallet]: failure to delete key', { key, error, }); @@ -1277,10 +1272,10 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): // First we need to check if that key already exists const keyFound = await keychain.hasKey(`${id}_${seedPhraseKey}`); if (!keyFound) { - logger.debug('[loadAndMigrate] - key not found, should need migration', {}, DebugContext.wallet); + logger.debug('[wallet]: key not found, should need migration', {}, DebugContext.wallet); // if it doesn't we might have a migration pending const isSeedPhraseMigrated = await keychain.loadString(oldSeedPhraseMigratedKey); - logger.debug(`[loadAndMigrate] - Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); + logger.debug(`[wallet]: Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); // We need to migrate the seedphrase & private key first // In that case we regenerate the existing private key to store it with the new format @@ -1288,24 +1283,24 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): const migratedSecrets = await migrateSecrets(); seedPhrase = migratedSecrets?.seedphrase ?? null; } else { - logger.error(new RainbowError('[loadAndMigrate] - Migrated flag was set but there is no key!'), { id }); + logger.error(new RainbowError('[wallet]: Migrated flag was set but there is no key!'), { id }); } } else { - logger.debug('[loadAndMigrate] - Getting seed directly', {}, DebugContext.wallet); + logger.debug('[wallet]: Getting seed directly', {}, DebugContext.wallet); const androidEncryptionPin = IS_ANDROID && !(await kc.getSupportedBiometryType()) ? await authenticateWithPIN() : undefined; const seedData = await getSeedPhrase(id, { androidEncryptionPin }); seedPhrase = seedData?.seedphrase ?? null; if (seedPhrase) { - logger.debug('[loadAndMigrate] - got seed succesfully', {}, DebugContext.wallet); + logger.debug('[wallet]: got seed succesfully', {}, DebugContext.wallet); } else { - logger.error(new RainbowError('[loadAndMigrate] - Missing seed for wallet - (Key exists but value isnt valid)!')); + logger.error(new RainbowError('[wallet]: Missing seed for wallet - (Key exists but value isnt valid)!')); } } return seedPhrase; } catch (error) { - logger.error(new RainbowError('[loadAndMigrate] - Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); + logger.error(new RainbowError('[wallet]: Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); throw error; } }; diff --git a/src/navigation/HardwareWalletTxNavigator.tsx b/src/navigation/HardwareWalletTxNavigator.tsx index f91b59e5a83..cff47c33435 100644 --- a/src/navigation/HardwareWalletTxNavigator.tsx +++ b/src/navigation/HardwareWalletTxNavigator.tsx @@ -23,7 +23,7 @@ export const ledgerStorage = new MMKV({ export const HARDWARE_TX_ERROR_KEY = 'hardwareTXError'; export const setHardwareTXError = (value: boolean) => { - logger.info(`setHardwareTXError`, { value }); + logger.warn(`[HardwareWalletTxNavigator]: setHardwareTXError`, { value }); ledgerStorage.set(HARDWARE_TX_ERROR_KEY, value); }; @@ -83,14 +83,14 @@ export const HardwareWalletTxNavigator = () => { ); const successCallback = useCallback(() => { - logger.debug('[LedgerTx] - submitting tx', {}, DebugContext.ledger); + logger.debug('[HardwareWalletTxNavigator]: submitting tx', {}, DebugContext.ledger); if (!isReady) { setReadyForPolling(false); setIsReady(true); setHardwareTXError(false); submit(); } else { - logger.debug('[LedgerTx] - already submitted', {}, DebugContext.ledger); + logger.debug('[HardwareWalletTxNavigator]: already submitted', {}, DebugContext.ledger); } }, [isReady, setIsReady, setReadyForPolling, submit]); diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index b3f89f1ec1e..361fbf5d462 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -7,8 +7,7 @@ import { TestnetToast } from '@/components/toasts'; import { DAPP_BROWSER, POINTS, useExperimentalFlag } from '@/config'; import { Box, Columns, globalColors, Stack, useForegroundColor, Text, Cover, useColorMode } from '@/design-system'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import { web3Provider } from '@/handlers/web3'; -import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; +import { isUsingButtonNavigation } from '@/utils/deviceUtils'; import { useAccountAccentColor, useAccountSettings, useCoinListEdited, useDimensions, usePendingTransactions } from '@/hooks'; import { useRemoteConfig } from '@/model/remoteConfig'; import RecyclerListViewScrollToTopProvider, { @@ -52,7 +51,7 @@ function getTabBarHeight() { return 82; } if (!isUsingButtonNavigation()) { - return 72; + return 82; } return 48; } @@ -92,7 +91,13 @@ const ActivityTabIcon = React.memo( }, [pendingCount]); return pendingCount > 0 ? ( - + @@ -440,7 +445,7 @@ function SwipeNavigatorScreens() { } export function SwipeNavigator() { - const { network } = useAccountSettings(); + const { chainId } = useAccountSettings(); const { colors } = useTheme(); return ( @@ -456,7 +461,7 @@ export function SwipeNavigator() { - + ); } diff --git a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx index 5de36d9bbfb..9c215ed804d 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx @@ -3,15 +3,15 @@ import React, { useCallback } from 'react'; import { StyleSheet, TouchableWithoutFeedback, View } from 'react-native'; const BottomSheetBackground = () => { - //#region hooks + // #region hooks const { close } = useBottomSheet(); - //#endregion + // #endregion - //#region callbacks + // #region callbacks const handleOnPress = useCallback(() => { close(); }, [close]); - //#endregion + // #endregion return ( diff --git a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx index b01d9db999c..8e17cd77670 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx @@ -12,11 +12,11 @@ type Props = BottomSheetNavigationConfig & { }; const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => { - //#region hooks + // #region hooks const forceUpdate = useForceUpdate(); - //#endregion + // #endregion - //#region variables + // #region variables const descriptorsCache = useRef({}); const [firstKey, ...restKeys] = useMemo( // @ts-ignore navigation type mismatch @@ -40,9 +40,9 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => .forEach(key => { descriptorsCache.current[key].removing = true; }); - //#endregion + // #endregion - //#region callbacks + // #region callbacks const handleOnDismiss = useCallback((key: string, removed: boolean) => { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete descriptorsCache.current[key]; @@ -64,7 +64,7 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - //#endregion + // #endregion return ( {descriptors[firstKey].render()} diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 3e9de2383ff..182550aa6c8 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import { Keyboard, StatusBar } from 'react-native'; +import { Keyboard } from 'react-native'; import { useTheme } from '@/theme/ThemeContext'; import colors from '@/theme/currentColors'; import styled from '@/styled-thing'; import { fonts } from '@/styles'; -import networkTypes from '@/helpers/networkTypes'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; -import { getNetworkObj } from '@/networks'; import { getPositionSheetHeight } from '@/screens/positions/PositionSheet'; import { Icon } from '@/components/icons'; @@ -29,6 +27,7 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -490,7 +489,7 @@ export const ensAdditionalRecordsSheetConfig: PartialNavigatorConfigOptions = { }; export const explainSheetConfig: PartialNavigatorConfigOptions = { - options: ({ route: { params = { network: getNetworkObj(networkTypes.mainnet).name } } }) => { + options: ({ route: { params = { network: chainIdToNameMapping[ChainId.mainnet] } } }) => { // @ts-ignore const explainerConfig = explainers(params.network)[params?.type]; return buildCoolModalConfig({ diff --git a/src/navigation/types.ts b/src/navigation/types.ts index 8a35cda2633..d84c2139148 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -72,4 +72,7 @@ export type RootStackParamList = { onSuccess: () => Promise; onFailure: () => Promise; }; + [Routes.SWAP]: { + action?: 'open_swap_settings'; + }; }; diff --git a/src/networks/README.md b/src/networks/README.md index 47dd785beaa..678269fb209 100644 --- a/src/networks/README.md +++ b/src/networks/README.md @@ -3,9 +3,9 @@ Handling for networks throughout the codebase. ```typescript -import { getNetworkObj, Networks } from '@/networks'; +import { getNetworkObject } from '@/networks'; -const networkObj = getNetworkObj(Networks.mainnet); +const networkObj = getNetworkObject({ chainId: ChainId.mainnet }); // Get static properties based on network const networkName = networkObj.name; @@ -19,10 +19,10 @@ const gasPrices = networkObj.getGasPrices(); // Getting a subset of network objects -const layer2s = RainbowNetworks.filter(network => network.networkType === 'layer2'); +const layer2s = RainbowNetworkObjects.filter(network => network.networkType === 'layer2'); // Or networks that match specific properties -const walletconnectNetworks = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.value); +const walletconnectNetworks = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.value); ``` ## Network Objects diff --git a/src/networks/arbitrum.ts b/src/networks/arbitrum.ts index 72c95c4cad5..35c33af556b 100644 --- a/src/networks/arbitrum.ts +++ b/src/networks/arbitrum.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { arbitrum } from '@wagmi/chains'; import { ARBITRUM_ETH_ADDRESS } from '@/references'; import { getArbitrumGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getArbitrumNetworkObject = (): NetworkProperties => { const { arbitrum_enabled, arbitrum_tx_enabled } = getRemoteConfig(); @@ -25,7 +26,7 @@ export const getArbitrumNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(arbitrum.id), - getProvider: () => getProviderForNetwork(Network.arbitrum), + getProvider: () => getProvider({ chainId: ChainId.arbitrum }), balanceCheckerAddress: '0x54A4E5800345c01455a7798E0D96438364e22723', // features diff --git a/src/networks/avalanche.ts b/src/networks/avalanche.ts index ecb8b628d99..cc411357c81 100644 --- a/src/networks/avalanche.ts +++ b/src/networks/avalanche.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { avalanche } from '@wagmi/chains'; import { AVAX_AVALANCHE_ADDRESS } from '@/references'; import { getAvalancheGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getAvalancheNetworkObject = (): NetworkProperties => { const { avalanche_enabled, avalanche_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getAvalancheNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(avalanche.id), - getProvider: () => getProviderForNetwork(Network.avalanche), + getProvider: () => getProvider({ chainId: ChainId.avalanche }), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/base.ts b/src/networks/base.ts index ea2b6163d5a..7a978ddca6d 100644 --- a/src/networks/base.ts +++ b/src/networks/base.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { base } from '@wagmi/chains'; import { BASE_ETH_ADDRESS } from '@/references'; import { getBaseGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getBaseNetworkObject = (): NetworkProperties => { const { base_enabled, base_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getBaseNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(base.id), - getProvider: () => getProviderForNetwork(Network.base), + getProvider: () => getProvider({ chainId: ChainId.base }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/blast.ts b/src/networks/blast.ts index f7b168d3e72..6d4d482fc87 100644 --- a/src/networks/blast.ts +++ b/src/networks/blast.ts @@ -1,4 +1,4 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { blast } from 'viem/chains'; @@ -6,6 +6,7 @@ import { getBlastGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { BLAST_MAINNET_RPC } from 'react-native-dotenv'; import { BLAST_ETH_ADDRESS } from '@/references'; +import { ChainId } from '@/networks/types'; export const getBlastNetworkObject = (): NetworkProperties => { const { blast_enabled, blast_tx_enabled } = getRemoteConfig(); @@ -29,7 +30,7 @@ export const getBlastNetworkObject = (): NetworkProperties => { balanceCheckerAddress: '', rpc: () => proxyRpcEndpoint(blast.id), - getProvider: () => getProviderForNetwork(Network.blast), + getProvider: () => getProvider({ chainId: ChainId.blast }), // features features: { diff --git a/src/networks/bsc.ts b/src/networks/bsc.ts index 3e38d897969..89aa63c17d8 100644 --- a/src/networks/bsc.ts +++ b/src/networks/bsc.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { bsc } from '@wagmi/chains'; import { BNB_BSC_ADDRESS, BNB_MAINNET_ADDRESS } from '@/references'; import { getBscGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getBSCNetworkObject = (): NetworkProperties => { const { bsc_enabled, bsc_tx_enabled } = getRemoteConfig(); @@ -28,7 +29,7 @@ export const getBSCNetworkObject = (): NetworkProperties => { // this should be refactored to have less deps rpc: () => proxyRpcEndpoint(bsc.id), - getProvider: () => getProviderForNetwork(Network.bsc), + getProvider: () => getProvider({ chainId: ChainId.bsc }), balanceCheckerAddress: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', // features diff --git a/src/networks/degen.ts b/src/networks/degen.ts index bc7c43aad2d..044e3e6baba 100644 --- a/src/networks/degen.ts +++ b/src/networks/degen.ts @@ -1,4 +1,4 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { degen } from 'viem/chains'; @@ -6,6 +6,7 @@ import { DEGEN_CHAIN_DEGEN_ADDRESS } from '@/references'; import { getDegenGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { DEGEN_MAINNET_RPC } from 'react-native-dotenv'; +import { ChainId } from '@/networks/types'; export const getDegenNetworkObject = (): NetworkProperties => { const { degen_enabled, degen_tx_enabled } = getRemoteConfig(); @@ -28,7 +29,7 @@ export const getDegenNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(degen.id), - getProvider: () => getProviderForNetwork(Network.degen), + getProvider: () => getProvider({ chainId: ChainId.degen }), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/gnosis.ts b/src/networks/gnosis.ts index f6e18c742b7..cac594c0e13 100644 --- a/src/networks/gnosis.ts +++ b/src/networks/gnosis.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { gnosis } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; +import { ChainId } from '@/networks/types'; export const getGnosisNetworkObject = (): NetworkProperties => { return { @@ -24,7 +25,7 @@ export const getGnosisNetworkObject = (): NetworkProperties => { }, rpc: () => '', - getProvider: () => getProviderForNetwork(Network.optimism), + getProvider: () => getProvider({ chainId: ChainId.gnosis }), balanceCheckerAddress: '', // features diff --git a/src/networks/goerli.ts b/src/networks/goerli.ts index 2d9423a7ac2..71d3e19aeca 100644 --- a/src/networks/goerli.ts +++ b/src/networks/goerli.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { goerli } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getGoerliNetworkObject = (): NetworkProperties => { const { goerli_enabled, goerli_tx_enabled } = getRemoteConfig(); @@ -25,7 +26,7 @@ export const getGoerliNetworkObject = (): NetworkProperties => { }, // this should be refactored to have less deps - getProvider: () => getProviderForNetwork(Network.goerli), + getProvider: () => getProvider({ chainId: ChainId.goerli }), rpc: () => proxyRpcEndpoint(goerli.id), balanceCheckerAddress: '0xf3352813b612a2d198e437691557069316b84ebe', diff --git a/src/networks/index.ts b/src/networks/index.ts index 79c9089f23a..b4c5dd2f1f6 100644 --- a/src/networks/index.ts +++ b/src/networks/index.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import store from '@/redux/store'; import * as ls from '@/storage'; import { getArbitrumNetworkObject } from './arbitrum'; @@ -12,14 +12,14 @@ import { getGoerliNetworkObject } from './goerli'; import { getMainnetNetworkObject } from './mainnet'; import { getOptimismNetworkObject } from './optimism'; import { getPolygonNetworkObject } from './polygon'; -import { Network, NetworkProperties } from './types'; +import { NetworkProperties } from './types'; import { getZoraNetworkObject } from './zora'; /** * Array of all Rainbow Networks * the ordering is the default sorting */ -export const RainbowNetworks = [ +export const RainbowNetworkObjects = [ getMainnetNetworkObject(), getArbitrumNetworkObject(), getBaseNetworkObject(), @@ -34,46 +34,24 @@ export const RainbowNetworks = [ getDegenNetworkObject(), ]; +export const RainbowSupportedChainIds = [ + ChainId.mainnet, + ChainId.arbitrum, + ChainId.base, + ChainId.optimism, + ChainId.polygon, + ChainId.zora, + ChainId.gnosis, + ChainId.goerli, + ChainId.bsc, + ChainId.avalanche, + ChainId.blast, + ChainId.degen, +]; + /** * Helper function to get specific Rainbow Network's Object */ -export function getNetworkObj(network: Network): NetworkProperties { - switch (network) { - // Mainnet - case Network.mainnet: - return getMainnetNetworkObject(); - - // L2s - case Network.arbitrum: - return getArbitrumNetworkObject(); - case Network.base: - return getBaseNetworkObject(); - case Network.bsc: - return getBSCNetworkObject(); - case Network.optimism: - return getOptimismNetworkObject(); - case Network.polygon: - return getPolygonNetworkObject(); - case Network.zora: - return getZoraNetworkObject(); - case Network.gnosis: - return getGnosisNetworkObject(); - case Network.avalanche: - return getAvalancheNetworkObject(); - case Network.blast: - return getBlastNetworkObject(); - case Network.degen: - return getDegenNetworkObject(); - // Testnets - case Network.goerli: - return getGoerliNetworkObject(); - - // Fallback - default: - return getMainnetNetworkObject(); - } -} - export function getNetworkObject({ chainId }: { chainId: ChainId }): NetworkProperties { switch (chainId) { // Mainnet @@ -125,14 +103,14 @@ export function sortNetworks(): NetworkProperties[] { return count1 > count2 ? -1 : 1; }; - return RainbowNetworks.sort(tokenSort); + return RainbowNetworkObjects.sort(tokenSort); } export function getSwappableNetworks(): NetworkProperties[] { - return RainbowNetworks.filter(network => network.features.swaps); + return RainbowNetworkObjects.filter(network => network.features.swaps); } -export const RainbowNetworkByChainId = RainbowNetworks.reduce( +export const RainbowNetworkByChainId = RainbowNetworkObjects.reduce( (acc, network) => { acc[network.id] = network; return acc; diff --git a/src/networks/mainnet.ts b/src/networks/mainnet.ts index 2c3644511ec..e185b5c1b25 100644 --- a/src/networks/mainnet.ts +++ b/src/networks/mainnet.ts @@ -1,9 +1,10 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; -import { Network, NetworkProperties } from './types'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { Network, NetworkProperties, ChainId } from './types'; import { gasUtils } from '@/utils'; import { mainnet } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const getMainnetNetworkObject = (): NetworkProperties => { const { mainnet_enabled, mainnet_tx_enabled } = getRemoteConfig(); @@ -24,9 +25,8 @@ export const getMainnetNetworkObject = (): NetworkProperties => { address: ETH_ADDRESS, }, - // this should be refactored to have less deps - getProvider: () => getProviderForNetwork(Network.mainnet), - rpc: () => proxyRpcEndpoint(mainnet.id), + getProvider: () => getProvider({ chainId: ChainId.mainnet }), + rpc: () => (useConnectedToHardhatStore.getState().connectedToHardhat ? 'http://127.0.0.1:8545' : proxyRpcEndpoint(mainnet.id)), balanceCheckerAddress: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', // features diff --git a/src/networks/optimism.ts b/src/networks/optimism.ts index b2d6ce8c8a6..2a9bf3c4884 100644 --- a/src/networks/optimism.ts +++ b/src/networks/optimism.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { optimism } from '@wagmi/chains'; import { OPTIMISM_ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getOptimismNetworkObject = (): NetworkProperties => { const { optimism_enabled, optimism_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getOptimismNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(optimism.id), - getProvider: () => getProviderForNetwork(Network.optimism), + getProvider: () => getProvider({ chainId: ChainId.optimism }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/polygon.ts b/src/networks/polygon.ts index 7866a77a4c4..49f4feba581 100644 --- a/src/networks/polygon.ts +++ b/src/networks/polygon.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { polygon } from '@wagmi/chains'; import { MATIC_MAINNET_ADDRESS, MATIC_POLYGON_ADDRESS } from '@/references'; import { getPolygonGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getPolygonNetworkObject = (): NetworkProperties => { const { polygon_tx_enabled } = getRemoteConfig(); @@ -27,7 +28,7 @@ export const getPolygonNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(polygon.id), - getProvider: () => getProviderForNetwork(Network.polygon), + getProvider: () => getProvider({ chainId: ChainId.polygon }), balanceCheckerAddress: '0x54A4E5800345c01455a77798E0D96438364e22723', // features diff --git a/src/networks/types.ts b/src/networks/types.ts index 052080ce5c7..6e94851fc28 100644 --- a/src/networks/types.ts +++ b/src/networks/types.ts @@ -2,7 +2,11 @@ import { EthereumAddress } from '@/entities'; import { GasPricesAPIData } from '@/entities/gas'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { Chain } from '@wagmi/chains'; -// network.ts +import * as chain from 'viem/chains'; + +const HARDHAT_CHAIN_ID = 1337; +const HARDHAT_OP_CHAIN_ID = 1338; + export enum Network { arbitrum = 'arbitrum', goerli = 'goerli', @@ -18,6 +22,199 @@ export enum Network { degen = 'degen', } +export enum ChainId { + arbitrum = chain.arbitrum.id, + arbitrumNova = chain.arbitrumNova.id, + arbitrumSepolia = chain.arbitrumSepolia.id, + avalanche = chain.avalanche.id, + avalancheFuji = chain.avalancheFuji.id, + base = chain.base.id, + baseSepolia = chain.baseSepolia.id, + blast = chain.blast.id, + blastSepolia = chain.blastSepolia.id, + bsc = chain.bsc.id, + bscTestnet = chain.bscTestnet.id, + celo = chain.celo.id, + degen = chain.degen.id, + gnosis = chain.gnosis.id, + goerli = chain.goerli.id, + hardhat = HARDHAT_CHAIN_ID, + hardhatOptimism = HARDHAT_OP_CHAIN_ID, + holesky = chain.holesky.id, + linea = chain.linea.id, + mainnet = chain.mainnet.id, + manta = chain.manta.id, + optimism = chain.optimism.id, + optimismSepolia = chain.optimismSepolia.id, + polygon = chain.polygon.id, + polygonAmoy = chain.polygonAmoy.id, + polygonMumbai = chain.polygonMumbai.id, + polygonZkEvm = chain.polygonZkEvm.id, + rari = 1380012617, + scroll = chain.scroll.id, + sepolia = chain.sepolia.id, + zora = chain.zora.id, + zoraSepolia = chain.zoraSepolia.id, +} + +export enum ChainName { + arbitrum = 'arbitrum', + arbitrumNova = 'arbitrum-nova', + arbitrumSepolia = 'arbitrum-sepolia', + avalanche = 'avalanche', + avalancheFuji = 'avalanche-fuji', + base = 'base', + blast = 'blast', + blastSepolia = 'blast-sepolia', + bsc = 'bsc', + celo = 'celo', + degen = 'degen', + gnosis = 'gnosis', + goerli = 'goerli', + linea = 'linea', + manta = 'manta', + optimism = 'optimism', + polygon = 'polygon', + polygonZkEvm = 'polygon-zkevm', + rari = 'rari', + scroll = 'scroll', + zora = 'zora', + mainnet = 'mainnet', + holesky = 'holesky', + hardhat = 'hardhat', + hardhatOptimism = 'hardhat-optimism', + sepolia = 'sepolia', + optimismSepolia = 'optimism-sepolia', + bscTestnet = 'bsc-testnet', + polygonMumbai = 'polygon-mumbai', + baseSepolia = 'base-sepolia', + zoraSepolia = 'zora-sepolia', + polygonAmoy = 'polygon-amoy', +} + +export const networkToIdMapping: { [key in Network]: ChainId } = { + [Network.arbitrum]: ChainId.arbitrum, + [Network.goerli]: ChainId.goerli, + [Network.mainnet]: ChainId.mainnet, + [Network.optimism]: ChainId.optimism, + [Network.polygon]: ChainId.polygon, + [Network.base]: ChainId.base, + [Network.bsc]: ChainId.bsc, + [Network.zora]: ChainId.zora, + [Network.gnosis]: ChainId.gnosis, + [Network.avalanche]: ChainId.avalanche, + [Network.blast]: ChainId.blast, + [Network.degen]: ChainId.degen, +}; + +export const chainNameToIdMapping: { + [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; +} = { + ['ethereum']: ChainId.mainnet, + [ChainName.arbitrum]: ChainId.arbitrum, + [ChainName.arbitrumNova]: ChainId.arbitrumNova, + [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, + [ChainName.avalanche]: ChainId.avalanche, + [ChainName.avalancheFuji]: ChainId.avalancheFuji, + [ChainName.base]: ChainId.base, + [ChainName.bsc]: ChainId.bsc, + [ChainName.celo]: ChainId.celo, + [ChainName.degen]: ChainId.degen, + [ChainName.gnosis]: ChainId.gnosis, + [ChainName.linea]: ChainId.linea, + [ChainName.manta]: ChainId.manta, + [ChainName.optimism]: ChainId.optimism, + [ChainName.goerli]: ChainId.goerli, + [ChainName.polygon]: ChainId.polygon, + [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, + [ChainName.rari]: ChainId.rari, + [ChainName.scroll]: ChainId.scroll, + [ChainName.zora]: ChainId.zora, + [ChainName.mainnet]: ChainId.mainnet, + [ChainName.holesky]: ChainId.holesky, + [ChainName.hardhat]: ChainId.hardhat, + [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, + ['ethereum-sepolia']: ChainId.sepolia, + [ChainName.sepolia]: ChainId.sepolia, + [ChainName.optimismSepolia]: ChainId.optimismSepolia, + [ChainName.bscTestnet]: ChainId.bscTestnet, + [ChainName.polygonMumbai]: ChainId.polygonMumbai, + [ChainName.baseSepolia]: ChainId.baseSepolia, + [ChainName.zoraSepolia]: ChainId.zoraSepolia, + [ChainName.blast]: ChainId.blast, + [ChainName.blastSepolia]: ChainId.blastSepolia, + [ChainName.polygonAmoy]: ChainId.polygonAmoy, +}; + +export const chainIdToNameMapping: { + [key in ChainId]: ChainName; +} = { + [ChainId.arbitrum]: ChainName.arbitrum, + [ChainId.arbitrumNova]: ChainName.arbitrumNova, + [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, + [ChainId.avalanche]: ChainName.avalanche, + [ChainId.avalancheFuji]: ChainName.avalancheFuji, + [ChainId.base]: ChainName.base, + [ChainId.blast]: ChainName.blast, + [ChainId.blastSepolia]: ChainName.blastSepolia, + [ChainId.bsc]: ChainName.bsc, + [ChainId.celo]: ChainName.celo, + [ChainId.degen]: ChainName.degen, + [ChainId.gnosis]: ChainName.gnosis, + [ChainId.linea]: ChainName.linea, + [ChainId.manta]: ChainName.manta, + [ChainId.optimism]: ChainName.optimism, + [ChainId.polygon]: ChainName.polygon, + [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, + [ChainId.rari]: ChainName.rari, + [ChainId.scroll]: ChainName.scroll, + [ChainId.zora]: ChainName.zora, + [ChainId.mainnet]: ChainName.mainnet, + [ChainId.holesky]: ChainName.holesky, + [ChainId.hardhat]: ChainName.hardhat, + [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, + [ChainId.sepolia]: ChainName.sepolia, + [ChainId.optimismSepolia]: ChainName.optimismSepolia, + [ChainId.bscTestnet]: ChainName.bscTestnet, + [ChainId.polygonMumbai]: ChainName.polygonMumbai, + [ChainId.baseSepolia]: ChainName.baseSepolia, + [ChainId.zoraSepolia]: ChainName.zoraSepolia, + [ChainId.polygonAmoy]: ChainName.polygonAmoy, +}; + +export const ChainNameDisplay = { + [ChainId.arbitrum]: 'Arbitrum', + [ChainId.arbitrumNova]: chain.arbitrumNova.name, + [ChainId.avalanche]: 'Avalanche', + [ChainId.avalancheFuji]: 'Avalanche Fuji', + [ChainId.base]: 'Base', + [ChainId.blast]: 'Blast', + [ChainId.blastSepolia]: 'Blast Sepolia', + [ChainId.bsc]: 'BSC', + [ChainId.celo]: chain.celo.name, + [ChainId.degen]: 'Degen Chain', + [ChainId.linea]: 'Linea', + [ChainId.manta]: 'Manta', + [ChainId.optimism]: 'Optimism', + [ChainId.polygon]: 'Polygon', + [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, + [ChainId.rari]: 'RARI Chain', + [ChainId.scroll]: chain.scroll.name, + [ChainId.zora]: 'Zora', + [ChainId.mainnet]: 'Ethereum', + [ChainId.hardhat]: 'Hardhat', + [ChainId.hardhatOptimism]: 'Hardhat OP', + [ChainId.sepolia]: chain.sepolia.name, + [ChainId.holesky]: chain.holesky.name, + [ChainId.optimismSepolia]: chain.optimismSepolia.name, + [ChainId.bscTestnet]: 'BSC Testnet', + [ChainId.polygonMumbai]: chain.polygonMumbai.name, + [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, + [ChainId.baseSepolia]: chain.baseSepolia.name, + [ChainId.zoraSepolia]: 'Zora Sepolia', + [ChainId.polygonAmoy]: 'Polygon Amoy', +} as const; + export type NetworkTypes = 'layer1' | 'layer2' | 'testnet'; export interface NetworkProperties extends Chain { diff --git a/src/networks/zora.ts b/src/networks/zora.ts index e8c5da58ffd..58d95526fdc 100644 --- a/src/networks/zora.ts +++ b/src/networks/zora.ts @@ -1,10 +1,11 @@ -import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { zora } from '@wagmi/chains'; import { ZORA_ETH_ADDRESS } from '@/references'; import { getZoraGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; +import { ChainId } from '@/networks/types'; export const getZoraNetworkObject = (): NetworkProperties => { const { zora_enabled, zora_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -26,7 +27,7 @@ export const getZoraNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(zora.id), - getProvider: () => getProviderForNetwork(Network.zora), + getProvider: () => getProvider({ chainId: ChainId.arbitrum }), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/notifications/NotificationsHandler.tsx b/src/notifications/NotificationsHandler.tsx index d809e788d47..bb5240980db 100644 --- a/src/notifications/NotificationsHandler.tsx +++ b/src/notifications/NotificationsHandler.tsx @@ -22,7 +22,7 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { AppState as ApplicationState, AppStateStatus, NativeEventSubscription } from 'react-native'; import notifee, { Event as NotifeeEvent, EventType } from '@notifee/react-native'; -import { ethereumUtils, isLowerCaseMatch } from '@/utils'; +import { isLowerCaseMatch } from '@/utils'; import walletTypes from '@/helpers/walletTypes'; import { NotificationSubscriptionChangesListener, @@ -165,10 +165,11 @@ export const NotificationsHandler = ({ walletReady }: Props) => { } Navigation.handleAction(Routes.PROFILE_SCREEN, {}); - const network = ethereumUtils.getNetworkFromChainId(parseInt(data.chain, 10)); + const chainId = parseInt(data.chain, 10); + const transaction = await transactionFetchQuery({ hash: data.hash, - network: network, + chainId, address: walletAddress, currency: nativeCurrency, }); @@ -181,9 +182,9 @@ export const NotificationsHandler = ({ walletReady }: Props) => { transaction, }); } else if (type === NotificationTypes.walletConnect) { - logger.info(`NotificationsHandler: handling wallet connect notification`, { notification }); + logger.debug(`[NotificationsHandler]: handling wallet connect notification`, { notification }); } else if (type === NotificationTypes.marketing) { - logger.info(`NotificationsHandler: handling marketing notification`, { + logger.debug(`[NotificationsHandler]: handling marketing notification`, { notification, }); const data = notification.data as unknown as MarketingNotificationData; @@ -194,7 +195,7 @@ export const NotificationsHandler = ({ walletReady }: Props) => { }); } } else { - logger.warn(`NotificationsHandler: received unknown notification`, { + logger.warn(`[NotificationsHandler]: received unknown notification`, { notification, }); } diff --git a/src/notifications/foregroundHandler.ts b/src/notifications/foregroundHandler.ts index d8df820f7e1..75282a3f7a3 100644 --- a/src/notifications/foregroundHandler.ts +++ b/src/notifications/foregroundHandler.ts @@ -30,6 +30,8 @@ export function handleShowingForegroundNotification(remoteMessage: FixedRemoteMe } notifee.displayNotification(notification).catch(error => { - logger.error(new RainbowError('Error while displaying notification with notifee library'), { error }); + logger.error(new RainbowError('[notifications]: Error while displaying notification with notifee library'), { + error, + }); }); } diff --git a/src/notifications/permissions.ts b/src/notifications/permissions.ts index 11a794f542b..b2b1869fd32 100644 --- a/src/notifications/permissions.ts +++ b/src/notifications/permissions.ts @@ -25,7 +25,9 @@ export const checkPushNotificationPermissions = async () => { try { permissionStatus = await getPermissionStatus(); } catch (error) { - logger.error(new RainbowError('Error checking if a user has push notifications permission'), { error }); + logger.error(new RainbowError('[notifications]: Error checking if a user has push notifications permission'), { + error, + }); } if (permissionStatus !== messaging.AuthorizationStatus.AUTHORIZED && permissionStatus !== messaging.AuthorizationStatus.PROVISIONAL) { @@ -38,7 +40,7 @@ export const checkPushNotificationPermissions = async () => { trackPushNotificationPermissionStatus(status ? 'enabled' : 'disabled'); await saveFCMToken(); } catch (error) { - logger.error(new RainbowError('Error while getting permissions'), { error }); + logger.error(new RainbowError('[notifications]: Error while getting permissions'), { error }); } finally { resolve(true); } diff --git a/src/notifications/settings/firebase.ts b/src/notifications/settings/firebase.ts index 4448d4eb641..09fe6271985 100644 --- a/src/notifications/settings/firebase.ts +++ b/src/notifications/settings/firebase.ts @@ -35,7 +35,7 @@ export const subscribeWalletToNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ): Promise => { - logger.debug(`Notifications: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => trackChangedNotificationSettings(topic, 'subscribe', chainId, type)); @@ -47,7 +47,7 @@ export const unsubscribeWalletFromNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ) => { - logger.debug(`Notifications: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => { @@ -56,14 +56,14 @@ export const unsubscribeWalletFromNotificationTopic = async ( }; export const subscribeToGlobalNotificationTopic = async (topic: GlobalNotificationTopicType): Promise => { - logger.debug(`Notifications: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(topic) .then(() => trackChangedNotificationSettings(topic, 'subscribe')); }; export const unsubscribeFromGlobalNotificationTopic = async (topic: GlobalNotificationTopicType) => { - logger.debug(`Notifications: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`[notifications]: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(topic) .then(() => { diff --git a/src/notifications/settings/initialization.ts b/src/notifications/settings/initialization.ts index 4cd40737e78..58d67e30215 100644 --- a/src/notifications/settings/initialization.ts +++ b/src/notifications/settings/initialization.ts @@ -189,7 +189,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting await unsubscribeWalletFromAllNotificationTopics(newSettings.oldType, NOTIFICATIONS_DEFAULT_CHAIN_ID, newSettings.address); newSettings.oldType = undefined; } catch (e) { - logger.error(new RainbowError('Failed to unsubscribe old watcher mode notification topics')); + logger.error(new RainbowError('[notifications]: Failed to unsubscribe old watcher mode notification topics')); } } if (newSettings.type === WalletNotificationRelationship.OWNER && !newSettings.successfullyFinishedInitialSubscription) { @@ -198,7 +198,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting newSettings.successfullyFinishedInitialSubscription = true; newSettings.enabled = true; } catch (e) { - logger.error(new RainbowError('Failed to subscribe to default notification topics for newly added wallet')); + logger.error(new RainbowError('[notifications]: Failed to subscribe to default notification topics for newly added wallet')); } } diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index bbf194a46c7..8c88b9ee1bf 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -19,7 +19,7 @@ export const saveFCMToken = async () => { } } } catch (error) { - logger.warn('Error while getting and saving FCM token', { + logger.warn('[notifications]: Error while getting and saving FCM token', { error, }); } @@ -30,7 +30,7 @@ export async function getFCMToken(): Promise { const token = fcmTokenLocal?.data || undefined; if (!token) { - logger.debug('getFCMToken: No FCM token found'); + logger.debug('[notifications]: getFCMToken No FCM token found'); } return token; diff --git a/src/parsers/accounts.js b/src/parsers/accounts.js deleted file mode 100644 index aa31cce971b..00000000000 --- a/src/parsers/accounts.js +++ /dev/null @@ -1,72 +0,0 @@ -import isNil from 'lodash/isNil'; -import toUpper from 'lodash/toUpper'; -import { isNativeAsset } from '@/handlers/assets'; -import * as i18n from '@/languages'; -import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; -import { getTokenMetadata, isLowerCaseMatch } from '@/utils'; -import { memoFn } from '@/utils/memoFn'; -import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/__swaps__/types/chains'; - -// eslint-disable-next-line no-useless-escape -const sanitize = memoFn(s => s.replace(/[^a-z0-9áéíóúñü \.,_@:-]/gim, '')); - -export const parseAssetName = (metadata, name) => { - if (metadata?.name) return metadata?.name; - return name ? sanitize(name) : i18n.t(i18n.l.assets.unkown_token); -}; - -export const parseAssetSymbol = (metadata, symbol) => { - if (metadata?.symbol) return metadata?.symbol; - return symbol ? toUpper(sanitize(symbol)) : '———'; -}; - -/** - * @desc parse asset - * @param {Object} assetData - * @return The parsed asset. - */ -export const parseAsset = ({ asset_code: address, ...asset } = {}) => { - const metadata = getTokenMetadata(asset.mainnet_address || address); - const name = parseAssetName(metadata, asset.name); - const symbol = parseAssetSymbol(metadata, asset.symbol); - - const parsedAsset = { - ...asset, - ...metadata, - address, - isNativeAsset: isNativeAsset(address, asset.chain_id || ChainId.mainnet), - name, - symbol, - uniqueId: getUniqueId(address, asset.chain_id), - }; - - return parsedAsset; -}; - -export const parseAssetsNative = (assets, nativeCurrency) => assets.map(asset => parseAssetNative(asset, nativeCurrency)); - -export const parseAssetNative = (asset, nativeCurrency) => { - const assetNativePrice = asset?.price; - if (isNil(assetNativePrice)) { - return asset; - } - - const priceUnit = assetNativePrice?.value ?? 0; - const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); - return { - ...asset, - native: { - balance: nativeDisplay, - change: isLowerCaseMatch(asset.symbol, nativeCurrency) - ? null - : assetNativePrice.relative_change_24h - ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) - : '', - price: { - amount: priceUnit, - display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), - }, - }, - }; -}; diff --git a/src/parsers/accounts.ts b/src/parsers/accounts.ts new file mode 100644 index 00000000000..279e7eaa89a --- /dev/null +++ b/src/parsers/accounts.ts @@ -0,0 +1,32 @@ +import isNil from 'lodash/isNil'; +import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; +import { isLowerCaseMatch } from '@/utils'; +import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; + +export const parseAssetsNative = (assets: ParsedAddressAsset[], nativeCurrency: NativeCurrencyKey) => + assets.map(asset => parseAssetNative(asset, nativeCurrency)); + +export const parseAssetNative = (asset: ParsedAddressAsset, nativeCurrency: NativeCurrencyKey) => { + const assetNativePrice = asset?.price; + if (isNil(assetNativePrice)) { + return asset; + } + + const priceUnit = assetNativePrice?.value ?? 0; + const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); + return { + ...asset, + native: { + balance: nativeDisplay, + change: isLowerCaseMatch(asset.symbol, nativeCurrency) + ? undefined + : assetNativePrice.relative_change_24h + ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) + : '', + price: { + amount: priceUnit?.toString(), + display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), + }, + }, + }; +}; diff --git a/src/parsers/gas.ts b/src/parsers/gas.ts index 01058c09b71..ce48764e84b 100644 --- a/src/parsers/gas.ts +++ b/src/parsers/gas.ts @@ -33,7 +33,6 @@ import { multiply, toFixedDecimals, } from '@/helpers/utilities'; -import { Network } from '@/networks/types'; type BigNumberish = number | string | BigNumber; @@ -98,8 +97,7 @@ const parseGasDataConfirmationTime = ( }; export const parseRainbowMeteorologyData = ( - rainbowMeterologyData: RainbowMeteorologyData, - network: Network + rainbowMeterologyData: RainbowMeteorologyData ): { gasFeeParamsBySpeed: GasFeeParamsBySpeed; baseFeePerGas: GasFeeParam; diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 1f520d23bde..705b2cde5c0 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,4 +1,4 @@ -export { parseAssetName, parseAssetSymbol, parseAsset, parseAssetNative, parseAssetsNative } from './accounts'; +export { parseAssetNative, parseAssetsNative } from './accounts'; export { parseL2GasPrices, parseGasFeesBySpeed, diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 80c13845313..3bf47986390 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -43,7 +43,11 @@ export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { message = toUtf8String(message); } } catch (error) { - logger.debug('WC v2: getting display details, unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); + logger.warn( + '[parsers/requests]: WC v2: getting display details, unable to decode hex message to UTF8 string', + { error }, + logger.DebugContext.walletconnect + ); } return getMessageDisplayDetails(message, timestampInMs); } @@ -73,7 +77,7 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); + const nativeAsset = ethereumUtils.getNativeAssetForNetwork({ chainId }); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/parsers/transactions.ts b/src/parsers/transactions.ts index 495ff7ba0c4..712b8de45b7 100644 --- a/src/parsers/transactions.ts +++ b/src/parsers/transactions.ts @@ -20,7 +20,7 @@ import { TransactionType, TransactionWithChangesType, } from '@/resources/transactions/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const LAST_TXN_HASH_BUFFER = 20; @@ -113,10 +113,16 @@ export const parseTransaction = async ( iconUrl: meta.contract_icon_url, }; + // NOTE: For send transactions, the to address should be pulled from the outgoing change directly, not the txn.address_to + let to = txn.address_to; + if (meta.type === 'send') { + to = txn.changes.find(change => change?.direction === 'out')?.address_to ?? txn.address_to; + } + return { chainId, from: txn.address_from, - to: txn.address_to, + to, title: `${type}.${status}`, description, hash, diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 208f337024f..5143fd2e922 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -1,6 +1,5 @@ -import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { Network } from '@/helpers'; +import { NewTransaction, ParsedAddressAsset, TransactionGasParamAmounts } from '@/entities'; +import { getProvider } from '@/handlers/web3'; import { add, addBuffer, greaterThan, lessThan, multiply, subtract } from '@/helpers/utilities'; import { RainbowError } from '@/logger'; import store from '@/redux/store'; @@ -13,6 +12,7 @@ import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rai import { Address } from 'viem'; import { ActionProps } from '../references'; import { executeCrosschainSwap } from './crosschainSwap'; +import { ChainId } from '@/networks/types'; // This action is used to bridge the claimed funds to another chain export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) { @@ -51,7 +51,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // 2 - We use the default gas limit (already inflated) from the quote to calculate the aproximate gas fee const initalGasLimit = bridgeQuote.defaultGasLimit as string; - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism( // @ts-ignore @@ -153,17 +153,21 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps throw new Error('[CLAIM-BRIDGE]: executeCrosschainSwap returned undefined'); } - const typedAssetToBuy = { + const typedAssetToBuy: ParsedAddressAsset = { ...parameters.assetToBuy, network: getNetworkFromChainId(parameters.assetToBuy.chainId), + chainId: parameters.assetToBuy.chainId, colors: undefined, networks: undefined, + native: undefined, }; const typedAssetToSell = { ...parameters.assetToSell, network: getNetworkFromChainId(parameters.assetToSell.chainId), + chainId: parameters.assetToSell.chainId, colors: undefined, networks: undefined, + native: undefined, }; // 5 - if the swap was successful we add the transaction to the store @@ -197,7 +201,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps addNewTransaction({ address: bridgeQuote.from as Address, - network: getNetworkFromChainId(parameters.chainId), + chainId: parameters.chainId, transaction, }); diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index be6a0df7c9a..ddbd4789be3 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -1,10 +1,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { CrosschainQuote, fillCrosschainQuote } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { REFERRER, gasUnits, ReferrerType } from '@/references'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { addNewTransaction } from '@/state/pendingTransactions'; @@ -39,7 +39,7 @@ export const estimateCrosschainSwapGasLimit = async ({ quote: CrosschainQuote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -130,7 +130,7 @@ export const crosschainSwap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('crosschainSwap: error estimateCrosschainSwapGasLimit'), { + logger.error(new RainbowError('[raps/crosschainSwap]: error estimateCrosschainSwapGasLimit'), { message: (e as Error)?.message, }); throw e; @@ -158,11 +158,13 @@ export const crosschainSwap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('crosschainSwap: error executeCrosschainSwap'), { message: (e as Error)?.message }); + logger.error(new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'), { + message: (e as Error)?.message, + }); throw e; } - if (!swap) throw new RainbowError('crosschainSwap: error executeCrosschainSwap'); + if (!swap) throw new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'); // TODO: MARK - Replace this once we migrate network => chainId const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); @@ -199,8 +201,10 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToSell, network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), + chainId: parameters.assetToSell.chainId, colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, + native: undefined, }, value: quote.sellAmount.toString(), }, @@ -211,8 +215,10 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToBuy, network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), + chainId: parameters.assetToBuy.chainId, colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, + native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -221,7 +227,6 @@ export const crosschainSwap = async ({ hash: swap.hash as TxHash, // TODO: MARK - Replace this once we migrate network => chainId network, - // chainId: parameters.chainId, nonce: swap.nonce, status: 'pending', type: 'swap', @@ -231,8 +236,7 @@ export const crosschainSwap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - // chainId: parameters.chainId as ChainId, - network, + chainId, transaction, }); diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts index 6c804fad086..6fc53450458 100644 --- a/src/raps/actions/ens.ts +++ b/src/raps/actions/ens.ts @@ -6,15 +6,14 @@ import { analytics } from '@/analytics'; import { ENSRegistrationRecords, NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { estimateENSTransactionGasLimit, formatRecordsForTransaction } from '@/handlers/ens'; import { toHex } from '@/handlers/web3'; -import { NetworkTypes } from '@/helpers'; import { ENSRegistrationTransactionType, getENSExecutionDetails, REGISTRATION_MODES } from '@/helpers/ens'; import * as i18n from '@/languages'; import { saveCommitRegistrationParameters, updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; import store from '@/redux/store'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { parseGasParamAmounts } from '@/parsers'; import { addNewTransaction } from '@/state/pendingTransactions'; -import { Network } from '@/networks/types'; +import { ChainId, Network } from '@/networks/types'; import { createRegisterENSRap, createRenewENSRap, @@ -25,7 +24,6 @@ import { } from '../registerENS'; import { Logger } from '@ethersproject/logger'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { ChainId } from '@/__swaps__/types/chains'; export interface ENSRapActionResponse { baseNonce?: number | null; @@ -308,24 +306,23 @@ const ensAction = async ( type: ENSRegistrationTransactionType, baseNonce?: number ): Promise => { - logger.log(`[${actionName}] base nonce`, baseNonce, 'index:', index); + logger.debug(`[raps/ens]: [${actionName}] base nonce ${baseNonce} index: ${index}`); const { dispatch } = store; const { accountAddress: ownerAddress } = store.getState().settings; const { name, duration, rentPrice, records, salt, toAddress, mode } = parameters; - logger.log(`[${actionName}] rap for`, name); + logger.debug(`[raps/ens]: [${actionName}] rap for ${name}`); let gasLimit; const ensRegistrationRecords = formatRecordsForTransaction(records); try { - logger.sentry( - `[${actionName}] estimate gas`, - { + logger.debug(`[raps/ens]: [${actionName}] estimate gas`, { + data: { ...parameters, + type, }, - type - ); + }); // when registering the ENS if we try to estimate gas for setting records // (MULTICALL || SET_TEXT) it's going to fail if we put the account address @@ -346,8 +343,7 @@ const ensAction = async ( type, }); } catch (e) { - logger.sentry(`[${actionName}] Error estimating gas`); - captureException(e); + logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error estimating gas: ${e}`)); throw e; } let tx; @@ -358,8 +354,11 @@ const ensAction = async ( maxFeePerGas = gasParams.maxFeePerGas; maxPriorityFeePerGas = gasParams.maxPriorityFeePerGas; - logger.sentry(`[${actionName}] about to ${type}`, { - ...parameters, + logger.debug(`[raps/ens]: [${actionName}] about to ${type}`, { + data: { + ...parameters, + type, + }, }); const nonce = baseNonce ? baseNonce + index : null; @@ -457,12 +456,11 @@ const ensAction = async ( }); } } catch (e) { - logger.sentry(`[${actionName}] Error executing`); - captureException(e); + logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error executing: ${e}`)); throw e; } - logger.log(`[${actionName}] response`, tx); + logger.debug(`[raps/ens]: [${actionName}] response`, { data: tx }); const newTransaction: NewTransaction = { chainId: ChainId.mainnet, @@ -481,15 +479,16 @@ const ensAction = async ( }, to: tx?.to, value: toHex(tx.value), - network: NetworkTypes.mainnet, + network: Network.mainnet, status: 'pending', }; - logger.log(`[${actionName}] adding new txn`, newTransaction); + + logger.debug(`[raps/ens]: [${actionName}] adding new txn`, { data: newTransaction }); addNewTransaction({ address: ownerAddress, transaction: newTransaction, - network: Network.mainnet, + chainId: ChainId.mainnet, }); return tx?.nonce; }; @@ -670,11 +669,11 @@ const executeAction = async ( rapName: string, baseNonce?: number ): Promise => { - logger.log('[1 INNER] index', index); + logger.debug(`[raps/ens]: [${rapName}] 1 INNER index: ${index}`); const { type, parameters } = action; let nonce; try { - logger.log('[2 INNER] executing type', type); + logger.debug(`[raps/ens]: [${rapName}] 2 INNER executing type: ${type}`); const actionPromise = findENSActionByType(type); nonce = await performanceTracking.getState().executeFn({ fn: actionPromise, @@ -683,9 +682,7 @@ const executeAction = async ( })(wallet, rap, index, parameters as RapENSActionParameters, baseNonce); return { baseNonce: nonce, errorMessage: null }; } catch (error: any) { - logger.debug('Rap blew up', error); - logger.sentry('[3 INNER] error running action, code:', error?.code); - captureException(error); + logger.error(new RainbowError(`[raps/ens]: [${rapName}] Error executing action: ${action} ${error}`)); analytics.track('Rap failed', { category: 'raps', failed_action: type, @@ -694,7 +691,7 @@ const executeAction = async ( // If the first action failed, return an error message if (index === 0) { const errorMessage = parseError(error); - logger.log('[4 INNER] displaying error message', errorMessage); + logger.debug(`[raps/ens]: [${rapName}] 4 INNER displaying error message ${errorMessage}`); return { baseNonce: null, errorMessage }; } return { baseNonce: null, errorMessage: null }; @@ -722,7 +719,7 @@ export const executeENSRap = async ( let nonce = parameters?.nonce; - logger.log('[common - executing rap]: actions', actions); + logger.debug(`[raps/ens]: [${rapName}] actions`, { actions }); if (actions.length) { const firstAction = actions[0]; const { baseNonce, errorMessage } = await executeAction(firstAction, wallet, rap, 0, rapName, nonce); @@ -744,7 +741,8 @@ export const executeENSRap = async ( category: 'raps', label: rapName, }); - logger.log('[common - executing rap] finished execute rap function'); + + logger.debug(`[raps/ens]: [${rapName}] finished execute rap function`); return { nonce }; }; diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index 5dd61e809f2..2b1eddb690c 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -15,11 +15,11 @@ import { unwrapNativeAsset, wrapNativeAsset, } from '@rainbow-me/swaps'; -import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; +import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { Address } from 'viem'; import { metadataPOSTClient } from '@/graphql'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { add } from '@/helpers/utilities'; @@ -62,7 +62,7 @@ export const estimateSwapGasLimit = async ({ quote: Quote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -145,8 +145,7 @@ export const estimateUnlockAndSwapFromMetadata = async ({ chainId, }); - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const swapTransaction = await populateSwap({ provider, quote, @@ -267,7 +266,7 @@ export const swap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('swap: error estimateSwapGasLimit'), { + logger.error(new RainbowError('[raps/swap]: error estimateSwapGasLimit'), { message: (e as Error)?.message, }); @@ -295,7 +294,7 @@ export const swap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('swap: error executeSwap'), { + logger.error(new RainbowError('[raps/swap]: error executeSwap'), { message: (e as Error)?.message, }); throw e; @@ -303,9 +302,6 @@ export const swap = async ({ if (!swap || !swap?.hash) throw new RainbowError('swap: error executeSwap'); - // TODO: MARK - Replace this once we migrate network => chainId - const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); - const nativePriceForAssetToBuy = (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice ? { value: (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice, @@ -342,6 +338,7 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, + native: undefined, }, value: quote.sellAmount.toString(), }, @@ -354,6 +351,7 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, + native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -386,8 +384,7 @@ export const swap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - // chainId: parameters.chainId as ChainId, - network, + chainId: parameters.chainId, transaction, }); diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index d2c95ea1c00..b4377935b99 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -2,10 +2,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { MaxUint256 } from '@ethersproject/constants'; import { Contract, PopulatedTransaction } from '@ethersproject/contracts'; import { parseUnits } from '@ethersproject/units'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { Address, erc20Abi, erc721Abi } from 'viem'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { TransactionGasParams, TransactionLegacyGasParams } from '@/__swaps__/types/gas'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; @@ -35,13 +35,12 @@ export const getAssetRawAllowance = async ({ chainId: ChainId; }) => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(assetAddress, erc20Abi, provider); const allowance = await tokenContract.allowance(owner, spender); return allowance.toString(); } catch (error) { - logger.error(new RainbowError('getRawAllowance: error'), { + logger.error(new RainbowError('[raps/unlock]: error'), { message: (error as Error)?.message, }); return null; @@ -87,15 +86,14 @@ export const estimateApprove = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const gasLimit = await tokenContract.estimateGas.approve(spender, MaxUint256, { from: owner, }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('unlock: error estimateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -114,15 +112,14 @@ export const populateApprove = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const approveTransaction = await tokenContract.populateTransaction.approve(spender, MaxUint256, { from: owner, }); return approveTransaction; } catch (error) { - logger.error(new RainbowError(' error populateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error populateApprove'), { message: (error as Error)?.message, }); return null; @@ -141,15 +138,14 @@ export const estimateERC721Approval = async ({ chainId: ChainId; }): Promise => { try { - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); const gasLimit = await tokenContract.estimateGas.setApprovalForAll(spender, false, { from: owner, }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('estimateERC721Approval: error estimateApproval'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApproval'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -168,8 +164,7 @@ export const populateRevokeApproval = async ({ type: 'erc20' | 'nft'; }): Promise => { if (!tokenAddress || !spenderAddress || !chainId) return {}; - // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); + const provider = getProvider({ chainId }); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); if (type === 'erc20') { const amountToApprove = parseUnits('0', 'ether'); @@ -234,7 +229,7 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('unlock: error estimateApprove'), { + logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { message: (e as Error)?.message, }); throw e; @@ -260,13 +255,13 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('unlock: error executeApprove'), { + logger.error(new RainbowError('[raps/unlock]: error executeApprove'), { message: (e as Error)?.message, }); throw e; } - if (!approval) throw new RainbowError('unlock: error executeApprove'); + if (!approval) throw new RainbowError('[raps/unlock]: error executeApprove'); const transaction = { asset: { @@ -290,12 +285,9 @@ export const unlock = async ({ ...gasParams, } satisfies NewTransaction; - // TODO: MARK - Replace this once we migrate network => chainId - const network = ethereumUtils.getNetworkFromChainId(approval.chainId); - addNewTransaction({ address: parameters.fromAddress as Address, - network, + chainId: approval.chainId, transaction, }); diff --git a/src/raps/common.ts b/src/raps/common.ts index 82282d68678..477a0f68b46 100644 --- a/src/raps/common.ts +++ b/src/raps/common.ts @@ -98,7 +98,7 @@ export const createNewENSAction = (type: ENSRapActionType, parameters: ENSAction type, }; - logger.log('[common] Creating a new action', { + logger.debug('[raps/common]: Creating a new action', { extra: { ...newAction, }, diff --git a/src/raps/execute.ts b/src/raps/execute.ts index 5481e159b72..3ee2c9ec9b4 100644 --- a/src/raps/execute.ts +++ b/src/raps/execute.ts @@ -94,7 +94,7 @@ export async function executeAction({ const { nonce, hash } = (await typeAction(type, actionProps)()) as RapActionResult; return { baseNonce: nonce, errorMessage: null, hash }; } catch (error) { - logger.error(new RainbowError(`rap: ${rapName} - error execute action`), { + logger.error(new RainbowError(`[raps/execute]: ${rapName} - error execute action`), { message: (error as Error)?.message, }); if (index === 0) { diff --git a/src/raps/references.ts b/src/raps/references.ts index d8f68cf8bb9..6c6bf22dba7 100644 --- a/src/raps/references.ts +++ b/src/raps/references.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export enum SwapModalField { input = 'inputAmount', diff --git a/src/raps/unlockAndSwap.ts b/src/raps/unlockAndSwap.ts index 614e66b992c..cdf9349adcb 100644 --- a/src/raps/unlockAndSwap.ts +++ b/src/raps/unlockAndSwap.ts @@ -7,7 +7,7 @@ import { } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; import { isLowerCaseMatch } from '@/utils'; diff --git a/src/raps/utils.ts b/src/raps/utils.ts index b91b60cb99b..cacc8795bb2 100644 --- a/src/raps/utils.ts +++ b/src/raps/utils.ts @@ -5,11 +5,10 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { ALLOWS_PERMIT, CrosschainQuote, Quote, getQuoteExecutionDetails, getRainbowRouterContractAddress } from '@rainbow-me/swaps'; import { mainnet } from 'viem/chains'; import { Chain, erc20Abi } from 'viem'; -import { Network } from '@/helpers'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { ethereumUtils, gasUtils } from '@/utils'; import { add, greaterThan, multiply } from '@/helpers/utilities'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { gasUnits } from '@/references'; import { toHexNoLeadingZeros } from '@/handlers/web3'; diff --git a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js index a557b350e51..02a09ec6041 100644 --- a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js +++ b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js @@ -28,8 +28,8 @@ class BSpline { } seqAt(dim) { - let points = this.points; - let margin = this.degree + 1; + const points = this.points; + const margin = this.degree + 1; return function (n) { if (n < margin) { return points[0][dim]; @@ -84,9 +84,9 @@ class BSpline { } getInterpol(seq, t) { - let f = this.baseFunc; - let rangeInt = this.baseFuncRangeInt; - let tInt = Math.floor(t); + const f = this.baseFunc; + const rangeInt = this.baseFuncRangeInt; + const tInt = Math.floor(t); let result = 0; for (let i = tInt - rangeInt; i <= tInt + rangeInt; i++) { result += seq(i) * f(t - i); @@ -113,13 +113,13 @@ class BSpline { } calcAt(t) { - t = t * ((this.degree + 1) * 2 + this.points.length); //t must be in [0,1] + t = t * ((this.degree + 1) * 2 + this.points.length); // t must be in [0,1] if (this.dimension === 2) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t)]; } else if (this.dimension === 3) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t), this.getInterpol(this.seqAt(2), t)]; } else { - let res = []; + const res = []; for (let i = 0; i < this.dimension; i++) { res.push(this.getInterpol(this.seqAt(i), t)); } diff --git a/src/react-native-cool-modals/createNativeStackNavigator.js b/src/react-native-cool-modals/createNativeStackNavigator.js index 391384ec141..f4bba2bb834 100644 --- a/src/react-native-cool-modals/createNativeStackNavigator.js +++ b/src/react-native-cool-modals/createNativeStackNavigator.js @@ -2,7 +2,7 @@ import { createNavigatorFactory, StackRouter as OldStackRouter, StackActions, us import * as React from 'react'; import NativeStackView from './NativeStackView'; -import logger from '@/utils/logger'; +import { logger } from '@/logger'; function NativeStackNavigator(props) { const { children, initialRouteName, screenOptions, ...rest } = props; @@ -13,7 +13,7 @@ function NativeStackNavigator(props) { getStateForAction(state, action, options) { if (action.type === 'PUSH') { if (state.routes[state.routes.length - 1].name === action.payload.name) { - logger.log('pushing twice the same name is not allowed'); + logger.debug(`[NativeStackNavigator]: pushing twice the same name is not allowed`); return state; } } diff --git a/src/redux/contacts.ts b/src/redux/contacts.ts index bfa15e0e912..7de2fe6610f 100644 --- a/src/redux/contacts.ts +++ b/src/redux/contacts.ts @@ -1,6 +1,5 @@ import { Dispatch } from 'redux'; import { getContacts, saveContacts } from '@/handlers/localstorage/contacts'; -import { Network } from '@/helpers/networkTypes'; import { omitFlatten } from '@/helpers/utilities'; import { AppGetState } from '@/redux/store'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -33,11 +32,6 @@ export interface Contact { */ ens: string; - /** - * The network. - */ - network: Network; - /** * The contact's nickname. */ @@ -85,8 +79,7 @@ export const contactsLoadState = () => async (dispatch: Dispatch - (dispatch: Dispatch, getState: AppGetState) => { + (address: string, nickname: string, color: number, ens: string) => (dispatch: Dispatch, getState: AppGetState) => { const loweredAddress = address.toLowerCase(); const { contacts } = getState().contacts; const updatedContacts = { @@ -95,7 +88,6 @@ export const contactsAddOrUpdate = address: loweredAddress, color, ens, - network, nickname, }, }; diff --git a/src/redux/ensRegistration.ts b/src/redux/ensRegistration.ts index 69d95299692..44c6d1041d4 100644 --- a/src/redux/ensRegistration.ts +++ b/src/redux/ensRegistration.ts @@ -3,9 +3,9 @@ import { Dispatch } from 'react'; import { AppDispatch, AppGetState } from './store'; import { ENSRegistrations, ENSRegistrationState, Records, RegistrationParameters, TransactionRegistrationParameters } from '@/entities'; import { getLocalENSRegistrations, saveLocalENSRegistrations } from '@/handlers/localstorage/accountLocal'; -import { NetworkTypes } from '@/helpers'; import { ENS_RECORDS, REGISTRATION_MODES } from '@/helpers/ens'; import { omitFlatten } from '@/helpers/utilities'; +import { Network } from '@/networks/types'; const ENS_REGISTRATION_SET_CHANGED_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_CHANGED_RECORDS'; const ENS_REGISTRATION_SET_INITIAL_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_INITIAL_RECORDS'; @@ -345,7 +345,7 @@ export const saveCommitRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -382,7 +382,7 @@ export const updateTransactionRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -408,7 +408,7 @@ export const removeRegistrationByName = (name: string) => async (dispatch: AppDi }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); dispatch({ payload: updatedEnsRegistrationManager, diff --git a/src/redux/explorer.ts b/src/redux/explorer.ts index 20c9f151f31..0892e4b5e8c 100644 --- a/src/redux/explorer.ts +++ b/src/redux/explorer.ts @@ -4,8 +4,8 @@ import { ThunkDispatch } from 'redux-thunk'; import { io, Socket } from 'socket.io-client'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AppGetState, AppState } from './store'; -import { getProviderForNetwork, isHardHat } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; // -- Constants --------------------------------------- // const EXPLORER_UPDATE_SOCKETS = 'explorer/EXPLORER_UPDATE_SOCKETS'; @@ -110,7 +110,7 @@ export const explorerClearState = () => (dispatch: ThunkDispatch (dispatch: ThunkDispatch, getState: AppGetState) => { - const { network, accountAddress } = getState().settings; + const { accountAddress, chainId } = getState().settings; const { addressSocket } = getState().explorer; // if there is another socket unsubscribe first @@ -118,9 +118,7 @@ export const explorerInit = () => (dispatch: ThunkDispatch void) => await mutex.runExclusive(callback); -const getGasPricePollingInterval = (network: Network): number => { - return getNetworkObj(network).gas.pollingIntervalInMs; +const getGasPricePollingInterval = (chainId: ChainId): number => { + return getNetworkObject({ chainId }).gas.pollingIntervalInMs; }; -const getDefaultGasLimit = (network: Network, defaultGasLimit: number): number => { - switch (network) { - case Network.arbitrum: +const getDefaultGasLimit = (chainId: ChainId, defaultGasLimit: number): number => { + switch (chainId) { + case ChainId.arbitrum: return ethUnits.arbitrum_basic_tx; - case Network.polygon: - case Network.bsc: - case Network.optimism: - case Network.mainnet: - case Network.zora: + case ChainId.polygon: + case ChainId.bsc: + case ChainId.optimism: + case ChainId.mainnet: + case ChainId.zora: default: return defaultGasLimit; } @@ -73,7 +74,7 @@ export interface GasState { gasFeeParamsBySpeed: GasFeeParamsBySpeed | LegacyGasFeeParamsBySpeed; selectedGasFee: SelectedGasFee | LegacySelectedGasFee; gasFeesBySpeed: GasFeesBySpeed | LegacyGasFeesBySpeed; - txNetwork: Network | null; + chainId: ChainId; currentBlockParams: CurrentBlockParams; blocksToConfirmation: BlocksToConfirmation; customGasFeeModifiedByUser: boolean; @@ -121,22 +122,22 @@ const getUpdatedGasFeeParams = ( gasLimit: string, nativeCurrency: NativeCurrencyKey, selectedGasFeeOption: string, - txNetwork: Network, + chainId: ChainId, l1GasFeeOptimism: BigNumber | null = null ) => { let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (txNetwork) { - case Network.polygon: + switch (chainId) { + case ChainId.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case Network.bsc: + case ChainId.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; - case Network.avalanche: + case ChainId.avalanche: nativeTokenPriceUnit = ethereumUtils.getAvaxPriceUnit(); break; - case Network.degen: + case ChainId.degen: nativeTokenPriceUnit = ethereumUtils.getDegenPriceUnit(); break; default: @@ -144,7 +145,7 @@ const getUpdatedGasFeeParams = ( break; } - const isLegacyGasNetwork = getNetworkObj(txNetwork).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; const gasFeesBySpeed = isLegacyGasNetwork ? parseLegacyGasFeesBySpeed( @@ -192,7 +193,7 @@ export const updateGasFeeForSpeed = export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispatch: AppDispatch, getState: AppGetState) => { const { - txNetwork, + chainId, defaultGasLimit, gasFeesBySpeed, gasFeeParamsBySpeed, @@ -204,15 +205,15 @@ export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispa } = getState().gas; const { nativeCurrency } = getState().settings; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (txNetwork) { - case Network.polygon: + switch (chainId) { + case ChainId.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case Network.bsc: + case ChainId.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; default: @@ -257,7 +258,7 @@ export const getPolygonGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(Network.polygon)) as { + } = (await rainbowMeteorologyGetData(ChainId.polygon)) as { data: RainbowMeteorologyLegacyData; }; const polygonGasPriceBumpFactor = 1.05; @@ -276,7 +277,7 @@ export const getPolygonGasPrices = async () => { }; return polygonGasStationPrices; } catch (e) { - logger.error(new RainbowError(`failed to fetch polygon gas prices ${e}`)); + logger.error(new RainbowError(`[redux/gas]: failed to fetch polygon gas prices ${e}`)); return null; } }; @@ -287,7 +288,7 @@ export const getBscGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(Network.bsc)) as { + } = (await rainbowMeteorologyGetData(ChainId.bsc)) as { data: RainbowMeteorologyLegacyData; }; @@ -307,12 +308,12 @@ export const getBscGasPrices = async () => { }; return bscGasStationPrices; } catch (e) { - logger.error(new RainbowError(`failed to fetch BSC gas prices ${e}`)); + logger.error(new RainbowError(`[redux/gas]: failed to fetch BSC gas prices ${e}`)); return null; } }; export const getArbitrumGasPrices = async () => { - const provider = getProviderForNetwork(Network.arbitrum); + const provider = getProvider({ chainId: ChainId.arbitrum }); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -330,7 +331,7 @@ export const getArbitrumGasPrices = async () => { }; export const getOptimismGasPrices = async () => { - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -347,7 +348,7 @@ export const getOptimismGasPrices = async () => { }; export const getBaseGasPrices = async () => { - const provider = getProviderForNetwork(Network.base); + const provider = getProvider({ chainId: ChainId.base }); const baseGasPrice = await provider.getGasPrice(); const BasePriceBumpFactor = 1.05; @@ -366,7 +367,7 @@ export const getBaseGasPrices = async () => { }; export const getAvalancheGasPrices = async () => { - const provider = getProviderForNetwork(Network.avalanche); + const provider = getProvider({ chainId: ChainId.avalanche }); const baseGasPrice = await provider.getGasPrice(); const AvalanchePriceBumpFactor = 1.05; @@ -385,7 +386,7 @@ export const getAvalancheGasPrices = async () => { }; export const getDegenGasPrices = async () => { - const provider = getProviderForNetwork(Network.degen); + const provider = getProvider({ chainId: ChainId.degen }); const baseGasPrice = await provider.getGasPrice(); const DegenPriceBumpFactor = 1.05; @@ -404,7 +405,7 @@ export const getDegenGasPrices = async () => { }; export const getBlastGasPrices = async () => { - const provider = getProviderForNetwork(Network.blast); + const provider = getProvider({ chainId: ChainId.blast }); const baseGasPrice = await provider.getGasPrice(); const BlastPriceBumpFactor = 1.05; @@ -423,7 +424,7 @@ export const getBlastGasPrices = async () => { }; export const getZoraGasPrices = async () => { - const provider = getProviderForNetwork(Network.zora); + const provider = getProvider({ chainId: ChainId.zora }); const baseGasPrice = await provider.getGasPrice(); const ZoraPriceBumpFactor = 1.05; @@ -441,12 +442,12 @@ export const getZoraGasPrices = async () => { return priceData; }; -export const getEIP1559GasParams = async (network: Network) => { - const { data } = (await rainbowMeteorologyGetData(network)) as { +export const getEIP1559GasParams = async (chainId: ChainId) => { + const { data } = (await rainbowMeteorologyGetData(chainId)) as { data: RainbowMeteorologyData; }; const { gasFeeParamsBySpeed, baseFeePerGas, baseFeeTrend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - parseRainbowMeteorologyData(data, network); + parseRainbowMeteorologyData(data); return { baseFeePerGas, blocksToConfirmation, @@ -458,44 +459,45 @@ export const getEIP1559GasParams = async (network: Network) => { }; export const gasPricesStartPolling = - (network = Network.mainnet, flashbots = false) => + (chainId = ChainId.mainnet, flashbots = false) => async (dispatch: AppDispatch, getState: AppGetState) => { dispatch(gasPricesStopPolling()); // this should be chain agnostic - const getGasPrices = (network: Network) => + const getGasPrices = () => withRunExclusive( () => new Promise(async (fetchResolve, fetchReject) => { try { - dispatch({ - payload: network, - type: GAS_UPDATE_TRANSACTION_NETWORK, - }); const { gasFeeParamsBySpeed: existingGasFees, customGasFeeModifiedByUser, defaultGasLimit, gasLimit, selectedGasFee, - txNetwork, + chainId, selectedGasFee: lastSelectedGasFee, gasFeesBySpeed: lastGasFeesBySpeed, currentBlockParams, l1GasFeeOptimism, } = getState().gas; + dispatch({ + payload: chainId, + type: GAS_UPDATE_TRANSACTION_NETWORK, + }); + const { nativeCurrency } = getState().settings; - const networkObj = getNetworkObj(network); + const networkObject = getNetworkObject({ chainId }); let dataIsReady = true; - if (networkObj.gas.gasType === 'legacy') { + if (networkObject.gas.gasType === 'legacy') { // OP chains have an additional fee we need to load - if (getNetworkObj(network).gas?.OptimismTxFee) { + if (networkObject.gas?.OptimismTxFee) { dataIsReady = l1GasFeeOptimism !== null; } - const adjustedGasFees = await networkObj.gas.getGasPrices(); + const adjustedGasFees = await networkObject.gas.getGasPrices(); if (!adjustedGasFees) return; @@ -503,7 +505,7 @@ export const gasPricesStartPolling = if (!gasFeeParamsBySpeed) return; const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed: updatedGasFeesBySpeed } = dataIsReady ? getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -511,7 +513,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ) : { @@ -529,7 +531,7 @@ export const gasPricesStartPolling = } else { try { const { gasFeeParamsBySpeed, baseFeePerGas, trend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - await getEIP1559GasParams(network); + await getEIP1559GasParams(chainId); if (flashbots) { [SLOW, NORMAL, FAST, URGENT].forEach(speed => { @@ -553,16 +555,8 @@ export const gasPricesStartPolling = // Set a really gas estimate to guarantee that we're gonna be over // the basefee at the time we fork mainnet during our hardhat tests let baseFee = baseFeePerGas; - if (network === Network.mainnet && IS_TESTING === 'true') { - const providerUrl = ( - web3Provider || - ({} as { - connection: { url: string }; - }) - )?.connection?.url; - if (isHardHat(providerUrl)) { - baseFee = parseGasFeeParam(gweiToWei(1000)); - } + if (chainId === ChainId.mainnet && IS_TESTING === 'true' && useConnectedToHardhatStore.getState().connectedToHardhat) { + baseFee = parseGasFeeParam(gweiToWei(1000)); } if (customGasFeeModifiedByUser) { @@ -576,7 +570,7 @@ export const gasPricesStartPolling = gasFeeParamsBySpeed[CUSTOM] = gasFeeParamsBySpeed[URGENT]; } const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBaseFee, @@ -584,7 +578,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ); @@ -603,33 +597,32 @@ export const gasPricesStartPolling = type: GAS_FEES_SUCCESS, }); } catch (e) { - logger.error(new RainbowError(`Etherscan gas estimates error: ${e}`)); - logger.debug('falling back to eth gas station'); + logger.error(new RainbowError(`[redux/gas]: Etherscan gas estimates error: ${e}`)); } } fetchResolve(true); } catch (e) { - logger.error(new RainbowError(`Gas Estimates Failed for ${network}: ${e}`)); + logger.error(new RainbowError(`[redux/gas]: Gas Estimates Failed for ${chainId}: ${e}`)); fetchReject(e); } }) ); - const watchGasPrices = async (network: Network, pollingInterval: number) => { + const watchGasPrices = async (chainId: ChainId, pollingInterval: number) => { try { - await getGasPrices(network); + await getGasPrices(); // eslint-disable-next-line no-empty } catch (e) { } finally { gasPricesHandle && clearTimeout(gasPricesHandle); gasPricesHandle = setTimeout(() => { - watchGasPrices(network, pollingInterval); + watchGasPrices(chainId, pollingInterval); }, pollingInterval); } }; - const pollingInterval = getGasPricePollingInterval(network); - watchGasPrices(network, pollingInterval); + const pollingInterval = getGasPricePollingInterval(chainId); + watchGasPrices(chainId, pollingInterval); }; export const gasUpdateGasFeeOption = (newGasPriceOption: string) => (dispatch: AppDispatch, getState: AppGetState) => @@ -661,10 +654,10 @@ export const gasUpdateTxFee = (updatedGasLimit?: number, overrideGasOption?: string, l1GasFeeOptimism: BigNumber | null = null) => (dispatch: AppDispatch, getState: AppGetState) => withRunExclusive(async () => { - const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, txNetwork, currentBlockParams } = getState().gas; + const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, chainId, currentBlockParams } = getState().gas; const { nativeCurrency } = getState().settings; - if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObj(txNetwork).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { + if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObject({ chainId }).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { // if fee prices not ready, we need to store the gas limit for future calculations // the rest is as the initial state value if (updatedGasLimit) { @@ -675,7 +668,7 @@ export const gasUpdateTxFee = } } else { const _selectedGasFeeOption = overrideGasOption || selectedGasFee.option || NORMAL; - const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); + const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -683,7 +676,7 @@ export const gasUpdateTxFee = _gasLimit, nativeCurrency, _selectedGasFeeOption, - txNetwork, + chainId, l1GasFeeOptimism ); dispatch({ @@ -716,7 +709,7 @@ const INITIAL_STATE: GasState = { gasLimit: null, l1GasFeeOptimism: null, selectedGasFee: {} as SelectedGasFee, - txNetwork: null, + chainId: ChainId.mainnet, secondsPerNewBlock: 15, }; @@ -771,7 +764,7 @@ export default (state = INITIAL_STATE, action: { type: string; payload: any }) = case GAS_UPDATE_TRANSACTION_NETWORK: return { ...state, - txNetwork: action.payload, + chainId: action.payload, }; case GAS_PRICES_RESET: return INITIAL_STATE; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index d583e40cae1..a15206fbace 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -5,10 +5,8 @@ import { maybeSignUri } from '@/handlers/imgix'; import { getLocalRequests, removeLocalRequest, saveLocalRequests } from '@/handlers/localstorage/walletconnectRequests'; import { omitFlatten } from '@/helpers/utilities'; import { getRequestDisplayDetails } from '@/parsers'; -import { ethereumUtils } from '@/utils'; -import logger from '@/utils/logger'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { logger } from '@/logger'; +import { ChainId } from '@/networks/types'; // -- Constants --------------------------------------- // @@ -172,7 +170,7 @@ export const addRequestToApprove = // by an invalid access might be caught or expected elsewhere, so for now // `ts-expect-error` is used. if (displayDetails.timestampInMs < oneHourAgoTs) { - logger.log('request expired!'); + logger.debug(`[redux/requests]: [${requestId}] request expired!`); return; } const unsafeImageUrl = peerMeta?.icons?.[0]; diff --git a/src/redux/settings.ts b/src/redux/settings.ts index ab3e0a2d4aa..844d28b1348 100644 --- a/src/redux/settings.ts +++ b/src/redux/settings.ts @@ -10,24 +10,23 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { getAppIcon, + getChainId, getFlashbotsEnabled, getLanguage, getNativeCurrency, - getNetwork, getTestnetsEnabled, saveAppIcon, + saveChainId, saveFlashbotsEnabled, saveLanguage, saveNativeCurrency, - saveNetwork, saveTestnetsEnabled, } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; import { explorerClearState, explorerInit } from '@/redux/explorer'; import { AppState } from '@/redux/store'; -import { ethereumUtils } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; +import { Network, ChainId } from '@/networks/types'; // -- Constants ------------------------------------------------------------- // const SETTINGS_UPDATE_SETTINGS_ADDRESS = 'settings/SETTINGS_UPDATE_SETTINGS_ADDRESS'; @@ -105,7 +104,6 @@ interface SettingsStateUpdateNetworkSuccessAction { type: typeof SETTINGS_UPDATE_NETWORK_SUCCESS; payload: { chainId: SettingsState['chainId']; - network: SettingsState['network']; }; } @@ -145,21 +143,20 @@ export const settingsLoadState = type: SETTINGS_UPDATE_ACCOUNT_SETTINGS_SUCCESS, }); } catch (error) { - logger.log('Error loading native currency and testnets pref', error); + logger.error(new RainbowError(`[redux/settings]: Error loading native currency and testnets pref: ${error}`)); } }; export const settingsLoadNetwork = () => async (dispatch: Dispatch) => { try { - const network = await getNetwork(); - const chainId = ethereumUtils.getChainIdFromNetwork(network); - await web3SetHttpProvider(network); + const chainId = await getChainId(); + await web3SetHttpProvider(chainId); dispatch({ - payload: { chainId, network }, + payload: { chainId }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); } catch (error) { - logger.log('Error loading network settings', error); + logger.error(new RainbowError(`[redux/settings]: Error loading network settings: ${error}`)); } }; @@ -175,7 +172,7 @@ export const settingsLoadLanguage = () => async (dispatch: Dispatch (dispatch: Dispatch) => { const callback = async () => { - logger.log('changing app icon to', appIcon); + logger.debug(`[redux/settings]: changing app icon to ${appIcon}`); try { await changeIcon(appIcon); - logger.log('icon changed to ', appIcon); + logger.debug(`[redux/settings]: icon changed to ${appIcon}`); saveAppIcon(appIcon); dispatch({ payload: appIcon, type: SETTINGS_UPDATE_APP_ICON_SUCCESS, }); } catch (error) { - logger.log('Error changing app icon', error); + logger.error(new RainbowError(`[redux/settings]: Error changing app icon: ${error}`)); } }; @@ -237,17 +234,16 @@ export const settingsUpdateAccountAddress = }); }; -export const settingsUpdateNetwork = (network: Network) => async (dispatch: Dispatch) => { - const chainId = ethereumUtils.getChainIdFromNetwork(network); - await web3SetHttpProvider(network); +export const settingsUpdateNetwork = (chainId: ChainId) => async (dispatch: Dispatch) => { + await web3SetHttpProvider(chainId); try { dispatch({ - payload: { chainId, network }, + payload: { chainId }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); - saveNetwork(network); + saveChainId(chainId); } catch (error) { - logger.log('Error updating network settings', error); + logger.error(new RainbowError(`[redux/settings]: Error updating network settings: ${error}`)); } }; @@ -261,7 +257,7 @@ export const settingsChangeLanguage = (language: Language) => async (dispatch: D saveLanguage(language); analytics.identify({ language }); } catch (error) { - logger.log('Error changing language', error); + logger.error(new RainbowError(`[redux/settings]: Error changing language: ${error}`)); } }; @@ -278,7 +274,7 @@ export const settingsChangeNativeCurrency = saveNativeCurrency(nativeCurrency); analytics.identify({ currency: nativeCurrency }); } catch (error) { - logger.log('Error changing native currency', error); + logger.error(new RainbowError(`[redux/settings]: Error changing native currency: ${error}`)); } }; @@ -315,7 +311,6 @@ export default (state = INITIAL_STATE, action: SettingsStateUpdateAction) => { return { ...state, chainId: action.payload.chainId, - network: action.payload.network, }; case SETTINGS_UPDATE_LANGUAGE_SUCCESS: return { diff --git a/src/redux/showcaseTokens.ts b/src/redux/showcaseTokens.ts index d0d1d934ab5..23252088f9f 100644 --- a/src/redux/showcaseTokens.ts +++ b/src/redux/showcaseTokens.ts @@ -4,8 +4,8 @@ import { Dispatch } from 'redux'; import { getPreference } from '../model/preferences'; import { AppGetState } from './store'; import { getShowcaseTokens, getWebDataEnabled, saveShowcaseTokens, saveWebDataEnabled } from '@/handlers/localstorage/accountLocal'; -import networkTypes from '@/helpers/networkTypes'; import WalletTypes from '@/helpers/walletTypes'; +import { Network } from '@/networks/types'; // -- Constants --------------------------------------- // @@ -204,7 +204,7 @@ export const removeShowcaseToken = (tokenId: string) => (dispatch: Dispatch + (enabled: boolean, address: string, network = Network.mainnet) => async (dispatch: Dispatch) => { dispatch({ payload: enabled, diff --git a/src/redux/swap.ts b/src/redux/swap.ts index 10bf5a81080..0ca1da8addd 100644 --- a/src/redux/swap.ts +++ b/src/redux/swap.ts @@ -109,7 +109,7 @@ export const updateSwapInputCurrency = dispatch({ payload: newInputCurrency, type: SWAP_UPDATE_INPUT_CURRENCY }); if ( type === ExchangeModalTypes.swap && - newInputCurrency?.network !== outputCurrency?.network && + newInputCurrency?.chainId !== outputCurrency?.chainId && newInputCurrency && !ignoreTypeCheck ) { @@ -131,7 +131,7 @@ export const updateSwapOutputCurrency = } else { if ( type === ExchangeModalTypes.swap && - newOutputCurrency?.network !== inputCurrency?.network && + newOutputCurrency?.chainId !== inputCurrency?.chainId && newOutputCurrency && !ignoreTypeCheck ) { diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index 564d2893e5e..f75a286e044 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -25,13 +25,15 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { convertHexToString, delay, omitBy, pickBy } from '@/helpers/utilities'; import WalletConnectApprovalSheetType from '@/helpers/walletConnectApprovalSheetTypes'; import Routes from '@/navigation/routesNames'; -import { ethereumUtils, watchingAlert } from '@/utils'; +import { watchingAlert } from '@/utils'; import { getFCMToken } from '@/notifications/tokens'; import { logger, RainbowError } from '@/logger'; import { IS_DEV, IS_IOS, IS_TEST } from '@/env'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { Verify } from '@walletconnect/types'; import { RequestSource, handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; +import { ChainId } from '@/networks/types'; +import { Address } from 'viem'; // -- Variables --------------------------------------- // let showRedirectSheetThreshold = 300; @@ -132,8 +134,8 @@ export type WalletconnectResultType = 'timedOut' | 'sign' | 'transaction' | 'sig export interface WalletconnectApprovalSheetRouteParams { callback: ( approved: boolean, - chainId: number, - accountAddress: string, + chainId: ChainId, + accountAddress: Address, peerId: WalletconnectRequestData['peerId'], dappScheme: WalletconnectRequestData['dappScheme'], dappName: WalletconnectRequestData['dappName'], @@ -189,7 +191,7 @@ const getNativeOptions = async () => { const token = await getFCMToken(); if (!token && !IS_DEV) { - logger.error(new RainbowError(`WC: FCM token not found, push notifications will not be received`)); + logger.error(new RainbowError(`[redux/walletconnect]: FCM token not found, push notifications will not be received`)); } const nativeOptions = { @@ -353,7 +355,7 @@ export const walletConnectOnSessionRequest = error, payload, }); - logger.error(new RainbowError('WC: Error on wc session_request'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc session_request'), { error, payload, }); @@ -424,7 +426,7 @@ export const walletConnectOnSessionRequest = }, 2000); } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('WC: Exception during wc session_request'), { error }); + logger.error(new RainbowError('[redux/walletconnect]: Exception during wc session_request'), { error }); analytics.track('Exception on wc session_request', { error, }); @@ -432,7 +434,7 @@ export const walletConnectOnSessionRequest = } } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('WC: FCM exception during wc session_request'), { error }); + logger.error(new RainbowError('[redux/walletconnect]: FCM exception during wc session_request'), { error }); analytics.track('FCM exception on wc session_request', { error, }); @@ -449,7 +451,7 @@ export const walletConnectOnSessionRequest = const listenOnNewMessages = (walletConnector: WalletConnect) => (dispatch: ThunkDispatch, getState: AppGetState) => { walletConnector.on('call_request', async (error, payload) => { - logger.debug('WC: Request!', { error, payload }, logger.DebugContext.walletconnect); + logger.debug('[redux/walletconnect]: Request!', { error, payload }, logger.DebugContext.walletconnect); if (error) { analytics.track('Error on wc call_request', { @@ -457,7 +459,7 @@ const listenOnNewMessages = error, payload, }); - logger.error(new RainbowError('WC: Error on wc call_request'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc call_request'), { message: error, }); return; @@ -470,11 +472,11 @@ const listenOnNewMessages = const requestId = payload.id; if (payload.method === 'wallet_addEthereumChain' || payload.method === `wallet_switchEthereumChain`) { const { chainId } = payload.params[0]; - const currentNetwork = ethereumUtils.getNetworkFromChainId( - // @ts-expect-error "_chainId" is private. - Number(walletConnector._chainId) + // @ts-expect-error "_chainId" is private. + const currentChainId = Number(walletConnector._chainId); + const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => + network.id.toString() ); - const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { dispatch(walletConnectSetPendingRedirect()); @@ -486,7 +488,7 @@ const listenOnNewMessages = result: null, }); const { accountAddress } = getState().settings; - logger.debug('WC: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); + logger.debug('[redux/walletconnect]: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); await walletConnector.updateSession({ accounts: [accountAddress], // @ts-expect-error "numericChainId" is a string, not a number. @@ -511,7 +513,7 @@ const listenOnNewMessages = }); } }, - currentNetwork, + currentChainId, meta: { chainIds: [Number(numericChainId)], dappName, @@ -521,7 +523,7 @@ const listenOnNewMessages = type: WalletConnectApprovalSheetType.switch_chain, }); } else { - logger.info('WC: NOT SUPPORTED CHAIN'); + logger.warn(`[redux/walletconnect]: Unsupported chain ${numericChainId}`); walletConnector.rejectRequest({ error: { message: 'Chain currently not supported' }, id: requestId, @@ -573,7 +575,7 @@ const listenOnNewMessages = }); walletConnector.on('disconnect', error => { if (error) { - logger.error(new RainbowError('WC: Error on wc disconnect'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc disconnect'), { message: error, }); @@ -620,7 +622,7 @@ export const walletConnectLoadState = // @ts-ignore error, }); - logger.error(new RainbowError('WC: Error on wc walletConnectLoadState'), { + logger.error(new RainbowError('[redux/walletconnect]: Error on wc walletConnectLoadState'), { error, }); newWalletConnectors = {}; diff --git a/src/redux/wallets.ts b/src/redux/wallets.ts index 519223b54c3..deb49a5ea9b 100644 --- a/src/redux/wallets.ts +++ b/src/redux/wallets.ts @@ -157,12 +157,12 @@ export const walletsLoadState = } else { keys(wallets).some(key => { const someWallet = wallets[key]; - const found = someWallet.addresses.some(account => { + const found = (someWallet.addresses || []).some(account => { return toChecksumAddress(account.address) === toChecksumAddress(address!); }); if (found) { selectedWallet = someWallet; - logger.info('Found selected wallet based on loadAddress result'); + logger.debug('[redux/wallets]: Found selected wallet based on loadAddress result'); } return found; }); @@ -172,7 +172,7 @@ export const walletsLoadState = // Recover from broken state (account address not in selected wallet) if (!addressFromKeychain) { addressFromKeychain = await loadAddress(); - logger.info("addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); + logger.debug("[redux/wallets]: addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); } const selectedAddress = selectedWallet?.addresses.find(a => { @@ -184,7 +184,7 @@ export const walletsLoadState = const allWallets = Object.values(allWalletsResult?.wallets || {}); let account = null; for (const wallet of allWallets) { - for (const rainbowAccount of wallet.addresses) { + for (const rainbowAccount of wallet.addresses || []) { if (rainbowAccount.visible) { account = rainbowAccount; break; @@ -194,7 +194,7 @@ export const walletsLoadState = if (!account) return; await dispatch(settingsUpdateAccountAddress(account.address)); await saveAddress(account.address); - logger.info('Selected the first visible address because there was not selected one'); + logger.debug('[redux/wallets]: Selected the first visible address because there was not selected one'); } const walletNames = await getWalletNames(); @@ -209,7 +209,7 @@ export const walletsLoadState = return wallets; } catch (error) { - logger.error(new RainbowError('Exception during walletsLoadState'), { + logger.error(new RainbowError('[redux/wallets]: Exception during walletsLoadState'), { message: (error as Error)?.message, }); } @@ -281,7 +281,7 @@ export const setAllWalletsWithIdsAsBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('Saving multiple wallets UserData to cloud failed.'), { + logger.error(new RainbowError('[redux/wallets]: Saving multiple wallets UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -325,7 +325,7 @@ export const setWalletBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('Saving wallet UserData to cloud failed.'), { + logger.error(new RainbowError('[redux/wallets]: Saving wallet UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -346,7 +346,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = try { currentUserData = await fetchUserDataFromCloud(); } catch (error) { - logger.error(new RainbowError('There was an error when trying to update wallet backup statuses'), { + logger.error(new RainbowError('[redux/wallets]: There was an error when trying to update wallet backup statuses'), { error: (error as Error).message, }); return; @@ -358,7 +358,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = // build hashmap of address to wallet based on backup metadata const addressToWalletLookup = new Map(); Object.values(currentUserData.wallets).forEach(wallet => { - wallet.addresses.forEach(account => { + wallet.addresses?.forEach(account => { addressToWalletLookup.set(account.address, wallet); }); }); @@ -376,7 +376,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = const localWalletId = wallet.id; let relatedCloudWalletId: string | null = null; - for (const account of wallet.addresses) { + for (const account of wallet.addresses || []) { const walletDataForCurrentAddress = addressToWalletLookup.get(account.address); if (!walletDataForCurrentAddress) { return; @@ -385,7 +385,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = relatedCloudWalletId = walletDataForCurrentAddress.id; } else if (relatedCloudWalletId !== walletDataForCurrentAddress.id) { logger.warn( - 'Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' + '[redux/wallets]: Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' ); return; } @@ -584,7 +584,7 @@ export const fetchWalletNames = () => async (dispatch: Dispatch { - const visibleAccounts = wallet.addresses?.filter(address => address.visible); + const visibleAccounts = (wallet.addresses || []).filter(address => address.visible); return visibleAccounts.map(async account => { try { const ens = await fetchReverseRecordWithRetry(account.address); @@ -612,37 +612,37 @@ export const fetchWalletNames = () => async (dispatch: Dispatch async (dispatch: ThunkDispatch, getState: AppGetState) => { try { let healthyKeychain = true; - logger.info('[KeychainIntegrityCheck]: starting checks'); + logger.debug('[redux/wallets]: Starting keychain integrity checks'); const hasAddress = await hasKey(addressKey); if (hasAddress) { - logger.info('[KeychainIntegrityCheck]: address is ok'); + logger.debug('[redux/wallets]: address is ok'); } else { healthyKeychain = false; - logger.info(`[KeychainIntegrityCheck]: address is missing: ${hasAddress}`); + logger.debug(`[redux/wallets]: address is missing: ${hasAddress}`); } const hasOldSeedPhraseMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (hasOldSeedPhraseMigratedFlag) { - logger.info('[KeychainIntegrityCheck]: migrated flag is OK'); + logger.debug('[redux/wallets]: migrated flag is OK'); } else { - logger.info(`[KeychainIntegrityCheck]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); + logger.debug(`[redux/wallets]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); } const hasOldSeedphrase = await hasKey(seedPhraseKey); if (hasOldSeedphrase) { - logger.info('[KeychainIntegrityCheck]: old seed is still present!'); + logger.debug('[redux/wallets]: old seed is still present!'); } else { - logger.info(`[KeychainIntegrityCheck]: old seed is present: ${hasOldSeedphrase}`); + logger.debug(`[redux/wallets]: old seed is present: ${hasOldSeedphrase}`); } const { wallets, selected } = getState().wallets; if (!wallets) { - logger.warn('[KeychainIntegrityCheck]: wallets are missing from redux'); + logger.warn('[redux/wallets]: wallets are missing from redux'); } if (!selected) { - logger.warn('[KeychainIntegrityCheck]: selectedWallet is missing from redux'); + logger.warn('[redux/wallets]: selectedWallet is missing from redux'); } const nonReadOnlyWalletKeys = keys(wallets).filter(key => wallets![key].type !== WalletTypes.readOnly); @@ -654,18 +654,18 @@ export const checkKeychainIntegrity = () => async (dispatch: ThunkDispatch async (dispatch: ThunkDispatch - [ - // In default order of appearance - mainnet, - base, - optimism, - arbitrum, - polygon, - zora, - blast, - degen, - avalanche, - bsc, +export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => { + const mainnetChains: Chain[] = [mainnet, base, optimism, arbitrum, polygon, zora, blast, degen, avalanche, bsc]; - // Testnets + const testnetChains: Chain[] = [ goerli, holesky, sepolia, @@ -255,12 +243,12 @@ export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolea zoraSepolia, avalancheFuji, bscTestnet, - ].reduce((chainList, chain) => { - if (testnetMode || !chain.testnet) { - chainList.push({ ...chain, name: ChainNameDisplay[chain.id] }); - } - return chainList; - }, [] as Chain[]); + ]; + + const allChains = mainnetChains.concat(testnetMode ? testnetChains : []); + + return allChains.map(chain => ({ ...chain, name: ChainNameDisplay[chain.id] ?? chain.name })); +}; export const SUPPORTED_CHAIN_IDS = ({ testnetMode = false }: { testnetMode?: boolean }): ChainId[] => SUPPORTED_CHAINS({ testnetMode }).map(chain => chain.id); diff --git a/src/references/rainbow-token-list/index.ts b/src/references/rainbow-token-list/index.ts index 845443a6cc3..688e07eef13 100644 --- a/src/references/rainbow-token-list/index.ts +++ b/src/references/rainbow-token-list/index.ts @@ -1,14 +1,12 @@ import { EventEmitter } from 'events'; -import { captureException } from '@sentry/react-native'; import { keyBy } from 'lodash'; import { MMKV } from 'react-native-mmkv'; import { ETH_ADDRESS } from '../index'; import RAINBOW_TOKEN_LIST_DATA from './rainbow-token-list.json'; import { RainbowToken } from '@/entities'; import { STORAGE_IDS } from '@/model/mmkv'; -import logger from '@/utils/logger'; -import { Network } from '@/networks/types'; -import { ChainId } from '@/__swaps__/types/chains'; +import { logger, RainbowError } from '@/logger'; +import { Network, ChainId } from '@/networks/types'; export const rainbowListStorage = new MMKV({ id: STORAGE_IDS.RAINBOW_TOKEN_LIST, @@ -79,10 +77,7 @@ function readJson(key: string): T | null { return JSON.parse(data); } catch (error) { - logger.sentry('Error parsing token-list-cache data'); - logger.error(error); - captureException(error); - + logger.error(new RainbowError(`[rainbow-token-list]: Error parsing token-list-cache data: ${error}`)); return null; } } @@ -91,9 +86,7 @@ function writeJson(key: string, data: T) { try { rainbowListStorage.set(key, JSON.stringify(data)); } catch (error) { - logger.sentry(`Token List: Error saving ${key}`); - logger.error(error); - captureException(error); + logger.error(new RainbowError(`[rainbow-token-list]: Error saving ${key}: ${error}`)); } } @@ -115,7 +108,7 @@ class RainbowTokenList extends EventEmitter { } } - logger.debug('Token list initialized'); + logger.debug('[rainbow-token-list]: Token list initialized'); } // Wrapping #tokenListDataStorage so we can add events around updates. @@ -127,7 +120,7 @@ class RainbowTokenList extends EventEmitter { this.#tokenListDataStorage = val; this.#derivedData = generateDerivedData(RAINBOW_TOKEN_LIST_DATA); this.emit('update'); - logger.debug('Token list data replaced'); + logger.debug('[rainbow-token-list]: Token list data replaced'); } get CURATED_TOKENS() { diff --git a/src/references/shitcoins.ts b/src/references/shitcoins.ts index a3ba26b1621..dec188eb98c 100644 --- a/src/references/shitcoins.ts +++ b/src/references/shitcoins.ts @@ -1,7 +1,7 @@ export default [ '0xe233a118042ca180570bb450cceecc8f46c23710', // PolkaCover (fake) '0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e', // Kick token - '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', //启动公链 + '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', // 启动公链 '0xf222ba8af81d799c565241b0d3eedf9bdc4fc462', // betbeb.com空投1万个 '0xdbadabe39b91f2069e27291add14a1d95e3ff54f', // betbeb.com 挖矿每天 '0xc92e74b131d7b1d46e60e07f3fae5d8877dd03f0', // Minereum diff --git a/src/references/testnet-assets-by-chain.ts b/src/references/testnet-assets-by-chain.ts new file mode 100644 index 00000000000..8bfb3c1d61f --- /dev/null +++ b/src/references/testnet-assets-by-chain.ts @@ -0,0 +1,69 @@ +import { UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; +import { ChainId, ChainName } from '@/networks/types'; + +type ChainAssets = { + [uniqueId: UniqueId]: { + asset: ZerionAsset; + quantity: string; + }; +}; + +// NOTE: Don't import `ETH_ADDRESS` as it's resolving to undefined... +export const chainAssets: Partial> = { + [ChainId.goerli]: { + eth_5: { + asset: { + asset_code: 'eth', + mainnet_address: 'eth', + colors: { + fallback: '#E8EAF5', + primary: '#808088', + }, + implementations: {}, + bridging: { + bridgeable: true, + networks: {}, // TODO: Add bridgeable networks + }, + decimals: 18, + icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', + name: 'Goerli', + network: ChainName.goerli, + price: { + relative_change_24h: -4.586615622469276, + value: 2590.2, + }, + symbol: 'ETH', + }, + quantity: '0', + }, + }, + [ChainId.mainnet]: { + eth_1: { + asset: { + asset_code: 'eth', + mainnet_address: 'eth', + colors: { + fallback: '#E8EAF5', + primary: '#808088', + }, + decimals: 18, + icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', + name: 'Ethereum', + network: ChainName.mainnet, + implementations: {}, + bridging: { + bridgeable: true, + networks: {}, // TODO: Add bridgeable networks + }, + price: { + relative_change_24h: -4.586615622469276, + value: 2590.2, + }, + symbol: 'ETH', + }, + quantity: '0', + }, + }, +}; + +export default chainAssets; diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts index 5f2ea008d0e..47db136d5e2 100644 --- a/src/resources/assets/UserAssetsQuery.ts +++ b/src/resources/assets/UserAssetsQuery.ts @@ -2,15 +2,16 @@ import isEmpty from 'lodash/isEmpty'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { NativeCurrencyKey } from '@/entities'; import { saveAccountEmptyState } from '@/handlers/localstorage/accountLocal'; -import { Network } from '@/helpers/networkTypes'; import { greaterThan } from '@/helpers/utilities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { createQueryKey, queryClient, QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { useQuery } from '@tanstack/react-query'; import { filterPositionsData, parseAddressAsset } from './assets'; import { fetchHardhatBalances } from './hardhatAssets'; import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types'; +import { Network } from '@/networks/types'; +import { staleBalancesStore } from '@/state/staleBalances'; // /////////////////////////////////////////////// // Query Types @@ -32,15 +33,26 @@ type UserAssetsQueryKey = ReturnType; // /////////////////////////////////////////////// // Query Function -const fetchUserAssetsForChainIds = async (address: string, currency: NativeCurrencyKey, chainIds: number[]) => { +const fetchUserAssetsForChainIds = async ({ + address, + currency, + chainIds, + staleBalanceParam, +}: { + address: string; + currency: NativeCurrencyKey; + chainIds: number[]; + staleBalanceParam?: string; +}) => { const chainIdsString = chainIds.join(','); - const url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets`; + let url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets?currency=${currency.toLowerCase()}`; + + if (staleBalanceParam) { + url += url + staleBalanceParam; + } const response = await rainbowFetch(url, { method: 'get', - params: { - currency: currency.toLowerCase(), - }, headers: { Authorization: `Bearer ${ADDYS_API_KEY}`, }, @@ -62,9 +74,14 @@ async function userAssetsQueryFunction({ } try { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( + network => network.id + ); - const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds(address, currency, chainIds); + staleBalancesStore.getState().clearExpiredData(address); + const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address); + + const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); let parsedSuccessResults = results; // grab cached data for chain IDs with errors @@ -100,7 +117,7 @@ const retryErroredChainIds = async ( connectedToHardhat: boolean, erroredChainIds: number[] ) => { - const { meta, results } = await fetchAndParseUserAssetsForChainIds(address, currency, erroredChainIds); + const { meta, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds: erroredChainIds }); let parsedSuccessResults = results; const successChainIds = meta?.chain_ids; @@ -140,12 +157,18 @@ interface AssetsAndMetadata { results: RainbowAddressAssets; } -const fetchAndParseUserAssetsForChainIds = async ( - address: string, - currency: NativeCurrencyKey, - chainIds: number[] -): Promise => { - const data = await fetchUserAssetsForChainIds(address, currency, chainIds); +const fetchAndParseUserAssetsForChainIds = async ({ + address, + currency, + chainIds, + staleBalanceParam, +}: { + address: string; + currency: NativeCurrencyKey; + chainIds: number[]; + staleBalanceParam?: string; +}): Promise => { + const data = await fetchUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); let parsedSuccessResults = parseUserAssetsByChain(data); // filter out positions data diff --git a/src/resources/assets/assetSelectors.ts b/src/resources/assets/assetSelectors.ts index c5fb9bf467b..41d6d2ba9e5 100644 --- a/src/resources/assets/assetSelectors.ts +++ b/src/resources/assets/assetSelectors.ts @@ -1,4 +1,4 @@ -import { ParsedAddressAsset } from '@/entities'; +import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { parseAssetsNative } from '@/parsers'; import isEmpty from 'lodash/isEmpty'; import isNil from 'lodash/isNil'; @@ -12,13 +12,13 @@ export function selectUserAssetWithUniqueId(uniqueId: string) { }; } -export function selectSortedUserAssets(nativeCurrency: string) { +export function selectSortedUserAssets(nativeCurrency: NativeCurrencyKey) { return (accountAssets: RainbowAddressAssets) => { return sortAssetsByNativeAmount(accountAssets, nativeCurrency); }; } -const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: string): ParsedAddressAsset[] => { +const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: NativeCurrencyKey): ParsedAddressAsset[] => { let assetsNativePrices = Object.values(accountAssets); if (!isEmpty(assetsNativePrices)) { diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index 48cf161d614..37cee5c850b 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -3,7 +3,6 @@ import isEmpty from 'lodash/isEmpty'; import { MMKV } from 'react-native-mmkv'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers/networkTypes'; import { convertRawAmountToBalance } from '@/helpers/utilities'; import { BooleanMap } from '@/hooks/useCoinListEditOptions'; import { queryClient } from '@/react-query'; @@ -14,11 +13,10 @@ import { RainbowPositions } from '@/resources/defi/types'; import { ethereumUtils } from '@/utils'; import { AddysAddressAsset, AddysAsset, ParsedAsset, RainbowAddressAssets } from './types'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/networks/types'; const storage = new MMKV(); -const MAINNET_CHAIN_ID = ethereumUtils.getChainIdFromNetwork(Network.mainnet); - export const filterPositionsData = ( address: string, currency: NativeCurrencyKey, @@ -43,7 +41,7 @@ export const filterPositionsData = ( export function parseAsset({ address, asset }: { address: string; asset: AddysAsset }): ParsedAsset { const network = asset?.network; const chainId = ethereumUtils.getChainIdFromNetwork(network); - const mainnetAddress = asset?.networks?.[MAINNET_CHAIN_ID]?.address; + const mainnetAddress = asset?.networks?.[ChainId.mainnet]?.address; const uniqueId = getUniqueId(address, chainId); const parsedAsset = { @@ -74,6 +72,7 @@ export function parseAddressAsset({ assetData }: { assetData: AddysAddressAsset const asset = assetData?.asset; const quantity = assetData?.quantity; const address = assetData?.asset?.asset_code; + const parsedAsset = parseAsset({ address, asset, diff --git a/src/resources/assets/externalAssetsQuery.ts b/src/resources/assets/externalAssetsQuery.ts index 0092abdeebc..de29cf9d322 100644 --- a/src/resources/assets/externalAssetsQuery.ts +++ b/src/resources/assets/externalAssetsQuery.ts @@ -4,8 +4,9 @@ import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunct import { convertAmountAndPriceToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; import { NativeCurrencyKey } from '@/entities'; import { Token } from '@/graphql/__generated__/metadata'; -import { ethereumUtils } from '@/utils'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; +import { isNativeAsset } from '@/__swaps__/utils/chains'; +import { AddressOrEth } from '@/__swaps__/types/assets'; export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute @@ -19,7 +20,9 @@ export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute // Types type ExternalToken = Pick; export type FormattedExternalAsset = ExternalToken & { + address: string; icon_url?: string; + isNativeAsset: boolean; native: { change: string; price: { @@ -43,9 +46,16 @@ export const externalTokenQueryKey = ({ address, chainId, currency }: ExternalTo type externalTokenQueryKey = ReturnType; // Helpers -const formatExternalAsset = (asset: ExternalToken, nativeCurrency: NativeCurrencyKey): FormattedExternalAsset => { +const formatExternalAsset = ( + address: string, + chainId: ChainId, + asset: ExternalToken, + nativeCurrency: NativeCurrencyKey +): FormattedExternalAsset => { return { ...asset, + address, + isNativeAsset: isNativeAsset(address as AddressOrEth, chainId), native: { change: asset?.price?.relativeChange24h ? convertAmountToPercentageDisplay(`${asset?.price?.relativeChange24h}`) : '', price: convertAmountAndPriceToNativeDisplay(1, asset?.price?.value || 0, nativeCurrency), @@ -62,7 +72,7 @@ export async function fetchExternalToken({ address, chainId, currency }: Externa currency, }); if (response.token) { - return formatExternalAsset(response.token, currency); + return formatExternalAsset(address, chainId, response.token, currency); } else { return null; } diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/hardhatAssets.ts index 697900446c8..606dad3c8b3 100644 --- a/src/resources/assets/hardhatAssets.ts +++ b/src/resources/assets/hardhatAssets.ts @@ -1,22 +1,24 @@ import { Contract } from '@ethersproject/contracts'; -import { captureException } from '@sentry/react-native'; import { keyBy, mapValues } from 'lodash'; -import { Network } from '@/helpers/networkTypes'; -import { web3Provider } from '@/handlers/web3'; // TODO JIN -import { getNetworkObj } from '@/networks'; -import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS } from '@/references'; +import { getProvider } from '@/handlers/web3'; +import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS, SUPPORTED_CHAIN_IDS } from '@/references'; import { parseAddressAsset } from './assets'; import { RainbowAddressAssets } from './types'; -import logger from '@/utils/logger'; - -const ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT = '0x0000000000000000000000000000000000000000'; +import { logger, RainbowError } from '@/logger'; +import { AddressOrEth, UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; +import { AddressZero } from '@ethersproject/constants'; +import chainAssetsByChainId from '@/references/testnet-assets-by-chain'; +import { getNetworkObject } from '@/networks'; +import { ChainId, ChainName, Network } from '@/networks/types'; const fetchHardhatBalancesWithBalanceChecker = async ( tokens: string[], address: string, - network: Network = Network.mainnet + chainId: ChainId = ChainId.mainnet ): Promise<{ [tokenAddress: string]: string } | null> => { - const balanceCheckerContract = new Contract(getNetworkObj(network).balanceCheckerAddress, balanceCheckerContractAbi, web3Provider); + const networkObject = getNetworkObject({ chainId }); + const provider = getProvider({ chainId }); + const balanceCheckerContract = new Contract(networkObject.balanceCheckerAddress, balanceCheckerContractAbi, provider); try { const values = await balanceCheckerContract.balances([address], tokens); const balances: { @@ -24,24 +26,33 @@ const fetchHardhatBalancesWithBalanceChecker = async ( } = {}; tokens.forEach((tokenAddr, tokenIdx) => { const balance = values[tokenIdx]; - const assetCode = tokenAddr === ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT ? ETH_ADDRESS : tokenAddr; + const assetCode = tokenAddr === AddressZero ? ETH_ADDRESS : tokenAddr; balances[assetCode] = balance.toString(); }); return balances; } catch (e) { - logger.sentry('Error fetching balances from balanceCheckerContract', network, e); - captureException(new Error('fallbackExplorer::balanceChecker failure')); + logger.error(new RainbowError(`[hardhatAssets]: Error fetching balances from balanceCheckerContract: ${e}`)); return null; } }; -export const fetchHardhatBalances = async (accountAddress: string, network: Network = Network.mainnet): Promise => { - const chainAssetsMap = keyBy(chainAssets[network as keyof typeof chainAssets], ({ asset }) => `${asset.asset_code}_${asset.network}`); +/** + * @deprecated - to be removed once rest of the app is converted to new userAssetsStore + * Fetches the balances of the hardhat assets for the given account address and network. + * @param accountAddress - The address of the account to fetch the balances for. + * @param network - The network to fetch the balances for. + * @returns The balances of the hardhat assets for the given account address and network. + */ +export const fetchHardhatBalances = async (accountAddress: string, chainId: ChainId = ChainId.mainnet): Promise => { + const chainAssetsMap = keyBy( + chainAssets[`${chainId}` as keyof typeof chainAssets], + ({ asset }) => `${asset.asset_code}_${asset.chainId}` + ); const tokenAddresses = Object.values(chainAssetsMap).map(({ asset: { asset_code } }) => - asset_code === ETH_ADDRESS ? ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT : asset_code.toLowerCase() + asset_code === ETH_ADDRESS ? AddressZero : asset_code.toLowerCase() ); - const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); if (!balances) return {}; const updatedAssets = mapValues(chainAssetsMap, chainAsset => { @@ -57,3 +68,62 @@ export const fetchHardhatBalances = async (accountAddress: string, network: Netw }); return updatedAssets; }; + +export const fetchHardhatBalancesByChainId = async ( + accountAddress: string, + chainId: ChainId = ChainId.mainnet +): Promise<{ + assets: { + [uniqueId: UniqueId]: { + asset: ZerionAsset; + quantity: string; + }; + }; + chainIdsInResponse: ChainId[]; +}> => { + const chainAssetsMap = chainAssetsByChainId[`${chainId}` as keyof typeof chainAssets] || {}; + + const tokenAddresses = Object.values(chainAssetsMap).map(({ asset }) => + asset.asset_code === ETH_ADDRESS ? AddressZero : asset.asset_code.toLowerCase() + ); + + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); + if (!balances) + return { + assets: {}, + chainIdsInResponse: [], + }; + + const updatedAssets = Object.entries(chainAssetsMap).reduce( + (acc, [uniqueId, chainAsset]) => { + const assetCode = chainAsset.asset.asset_code || ETH_ADDRESS; + const quantity = balances[assetCode.toLowerCase()] || '0'; + + const asset: ZerionAsset = { + ...chainAsset.asset, + asset_code: assetCode as AddressOrEth, + mainnet_address: (chainAsset.asset.mainnet_address as AddressOrEth) || (assetCode as AddressOrEth), + network: (chainAsset.asset.network as ChainName) || ChainName.mainnet, + bridging: chainAsset.asset.bridging || { + bridgeable: false, + networks: {}, + }, + implementations: chainAsset.asset.implementations || {}, + name: chainAsset.asset.name || 'Unknown Token', + symbol: chainAsset.asset.symbol || 'UNKNOWN', + decimals: chainAsset.asset.decimals || 18, + icon_url: chainAsset.asset.icon_url || '', + price: chainAsset.asset.price || { value: 0, relative_change_24h: 0 }, + }; + + acc[uniqueId] = { asset, quantity }; + return acc; + }, + {} as { [uniqueId: UniqueId]: { asset: ZerionAsset; quantity: string } } + ); + + return { + assets: updatedAssets, + chainIdsInResponse: SUPPORTED_CHAIN_IDS({ testnetMode: true }), + }; +}; diff --git a/src/resources/assets/types.ts b/src/resources/assets/types.ts index 39a5f41ea6d..a056e695b91 100644 --- a/src/resources/assets/types.ts +++ b/src/resources/assets/types.ts @@ -1,6 +1,6 @@ -import { Network } from '@/helpers/networkTypes'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { Network } from '@/networks/types'; export type AddysAccountAssetsResponse = { meta: AddysAccountAssetsMeta; @@ -54,7 +54,7 @@ export interface ParsedAsset { address: string; color?: string; colors?: TokenColors; - chainId?: number; + chainId: number; chainName?: string; decimals: number; icon_url?: string; diff --git a/src/resources/assets/useSortedUserAssets.ts b/src/resources/assets/useSortedUserAssets.ts index 96cd2e91490..b7e80d92c71 100644 --- a/src/resources/assets/useSortedUserAssets.ts +++ b/src/resources/assets/useSortedUserAssets.ts @@ -1,11 +1,11 @@ -import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { selectSortedUserAssets } from '@/resources/assets/assetSelectors'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export function useSortedUserAssets() { const { accountAddress, nativeCurrency } = useAccountSettings(); - const connectedToHardhat = getIsHardhatConnected(); + const { connectedToHardhat } = useConnectedToHardhatStore(); return useUserAssets( { diff --git a/src/resources/assets/useUserAsset.ts b/src/resources/assets/useUserAsset.ts index b94d22c15ec..bad1a672d05 100644 --- a/src/resources/assets/useUserAsset.ts +++ b/src/resources/assets/useUserAsset.ts @@ -1,14 +1,14 @@ -import { ChainId } from '@/__swaps__/types/chains'; -import { getIsHardhatConnected } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; import { useAccountSettings } from '@/hooks'; import { getNetworkObject } from '@/networks'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; import { selectUserAssetWithUniqueId } from '@/resources/assets/assetSelectors'; import { getUniqueId } from '@/utils/ethereumUtils'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export function useUserAsset(uniqueId: string) { const { accountAddress, nativeCurrency } = useAccountSettings(); - const connectedToHardhat = getIsHardhatConnected(); + const { connectedToHardhat } = useConnectedToHardhatStore(); return useUserAssets( { diff --git a/src/resources/assets/useUserAssetCount.ts b/src/resources/assets/useUserAssetCount.ts index bd414a8c651..7cf00ced409 100644 --- a/src/resources/assets/useUserAssetCount.ts +++ b/src/resources/assets/useUserAssetCount.ts @@ -1,13 +1,13 @@ -import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; import { RainbowAddressAssets } from './types'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const countSelector = (accountAssets: RainbowAddressAssets) => accountAssets?.length; export function useUserAssetCount() { const { accountAddress, nativeCurrency } = useAccountSettings(); - const connectedToHardhat = getIsHardhatConnected(); + const { connectedToHardhat } = useConnectedToHardhatStore(); return useUserAssets( { diff --git a/src/resources/defi/PositionsQuery.ts b/src/resources/defi/PositionsQuery.ts index 8579523d939..124948a1c93 100644 --- a/src/resources/defi/PositionsQuery.ts +++ b/src/resources/defi/PositionsQuery.ts @@ -3,14 +3,14 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { NativeCurrencyKey } from '@/entities'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { AddysPositionsResponse, PositionsArgs } from './types'; import { parsePositions } from './utils'; export const buildPositionsUrl = (address: string) => { - const networkString = RainbowNetworks.filter(network => network.enabled) + const networkString = RainbowNetworkObjects.filter(network => network.enabled) .map(network => network.id) .join(','); return `https://addys.p.rainbow.me/v3/${networkString}/${address}/positions`; diff --git a/src/resources/ens/ensAddressQuery.ts b/src/resources/ens/ensAddressQuery.ts index f84bb000bb8..a4110e5c5d1 100644 --- a/src/resources/ens/ensAddressQuery.ts +++ b/src/resources/ens/ensAddressQuery.ts @@ -1,7 +1,8 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryFunctionArgs } from '@/react-query'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; +import { ChainId } from '@/networks/types'; // Set a default stale time of 10 seconds so we don't over-fetch // (query will serve cached data & invalidate after 10s). @@ -23,7 +24,7 @@ const ensAddressQueryKey = ({ name }: ENSAddressArgs) => createQueryKey('ensAddr // Query Function async function ensAddressQueryFunction({ queryKey: [{ name }] }: QueryFunctionArgs) { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.mainnet }); const address = await provider.resolveName(name); return address; } diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 3bb6096f8aa..6a3b918f42a 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -1,8 +1,7 @@ import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { NativeCurrencyKeys, RainbowToken } from '@/entities'; -import { Network } from '@/networks/types'; import { createQueryKey, queryClient } from '@/react-query'; import { DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { promiseUtils } from '@/utils'; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultById.ts b/src/resources/featuredResults/_selectors/getFeaturedResultById.ts new file mode 100644 index 00000000000..7e4a79456ec --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultById.ts @@ -0,0 +1,6 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultById = (data: FeaturedResults, id: FeaturedResult['id']): FeaturedResult | undefined => { + return data.featuredResults.items?.find(item => item.id === id); +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts new file mode 100644 index 00000000000..429bc501e42 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts @@ -0,0 +1,5 @@ +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsById = (data: FeaturedResults) => { + return data.featuredResults.items?.map(item => item.id) ?? []; +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts new file mode 100644 index 00000000000..a4e6a04ee67 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts @@ -0,0 +1,6 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsForPlacement = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { + return data.featuredResults.items?.filter(item => item.placementSlug === placement) ?? []; +}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts new file mode 100644 index 00000000000..9ee209fab76 --- /dev/null +++ b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts @@ -0,0 +1,13 @@ +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; + +export const getFeaturedResultsForPlacementWithIds = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { + return ( + data.featuredResults.items?.reduce((acc, item) => { + if (item.placementSlug === placement) { + acc.push(item.id); + } + return acc; + }, [] as string[]) ?? [] + ); +}; diff --git a/src/resources/featuredResults/getFeaturedResults.ts b/src/resources/featuredResults/getFeaturedResults.ts new file mode 100644 index 00000000000..99e80c3ef33 --- /dev/null +++ b/src/resources/featuredResults/getFeaturedResults.ts @@ -0,0 +1,30 @@ +import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { arcClient } from '@/graphql'; + +const defaultStaleTime = 60_000; // 1 minute +const defaultCacheTime = 1000 * 60 * 60 * 24; // 1 day + +export type FeaturedResultsVariables = Parameters['0']; +export type FeaturedResults = Awaited>; + +// /////////////////////////////////////////////// +// Query Key +export const featuredResultsQueryKey = (props: FeaturedResultsVariables) => + createQueryKey('featured-results', props, { persisterVersion: 1 }); + +export type FeaturedResultsQueryKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Hook + +export function useFeaturedResults( + props: FeaturedResultsVariables, + config: QueryConfigWithSelect = {} +) { + return useQuery(featuredResultsQueryKey(props), () => arcClient.getFeaturedResults(props), { + ...config, + staleTime: defaultStaleTime, + cacheTime: defaultCacheTime, + }); +} diff --git a/src/resources/featuredResults/trackFeaturedResult.ts b/src/resources/featuredResults/trackFeaturedResult.ts new file mode 100644 index 00000000000..094882b369b --- /dev/null +++ b/src/resources/featuredResults/trackFeaturedResult.ts @@ -0,0 +1,25 @@ +import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { arcPOSTClient } from '@/graphql'; + +export type TrackFeaturedResultVariables = Parameters['0']; +export type TrackFeaturedResultResult = Awaited>; + +// /////////////////////////////////////////////// +// Mutation Key +export const trackFeaturedResultMutationKey = (props: Partial) => + createQueryKey('track-featured-result', props, { persisterVersion: 1 }); + +export type TrackFeaturedResultMutationKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Hook + +export function useTrackFeaturedResult( + props: Partial = {}, + config: QueryConfigWithSelect = {} +) { + return useMutation(trackFeaturedResultMutationKey(props), arcPOSTClient.trackFeaturedResult, { + ...config, + }); +} diff --git a/src/resources/metadata/dapps.tsx b/src/resources/metadata/dapps.tsx index 0b7832d2fde..feeee95dc99 100644 --- a/src/resources/metadata/dapps.tsx +++ b/src/resources/metadata/dapps.tsx @@ -64,7 +64,7 @@ export function useDapps(config?: UseQueryOptions): { dapps: Dapp[] } { }, })); } catch (e: any) { - logger.error(new RainbowError('Failed to fetch dApps'), { message: e.message }); + logger.error(new RainbowError('[dapps]: Failed to fetch dApps'), { message: e.message }); return []; } }, diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index c2f78124ffc..c7c92601830 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,14 +1,14 @@ -import { UniqueAsset } from '@/entities'; -import { arcClient } from '@/graphql'; -import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; -import { Network } from '@/helpers'; +import { QueryFunction, useQuery } from '@tanstack/react-query'; import { QueryConfigWithSelect, createQueryKey, queryClient } from '@/react-query'; -import { AppState } from '@/redux/store'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; -import { QueryFunction, useQuery } from '@tanstack/react-query'; import { useSelector } from 'react-redux'; +import { AppState } from '@/redux/store'; +import { UniqueAsset } from '@/entities'; +import { arcClient } from '@/graphql'; +import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; import { createSelector } from 'reselect'; +import { ChainId } from '@/networks/types'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour @@ -31,12 +31,12 @@ export const invalidateAddressNftsQueries = (address: string) => { export const nftListingQueryKey = ({ contractAddress, tokenId, - network, + chainId, }: { contractAddress: string; tokenId: string; - network: Omit; -}) => createQueryKey('nftListing', { contractAddress, tokenId, network }); + chainId: Omit; +}) => createQueryKey('nftListing', { contractAddress, tokenId, chainId }); const walletsSelector = (state: AppState) => state.wallets?.wallets; @@ -48,7 +48,7 @@ const isImportedWalletSelector = createSelector( return false; } for (const wallet of Object.values(wallets)) { - if (wallet.addresses.some(account => account.address === address)) { + if ((wallet.addresses || []).some(account => account.address === address)) { return true; } } @@ -117,17 +117,17 @@ export function useLegacyNFTs({ export function useNFTListing({ contractAddress, tokenId, - network, + chainId, }: { contractAddress: string; tokenId: string; - network: Omit; + chainId: Omit; }) { return useQuery( - nftListingQueryKey({ contractAddress, tokenId, network }), - async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, network)) ?? null, + nftListingQueryKey({ contractAddress, tokenId, chainId }), + async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, chainId)) ?? null, { - enabled: !!network && !!contractAddress && !!tokenId, + enabled: !!chainId && !!contractAddress && !!tokenId, staleTime: 0, cacheTime: 0, } diff --git a/src/resources/nfts/simplehash/index.ts b/src/resources/nfts/simplehash/index.ts index 4e39000052b..5d716846c47 100644 --- a/src/resources/nfts/simplehash/index.ts +++ b/src/resources/nfts/simplehash/index.ts @@ -1,11 +1,11 @@ import { NFT_API_KEY, NFT_API_URL } from 'react-native-dotenv'; import { RainbowFetchClient } from '@/rainbow-fetch'; -import { Network } from '@/helpers'; import { SimpleHashListing, SimpleHashNFT, SimpleHashMarketplaceId } from '@/resources/nfts/simplehash/types'; -import { getNetworkObj } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { UniqueAsset } from '@/entities'; import { RainbowError, logger } from '@/logger'; import { getGnosisNetworkObject } from '@/networks/gnosis'; +import { ChainId } from '@/networks/types'; export const START_CURSOR = 'start'; @@ -18,16 +18,16 @@ const createCursorSuffix = (cursor: string) => (cursor === START_CURSOR ? '' : ` export async function fetchSimpleHashNFT( contractAddress: string, tokenId: string, - network: Omit = Network.mainnet + chainId: Omit = ChainId.mainnet ): Promise { - const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; + const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; - if (!chain) { - logger.error(new RainbowError(`fetchSimpleHashNFT: no SimpleHash chain for network: ${network}`)); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); return; } - const response = await nftApi.get(`/nfts/${chain}/${contractAddress}/${tokenId}`, { + const response = await nftApi.get(`/nfts/${simplehashNetwork}/${contractAddress}/${tokenId}`, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', @@ -40,15 +40,15 @@ export async function fetchSimpleHashNFT( export async function fetchSimpleHashNFTListing( contractAddress: string, tokenId: string, - network: Omit = Network.mainnet + chainId: Omit = ChainId.mainnet ): Promise { // array of all eth listings on OpenSea for this token let listings: SimpleHashListing[] = []; let cursor = START_CURSOR; - const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; + const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; - if (!chain) { - logger.error(new RainbowError(`fetchSimpleHashNFTListing: no SimpleHash chain for network: ${network}`)); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); return; } @@ -57,7 +57,7 @@ export async function fetchSimpleHashNFTListing( // eslint-disable-next-line no-await-in-loop const response = await nftApi.get( // OpenSea ETH offers only for now - `/nfts/listings/${chain}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, + `/nfts/listings/${simplehashNetwork}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, { headers: { 'Accept': 'application/json', @@ -84,16 +84,16 @@ export async function fetchSimpleHashNFTListing( * @param nft */ export async function refreshNFTContractMetadata(nft: UniqueAsset) { - const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; + const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; - if (!chain) { - logger.error(new RainbowError(`refreshNFTContractMetadata: no SimpleHash chain for network: ${nft.network}`)); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); return; } try { await nftApi.post( - `/nfts/refresh/${chain}/${nft.asset_contract.address}`, + `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}`, {}, { headers: { @@ -105,13 +105,13 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { ); } catch { logger.warn( - `refreshNFTContractMetadata: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` + `[simplehash]: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` ); try { // If the collection has > 20k NFTs, the above request will fail. // In that case, we need to refresh the given NFT individually. await nftApi.post( - `/nfts/refresh/${chain}/${nft.asset_contract.address}/${nft.id}`, + `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}/${nft.id}`, {}, { headers: { @@ -124,7 +124,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { } catch { logger.error( new RainbowError( - `refreshNFTContractMetadata: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` + `[simplehash]: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` ) ); } @@ -136,10 +136,10 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { * @param nft */ export async function reportNFT(nft: UniqueAsset) { - const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; + const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; - if (!chain) { - logger.error(new RainbowError(`reportNFT: no SimpleHash chain for network: ${nft.network}`)); + if (!simplehashNetwork) { + logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); return; } @@ -148,7 +148,7 @@ export async function reportNFT(nft: UniqueAsset) { '/nfts/report/spam', { contract_address: nft.asset_contract.address, - chain_id: chain, + chain_id: simplehashNetwork, token_id: nft.id, event_type: 'mark_as_spam', }, @@ -161,6 +161,6 @@ export async function reportNFT(nft: UniqueAsset) { } ); } catch { - logger.error(new RainbowError(`reportNFT: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); + logger.error(new RainbowError(`[simplehash]: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); } } diff --git a/src/resources/nfts/simplehash/types.ts b/src/resources/nfts/simplehash/types.ts index a26cddcf86b..7cc2c802cbe 100644 --- a/src/resources/nfts/simplehash/types.ts +++ b/src/resources/nfts/simplehash/types.ts @@ -1,4 +1,4 @@ -import { Network } from '@/helpers'; +import { Network } from '@/networks/types'; /** * @see https://docs.simplehash.com/reference/sale-model diff --git a/src/resources/nfts/simplehash/utils.ts b/src/resources/nfts/simplehash/utils.ts index 68929492ca2..8cf2c3e8ac6 100644 --- a/src/resources/nfts/simplehash/utils.ts +++ b/src/resources/nfts/simplehash/utils.ts @@ -20,7 +20,7 @@ import { deviceUtils } from '@/utils'; import { TokenStandard } from '@/handlers/web3'; import { handleNFTImages } from '@/utils/handleNFTImages'; import { SimpleHashNft } from '@/graphql/__generated__/arc'; -import { Network } from '@/helpers'; +import { Network, chainNameToIdMapping } from '@/networks/types'; const ENS_COLLECTION_NAME = 'ENS'; const SVG_MIME_TYPE = 'image/svg+xml'; @@ -78,6 +78,7 @@ export function simpleHashNFTToUniqueAsset(nft: SimpleHashNft, address: string): slug: marketplace?.marketplace_collection_id ?? '', twitter_username: collection.twitter_username, }, + chainId: chainNameToIdMapping[nft.chain as keyof typeof chainNameToIdMapping], description: nft.description, external_link: nft.external_url, familyImage: collection.image_url, diff --git a/src/resources/nfts/types.ts b/src/resources/nfts/types.ts index aafbe3f300a..5f92ca09560 100644 --- a/src/resources/nfts/types.ts +++ b/src/resources/nfts/types.ts @@ -1,5 +1,5 @@ -import { Network } from '@/helpers/networkTypes'; import { Asset, AssetContract, AssetType } from '@/entities'; +import { Network } from '@/networks/types'; import { UniqueTokenType } from '@/utils/uniqueTokens'; export enum NFTMarketplaceId { diff --git a/src/resources/nfts/utils.ts b/src/resources/nfts/utils.ts index 8934f01eddc..587bd19d742 100644 --- a/src/resources/nfts/utils.ts +++ b/src/resources/nfts/utils.ts @@ -3,9 +3,9 @@ import { gretch } from 'gretchen'; import { paths } from '@reservoir0x/reservoir-sdk'; import { RainbowError, logger } from '@/logger'; import { handleSignificantDecimals } from '@/helpers/utilities'; -import { Network } from '@/helpers'; import { IS_PROD } from '@/env'; import { RESERVOIR_API_KEY_DEV, RESERVOIR_API_KEY_PROD } from 'react-native-dotenv'; +import { Network } from '@/networks/types'; const SUPPORTED_NETWORKS = [Network.mainnet, Network.polygon, Network.bsc, Network.arbitrum, Network.optimism, Network.base, Network.zora]; @@ -42,7 +42,7 @@ export async function fetchReservoirNFTFloorPrice(nft: UniqueAsset): Promise { Alert.alert( @@ -17,14 +16,14 @@ const showAlert = () => { { cancelable: false } ); }; -export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, network: Network) => { - logger.debug('Mints: Navigating to Mint Collection', { + +export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, chainId: ChainId) => { + logger.debug('[mints]: Navigating to Mint Collection', { contractAddress, - network, + chainId, }); try { - const chainId = getNetworkObj(network).id; const res = await arcClient.getReservoirCollection({ contractAddress, chainId, @@ -35,13 +34,13 @@ export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint, }); } else { - logger.warn('Mints: No collection found', { contractAddress, network }); + logger.warn('[mints]: No collection found', { contractAddress, chainId }); showAlert(); } } catch (e) { - logger.warn('Mints: navigateToMintCollection error', { + logger.warn(`[mints]: navigateToMintCollection error`, { contractAddress, - network, + chainId, error: e, }); showAlert(); diff --git a/src/resources/reservoir/utils.ts b/src/resources/reservoir/utils.ts index ec4dcf2140c..4e027b18c67 100644 --- a/src/resources/reservoir/utils.ts +++ b/src/resources/reservoir/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const RAINBOW_FEE_ADDRESS_MAINNET = '0x69d6d375de8c7ade7e44446df97f49e661fdad7d'; const RAINBOW_FEE_ADDRESS_POLYGON = '0xfb9af3db5e19c4165f413f53fe3bbe6226834548'; diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts index b1324ca1e4b..2d01ef35f24 100644 --- a/src/resources/transactions/consolidatedTransactions.ts +++ b/src/resources/transactions/consolidatedTransactions.ts @@ -5,7 +5,7 @@ import { TransactionApiResponse, TransactionsReceivedMessage } from './types'; import { RainbowError, logger } from '@/logger'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { parseTransaction } from '@/parsers/transactions'; import { ethereumUtils } from '@/utils'; @@ -83,7 +83,7 @@ export async function consolidatedTransactionsQueryFunction({ transactions: consolidatedTransactions, }; } catch (e) { - logger.error(new RainbowError('consolidatedTransactionsQueryFunction: '), { + logger.error(new RainbowError('[consolidatedTransactions]: '), { message: e, }); return { transactions: [] }; @@ -125,7 +125,7 @@ export function useConsolidatedTransactions( { address, currency }: Pick, config: InfiniteQueryConfig = {} ) { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); return useInfiniteQuery( consolidatedTransactionsQueryKey({ diff --git a/src/resources/transactions/firstTransactionTimestampQuery.ts b/src/resources/transactions/firstTransactionTimestampQuery.ts index 033c295dad8..c1bbbc42888 100644 --- a/src/resources/transactions/firstTransactionTimestampQuery.ts +++ b/src/resources/transactions/firstTransactionTimestampQuery.ts @@ -1,5 +1,4 @@ import { useQuery } from '@tanstack/react-query'; -import PQueue from 'p-queue/dist'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { getFirstTransactionTimestamp } from '@/utils/ethereumUtils'; @@ -23,8 +22,6 @@ export type FirstTransactionTimestampQueryKey = ReturnType) { @@ -35,7 +32,10 @@ export async function firstTransactionTimestampQueryFunction({ address = (await fetchENSAddress({ name: addressOrName })) ?? ''; } - const timestamp = address ? await queue.add(async () => getFirstTransactionTimestamp(address)) : null; + let timestamp; + if (address) { + timestamp = await getFirstTransactionTimestamp(address); + } return timestamp ?? null; } diff --git a/src/resources/transactions/transaction.ts b/src/resources/transactions/transaction.ts index b4210f302bc..ded3b12f7a9 100644 --- a/src/resources/transactions/transaction.ts +++ b/src/resources/transactions/transaction.ts @@ -3,13 +3,13 @@ import { createQueryKey, queryClient, QueryFunctionArgs, QueryFunctionResult } f import { useQuery } from '@tanstack/react-query'; import { consolidatedTransactionsQueryFunction, consolidatedTransactionsQueryKey } from './consolidatedTransactions'; import { useAccountSettings } from '@/hooks'; -import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { parseTransaction } from '@/parsers/transactions'; -import { Network } from '@/networks/types'; import { RainbowError, logger } from '@/logger'; import { TransactionApiResponse } from './types'; +import { ChainId } from '@/networks/types'; export type ConsolidatedTransactionsResult = QueryFunctionResult; export type PaginatedTransactions = { pages: ConsolidatedTransactionsResult[] }; @@ -18,25 +18,24 @@ export type TransactionArgs = { hash: string; address: string; currency: NativeCurrencyKey; - network: Network; + chainId: ChainId; }; type TransactionQueryKey = ReturnType; export type BackendTransactionArgs = { hash: string; - network: Network; + chainId: ChainId; enabled: boolean; }; -export const transactionQueryKey = ({ hash, address, currency, network }: TransactionArgs) => - createQueryKey('transactions', { address, currency, network, hash }, { persisterVersion: 1 }); +export const transactionQueryKey = ({ hash, address, currency, chainId }: TransactionArgs) => + createQueryKey('transactions', { address, currency, chainId, hash }, { persisterVersion: 1 }); export const fetchTransaction = async ({ - queryKey: [{ address, currency, network, hash }], + queryKey: [{ address, currency, chainId, hash }], }: QueryFunctionArgs): Promise => { try { - const chainId = getNetworkObj(network).id; const url = `https://addys.p.rainbow.me/v3/${chainId}/${address}/transactions/${hash}`; const response = await rainbowFetch<{ payload: { transaction: TransactionApiResponse } }>(url, { method: 'get', @@ -57,7 +56,7 @@ export const fetchTransaction = async ({ if (!parsedTx) throw new Error('Failed to parse transaction'); return parsedTx; } catch (e) { - logger.error(new RainbowError('fetchTransaction: '), { + logger.error(new RainbowError('[transaction]: Failed to fetch transaction'), { message: (e as Error)?.message, }); return null; @@ -70,19 +69,19 @@ export const fetchTransaction = async ({ export const transactionFetchQuery = async ({ address, currency, - network, + chainId, hash, }: { address: string; currency: NativeCurrencyKey; - network: Network; + chainId: ChainId; hash: string; -}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, network, hash }), fetchTransaction); +}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, chainId, hash }), fetchTransaction); -export function useBackendTransaction({ hash, network }: BackendTransactionArgs) { +export function useBackendTransaction({ hash, chainId }: BackendTransactionArgs) { const { accountAddress, nativeCurrency } = useAccountSettings(); - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, @@ -94,11 +93,11 @@ export function useBackendTransaction({ hash, network }: BackendTransactionArgs) hash: hash, address: accountAddress, currency: nativeCurrency, - network: network, + chainId: chainId, }; return useQuery(transactionQueryKey(params), fetchTransaction, { - enabled: !!hash && !!accountAddress && !!network, + enabled: !!hash && !!accountAddress && !!chainId, initialData: () => { const queryData = queryClient.getQueryData(paginatedTransactionsKey); const pages = queryData?.pages || []; @@ -114,15 +113,15 @@ export function useBackendTransaction({ hash, network }: BackendTransactionArgs) }); } -export const useTransaction = ({ network, hash }: { network: Network; hash: string }) => { +export const useTransaction = ({ chainId, hash }: { chainId: ChainId; hash: string }) => { const { data: backendTransaction, isLoading: backendTransactionIsLoading, isFetched: backendTransactionIsFetched, } = useBackendTransaction({ hash, - network, - enabled: !!hash && !!network, + chainId, + enabled: !!hash && !!chainId, }); return { diff --git a/src/screens/AddCash/components/ProviderCard.tsx b/src/screens/AddCash/components/ProviderCard.tsx index cbe8ffcb9a1..9dae223ca5f 100644 --- a/src/screens/AddCash/components/ProviderCard.tsx +++ b/src/screens/AddCash/components/ProviderCard.tsx @@ -5,20 +5,18 @@ import chroma from 'chroma-js'; import { IS_IOS } from '@/env'; import { Box, Text, Inline, Bleed, useBackgroundColor } from '@/design-system'; -import { Network } from '@/helpers/networkTypes'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { Ramp as RampLogo } from '@/components/icons/svg/Ramp'; -import { Ratio as RatioLogo } from '@/components/icons/svg/Ratio'; import { Coinbase as CoinbaseLogo } from '@/components/icons/svg/Coinbase'; import { Moonpay as MoonpayLogo } from '@/components/icons/svg/Moonpay'; import { FiatProviderName } from '@/entities/f2c'; -import { convertAPINetworkToInternalNetwork } from '@/screens/AddCash/utils'; +import { convertAPINetworkToInternalChainIds } from '@/screens/AddCash/utils'; import { ProviderConfig, CalloutType, PaymentMethod } from '@/screens/AddCash/types'; import * as i18n from '@/languages'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ethereumUtils } from '@/utils'; +import { ChainId } from '@/networks/types'; type PaymentMethodConfig = { name: string; @@ -81,26 +79,22 @@ function getPaymentMethodConfigs(paymentMethods: { type: PaymentMethod }[]) { return methods; } -function NetworkIcons({ networks }: { networks: Network[] }) { +function NetworkIcons({ chainIds }: { chainIds?: ChainId[] }) { return ( - {networks.map((network, index) => { + {chainIds?.map((chainId, index) => { return ( 0 ? -6 : 0 }} style={{ position: 'relative', - zIndex: networks.length - index, + zIndex: chainIds.length - index, borderRadius: 30, }} > - {network !== Network.mainnet ? ( - - ) : ( - - )} + {chainId !== ChainId.mainnet ? : } ); })} @@ -233,7 +227,7 @@ export function ProviderCard({ config }: { config: ProviderConfig }) { ); diff --git a/src/screens/AddCash/index.tsx b/src/screens/AddCash/index.tsx index bba373b1960..5d8da30e7b0 100644 --- a/src/screens/AddCash/index.tsx +++ b/src/screens/AddCash/index.tsx @@ -53,7 +53,7 @@ export function AddCashSheet() { const [{ data, error }] = await wait(1000, [await getProviders()]); if (!data || error) { - const e = new RainbowError('F2C: failed to fetch providers'); + const e = new RainbowError('[AddCash]: failed to fetch providers'); logger.error(e); diff --git a/src/screens/AddCash/providers/Coinbase/index.tsx b/src/screens/AddCash/providers/Coinbase/index.tsx index 0bf8b26c88d..ef82205ad2c 100644 --- a/src/screens/AddCash/providers/Coinbase/index.tsx +++ b/src/screens/AddCash/providers/Coinbase/index.tsx @@ -34,13 +34,13 @@ export function Coinbase({ accountAddress, config }: { accountAddress: string; c sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Coinbase, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Coinbase, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Moonpay/index.tsx b/src/screens/AddCash/providers/Moonpay/index.tsx index cc45c5306c9..64444471b4e 100644 --- a/src/screens/AddCash/providers/Moonpay/index.tsx +++ b/src/screens/AddCash/providers/Moonpay/index.tsx @@ -35,13 +35,13 @@ export function Moonpay({ accountAddress, config }: { accountAddress: string; co sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Moonpay, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Moonpay, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Ramp/index.tsx b/src/screens/AddCash/providers/Ramp/index.tsx index 679cfd3e02b..8b956a11dd4 100644 --- a/src/screens/AddCash/providers/Ramp/index.tsx +++ b/src/screens/AddCash/providers/Ramp/index.tsx @@ -35,13 +35,13 @@ export function Ramp({ accountAddress, config }: { accountAddress: string; confi sessionId, }); - logger.info('F2C: opening provider', { + logger.debug('[AddCash]: opening provider', { provider: FiatProviderName.Ramp, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('F2C: failed to open provider'), { + logger.error(new RainbowError('[AddCash]: failed to open provider'), { provider: FiatProviderName.Ramp, message: (e as Error).message, }); diff --git a/src/screens/AddCash/utils.ts b/src/screens/AddCash/utils.ts index cedf027759e..aa4508030c3 100644 --- a/src/screens/AddCash/utils.ts +++ b/src/screens/AddCash/utils.ts @@ -1,14 +1,14 @@ -import { Network } from '@/helpers/networkTypes'; +import { ChainId } from '@/networks/types'; import { Network as APINetwork } from '@/screens/AddCash/types'; -export function convertAPINetworkToInternalNetwork(network: APINetwork): Network | undefined { +export function convertAPINetworkToInternalChainIds(network: APINetwork): ChainId | undefined { const networkMap = { - [APINetwork.Ethereum]: Network.mainnet, - [APINetwork.Arbitrum]: Network.arbitrum, - [APINetwork.Optimism]: Network.optimism, - [APINetwork.Polygon]: Network.polygon, - [APINetwork.Base]: Network.base, - [APINetwork.BSC]: Network.bsc, + [APINetwork.Ethereum]: ChainId.mainnet, + [APINetwork.Arbitrum]: ChainId.arbitrum, + [APINetwork.Optimism]: ChainId.optimism, + [APINetwork.Polygon]: ChainId.polygon, + [APINetwork.Base]: ChainId.base, + [APINetwork.BSC]: ChainId.bsc, }; // @ts-ignore diff --git a/src/screens/AddWalletSheet.tsx b/src/screens/AddWalletSheet.tsx index dbad7feed37..67c13b27d42 100644 --- a/src/screens/AddWalletSheet.tsx +++ b/src/screens/AddWalletSheet.tsx @@ -123,10 +123,9 @@ export const AddWalletSheet = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Updating wallet userdata failed after new account creation', + logger.error(new RainbowError('[AddWalletSheet]: Updating wallet userdata failed after new account creation'), { + error: e, }); - captureException(e); throw e; } } @@ -143,10 +142,9 @@ export const AddWalletSheet = () => { await initializeWallet(); } } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { + error: e, }); - captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -165,8 +163,8 @@ export const AddWalletSheet = () => { }, 50); }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { + error: e, }); } }; @@ -213,7 +211,9 @@ export const AddWalletSheet = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError('[AddWalletSheet]: Error while trying to restore from cloud'), { + error: e, + }); } } else { const isAvailable = await isCloudBackupAvailable(); diff --git a/src/screens/ChangeWalletSheet.tsx b/src/screens/ChangeWalletSheet.tsx index f345079b3e3..82918ffdcd7 100644 --- a/src/screens/ChangeWalletSheet.tsx +++ b/src/screens/ChangeWalletSheet.tsx @@ -20,7 +20,7 @@ import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBala import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { doesWalletsContainAddress, showActionSheetWithOptions } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { useTheme } from '@/theme'; import { EthereumAddress } from '@/entities'; import { getNotificationSettingsForWalletWithAddress } from '@/notifications/settings/storage'; @@ -149,7 +149,9 @@ export default function ChangeWalletSheet() { setTimeout(runChecks, 10_000); } } catch (e) { - logger.log('error while switching account', e); + logger.error(new RainbowError('[ChangeWalletSheet]: Error while switching account'), { + error: e, + }); } }, [currentAddress, dispatch, editMode, goBack, initializeWallet, onChangeWallet, runChecks, wallets, watchOnly] @@ -165,14 +167,14 @@ export default function ChangeWalletSheet() { ...wallets, [walletId]: { ...currentWallet, - addresses: (currentWallet.addresses ?? []).map(account => + addresses: (currentWallet.addresses || []).map(account => account.address.toLowerCase() === address.toLowerCase() ? { ...account, visible: false } : account ), }, }; // If there are no visible wallets // then delete the wallet - const visibleAddresses = (newWallets as any)[walletId].addresses.filter((account: any) => account.visible); + const visibleAddresses = ((newWallets as any)[walletId]?.addresses || []).filter((account: any) => account.visible); if (visibleAddresses.length === 0) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete newWallets[walletId]; @@ -189,7 +191,7 @@ export default function ChangeWalletSheet() { (walletId: string, address: string) => { const wallet = wallets?.[walletId]; if (!wallet) return; - const account = wallet.addresses.find(account => account.address === address); + const account = wallet.addresses?.find(account => account.address === address); InteractionManager.runAfterInteractions(() => { goBack(); diff --git a/src/screens/CheckIdentifierScreen.tsx b/src/screens/CheckIdentifierScreen.tsx index f4b19d9aa62..060d4831031 100644 --- a/src/screens/CheckIdentifierScreen.tsx +++ b/src/screens/CheckIdentifierScreen.tsx @@ -60,14 +60,14 @@ export default function CheckIdentifierScreen() { const allKeys = await kc.getAllKeys(); if (!allKeys?.length) { - logger.error(new RainbowError('Unable to retrieve keychain values')); + logger.error(new RainbowError('[CheckIdentifierScreen]: Unable to retrieve keychain values')); ErrorAlert(); return; } const allAccountKeys = allKeys.filter(item => item.username.includes('_rainbowPrivateKey')); if (!allAccountKeys?.length) { - logger.error(new RainbowError('No private keys found in keychain')); + logger.error(new RainbowError('[CheckIdentifierScreen]: No private keys found in keychain')); return onFailure(); } @@ -81,7 +81,7 @@ export default function CheckIdentifierScreen() { }); if (hasAccountWithoutPrivateKey) { - logger.error(new RainbowError('Detected account without matching private key')); + logger.error(new RainbowError('[CheckIdentifierScreen]: Detected account without matching private key')); return onFailure(); } diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx index 25abf58d032..9d141f2b049 100644 --- a/src/screens/CurrencySelectModal.tsx +++ b/src/screens/CurrencySelectModal.tsx @@ -15,7 +15,7 @@ import { Modal } from '../components/modal'; import { STORAGE_IDS } from '../model/mmkv'; import { analytics } from '@/analytics'; import { addHexPrefix, isL2Chain } from '@/handlers/web3'; -import { CurrencySelectionTypes, Network, TokenSectionTypes } from '@/helpers'; +import { CurrencySelectionTypes, TokenSectionTypes } from '@/helpers'; import { useAccountSettings, useInteraction, @@ -40,7 +40,7 @@ import DiscoverSearchInput from '@/components/discover/DiscoverSearchInput'; import { externalTokenQueryKey, fetchExternalToken } from '@/resources/assets/externalAssetsQuery'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { queryClient } from '@/react-query/queryClient'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; export interface EnrichedExchangeAsset extends SwappableAsset { ens: boolean; @@ -151,15 +151,11 @@ export default function CurrencySelectModal() { (newAsset: any, selectAsset: any, type: any) => { const otherAsset = type === 'input' ? outputCurrency : inputCurrency; const hasShownWarning = getHasShownWarning(); - if ( - otherAsset && - ethereumUtils.getChainIdFromNetwork(newAsset?.network) !== ethereumUtils.getChainIdFromNetwork(otherAsset?.network) && - !hasShownWarning - ) { + if (otherAsset && newAsset?.chainId !== otherAsset?.chainId && !hasShownWarning) { Keyboard.dismiss(); InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { - network: newAsset?.network, + chainId: newAsset?.chainId, onClose: () => { setHasShownWarning(); selectAsset(); @@ -213,6 +209,7 @@ export default function CurrencySelectModal() { name: 'Unswappable', symbol: 'UNSWAP', network: Network.mainnet, + chainId: ChainId.mainnet, id: 'foobar', uniqueId: '0x123', }); @@ -293,14 +290,14 @@ export default function CurrencySelectModal() { screen: Routes.MAIN_EXCHANGE_SCREEN, }); setSearchQuery(''); - setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); + setCurrentChainId(item.chainId); }, android ? 500 : 0 ); } else { navigate(Routes.MAIN_EXCHANGE_SCREEN); setSearchQuery(''); - setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); + setCurrentChainId(item.chainId); } if (searchQueryForSearch) { analytics.track('Selected a search result in Swap', { @@ -326,8 +323,7 @@ export default function CurrencySelectModal() { InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { assetName: item?.symbol, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - networkName: currentL2Name, + chainId: currentChainId, onClose: linkToHop, type: 'obtainL2Assets', }); @@ -430,11 +426,10 @@ export default function CurrencySelectModal() { const handleBackButton = useCallback(() => { setSearchQuery(''); InteractionManager.runAfterInteractions(() => { - const inputChainId = ethereumUtils.getChainIdFromNetwork(inputCurrency?.network); - setCurrentChainId(inputChainId); + setCurrentChainId(inputCurrency?.chainId); }); setIsTransitioning(true); // continue to display list while transitiong back - }, [inputCurrency?.network]); + }, [inputCurrency?.chainId]); useEffect(() => { // check if list has items before attempting to scroll diff --git a/src/screens/Diagnostics/DiagnosticsItemRow.tsx b/src/screens/Diagnostics/DiagnosticsItemRow.tsx index e109d7a7163..4253c1d8eb4 100644 --- a/src/screens/Diagnostics/DiagnosticsItemRow.tsx +++ b/src/screens/Diagnostics/DiagnosticsItemRow.tsx @@ -28,7 +28,7 @@ export const DiagnosticsItemRow = ({ data }: any) => { // @ts-expect-error poorly typed function await handlePressImportButton(null, data.secret); } catch (error) { - logger.error(new RainbowError('Error restoring from wallet diagnostics'), { + logger.error(new RainbowError('[DiagnosticsItemRow]: Error restoring from wallet diagnostics'), { message: (error as Error).message, context: 'restore', }); diff --git a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts index 60abfb63ea0..fec38fd194b 100644 --- a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts +++ b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts @@ -72,6 +72,6 @@ export async function createAndShareStateDumpFile() { // clean up the file since we don't need it anymore await RNFS.unlink(documentsFilePath); } catch (error) { - logger.error(new RainbowError('Saving app state dump data failed')); + logger.error(new RainbowError('[createAndShareStateDumpFile]: Saving app state dump data failed')); } } diff --git a/src/screens/Diagnostics/index.tsx b/src/screens/Diagnostics/index.tsx index cdbbf4bd044..b3fe96aa2f3 100644 --- a/src/screens/Diagnostics/index.tsx +++ b/src/screens/Diagnostics/index.tsx @@ -102,7 +102,7 @@ export const WalletDiagnosticsSheet = () => { setKeys(processedKeys); } } catch (error) { - logger.error(new RainbowError('Error processing keys for wallet diagnostics'), { + logger.error(new RainbowError('[WalletDiagnosticsSheet]: Error processing keys for wallet diagnostics'), { message: (error as Error).message, context: 'init', }); diff --git a/src/screens/ENSConfirmRegisterSheet.tsx b/src/screens/ENSConfirmRegisterSheet.tsx index beb2f56167c..82daf89ba8e 100644 --- a/src/screens/ENSConfirmRegisterSheet.tsx +++ b/src/screens/ENSConfirmRegisterSheet.tsx @@ -40,7 +40,7 @@ import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDomina import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { ActionTypes } from '@/hooks/useENSRegistrationActionHandler'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const ENSConfirmRegisterSheetHeight = 600; export const ENSConfirmRenewSheetHeight = 560; diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 7398cd95bb7..86441c24004 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -27,7 +27,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { Box, Row, Rows } from '@/design-system'; import { GasFee, LegacyGasFee, LegacyGasFeeParams, SwappableAsset } from '@/entities'; -import { ExchangeModalTypes, isKeyboardOpen, Network } from '@/helpers'; +import { ExchangeModalTypes, isKeyboardOpen } from '@/helpers'; import { KeyboardType } from '@/helpers/keyboardTypes'; import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; import { delay, greaterThan } from '@/helpers/utilities'; @@ -54,7 +54,7 @@ import { ethUnits } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, gasUtils } from '@/utils'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; import store from '@/redux/store'; @@ -63,7 +63,6 @@ import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; import { Wallet } from '@ethersproject/wallet'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { useTheme } from '@/theme'; -import { logger as loggr } from '@/logger'; import { getNetworkObject } from '@/networks'; import Animated from 'react-native-reanimated'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -71,7 +70,7 @@ import { ReviewPromptAction } from '@/storage/schema'; import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; import { getNextNonce } from '@/state/nonces'; import { getChainName } from '@/__swaps__/utils/chains'; -import { ChainId, ChainName } from '@/__swaps__/types/chains'; +import { ChainId, ChainName } from '@/networks/types'; import { AddressOrEth, ParsedAsset } from '@/__swaps__/types/assets'; import { TokenColors } from '@/graphql/__generated__/metadata'; import { estimateSwapGasLimit } from '@/raps/actions'; @@ -83,7 +82,7 @@ export const DEFAULT_SLIPPAGE_BIPS = { [ChainId.polygon]: 200, [ChainId.base]: 200, [ChainId.bsc]: 200, - [Network.optimism]: 200, + [ChainId.optimism]: 200, [ChainId.arbitrum]: 200, [ChainId.goerli]: 100, [ChainId.gnosis]: 200, @@ -146,15 +145,14 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty updateDefaultGasLimit, updateGasFeeOption, updateTxFee, - txNetwork, - + chainId, isGasReady, } = useGas(); const { accountAddress, flashbotsEnabled, nativeCurrency } = useAccountSettings(); const [isAuthorizing, setIsAuthorizing] = useState(false); const prevGasFeesParamsBySpeed = usePrevious(gasFeeParamsBySpeed); - const prevChainId = usePrevious(ethereumUtils.getChainIdFromNetwork(txNetwork)); + const prevChainId = usePrevious(chainId); const keyboardListenerSubscription = useRef(); @@ -223,7 +221,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty if (currentChainId !== prevChainId) { speedUrgentSelected.current = false; } - }, [currentChainId, prevChainId, txNetwork]); + }, [currentChainId, prevChainId]); const defaultGasLimit = useMemo(() => { return ethereumUtils.getBasicSwapGasLimit(Number(currentChainId)); @@ -348,14 +346,14 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty ) { updateGasLimit(); } - }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, txNetwork, updateGasLimit]); + }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, updateGasLimit]); // Listen to gas prices, Uniswap reserves updates useEffect(() => { updateDefaultGasLimit(defaultGasLimit); InteractionManager.runAfterInteractions(() => { // Start polling in the current network - startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId), flashbots); + startPollingGasFees(currentChainId, flashbots); }); return () => { stopPollingGasFees(); @@ -407,7 +405,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty }); if (!wallet) { setIsAuthorizing(false); - logger.sentry(`aborting ${type} due to missing wallet`); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing wallet`)); Alert.alert('Unable to determine wallet address'); return false; } @@ -415,25 +413,26 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Switch to the flashbots provider if enabled // TODO(skylarbarrera): need to check if ledger and handle differently here if (flashbots && getNetworkObject({ chainId: currentChainId }).features?.flashbots && wallet instanceof Wallet) { - logger.debug('flashbots provider being set on mainnet'); + logger.debug('[ExchangeModal]: flashbots provider being set on mainnet'); const flashbotsProvider = await getFlashbotsProvider(); wallet = new Wallet(wallet.privateKey, flashbotsProvider); } if (!inputAmount || !outputAmount) { - logger.log('[exchange - handle submit] inputAmount or outputAmount is missing'); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing inputAmount or outputAmount`)); Alert.alert('Input amount or output amount is missing'); return false; } if (!tradeDetails) { - logger.log('[exchange - handle submit] tradeDetails is missing'); + logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing tradeDetails`)); Alert.alert('Missing trade details for swap'); return false; } - logger.log('[exchange - handle submit] rap'); - const currentNonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + logger.debug(`[ExchangeModal]: getting nonce for account ${accountAddress}`); + const currentNonce = await getNextNonce({ address: accountAddress, chainId: currentChainId }); + logger.debug(`[ExchangeModal]: nonce for account ${accountAddress} is ${currentNonce}`); const { independentField, independentValue, slippageInBips, source } = store.getState().swap; const transformedAssetToSell = { @@ -496,9 +495,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setIsAuthorizing(false); // if the transaction was not successful, we need to bubble that up to the caller if (errorMessage) { - loggr.debug('[ExchangeModal] transaction was not successful', { - errorMessage, - }); + logger.error(new RainbowError(`[ExchangeModal]: transaction was not successful: ${errorMessage}`)); if (wallet instanceof Wallet) { Alert.alert(errorMessage); } else { @@ -507,7 +504,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return false; } - logger.log('[exchange - handle submit] executed rap!'); + logger.debug('[ExchangeModal]: executed rap!'); const slippage = slippageInBips / 100; analytics.track(`Completed ${type}`, { aggregator: tradeDetails?.source || '', @@ -547,7 +544,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return true; } catch (error) { setIsAuthorizing(false); - logger.log('[exchange - handle submit] error submitting swap', error); + logger.error(new RainbowError(`[ExchangeModal]: error submitting swap: ${error}`)); setParams({ focused: false }); // close the hardware wallet modal before navigating if (isHardwareWallet) { @@ -588,7 +585,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Tell iOS we're running a rap (for tracking purposes) NotificationManager?.postNotification('rapInProgress'); } catch (e) { - logger.log('error getting the swap amount in USD price', e); + logger.error(new RainbowError(`[ExchangeModal]: error posting notification for rapInProgress: ${e}`)); } finally { const slippage = slippageInBips / 100; analytics.track(`Submitted ${type}`, { @@ -651,7 +648,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty const confirmButtonProps = useMemoOne( () => ({ - currentNetwork: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, disabled: !Number(inputAmount) || (!loading && !tradeDetails), inputAmount, isAuthorizing, @@ -693,7 +690,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setParams({ focused: false }); navigate(Routes.SWAP_SETTINGS_SHEET, { asset: outputCurrency, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, restoreFocusOnSwapModal: () => { android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); setParams({ focused: true }); @@ -783,8 +780,8 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty lastFocusedInput?.blur(); navigate(Routes.EXPLAIN_SHEET, { inputToken: inputCurrency?.symbol, - fromNetwork: ethereumUtils.getNetworkFromChainId(inputChainId), - toNetwork: ethereumUtils.getNetworkFromChainId(outputChainId), + fromChainId: inputChainId, + toChainId: outputChainId, isCrosschainSwap, isBridgeSwap, onClose: () => { diff --git a/src/screens/ExplainSheet.js b/src/screens/ExplainSheet.js index d5ce85ef357..f92f02ea5a9 100644 --- a/src/screens/ExplainSheet.js +++ b/src/screens/ExplainSheet.js @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; @@ -10,21 +11,19 @@ import { Emoji, GradientText, Text } from '../components/text'; import { useNavigation } from '../navigation/Navigation'; import { DoubleChevron } from '@/components/icons'; import { Box } from '@/design-system'; -import networkTypes from '@/helpers/networkTypes'; import { useDimensions } from '@/hooks'; import styled from '@/styled-thing'; import { fonts, fontWithWidth, padding, position } from '@/styles'; -import { ethereumUtils, gasUtils, getTokenMetadata } from '@/utils'; +import { ethereumUtils, gasUtils } from '@/utils'; import { buildRainbowLearnUrl } from '@/utils/buildRainbowUrl'; import { cloudPlatformAccountName } from '@/utils/platform'; import { useTheme } from '@/theme'; import { isL2Chain } from '@/handlers/web3'; import { IS_ANDROID } from '@/env'; import * as i18n from '@/languages'; -import { getNetworkObj } from '@/networks'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const { GAS_TRENDS } = gasUtils; export const ExplainSheetHeight = android ? 454 : 434; @@ -78,8 +77,8 @@ const FLOOR_PRICE_EXPLAINER = lang.t('explain.floor_price.text'); const gasExplainer = network => lang.t('explain.gas.text', { networkName: network }); -const availableNetworksExplainer = (tokenSymbol, networks) => { - const readableNetworks = networks?.map(network => getNetworkObj(network).name)?.join(', '); +const availableNetworksExplainer = (tokenSymbol, chainIds) => { + const readableNetworks = chainIds?.map(chainId => chainIdToNameMapping[chainId])?.join(', '); return lang.t('explain.available_networks.text', { tokenSymbol: tokenSymbol, @@ -158,8 +157,11 @@ const ENS_CONFIGURATION_EXPLAINER = export const explainers = (params, theme) => { const colors = theme?.colors; - const fromNetworkObject = getNetworkObj(params?.fromNetwork); - const toNetworkObject = getNetworkObj(params?.toNetwork); + const chainId = params?.chainId; + const network = ethereumUtils.getNetworkFromChainId(chainId); + const networkName = chainIdToNameMapping[chainId]; + const fromChainId = params?.fromChainId; + const toChainId = params?.toChainId; return { op_rewards_airdrop_timing: { emoji: '📦', @@ -210,7 +212,7 @@ export const explainers = (params, theme) => { title: params?.inputToken ? lang.t(`explain.output_disabled.${params?.isCrosschainSwap ? 'title_crosschain' : 'title'}`, { inputToken: params?.inputToken, - fromNetwork: fromNetworkObject.name, + fromNetwork: chainIdToNameMapping[fromChainId], }) : lang.t('explain.output_disabled.title_empty'), @@ -218,18 +220,18 @@ export const explainers = (params, theme) => { ? lang.t(`explain.output_disabled.${params?.isBridgeSwap ? 'text_bridge' : 'text_crosschain'}`, { inputToken: params?.inputToken, outputToken: params?.outputToken, - fromNetwork: fromNetworkObject.name, - toNetwork: toNetworkObject.name, + fromNetwork: chainIdToNameMapping[fromChainId], + toNetwork: chainIdToNameMapping[toChainId], }) : lang.t('explain.output_disabled.text', { - fromNetwork: fromNetworkObject?.name, + fromNetwork: chainIdToNameMapping[fromChainId]?.name, inputToken: params?.inputToken, outputToken: params?.outputToken, }), - logo: !isL2Chain({ chainId: fromNetworkObject.id }) ? ( + logo: !isL2Chain({ chainId: fromChainId }) ? ( ) : ( - + ), }, floor_price: { @@ -244,15 +246,15 @@ export const explainers = (params, theme) => { size={40} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.network)} + chainId={chainId} colors={params?.nativeAsset?.colors} theme={theme} /> ), extraHeight: 2, - text: gasExplainer(getNetworkObj(params?.network).name), + text: gasExplainer(chainIdToNameMapping[chainId]), title: lang.t('explain.gas.title', { - networkName: getNetworkObj(params?.network).name, + networkName: chainIdToNameMapping[chainId], }), }, ens_primary_name: { @@ -533,22 +535,17 @@ export const explainers = (params, theme) => { }, swapResetInputs: { button: { - label: `Continue with ${getNetworkObj(params?.network)?.name}`, - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.06), - textColor: colors?.networkColors[params?.network] && colors?.networkColors?.[params?.network], + label: `Continue with ${networkName}`, + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.06), + textColor: colors?.networkColors[chainId] && colors?.networkColors?.[network], }, emoji: '🔐', extraHeight: -90, text: SWAP_RESET_EXPLAINER, - title: `Switching to ${getNetworkObj(params?.network)?.name}`, + title: `Switching to ${networkName}`, logo: - params?.network !== 'mainnet' ? ( - + chainId !== ChainId.mainnet ? ( + ) : ( ), @@ -596,7 +593,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -652,7 +649,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -661,7 +658,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} + chainId={params?.outputCurrency?.chainId} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -678,7 +675,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} + chainId={params?.inputCurrency?.chainId} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -687,7 +684,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} + chainId={params?.outputCurrency?.chainId} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -697,24 +694,24 @@ export const explainers = (params, theme) => { availableNetworks: { buttonText: `Go to Hop`, extraHeight: -90, - text: availableNetworksExplainer(params?.tokenSymbol, params?.networks), + text: availableNetworksExplainer(params?.tokenSymbol, params?.chainIds), title: - params?.networks?.length > 1 + params?.chainIds?.length > 1 ? lang.t('explain.available_networks.title_plural', { - length: params?.networks?.length, + length: params?.chainIds?.length, }) : lang.t('explain.available_networks.title_singular', { - network: params?.networks?.[0], + network: params?.chainIds?.[0], }), logo: ( - {params?.networks?.map((network, index) => { + {params?.chainIds?.map((chainId, index) => { return ( 0 ? -12 : params?.networks?.length % 2 === 0 ? -2 : -30, + custom: index > 0 ? -12 : params?.chainIds?.length % 2 === 0 ? -2 : -30, }} style={{ borderColor: colors.transparent, @@ -723,10 +720,10 @@ export const explainers = (params, theme) => { zIndex: index, }} width={{ custom: 40 }} - zIndex={params?.networks?.length - index} + zIndex={params?.chainIds?.length - index} > - {network !== 'mainnet' ? ( - + {chainId !== ChainId.mainnet ? ( + ) : ( @@ -779,7 +776,7 @@ export const explainers = (params, theme) => { {lang.t('explain.obtain_l2_asset.fragment3')} ), - logo: , + logo: , title: lang.t('explain.obtain_l2_asset.title', { networkName: params?.networkName, }), @@ -867,19 +864,12 @@ export const explainers = (params, theme) => { }, swap_refuel_add: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[params?.network], - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), + textColor: colors?.networkColors[chainId], + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_deduct: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[params?.network], - bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), + textColor: colors?.networkColors[chainId], + bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_notice: { extraHeight: 50, logo: ( - + diff --git a/src/screens/MintsSheet/card/Card.tsx b/src/screens/MintsSheet/card/Card.tsx index 1870ce8454e..d9e30975c0a 100644 --- a/src/screens/MintsSheet/card/Card.tsx +++ b/src/screens/MintsSheet/card/Card.tsx @@ -3,8 +3,7 @@ import React, { useEffect, useState } from 'react'; import { getTimeElapsedFromDate } from '../utils'; import { Bleed, Box, Cover, Inline, Inset, Stack, Text, useForegroundColor } from '@/design-system'; import { abbreviateNumber, convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; -import { getNetworkObj } from '@/networks'; -import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { getNetworkObject } from '@/networks'; import { ButtonPressAnimation } from '@/components/animations'; import { Placeholder, RecentMintCell } from './RecentMintCell'; import { View } from 'react-native'; @@ -14,7 +13,7 @@ import * as i18n from '@/languages'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; export const NUM_NFTS = 3; @@ -29,11 +28,9 @@ export function Card({ collection }: { collection: MintableCollection }) { const separatorTertiary = useForegroundColor('separatorTertiary'); const price = convertRawAmountToRoundedDecimal(collection.mintStatus.price, 18, 6); - const currencySymbol = getNetworkObj(getNetworkFromChainId(collection.chainId)).nativeCurrency.symbol; + const currencySymbol = getNetworkObject({ chainId: collection.chainId }).nativeCurrency.symbol; const isFree = !price; - const network = getNetworkFromChainId(collection.chainId); - // update elapsed time every minute if it's less than an hour useEffect(() => { if (timeElapsed && timeElapsed[timeElapsed.length - 1] === 'm') { @@ -106,7 +103,7 @@ export function Card({ collection }: { collection: MintableCollection }) { chainId: collection.chainId, priceInEth: price, }); - navigateToMintCollection(collection.contract, collection.mintStatus.price, network); + navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); }} style={{ borderRadius: 99, diff --git a/src/screens/NFTOffersSheet/OfferRow.tsx b/src/screens/NFTOffersSheet/OfferRow.tsx index 2fb228536a2..93b64b83c18 100644 --- a/src/screens/NFTOffersSheet/OfferRow.tsx +++ b/src/screens/NFTOffersSheet/OfferRow.tsx @@ -18,6 +18,7 @@ import { Network } from '@/networks/types'; import { useAccountSettings } from '@/hooks'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; +import { AddressOrEth } from '@/__swaps__/types/assets'; const NFT_SIZE = 50; const MARKETPLACE_ORB_SIZE = 18; @@ -100,7 +101,7 @@ export const OfferRow = ({ offer }: { offer: NftOffer }) => { const bgColor = useBackgroundColor('surfaceSecondaryElevated'); const chainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address, + address: offer.paymentToken.address as AddressOrEth, chainId, currency: nativeCurrency, }); diff --git a/src/screens/NFTSingleOfferSheet/index.tsx b/src/screens/NFTSingleOfferSheet/index.tsx index 2902e041e9b..16869b59a77 100644 --- a/src/screens/NFTSingleOfferSheet/index.tsx +++ b/src/screens/NFTSingleOfferSheet/index.tsx @@ -39,7 +39,7 @@ import { createWalletClient, http } from 'viem'; import { RainbowError, logger } from '@/logger'; import { useTheme } from '@/theme'; -import { Network } from '@/helpers'; +import { Network, ChainId } from '@/networks/types'; import { getNetworkObject } from '@/networks'; import { CardSize } from '@/components/unique-token/CardSize'; import { queryClient } from '@/react-query'; @@ -205,7 +205,7 @@ export function NFTSingleOfferSheet() { let reservoirEstimate = 0; const txs: Transaction[] = []; const fallbackEstimate = - offer.network === Network.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; + offerChainId === ChainId.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; steps.forEach(step => step.items?.forEach(item => { if (item?.data?.to && item?.data?.from && item?.data?.data) { @@ -240,23 +240,23 @@ export function NFTSingleOfferSheet() { }, }); } catch { - logger.error(new RainbowError('NFT Offer: Failed to estimate gas')); + logger.error(new RainbowError('[NFTSingleOfferSheet]: Failed to estimate gas')); } }, [accountAddress, feeParam, offerChainId, offer, updateTxFee]); // estimate gas useEffect(() => { if (!isReadOnlyWallet && !isExpired) { - startPollingGasFees(offer?.network as Network); + startPollingGasFees(offerChainId); estimateGas(); } return () => { stopPollingGasFees(); }; - }, [estimateGas, isExpired, isReadOnlyWallet, offer?.network, startPollingGasFees, stopPollingGasFees, updateTxFee]); + }, [estimateGas, isExpired, isReadOnlyWallet, offer.network, offerChainId, startPollingGasFees, stopPollingGasFees, updateTxFee]); const acceptOffer = useCallback(async () => { - logger.info(`Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.debug(`[NFTSingleOfferSheet]: Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); const analyticsEventObject = { nft: { contractAddress: offer.nft.contractAddress, @@ -287,7 +287,7 @@ export function NFTSingleOfferSheet() { chain: networkObj, transport: http(networkObj.rpc()), }); - const nonce = await getNextNonce({ address: accountAddress, network: networkObj.value }); + const nonce = await getNextNonce({ address: accountAddress, chainId: networkObj.id }); try { let errorMessage = ''; let didComplete = false; @@ -333,6 +333,7 @@ export function NFTSingleOfferSheet() { nonce: item?.txHashes?.length > 1 ? nonce + 1 : nonce, asset: { ...offer.paymentToken, + chainId: offerChainId, network: offer.network as Network, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, @@ -347,6 +348,7 @@ export function NFTSingleOfferSheet() { asset: { ...offer.paymentToken, network: offer.network as Network, + chainId: offerChainId, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, value: offer.grossAmount.raw, @@ -371,7 +373,7 @@ export function NFTSingleOfferSheet() { addNewTransaction({ transaction: tx, address: accountAddress, - network: offer.network as Network, + chainId: offerChainId, }); txsRef.current.push(tx.hash); } @@ -394,7 +396,7 @@ export function NFTSingleOfferSheet() { } ); - logger.info(`Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.debug(`[NFTSingleOfferSheet]: Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { status: 'completed', ...analyticsEventObject, @@ -404,7 +406,7 @@ export function NFTSingleOfferSheet() { } catch (e) { logger.error( new RainbowError( - `Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` + `[NFTSingleOfferSheet]: Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` ) ); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { diff --git a/src/screens/NotificationsPromoSheet/index.tsx b/src/screens/NotificationsPromoSheet/index.tsx index 3f9350a5827..cdafe241eb9 100644 --- a/src/screens/NotificationsPromoSheet/index.tsx +++ b/src/screens/NotificationsPromoSheet/index.tsx @@ -61,7 +61,7 @@ export function NotificationsPromoSheetInner({ const primaryButtonOnPress = React.useCallback(async () => { if (notificationsDenied) { - logger.debug(`NotificationsPromoSheet: notifications permissions denied (could be default state)`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions denied (could be default state)`); const result = await requestNotificationPermissions(); if (result.status === perms.RESULTS.BLOCKED) { analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsBlocked); @@ -71,11 +71,11 @@ export function NotificationsPromoSheetInner({ analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsGranted); } } else if (!hasSettingsEnabled || notificationsBlocked) { - logger.debug(`NotificationsPromoSheet: notifications permissions either blocked or all settings are disabled`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions either blocked or all settings are disabled`); analyticsV2.track(analyticsV2.event.notificationsPromoSystemSettingsOpened); await perms.openSettings(); } else if (notificationsEnabled) { - logger.debug(`NotificationsPromoSheet: notifications permissions enabled`); + logger.debug(`[NotificationsPromoSheet]: notifications permissions enabled`); analyticsV2.track(analyticsV2.event.notificationsPromoNotificationSettingsOpened); navigateToNotifications(); } else { diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index dcb196b20f1..9d1613e22b5 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -56,12 +56,12 @@ import styled from '@/styled-thing'; import { position } from '@/styles'; import { useTheme } from '@/theme'; import { ethereumUtils, getUniqueTokenType, promiseUtils } from '@/utils'; -import logger from '@/utils/logger'; -import { getNetworkObj } from '@/networks'; +import { logger, RainbowError } from '@/logger'; import { IS_ANDROID } from '@/env'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { performanceTracking, TimeToSignOperation, Screens } from '@/state/performance/performance'; +import { ChainId, chainIdToNameMapping } from '@/networks/types'; const Container = styled(Centered).attrs({ direction: 'column', @@ -97,12 +97,12 @@ const checkboxOffset = 44; export function getDefaultCheckboxes({ isENS, ensProfile, - network, + chainId, toAddress, }: { isENS: boolean; ensProfile: ENSProfile; - network: string; + chainId: ChainId; toAddress: string; }): Checkbox[] { if (isENS) { @@ -132,7 +132,7 @@ export function getDefaultCheckboxes({ checked: false, id: 'has-wallet-that-supports', label: lang.t('wallet.transaction.checkboxes.has_a_wallet_that_supports', { - networkName: capitalize(network), + networkName: capitalize(chainIdToNameMapping[chainId]), }), }, ]; @@ -195,7 +195,7 @@ export const SendConfirmationSheet = () => { }, []); const { - params: { amountDetails, asset, callback, ensProfile, isL2, isNft, network, to, toAddress }, + params: { amountDetails, asset, callback, ensProfile, isL2, isNft, chainId, to, toAddress }, // eslint-disable-next-line @typescript-eslint/no-explicit-any } = useRoute(); @@ -229,7 +229,7 @@ export const SendConfirmationSheet = () => { transactions.forEach(tx => { if (tx.to?.toLowerCase() === toAddress?.toLowerCase() && tx.from?.toLowerCase() === accountAddress?.toLowerCase()) { sends += 1; - if (tx.network === network) { + if (tx.chainId === chainId) { sendsCurrentNetwork += 1; } } @@ -241,7 +241,7 @@ export const SendConfirmationSheet = () => { } } } - }, [accountAddress, isSendingToUserAccount, network, toAddress, transactions]); + }, [accountAddress, isSendingToUserAccount, chainId, toAddress, transactions]); const contact = useMemo(() => { return contacts?.[toAddress?.toLowerCase()]; @@ -250,7 +250,7 @@ export const SendConfirmationSheet = () => { const uniqueTokenType = getUniqueTokenType(asset); const isENS = uniqueTokenType === 'ENS' && profilesEnabled; - const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, network, toAddress })); + const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, chainId, toAddress })); useEffect(() => { if (isENS) { @@ -318,7 +318,7 @@ export const SendConfirmationSheet = () => { updateTxFee(gasLimit, null); }) .catch(e => { - logger.sentry('Error calculating gas limit', e); + logger.error(new RainbowError(`[SendConfirmationSheet]: error calculating gas limit: ${e}`)); updateTxFee(null, null); }); } @@ -395,7 +395,7 @@ export const SendConfirmationSheet = () => { await callback(); } } catch (e) { - logger.sentry('TX submit failed', e); + logger.error(new RainbowError(`[SendConfirmationSheet]: error submitting transaction: ${e}`)); setIsAuthorizing(false); } }, @@ -500,7 +500,7 @@ export const SendConfirmationSheet = () => { badgeYPosition={0} borderRadius={10} imageUrl={imageUrl} - network={asset.network} + chainId={asset?.chainId} showLargeShadow size={50} /> @@ -508,7 +508,7 @@ export const SendConfirmationSheet = () => { { address: toAddress, name: avatarName || address(to, 4, 8), }} - network={network} + chainId={chainId} scaleTo={0.75} > { {/* @ts-expect-error JavaScript component */} { onPress={handleL2DisclaimerPress} prominent customText={i18n.t(i18n.l.expanded_state.asset.l2_disclaimer_send, { - network: getNetworkObj(asset.network).name, + network: chainIdToNameMapping[asset.chainId], })} symbol={asset.symbol} /> diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 17a7cfe86dd..3cff2615a5b 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -1,5 +1,4 @@ import { useRoute } from '@react-navigation/native'; -import { captureEvent, captureException } from '@sentry/react-native'; import lang from 'i18n-js'; import { isEmpty, isEqual, isString } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -23,9 +22,7 @@ import { getProvider, isL2Chain, resolveNameOrAddress, - web3Provider, } from '@/handlers/web3'; -import Network from '@/helpers/networkTypes'; import { checkIsValidAddressOrDomain, checkIsValidAddressOrDomainFormat, isENSAddressFormat } from '@/helpers/validators'; import { prefetchENSAvatar, @@ -54,7 +51,7 @@ import styled from '@/styled-thing'; import { borders } from '@/styles'; import { convertAmountAndPriceToNativeDisplay, convertAmountFromNativeValue, formatInputDecimals, lessThan } from '@/helpers/utilities'; import { deviceUtils, ethereumUtils, getUniqueTokenType, safeAreaInsetValues } from '@/utils'; -import logger from '@/utils/logger'; +import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_IOS } from '@/env'; import { NoResults } from '@/components/list'; import { NoResultsType } from '@/components/list/NoResults'; @@ -66,7 +63,7 @@ import { getNextNonce } from '@/state/nonces'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { REGISTRATION_STEPS } from '@/helpers/ens'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; @@ -120,7 +117,7 @@ export default function SendSheet(props) { const { contacts, onRemoveContact, filteredContacts } = useContacts(); const { userAccounts, watchedAccounts } = useUserAccounts(); const { sendableUniqueTokens } = useSendableUniqueTokens(); - const { accountAddress, nativeCurrency, network } = useAccountSettings(); + const { accountAddress, nativeCurrency, chainId } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const { action: transferENS } = useENSRegistrationActionHandler({ @@ -267,10 +264,10 @@ export default function SendSheet(props) { // belongs to if (prevChainId !== currentChainId) { InteractionManager.runAfterInteractions(() => { - startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId)); + startPollingGasFees(currentChainId); }); } - }, [startPollingGasFees, selected.network, prevChainId, currentChainId]); + }, [startPollingGasFees, selected.chainId, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -282,21 +279,19 @@ export default function SendSheet(props) { }, [stopPollingGasFees]); useEffect(() => { - const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); - const networkChainId = ethereumUtils.getChainIdFromNetwork(network); + const assetChainId = selected.chainId; if (assetChainId && (assetChainId !== currentChainId || !currentChainId || prevChainId !== currentChainId)) { - let provider = web3Provider; - if (networkChainId === ChainId.goerli) { + if (chainId === ChainId.goerli) { setCurrentChainId(ChainId.goerli); - provider = getProvider({ chainId: ChainId.goerli }); + const provider = getProvider({ chainId: ChainId.goerli }); setCurrentProvider(provider); } else { setCurrentChainId(assetChainId); - provider = getProvider({ chainId: currentChainId }); + const provider = getProvider({ chainId: currentChainId }); setCurrentProvider(provider); } } - }, [currentChainId, isNft, network, prevChainId, selected?.network, sendUpdateSelected]); + }, [currentChainId, isNft, chainId, prevChainId, selected?.chainId, sendUpdateSelected]); const onChangeNativeAmount = useCallback( newNativeAmount => { @@ -400,12 +395,11 @@ export default function SendSheet(props) { const validTransaction = isValidAddress && amountDetails.isSufficientBalance && isSufficientGas && isValidGas; if (!selectedGasFee?.gasFee?.estimatedFee || !validTransaction) { - logger.sentry('preventing tx submit for one of the following reasons:'); - logger.sentry('selectedGasFee ? ', selectedGasFee); - logger.sentry('selectedGasFee.maxFee ? ', selectedGasFee?.maxFee); - logger.sentry('validTransaction ? ', validTransaction); - logger.sentry('isValidGas ? ', isValidGas); - captureEvent('Preventing tx submit'); + logger.error(new RainbowError(`[SendSheet]: preventing tx submit because selectedGasFee is missing or validTransaction is false`), { + selectedGasFee, + validTransaction, + isValidGas, + }); return false; } @@ -425,7 +419,7 @@ export default function SendSheet(props) { }, true, currentProvider, - currentChainIdNetwork + currentChainId ); if (!lessThan(updatedGasLimit, gasLimit)) { @@ -467,7 +461,7 @@ export default function SendSheet(props) { from: accountAddress, gasLimit: gasLimitToUse, network: currentChainIdNetwork, - nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, network: currentChainIdNetwork })), + nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, chainId: currentChainId })), to: toAddress, ...gasParams, }; @@ -479,11 +473,10 @@ export default function SendSheet(props) { screen: isENS ? Screens.SEND_ENS : Screens.SEND, })(txDetails); if (!signableTransaction.to) { - logger.sentry('txDetails', txDetails); - logger.sentry('signableTransaction', signableTransaction); - logger.sentry('"to" field is missing!'); - const e = new Error('Transaction missing TO field'); - captureException(e); + logger.error(new RainbowError(`[SendSheet]: txDetails is missing the "to" field`), { + txDetails, + signableTransaction, + }); Alert.alert(lang.t('wallet.transaction.alert.invalid_transaction')); submitSuccess = false; } else { @@ -517,17 +510,17 @@ export default function SendSheet(props) { txDetails.status = 'pending'; addNewTransaction({ address: accountAddress, - network: currentChainIdNetwork, + chainId: currentChainId, transaction: txDetails, }); } } } catch (error) { submitSuccess = false; - logger.sentry('TX Details', txDetails); - logger.sentry('SendSheet onSubmit error'); - logger.sentry(error); - captureException(error); + logger.error(new RainbowError(`[SendSheet]: onSubmit error`), { + txDetails, + error, + }); // if hardware wallet, we need to tell hardware flow there was error // have to check inverse or we trigger unwanted BT permissions requests @@ -564,8 +557,9 @@ export default function SendSheet(props) { const submitTransaction = useCallback( async (...args) => { if (Number(amountDetails.assetAmount) <= 0) { - logger.sentry('amountDetails.assetAmount ? ', amountDetails?.assetAmount); - captureEvent('Preventing tx submit due to amount <= 0'); + logger.error(new RainbowError(`[SendSheet]: preventing tx submit because amountDetails.assetAmount is <= 0`), { + amountDetails, + }); return false; } const submitSuccessful = await onSubmit(...args); @@ -675,7 +669,7 @@ export default function SendSheet(props) { const checkboxes = getDefaultCheckboxes({ ensProfile, isENS: true, - network, + chainId, toAddress: recipient, }); navigate(Routes.SEND_CONFIRMATION_SHEET, { @@ -687,7 +681,7 @@ export default function SendSheet(props) { isENS, isL2, isNft, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + chainId: currentChainId, profilesEnabled, to: recipient, toAddress, @@ -702,7 +696,7 @@ export default function SendSheet(props) { isNft, nativeCurrencyInputRef, navigate, - network, + chainId, profilesEnabled, recipient, selected, @@ -752,11 +746,11 @@ export default function SendSheet(props) { const [ensSuggestions, setEnsSuggestions] = useState([]); const [loadingEnsSuggestions, setLoadingEnsSuggestions] = useState(false); useEffect(() => { - if (network === Network.mainnet && !recipientOverride && recipient?.length) { + if (chainId === ChainId.mainnet && !recipientOverride && recipient?.length) { setLoadingEnsSuggestions(true); debouncedFetchSuggestions(recipient, setEnsSuggestions, setLoadingEnsSuggestions, profilesEnabled); } - }, [network, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); + }, [chainId, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); useEffect(() => { checkAddress(debouncedInput); @@ -765,7 +759,7 @@ export default function SendSheet(props) { useEffect(() => { if (!currentProvider?._network?.chainId) return; - const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); + const assetChainId = selected.chainId; const currentProviderChainId = currentProvider._network.chainId; if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { @@ -778,7 +772,7 @@ export default function SendSheet(props) { }, false, currentProvider, - ethereumUtils.getNetworkFromChainId(currentChainId) + currentChainId ) .then(async gasLimit => { if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { @@ -788,7 +782,7 @@ export default function SendSheet(props) { } }) .catch(e => { - logger.sentry('Error calculating gas limit', e); + logger.error(new RainbowError(`[SendSheet]: error calculating gas limit: ${e}`)); updateTxFee(null, null); }); } @@ -802,7 +796,7 @@ export default function SendSheet(props) { toAddress, updateTxFee, updateTxFeeForOptimism, - network, + chainId, isNft, currentChainId, ]); @@ -855,7 +849,6 @@ export default function SendSheet(props) { { const onSelectIcon = useCallback( (icon: string) => { - Logger.log('onSelectIcon', icon); + logger.debug(`[AppIconSection]: onSelectIcon: ${icon}`); analytics.track('Set App Icon', { appIcon: icon }); settingsChangeAppIcon(icon); }, @@ -32,9 +32,9 @@ const AppIconSection = () => { (unlockedAppIcons, appIconKey) => { const appIcon = unlockableAppIcons[appIconKey]; const unlocked = unlockableAppIconStorage.getBoolean(appIconKey); - Logger.log('checking if unlocked', appIcon.displayName, unlocked, appIconKey); + logger.debug(`[AppIconSection]: checking if unlocked ${appIcon.displayName} ${unlocked} ${appIconKey}`); if (unlocked) { - Logger.log('unlocked', appIcon.displayName); + logger.debug(`[AppIconSection]: unlocked ${appIcon.displayName}`); unlockedAppIcons[appIconKey] = appIcon; } return unlockedAppIcons; diff --git a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx index b776bc6dd9a..6ea5ddbc87a 100644 --- a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx @@ -201,7 +201,7 @@ const ViewWalletBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError(`[ViewWalletBackup]: Logging into Google Drive failed`), { error: e }); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -281,10 +281,9 @@ const ViewWalletBackup = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Updating wallet userdata failed after new account creation', + logger.error(new RainbowError(`[ViewWalletBackup]: Updating wallet userdata failed after new account creation`), { + error: e, }); - captureException(e); throw e; } } @@ -301,10 +300,9 @@ const ViewWalletBackup = () => { await initializeWallet(); } } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { + error: e, }); - captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -323,8 +321,8 @@ const ViewWalletBackup = () => { }, 50); }); } catch (e) { - logger.error(e as RainbowError, { - description: 'Error while trying to add account', + logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { + error: e, }); } }, [creatingWallet, dispatch, isDamaged, navigate, initializeWallet, profilesEnabled, wallet]); diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx index 17f0290ca6c..9823fd2555f 100644 --- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx @@ -179,7 +179,9 @@ export const WalletsAndBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(e as RainbowError); + logger.error(new RainbowError(`[WalletsAndBackup]: Logging into Google Drive failed`), { + error: e, + }); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -232,10 +234,8 @@ export const WalletsAndBackup = () => { // @ts-expect-error - no params await initializeWallet(); } catch (err) { - logger.error(new RainbowError('Failed to create new secret phrase'), { - extra: { - error: err, - }, + logger.error(new RainbowError(`[WalletsAndBackup]: Failed to create new secret phrase`), { + error: err, }); } }, @@ -253,7 +253,7 @@ export const WalletsAndBackup = () => { (walletId: string, name: string) => { const wallet = wallets?.[walletId]; - const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? wallet.addresses[0].label : name; + const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? (wallet.addresses || [])[0].label : name; navigate(SETTINGS_BACKUP_ROUTES.VIEW_WALLET_BACKUP, { imported: wallet?.imported, title, diff --git a/src/screens/SettingsSheet/components/CurrencySection.tsx b/src/screens/SettingsSheet/components/CurrencySection.tsx index b276d398354..ef2ce846884 100644 --- a/src/screens/SettingsSheet/components/CurrencySection.tsx +++ b/src/screens/SettingsSheet/components/CurrencySection.tsx @@ -10,7 +10,7 @@ import { ETH_ADDRESS, WBTC_ADDRESS, emojis, supportedNativeCurrencies } from '@/ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const emojiData = Object.entries(emojis).map(([emoji, { name }]) => [name, emoji]); diff --git a/src/screens/SettingsSheet/components/DevSection.tsx b/src/screens/SettingsSheet/components/DevSection.tsx index 26538473b3b..8821b07b2b8 100644 --- a/src/screens/SettingsSheet/components/DevSection.tsx +++ b/src/screens/SettingsSheet/components/DevSection.tsx @@ -1,12 +1,6 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import lang from 'i18n-js'; import React, { useCallback, useContext, useState } from 'react'; -import { - // @ts-ignore - HARDHAT_URL_ANDROID, - // @ts-ignore - HARDHAT_URL_IOS, -} from 'react-native-dotenv'; // @ts-ignore import Restart from 'react-native-restart'; import { useDispatch } from 'react-redux'; @@ -16,10 +10,8 @@ import MenuContainer from './MenuContainer'; import MenuItem from './MenuItem'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { deleteAllBackups } from '@/handlers/cloudBackup'; -import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowContext } from '@/helpers/RainbowContext'; import isTestFlight from '@/helpers/isTestFlight'; -import networkTypes from '@/helpers/networkTypes'; import { useWallets } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { wipeKeychain } from '@/model/keychain'; @@ -31,7 +23,7 @@ import { clearImageMetadataCache } from '@/redux/imageMetadata'; import store from '@/redux/store'; import { walletsUpdate } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import logger from 'logger'; +import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet, useAllNotificationSettingsFromStorage, @@ -48,11 +40,13 @@ import { getFCMToken } from '@/notifications/tokens'; import { removeGlobalNotificationSettings } from '@/notifications/settings/settings'; import { nonceStore } from '@/state/nonces'; import { pendingTransactionsStore } from '@/state/pendingTransactions'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const DevSection = () => { const { navigate } = useNavigation(); const { config, setConfig } = useContext(RainbowContext) as any; const { wallets } = useWallets(); + const setConnectedToHardhat = useConnectedToHardhatStore.getState().setConnectedToHardhat; const { walletNotificationSettings } = useAllNotificationSettingsFromStorage(); const dispatch = useDispatch(); @@ -75,15 +69,16 @@ const DevSection = () => { const connectToHardhat = useCallback(async () => { try { - const ready = await web3SetHttpProvider((ios && HARDHAT_URL_IOS) || (android && HARDHAT_URL_ANDROID) || 'http://127.0.0.1:8545'); - logger.log('connected to hardhat', ready); + const connectToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; + setConnectedToHardhat(!connectToHardhat); + logger.debug(`[DevSection] connected to hardhat`); } catch (e) { - await web3SetHttpProvider(networkTypes.mainnet); - logger.log('error connecting to hardhat', e); + setConnectedToHardhat(false); + logger.error(new RainbowError(`[DevSection] error connecting to hardhat: ${e}`)); } navigate(Routes.PROFILE_SCREEN); dispatch(explorerInit()); - }, [dispatch, navigate]); + }, [dispatch, navigate, setConnectedToHardhat]); const checkAlert = useCallback(async () => { try { @@ -317,7 +312,15 @@ const DevSection = () => { onPress={connectToHardhat} size={52} testID="hardhat-section" - titleComponent={} + titleComponent={ + + } /> } diff --git a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx index 9f8d64e0a9f..b415e1d4d30 100644 --- a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx +++ b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx @@ -20,7 +20,7 @@ export const GoogleAccountSection: React.FC = () => { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`Fetching google account data to display in Backups Section failed`), { + logger.error(new RainbowError(`[GoogleAccountSection]: Fetching google account data to display in Backups Section failed`), { error: (error as Error).message, }); }) @@ -66,7 +66,7 @@ export const GoogleAccountSection: React.FC = () => { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`Logging into Google Drive failed.`), { + logger.error(new RainbowError(`[GoogleAccountSection]: Logging into Google Drive failed`), { error: (error as Error).message, }); } finally { diff --git a/src/screens/SettingsSheet/components/NetworkSection.tsx b/src/screens/SettingsSheet/components/NetworkSection.tsx index 7e8e32ffa21..b8edc2256cc 100644 --- a/src/screens/SettingsSheet/components/NetworkSection.tsx +++ b/src/screens/SettingsSheet/components/NetworkSection.tsx @@ -9,44 +9,44 @@ import { analytics } from '@/analytics'; import { Separator, Stack } from '@/design-system'; import { useAccountSettings, useInitializeAccountData, useLoadAccountData, useResetAccountState } from '@/hooks'; import { settingsUpdateNetwork } from '@/redux/settings'; -import { Network } from '@/helpers'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; +import { ChainId } from '@/networks/types'; -const networks = values(RainbowNetworks).filter(({ networkType }) => networkType !== 'layer2'); +const networkObjects = values(RainbowNetworkObjects).filter(({ networkType }) => networkType !== 'layer2'); interface NetworkSectionProps { inDevSection?: boolean; } const NetworkSection = ({ inDevSection }: NetworkSectionProps) => { - const { network, testnetsEnabled } = useAccountSettings(); + const { chainId, testnetsEnabled } = useAccountSettings(); const resetAccountState = useResetAccountState(); const loadAccountData = useLoadAccountData(); const initializeAccountData = useInitializeAccountData(); const dispatch = useDispatch(); const onNetworkChange = useCallback( - async (network: Network) => { + async (chainId: ChainId) => { await resetAccountState(); - await dispatch(settingsUpdateNetwork(network)); + await dispatch(settingsUpdateNetwork(chainId)); InteractionManager.runAfterInteractions(async () => { await loadAccountData(); initializeAccountData(); - analytics.track('Changed network', { network }); + analytics.track('Changed network', { chainId }); }); }, [dispatch, initializeAccountData, loadAccountData, resetAccountState] ); const renderNetworkList = useCallback(() => { - return networks.map(({ name, value, networkType }) => ( + return networkObjects.map(({ name, id, networkType }) => ( onNetworkChange(value)} - rightComponent={value === network && } + key={id} + onPress={() => onNetworkChange(id)} + rightComponent={id === chainId && } size={52} - testID={`${value}-network`} + testID={`${id}-network`} titleComponent={ { } /> )); - }, [inDevSection, network, onNetworkChange, testnetsEnabled]); + }, [inDevSection, chainId, onNetworkChange, testnetsEnabled]); return inDevSection ? ( }>{renderNetworkList()} diff --git a/src/screens/SettingsSheet/components/NotificationsSection.tsx b/src/screens/SettingsSheet/components/NotificationsSection.tsx index 6cbc3dee350..1fb1099dd9a 100644 --- a/src/screens/SettingsSheet/components/NotificationsSection.tsx +++ b/src/screens/SettingsSheet/components/NotificationsSection.tsx @@ -17,7 +17,7 @@ import { abbreviations, deviceUtils } from '@/utils'; import { Box } from '@/design-system'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { RainbowAccount } from '@/model/wallet'; -import { isTestnetNetwork } from '@/handlers/web3'; +import { isTestnetChain } from '@/handlers/web3'; import { useFocusEffect } from '@react-navigation/native'; import { SettingsLoadingIndicator } from '@/screens/SettingsSheet/components/SettingsLoadingIndicator'; import { showNotificationSubscriptionErrorAlert, showOfflineAlert } from '@/screens/SettingsSheet/components/notificationAlerts'; @@ -157,8 +157,8 @@ const WalletRow = ({ ens, groupOff, isTestnet, loading, notificationSettings, wa const NotificationsSection = () => { const { justBecameActive } = useAppState(); const { navigate } = useNavigation(); - const { network } = useAccountSettings(); - const isTestnet = isTestnetNetwork(network); + const { chainId } = useAccountSettings(); + const isTestnet = isTestnetChain({ chainId }); const { wallets, walletNames } = useWallets(); const { isConnected } = useNetInfo(); const { points_enabled, points_notifications_toggle } = useRemoteConfig(); diff --git a/src/screens/SettingsSheet/components/PrivacySection.tsx b/src/screens/SettingsSheet/components/PrivacySection.tsx index 2be68989d53..ef0da3ea34c 100644 --- a/src/screens/SettingsSheet/components/PrivacySection.tsx +++ b/src/screens/SettingsSheet/components/PrivacySection.tsx @@ -25,7 +25,7 @@ const PrivacySection = () => { analyticsEnabled => { if (analyticsEnabled) { device.set(['doNotTrack'], true); - logger.debug(`Analytics tracking disabled`); + logger.debug(`[PrivacySection]: Analytics tracking disabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingDisabled); logger.disable(); analyticsV2.disable(); @@ -34,7 +34,7 @@ const PrivacySection = () => { device.set(['doNotTrack'], false); logger.enable(); analyticsV2.enable(); - logger.debug(`Analytics tracking enabled`); + logger.debug(`[PrivacySection]: Analytics tracking enabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingEnabled); return true; } diff --git a/src/screens/SettingsSheet/components/SettingsSection.tsx b/src/screens/SettingsSheet/components/SettingsSection.tsx index 27c29e18593..9fae44a89eb 100644 --- a/src/screens/SettingsSheet/components/SettingsSection.tsx +++ b/src/screens/SettingsSheet/components/SettingsSection.tsx @@ -203,27 +203,6 @@ const SettingsSection = ({ testID="currency-section" titleComponent={} /> - {/* {(testnetsEnabled || IS_DEV) && ( - - } - onPress={onPressNetwork} - rightComponent={ - - {getNetworkObj(network).name} - - } - size={60} - testID="network-section" - titleComponent={ - - } - /> - )} */} wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth) .map(key => { const wallet = wallets[key]; - const visibleAccounts = wallet.addresses.filter(a => a.visible); + const visibleAccounts = (wallet.addresses || []).filter(a => a.visible); const totalAccounts = visibleAccounts.length; if ( diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index c3e68c1c306..7f48aa81aaa 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -43,7 +43,7 @@ import { TransactionSimulationResult, TransactionScanResultType, } from '@/graphql/__generated__/metadataPOST'; -import { Network } from '@/networks/types'; +import { Network, ChainId, chainIdToNameMapping } from '@/networks/types'; import { convertAmountToNativeDisplay, convertHexToString, @@ -64,7 +64,7 @@ import { IS_IOS } from '@/env'; import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { GasSpeedButton } from '@/components/gas'; -import { getNetworkObj, getNetworkObject } from '@/networks'; +import { getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; import { PERSONAL_SIGN, @@ -97,7 +97,7 @@ import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; import { getOnchainAssetBalance } from '@/handlers/assets'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { ChainId } from '@/__swaps__/types/chains'; +import { AddressOrEth } from '@/__swaps__/types/assets'; const COLLAPSED_CARD_HEIGHT = 56; const MAX_CARD_HEIGHT = 176; @@ -241,18 +241,18 @@ export const SignTransactionSheet = () => { const provider = getProvider({ chainId: currentChainId }); try { // attempt to re-run estimation - logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); // safety precaution: we want to ensure these properties are not used for gas estimation const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); if (rawGasLimit) { gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('WC: error estimating gas'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); } finally { - logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); const networkObject = getNetworkObject({ chainId: currentChainId }); if (networkObject && networkObject.gas.OptimismTxFee) { const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); @@ -283,8 +283,7 @@ export const SignTransactionSheet = () => { InteractionManager.runAfterInteractions(() => { if (currentChainId) { if (!isMessageRequest) { - const network = ethereumUtils.getNetworkFromChainId(currentChainId); - startPollingGasFees(network); + startPollingGasFees(currentChainId); fetchMethodName(transactionDetails?.payload?.params[0].data); } else { setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); @@ -368,14 +367,9 @@ export const SignTransactionSheet = () => { useEffect(() => { (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); + const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId: currentChainId, address: accountInfo.address }); if (asset && provider) { - const balance = await getOnchainAssetBalance( - asset, - accountInfo.address, - ethereumUtils.getNetworkFromChainId(currentChainId), - provider - ); + const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentChainId, provider); if (balance) { const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; setNativeAsset(assetWithOnchainBalance); @@ -390,7 +384,7 @@ export const SignTransactionSheet = () => { (async () => { if (!isMessageRequest && !nonceForDisplay) { try { - const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + const nonce = await getNextNonce({ address: currentAddress, chainId: currentChainId }); if (nonce || nonce === 0) { const nonceAsString = nonce.toString(); setNonceForDisplay(nonceAsString); @@ -459,7 +453,7 @@ export const SignTransactionSheet = () => { } } } catch (error) { - logger.error(new RainbowError('Error while simulating'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); } finally { setIsLoading(false); } @@ -519,7 +513,7 @@ export const SignTransactionSheet = () => { closeScreen(true); }, 300); } catch (error) { - logger.error(new RainbowError('WC: error while handling cancel request'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error while handling cancel request'), { error }); closeScreen(true); } }, @@ -608,7 +602,7 @@ export const SignTransactionSheet = () => { if (!currentChainId) return; try { logger.debug( - 'WC: gas suggested by dapp', + '[SignTransactionSheet]: gas suggested by dapp', { gas: convertHexToString(gas), gasLimitFromPayload: convertHexToString(gasLimitFromPayload), @@ -629,18 +623,22 @@ export const SignTransactionSheet = () => { (!isNil(gas) && greaterThan(rawGasLimit, convertHexToString(gas))) || (!isNil(gasLimitFromPayload) && greaterThan(rawGasLimit, convertHexToString(gasLimitFromPayload))) ) { - logger.debug('WC: using padded estimation!', { gas: rawGasLimit.toString() }, logger.DebugContext.walletconnect); + logger.debug( + '[SignTransactionSheet]: using padded estimation!', + { gas: rawGasLimit.toString() }, + logger.DebugContext.walletconnect + ); gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('WC: error estimating gas'), { error }); + logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); } // clean gas prices / fees sent from the dapp const cleanTxPayload = omitFlatten(txPayload, ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); + const nonce = await getNextNonce({ address: accountInfo.address, chainId: currentChainId }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -649,7 +647,7 @@ export const SignTransactionSheet = () => { }; txPayloadUpdated = omitFlatten(txPayloadUpdated, ['from', 'gas', 'chainId']); - logger.debug(`WC: ${transactionDetails.payload.method} payload`, { + logger.debug(`[SignTransactionSheet]: ${transactionDetails.payload.method} payload`, { txPayload, txPayloadUpdated, }); @@ -703,7 +701,7 @@ export const SignTransactionSheet = () => { }); } } catch (e) { - logger.error(new RainbowError(`WC: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); + logger.error(new RainbowError(`[SignTransactionSheet]: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); } if (response?.result) { @@ -736,7 +734,7 @@ export const SignTransactionSheet = () => { if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + chainId: currentChainId, address: accountAddress, }); txSavedInCurrentWallet = true; @@ -768,13 +766,13 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + chainId: currentChainId, address: txDetails?.from as string, }); }); } } else { - logger.error(new RainbowError(`WC: Tx failure - ${formattedDappUrl}`), { + logger.error(new RainbowError(`[SignTransactionSheet]: Tx failure - ${formattedDappUrl}`), { dappName: transactionDetails?.dappName, dappUrl: transactionDetails?.dappUrl, formattedDappUrl, @@ -924,7 +922,7 @@ export const SignTransactionSheet = () => { { /> ) : ( { }; interface SimulationCardProps { - currentNetwork: Network; + chainId: ChainId; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1099,7 +1097,7 @@ interface SimulationCardProps { } const SimulationCard = ({ - currentNetwork, + chainId, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1312,7 +1310,7 @@ const SimulationCard = ({ {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { symbol: walletBalance?.symbol, - network: getNetworkObj(currentNetwork).name, + network: chainIdToNameMapping[chainId], })} ) : ( @@ -1350,7 +1348,7 @@ const SimulationCard = ({ }; interface DetailsCardProps { - currentNetwork: Network; + chainId: ChainId; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1362,7 +1360,7 @@ interface DetailsCardProps { } const DetailsCard = ({ - currentNetwork, + chainId, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1420,15 +1418,15 @@ const DetailsCard = ({ - {} + {} {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - ethereumUtils.openAddressInBlockExplorer( - meta?.to?.address || toAddress || meta?.transferTo?.address || '', - ethereumUtils.getChainIdFromNetwork(currentNetwork) - ) + ethereumUtils.openAddressInBlockExplorer({ + address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', + chainId, + }) } value={ meta?.to?.name || @@ -1471,7 +1469,7 @@ const MessageCard = ({ displayMessage = sanitizedMessage; // eslint-disable-next-line no-empty } catch (e) { - logger.warn(''); + logger.warn('[SignTransactionSheet]: Error while parsing message'); } displayMessage = JSON.stringify(displayMessage, null, 4); @@ -1556,7 +1554,7 @@ const SimulatedEventRow = ({ const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const { data: externalAsset } = useExternalToken({ - address: asset?.assetCode || '', + address: (asset?.assetCode || '') as AddressOrEth, chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), currency: nativeCurrency, }); @@ -1632,12 +1630,12 @@ const SimulatedEventRow = ({ }; const DetailRow = ({ - currentNetwork, + chainId, detailType, onPress, value, }: { - currentNetwork?: Network; + chainId?: ChainId; detailType: DetailType; onPress?: () => void; value: string; @@ -1658,9 +1656,7 @@ const DetailRow = ({ {detailType === 'sourceCodeVerification' && ( )} - {detailType === 'chain' && currentNetwork && ( - - )} + {detailType === 'chain' && chainId && } {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( {value} diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index dc3f25f771b..0226d4e9323 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -1,5 +1,4 @@ import { useRoute } from '@react-navigation/native'; -import { captureException } from '@sentry/react-native'; import { BigNumber } from 'bignumber.js'; import lang from 'i18n-js'; import { isEmpty } from 'lodash'; @@ -17,7 +16,7 @@ import { Emoji, Text } from '../components/text'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { removeRegistrationByName, saveCommitRegistrationParameters } from '@/redux/ensRegistration'; import { GasFeeTypes } from '@/entities'; -import { getFlashbotsProvider, getProviderForNetwork, isL2Chain, toHex } from '@/handlers/web3'; +import { getFlashbotsProvider, getProvider, isL2Chain, toHex } from '@/handlers/web3'; import { greaterThan } from '@/helpers/utilities'; import { useAccountSettings, useDimensions, useGas, useWallets } from '@/hooks'; import { sendTransaction } from '@/model/wallet'; @@ -27,9 +26,9 @@ import { updateGasFeeForSpeed } from '@/redux/gas'; import { ethUnits } from '@/references'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { ethereumUtils, gasUtils, safeAreaInsetValues } from '@/utils'; -import logger from '@/utils/logger'; -import { getNetworkObj } from '@/networks'; +import { gasUtils, safeAreaInsetValues } from '@/utils'; +import { logger, RainbowError } from '@/logger'; +import { getNetworkObject } from '@/networks'; import * as i18n from '@/languages'; import { updateTransaction } from '@/state/pendingTransactions'; @@ -101,7 +100,7 @@ const calcGasParamRetryValue = prevWeiValue => { export default function SpeedUpAndCancelSheet() { const { navigate, goBack } = useNavigation(); - const { accountAddress, network } = useAccountSettings(); + const { accountAddress, chainId } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const dispatch = useDispatch(); const { height: deviceHeight } = useDimensions(); @@ -118,7 +117,7 @@ export default function SpeedUpAndCancelSheet() { const [minMaxPriorityFeePerGas, setMinMaxPriorityFeePerGas] = useState(calcGasParamRetryValue(tx.maxPriorityFeePerGas)); const [minMaxFeePerGas, setMinMaxFeePerGas] = useState(calcGasParamRetryValue(tx.maxFeePerGas)); const fetchedTx = useRef(false); - const [currentNetwork, setCurrentNetwork] = useState(null); + const [currentChainId, setCurrentChainId] = useState(null); const [currentProvider, setCurrentProvider] = useState(null); const [data, setData] = useState(null); const [gasLimit, setGasLimit] = useState(null); @@ -172,9 +171,13 @@ export default function SpeedUpAndCancelSheet() { updatedTx.hash = res.result?.hash; updatedTx.status = 'pending'; updatedTx.type = 'cancel'; - updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); + updateTransaction({ + address: accountAddress, + transaction: updatedTx, + chainId: currentChainId, + }); } catch (e) { - logger.log('Error submitting cancel tx', e); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting cancel tx: ${e}`)); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -185,7 +188,7 @@ export default function SpeedUpAndCancelSheet() { }, [ accountAddress, cancelCommitTransactionHash, - currentNetwork, + currentChainId, currentProvider, getNewTransactionGasParams, goBack, @@ -242,9 +245,13 @@ export default function SpeedUpAndCancelSheet() { updatedTx.status = 'pending'; updatedTx.type = 'speed_up'; - updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); + updateTransaction({ + address: accountAddress, + transaction: updatedTx, + chainId: currentChainId, + }); } catch (e) { - logger.log('Error submitting speed up tx', e); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting speed up tx: ${e}`)); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -254,7 +261,7 @@ export default function SpeedUpAndCancelSheet() { } }, [ accountAddress, - currentNetwork, + currentChainId, currentProvider, data, gasLimit, @@ -278,21 +285,21 @@ export default function SpeedUpAndCancelSheet() { // Set the network useEffect(() => { - setCurrentNetwork(tx?.network || network); - }, [network, tx.network]); + setCurrentChainId(tx?.chainId || chainId); + }, [chainId, tx.chainId]); // Set the provider useEffect(() => { - if (currentNetwork) { - startPollingGasFees(currentNetwork, tx.flashbots); + if (currentChainId) { + startPollingGasFees(currentChainId, tx.flashbots); const updateProvider = async () => { let provider; - if (getNetworkObj(tx?.network).features.flashbots && tx.flashbots) { - logger.debug('using flashbots provider'); + if (getNetworkObject({ chainId: tx?.chainId })?.features?.flashbots && tx.flashbots) { + logger.debug(`[SpeedUpAndCancelSheet]: using flashbots provider for chainId ${tx?.chainId}`); provider = await getFlashbotsProvider(); } else { - logger.debug('using normal provider'); - provider = getProviderForNetwork(currentNetwork); + logger.debug(`[SpeedUpAndCancelSheet]: using provider for network ${tx?.chainId}`); + provider = getProvider({ chainId: currentChainId }); } setCurrentProvider(provider); }; @@ -303,7 +310,7 @@ export default function SpeedUpAndCancelSheet() { stopPollingGasFees(); }; } - }, [currentNetwork, startPollingGasFees, stopPollingGasFees, tx.flashbots, tx?.network]); + }, [currentChainId, startPollingGasFees, stopPollingGasFees, tx?.chainId, tx.flashbots]); // Update gas limit useEffect(() => { @@ -313,11 +320,11 @@ export default function SpeedUpAndCancelSheet() { updateGasFeeOption(gasUtils.URGENT); speedUrgentSelected.current = true; } - }, [currentNetwork, gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); + }, [gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); useEffect(() => { const init = async () => { - if (currentNetwork && currentProvider && !fetchedTx.current) { + if (currentChainId && currentProvider && !fetchedTx.current) { try { fetchedTx.current = true; const hexGasLimit = toHex(tx?.gasLimit?.toString() || '0x'); @@ -342,11 +349,13 @@ export default function SpeedUpAndCancelSheet() { setMinGasPrice(calcGasParamRetryValue(hexGasPrice)); } } catch (e) { - logger.log('something went wrong while fetching tx info ', e); - logger.sentry('Error speeding up or canceling transaction: [error]', e); - logger.sentry('Error speeding up or canceling transaction: [transaction]', tx); - const speedUpOrCancelError = new Error('Error speeding up or canceling transaction'); - captureException(speedUpOrCancelError); + logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error fetching tx info: ${e}`), { + data: { + tx, + }, + }); + + // NOTE: We don't care about this for cancellations if (type === SPEED_UP) { Alert.alert(lang.t('wallet.speed_up.unable_to_speed_up'), lang.t('wallet.speed_up.problem_while_fetching_transaction_data'), [ { @@ -354,13 +363,12 @@ export default function SpeedUpAndCancelSheet() { }, ]); } - // We don't care about this for cancellations } } }; init(); - }, [currentNetwork, currentProvider, goBack, isL2, network, tx, tx.gasLimit, tx.hash, type, updateGasFeeOption]); + }, [currentChainId, currentProvider, goBack, isL2, tx, tx?.gasLimit, tx.hash, type, updateGasFeeOption]); useEffect(() => { if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current) { @@ -474,8 +482,7 @@ export default function SpeedUpAndCancelSheet() { ({ color: colors.alpha(colors.blueGreyDark, 0.3), @@ -69,8 +67,8 @@ const NetworkPill = ({ chainIds }) => { const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); const networkMenuItems = useMemo(() => { - RainbowNetworks.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ - actionKey: network.value, + RainbowNetworkObjects.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ + actionKey: network.id, actionTitle: network.name, icon: { iconType: 'ASSET', @@ -128,7 +126,7 @@ const NetworkPill = ({ chainIds }) => { ) : ( - {availableNetworkChainIds[0] !== Network.mainnet ? ( + {availableNetworkChainIds[0] !== ChainId.mainnet ? ( ) : ( @@ -136,7 +134,7 @@ const NetworkPill = ({ chainIds }) => { - {getNetworkObj(availableNetworkChainIds[0]).name} + {chainIdToNameMapping[availableNetworkChainIds[0]]} @@ -151,7 +149,7 @@ export default function WalletConnectApprovalSheet() { const { colors, isDarkMode } = useTheme(); const { goBack } = useNavigation(); const { params } = useRoute(); - const { network, accountAddress } = useAccountSettings(); + const { chainId: settingsChainId, accountAddress } = useAccountSettings(); const { navigate } = useNavigation(); const { selectedWallet, walletNames, wallets } = useWallets(); const handled = useRef(false); @@ -182,8 +180,8 @@ export default function WalletConnectApprovalSheet() { const failureExplainSheetVariant = params?.failureExplainSheetVariant; const chainIds = meta?.chainIds; // WC v2 supports multi-chain const chainId = meta?.proposedChainId || chainIds?.[0] || 1; // WC v1 only supports 1 - const currentNetwork = params?.currentNetwork; - const [approvalNetwork, setApprovalNetwork] = useState(currentNetwork || network); + const currentChainId = params?.currentChainId; + const [approvalChainId, setApprovalChainId] = useState(currentChainId || settingsChainId); const isWalletConnectV2 = meta.isWalletConnectV2; const { dappName, dappUrl, dappScheme, imageUrl, peerId } = meta; @@ -225,18 +223,18 @@ export default function WalletConnectApprovalSheet() { * v2. */ const approvalNetworkInfo = useMemo(() => { - const networkObj = getNetworkObj(approvalNetwork); + const networkObj = getNetworkObject({ chainId: approvalChainId }); return { chainId: networkObj.id, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, name: networkObj.name, value: networkObj.value, }; - }, [approvalNetwork, isDarkMode]); + }, [approvalChainId, isDarkMode]); const handleOnPressNetworksMenuItem = useCallback( - ({ nativeEvent }) => setApprovalNetwork(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), - [setApprovalNetwork] + ({ nativeEvent }) => setApprovalChainId(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), + [setApprovalChainId] ); const handleSuccess = useCallback( @@ -253,8 +251,7 @@ export default function WalletConnectApprovalSheet() { useEffect(() => { if (chainId && type === WalletConnectApprovalSheetType.connect) { - const network = ethereumUtils.getNetworkFromChainId(Number(chainId)); - setApprovalNetwork(network); + setApprovalChainId(chainId); } }, [chainId, type]); @@ -284,7 +281,7 @@ export default function WalletConnectApprovalSheet() { }, [handleSuccess, goBack]); const onPressAndroid = useCallback(() => { - androidShowNetworksActionSheet(({ network }) => setApprovalNetwork(network)); + androidShowNetworksActionSheet(({ chainId }) => setApprovalChainId(chainId)); }, []); const handlePressChangeWallet = useCallback(() => { @@ -358,19 +355,11 @@ export default function WalletConnectApprovalSheet() { }} > - + {`${ - type === WalletConnectApprovalSheetType.connect - ? approvalNetworkInfo.name - : ethereumUtils.getNetworkNameFromChainId(Number(chainId)) + type === WalletConnectApprovalSheetType.connect ? approvalNetworkInfo.name : chainIdToNameMapping[chainId] } ${type === WalletConnectApprovalSheetType.connect && menuItems.length > 1 ? '􀁰' : ''}`} @@ -379,8 +368,8 @@ export default function WalletConnectApprovalSheet() { } }, [ NetworkSwitcherParent, + approvalNetworkInfo.chainId, approvalNetworkInfo.name, - approvalNetworkInfo.value, chainId, chainIds, handleOnPressNetworksMenuItem, @@ -411,7 +400,7 @@ export default function WalletConnectApprovalSheet() { {type === WalletConnectApprovalSheetType.connect ? lang.t(lang.l.walletconnect.wants_to_connect) : lang.t(lang.l.walletconnect.wants_to_connect_to_network, { - network: ethereumUtils.getNetworkNameFromChainId(Number(chainId)), + network: chainIdToNameMapping[chainId], })} diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 02ea352ec5c..d78336c324e 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -1,22 +1,17 @@ -import { InteractionManager, View } from 'react-native'; -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { View } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; import { AssetList } from '../../components/asset-list'; import { Page } from '../../components/layout'; -import { Network } from '@/helpers'; import { useRemoveFirst } from '@/navigation/useRemoveFirst'; -import { settingsUpdateNetwork } from '@/redux/settings'; import { navbarHeight } from '@/components/navbar/Navbar'; import { Box } from '@/design-system'; import { useAccountAccentColor, useAccountSettings, - useInitializeAccountData, useInitializeWallet, - useLoadAccountData, useLoadAccountLateData, useLoadGlobalLateData, - useResetAccountState, useWalletSectionsData, } from '@/hooks'; import Routes from '@rainbow-me/routes'; @@ -51,28 +46,8 @@ const WalletScreen: React.FC = ({ navigation, route }) => { const loadAccountLateData = useLoadAccountLateData(); const loadGlobalLateData = useLoadGlobalLateData(); - const dispatch = useDispatch(); - const resetAccountState = useResetAccountState(); - const loadAccountData = useLoadAccountData(); - const initializeAccountData = useInitializeAccountData(); const insets = useSafeAreaInsets(); - const revertToMainnet = useCallback(async () => { - await resetAccountState(); - await dispatch(settingsUpdateNetwork(Network.mainnet)); - InteractionManager.runAfterInteractions(async () => { - await loadAccountData(); - initializeAccountData(); - }); - }, [dispatch, initializeAccountData, loadAccountData, resetAccountState]); - - useEffect(() => { - const supportedNetworks = [Network.mainnet]; - if (!supportedNetworks.includes(currentNetwork)) { - revertToMainnet(); - } - }, [currentNetwork, revertToMainnet]); - const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady); const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData(); diff --git a/src/screens/WelcomeScreen/index.tsx b/src/screens/WelcomeScreen/index.tsx index 462c892cfed..c175c259d0a 100644 --- a/src/screens/WelcomeScreen/index.tsx +++ b/src/screens/WelcomeScreen/index.tsx @@ -27,7 +27,7 @@ import Routes from '@rainbow-me/routes'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { ThemeContextProps, useTheme } from '@/theme'; -import logger from 'logger'; +import { logger } from '@/logger'; import { IS_ANDROID, IS_TEST } from '@/env'; import { WelcomeScreenRainbowButton } from '@/screens/WelcomeScreen/WelcomeScreenRainbowButton'; @@ -85,7 +85,7 @@ export default function WelcomeScreen() { useEffect(() => { const initialize = async () => { if (IS_TEST) { - logger.log('Skipping animations because IS_TEST is true'); + logger.debug('[WelcomeScreen] Skipping animations because IS_TEST is true'); contentAnimation.value = 1; createWalletButtonAnimation.value = 1; colorAnimation.value = 0; diff --git a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx new file mode 100644 index 00000000000..78a7249adf2 --- /dev/null +++ b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { GenericCard } from '@/components/cards/GenericCard'; +import { ImgixImage } from '@/components/images'; +import { HORIZONTAL_PADDING } from './DiscoverHome'; +import { deviceUtils } from '@/utils'; +import { FeaturedResult } from '@/graphql/__generated__/arc'; +import { StyleSheet } from 'react-native'; + +const { width: SCREEN_WIDTH } = deviceUtils.dimensions; +const CARD_WIDTH = SCREEN_WIDTH - HORIZONTAL_PADDING * 2; +const CARD_HEIGHT = 238; + +type DiscoverFeaturedResultsCardProps = { + handlePress: () => void; + featuredResult: FeaturedResult; +}; + +export const DiscoverFeaturedResultsCard = ({ handlePress, featuredResult }: DiscoverFeaturedResultsCardProps) => { + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + image: { + width: CARD_WIDTH, + height: CARD_HEIGHT, + borderRadius: 12, + }, +}); diff --git a/src/screens/discover/components/DiscoverHome.js b/src/screens/discover/components/DiscoverHome.tsx similarity index 69% rename from src/screens/discover/components/DiscoverHome.js rename to src/screens/discover/components/DiscoverHome.tsx index 1cf59a2c636..866c7688096 100644 --- a/src/screens/discover/components/DiscoverHome.js +++ b/src/screens/discover/components/DiscoverHome.tsx @@ -1,6 +1,13 @@ -import React, { useMemo } from 'react'; -import useExperimentalFlag, { OP_REWARDS, PROFILES, HARDWARE_WALLETS, MINTS, NFT_OFFERS } from '@rainbow-me/config/experimentalHooks'; -import { isTestnetNetwork } from '@/handlers/web3'; +import React, { useCallback } from 'react'; +import useExperimentalFlag, { + OP_REWARDS, + PROFILES, + HARDWARE_WALLETS, + MINTS, + NFT_OFFERS, + FEATURED_RESULTS, +} from '@rainbow-me/config/experimentalHooks'; +import { isTestnetChain } from '@/handlers/web3'; import { Inline, Inset, Stack, Box } from '@/design-system'; import { useAccountSettings, useWallets } from '@/hooks'; import { ENSCreateProfileCard } from '@/components/cards/ENSCreateProfileCard'; @@ -17,26 +24,43 @@ import { MintsCard } from '@/components/cards/MintsCard/MintsCard'; import { FeaturedMintCard } from '@/components/cards/FeaturedMintCard'; import { IS_TEST } from '@/env'; import { RemoteCardCarousel } from '@/components/cards/remote-cards'; +import { FeaturedResultStack } from '@/components/FeaturedResult/FeaturedResultStack'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { DiscoverFeaturedResultsCard } from './DiscoverFeaturedResultsCard'; + +export const HORIZONTAL_PADDING = 20; export default function DiscoverHome() { - const { profiles_enabled, mints_enabled, op_rewards_enabled } = useRemoteConfig(); - const { network } = useAccountSettings(); + const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results } = useRemoteConfig(); + const { chainId } = useAccountSettings(); const profilesEnabledLocalFlag = useExperimentalFlag(PROFILES); const profilesEnabledRemoteFlag = profiles_enabled; const hardwareWalletsEnabled = useExperimentalFlag(HARDWARE_WALLETS); const nftOffersEnabled = useExperimentalFlag(NFT_OFFERS); + const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; const opRewardsLocalFlag = useExperimentalFlag(OP_REWARDS); const opRewardsRemoteFlag = op_rewards_enabled; - const testNetwork = isTestnetNetwork(network); + const testNetwork = isTestnetChain({ chainId }); + const { navigate } = useNavigation(); const isProfilesEnabled = profilesEnabledLocalFlag && profilesEnabledRemoteFlag; const { wallets } = useWallets(); - const hasHardwareWallets = Object.keys(wallets || {}).filter(key => wallets[key].type === walletTypes.bluetooth).length > 0; + const hasHardwareWallets = Object.keys(wallets || {}).filter(key => (wallets || {})[key].type === walletTypes.bluetooth).length > 0; + + const onNavigate = useCallback( + (url: string) => { + navigate(Routes.DAPP_BROWSER_SCREEN, { + url, + }); + }, + [navigate] + ); return ( - + {!testNetwork ? ( @@ -55,6 +79,9 @@ export default function DiscoverHome() { {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */} {!IS_TEST && nftOffersEnabled && } {/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/} + {featuredResultsEnabled && ( + + )} {(opRewardsRemoteFlag || opRewardsLocalFlag) && } {hardwareWalletsEnabled && !hasHardwareWallets && } {isProfilesEnabled && } diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 74233112e6b..52741d9fc4e 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -19,11 +19,10 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { ethereumUtils } from '@/utils'; -import { Network } from '@/helpers'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; export const SearchContainer = styled(Row)({ height: '100%', @@ -134,7 +133,7 @@ export default function DiscoverSearch() { network === Network.optimism; } const contractAddress = query.split('/')[1]; - navigateToMintCollection(contractAddress, network); + navigateToMintCollection(contractAddress, ethereumUtils.getChainIdFromNetwork(network)); } }; checkAndHandleMint(searchQuery); diff --git a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx index 4842cdbc8cb..fca391ca357 100644 --- a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx +++ b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx @@ -109,10 +109,10 @@ export function PairHardwareWalletSigningSheet() { const importHardwareWallet = useCallback( async (deviceId: string) => { if (busy) { - logger.debug('[importHardwareWallet] - busy, already trying to import', { deviceId }, DebugContext.ledger); + logger.debug('[PairHardwareWalletSigningSheet]: busy, already trying to import', { deviceId }, DebugContext.ledger); return; } - logger.debug('[importHardwareWallet] - importing Hardware Wallet', { deviceId }, DebugContext.ledger); + logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId }, DebugContext.ledger); handleSetSeedPhrase(deviceId); handlePressImportButton(null, deviceId, null, null); }, @@ -137,8 +137,7 @@ export function PairHardwareWalletSigningSheet() { }, }); } else { - logger.error(new RainbowError('[importHardwareWallet] - Disconnected or Unkown Error'), { errorType }); - logger.info('[importHardwareWallet] - issue connecting, trying again '); + logger.error(new RainbowError('[PairHardwareWalletSigningSheet]: Disconnected or Unkown Error'), { errorType }); const transport = await TransportBLE.open(deviceId); await checkLedgerConnection({ transport, diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index 2a58d15fcf7..399b8ca3e56 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -56,7 +56,7 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const NFT_IMAGE_HEIGHT = 250; // inset * 2 -> 28 *2 @@ -200,7 +200,7 @@ const MintSheet = () => { // check address balance useEffect(() => { const checkInsufficientEth = async () => { - const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress))?.balance?.amount ?? 0; + const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }))?.balance?.amount ?? 0; const totalMintPrice = multiply(price.amount, quantity); if (greaterThanOrEqualTo(totalMintPrice, nativeBalance)) { @@ -237,8 +237,7 @@ const MintSheet = () => { // start poll gas price useEffect(() => { - const network = ethereumUtils.getNetworkFromChainId(chainId); - startPollingGasFees(network); + startPollingGasFees(chainId); return () => { stopPollingGasFees(); @@ -264,7 +263,7 @@ const MintSheet = () => { const txs: Transaction[] = []; steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${step.error}`)); + logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${step.error}`)); return; } step.items?.forEach(item => { @@ -296,7 +295,7 @@ const MintSheet = () => { }); } catch (e) { setGasError(true); - logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${(e as Error).message}`)); + logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${(e as Error).message}`)); } }; estimateMintGas(); @@ -344,7 +343,7 @@ const MintSheet = () => { return; } - logger.info('Minting NFT', { name: mintCollection.name }); + logger.debug('[MintSheet]: Minting NFT', { name: mintCollection.name }); analyticsV2.track(event.mintsMintingNFT, { collectionName: mintCollection.name || '', contract: mintCollection.id || '', @@ -365,9 +364,8 @@ const MintSheet = () => { }); const feeAddress = getRainbowFeeAddress(chainId); - const nonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(chainId) }); + const nonce = await getNextNonce({ address: accountAddress, chainId }); try { - const currentNetwork = ethereumUtils.getNetworkFromChainId(chainId); await getClient()?.actions.mintToken({ items: [ { @@ -381,17 +379,18 @@ const MintSheet = () => { onProgress: (steps: Execute['steps']) => { steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`Error minting NFT: ${step.error}`)); + logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${step.error}`)); setMintStatus('error'); return; } step.items?.forEach(item => { if (item.txHashes?.[0]?.txHash && txRef.current !== item.txHashes[0].txHash && item.status === 'incomplete') { const asset = { + chainId, type: 'nft', icon_url: imageUrl, address: mintCollection.id || '', - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), name: mintCollection.name || '', decimals: 18, symbol: 'NFT', @@ -401,7 +400,7 @@ const MintSheet = () => { const paymentAsset = { type: 'nft', address: ETH_ADDRESS, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), name: mintCollection.publicMintInfo?.price?.currency?.name || 'Ethereum', decimals: mintCollection.publicMintInfo?.price?.currency?.decimals || 18, symbol: ETH_SYMBOL, @@ -414,7 +413,7 @@ const MintSheet = () => { to: item.data?.to, from: item.data?.from, hash: item.txHashes[0].txHash, - network: currentNetwork, + network: ethereumUtils.getNetworkFromChainId(chainId), nonce, changes: [ { @@ -437,7 +436,7 @@ const MintSheet = () => { addNewTransaction({ transaction: tx, address: accountAddress, - network: currentNetwork, + chainId, }); analyticsV2.track(event.mintsMintedNFT, { collectionName: mintCollection.name || '', @@ -462,7 +461,7 @@ const MintSheet = () => { quantity, priceInEth: mintPriceAmount, }); - logger.error(new RainbowError(`Error minting NFT: ${(e as Error).message}`)); + logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${(e as Error).message}`)); } }, [ accountAddress, @@ -683,7 +682,9 @@ const MintSheet = () => { symbol="􀉆" label={i18n.t(i18n.l.minting.contract)} value={ - ethereumUtils.openAddressInBlockExplorer(mintCollection.id!, chainId)}> + ethereumUtils.openAddressInBlockExplorer({ address: mintCollection.id!, chainId })} + > {contractAddressDisplay} diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index 5bee2b44794..7fa9780b228 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -5,7 +5,7 @@ import { Bleed, Box, Text, TextShadow, globalColors, useBackgroundColor, useColo import * as i18n from '@/languages'; import { ListHeader, ListPanel, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; import { ChainImage } from '@/components/coin-icon/ChainImage'; -import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { ChainId, ChainNameDisplay } from '@/networks/types'; import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { useAccountAccentColor, useAccountProfile, useAccountSettings } from '@/hooks'; import { safeAreaInsetValues } from '@/utils'; @@ -17,7 +17,6 @@ import { PointsErrorType } from '@/graphql/__generated__/metadata'; import { useMutation } from '@tanstack/react-query'; import { invalidatePointsQuery, usePoints } from '@/resources/points'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; -import { Network } from '@/helpers'; import { ButtonPressAnimation } from '@/components/animations'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; @@ -29,7 +28,7 @@ import { walletExecuteRap } from '@/raps/execute'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { loadWallet } from '@/model/wallet'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { getProvider } from '@/handlers/web3'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { getGasSettingsBySpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -211,7 +210,7 @@ const ClaimingRewards = ({ }>({ mutationFn: async () => { // Fetch the native asset from the origin chain - const opEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.optimism); + const opEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.optimism }); const opEth = { ...opEth_, chainName: chainNameFromChainId(ChainId.optimism), @@ -220,9 +219,9 @@ const ClaimingRewards = ({ // Fetch the native asset from the destination chain let destinationEth_; if (chainId === ChainId.base) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.base); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.base }); } else if (chainId === ChainId.zora) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.zora); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.zora }); } else { destinationEth_ = opEth; } @@ -261,7 +260,7 @@ const ClaimingRewards = ({ gasParams, } satisfies RapSwapActionParameters<'claimBridge'>; - const provider = getProviderForNetwork(Network.optimism); + const provider = getProvider({ chainId: ChainId.optimism }); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, @@ -290,7 +289,7 @@ const ClaimingRewards = ({ setClaimStatus('bridge-error'); } - logger.error(new RainbowError('ETH REWARDS CLAIM ERROR'), { message: errorMessage }); + logger.error(new RainbowError('[ClaimRewardsPanel]: Failed to claim ETH rewards'), { message: errorMessage }); return { nonce: null }; } diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 60951803f17..915e6c22a8f 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -9,7 +9,6 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, isENSNFTRecord } from '@/utils'; import { address as formatAddress } from '@/utils/abbreviations'; -import { Network } from '@/networks/types'; import { ContactAvatar, showDeleteContactActionSheet } from '@/components/contacts'; import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; import MaskedView from '@react-native-masked-view/masked-view'; @@ -20,7 +19,7 @@ import { useTheme } from '@/theme'; import LinearGradient from 'react-native-linear-gradient'; import { ButtonPressAnimation } from '@/components/animations'; import { noop } from 'lodash'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -52,9 +51,8 @@ export const LeaderboardRow = memo(function LeaderboardRow({ const { setClipboard } = useClipboard(); const { contacts, onRemoveContact } = useContacts(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); - ``; - return visibleWallet.address.toLowerCase() === address?.toLowerCase(); + const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); + return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; @@ -129,7 +127,7 @@ export const LeaderboardRow = memo(function LeaderboardRow({ setClipboard(address); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); + ethereumUtils.openAddressInBlockExplorer({ address: address, chainId: ChainId.mainnet }); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/screens/points/content/PointsContent.tsx b/src/screens/points/content/PointsContent.tsx index e6e6012c8ed..c90c006f7af 100644 --- a/src/screens/points/content/PointsContent.tsx +++ b/src/screens/points/content/PointsContent.tsx @@ -62,7 +62,7 @@ import { format, intervalToDuration, isToday } from 'date-fns'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ETH_REWARDS, useExperimentalFlag } from '@/config'; import { RewardsActionButton } from '../components/RewardsActionButton'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const InfoCards = ({ points }: { points: GetPointsDataForWalletQuery | undefined }) => { const labelSecondary = useForegroundColor('labelSecondary'); diff --git a/src/screens/points/content/ReferralContent.tsx b/src/screens/points/content/ReferralContent.tsx index e17d4aa6c58..f9719f9fd16 100644 --- a/src/screens/points/content/ReferralContent.tsx +++ b/src/screens/points/content/ReferralContent.tsx @@ -87,7 +87,7 @@ export function ReferralContent() { setStatus('invalid'); haptics.notificationError(); } else { - logger.error(new RainbowError('Error validating referral code'), { + logger.error(new RainbowError('[ReferralContent]: Error validating referral code'), { referralCode: code, }); Alert.alert(i18n.t(i18n.l.points.referral.error)); diff --git a/src/screens/points/contexts/PointsProfileContext.tsx b/src/screens/points/contexts/PointsProfileContext.tsx index 193d206760e..bed940f82a9 100644 --- a/src/screens/points/contexts/PointsProfileContext.tsx +++ b/src/screens/points/contexts/PointsProfileContext.tsx @@ -13,10 +13,10 @@ import { loadWallet, signPersonalMessage } from '@/model/wallet'; import { RainbowError, logger } from '@/logger'; import { queryClient } from '@/react-query'; import { useNavigation } from '@/navigation'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { Network } from '@/networks/types'; +import { getProvider } from '@/handlers/web3'; import { analyticsV2 } from '@/analytics'; import { delay } from '@/utils/delay'; +import { ChainId } from '@/networks/types'; type PointsProfileContext = { step: RainbowPointsFlowSteps; @@ -134,7 +134,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); throw new RainbowError('Points: Error getting onboard challenge'); } - const provider = getProviderForNetwork(Network.mainnet); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ address: accountAddress, provider }); if (!wallet) { Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); @@ -176,7 +176,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); break; } - logger.info('Points: Failed to onboard user', { errorType }); + logger.error(new RainbowError('[PointsProfileContext]: Failed to onboard user'), { errorType }); analyticsV2.track(analyticsV2.event.pointsOnboardingScreenFailedToSignIn, { deeplinked, referralCode: !!referralCode, @@ -201,7 +201,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode hardwareWallet: isHardwareWallet, errorType: undefined, }); - logger.error(new RainbowError('Points: signIn error'), { error }); + logger.error(new RainbowError('[PointsProfileContext]: signIn error'), { error }); } }, [accountAddress, deeplinked, goBack, isHardwareWallet, referralCode]); diff --git a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx index 223fa5d7d06..9040b9a8e1b 100644 --- a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx @@ -101,7 +101,7 @@ export const TransactionDetailsHashAndActionsSection: React.FC = ({ trans weight="heavy" onPress={onViewOnBlockExplorerPress} label={i18n.t(i18n.l.wallet.action.view_on, { - blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer(chainId)), + blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer({ chainId })), })} lightShadows /> diff --git a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx index d0790ebe0e6..5672b52ae02 100644 --- a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx @@ -6,8 +6,6 @@ import { Box, Stack, globalColors } from '@/design-system'; import { TransactionDetailsDivider } from '@/screens/transaction-details/components/TransactionDetailsDivider'; import * as i18n from '@/languages'; -import { Network } from '@/networks/types'; - import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; import { useAccountSettings } from '@/hooks'; @@ -17,7 +15,7 @@ import ImgixImage from '@/components/images/ImgixImage'; import { View } from 'react-native'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; type Props = { transaction: RainbowTransaction; diff --git a/src/screens/transaction-details/components/TransactionMasthead.tsx b/src/screens/transaction-details/components/TransactionMasthead.tsx index 7461caddc5d..92c16db5100 100644 --- a/src/screens/transaction-details/components/TransactionMasthead.tsx +++ b/src/screens/transaction-details/components/TransactionMasthead.tsx @@ -34,7 +34,7 @@ import ImageAvatar from '@/components/contacts/ImageAvatar'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import * as lang from '@/languages'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; const TransactionMastheadHeight = android ? 153 : 135; @@ -128,7 +128,7 @@ function CurrencyTile({ } }); } - }, []); + }, [accountName, address, addressContact?.nickname]); useEffect(() => { if (!addressAccount?.image && (fetchedEnsName || addressContact?.ens)) { diff --git a/src/state/appSessions/index.test.ts b/src/state/appSessions/index.test.ts index 6ff71512df9..02e8cf06905 100644 --- a/src/state/appSessions/index.test.ts +++ b/src/state/appSessions/index.test.ts @@ -1,4 +1,4 @@ -import { Network } from '@/networks/types'; +import { ChainId } from '@/networks/types'; import { useAppSessionsStore } from '.'; const UNISWAP_HOST = 'uniswap.org'; @@ -15,13 +15,13 @@ test('should be able to add session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.mainnet, + chainId: ChainId.mainnet, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet }, + sessions: { [ADDRESS_1]: ChainId.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -33,13 +33,13 @@ test('should be able to add session to an existent host', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -51,19 +51,19 @@ test('should be able to add session to a new host', async () => { url: OPENSEA_URL, host: OPENSEA_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, [OPENSEA_HOST]: { url: OPENSEA_URL, host: OPENSEA_HOST, - sessions: { [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -76,7 +76,7 @@ test('should be able to remove app session for a host', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -89,7 +89,7 @@ test('should be able to remove a session for a host and address', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.mainnet }, + sessions: { [ADDRESS_1]: ChainId.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -101,7 +101,7 @@ test('should be able to update active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); updateActiveSession({ host: UNISWAP_HOST, address: ADDRESS_1 }); expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress).toStrictEqual(ADDRESS_1); @@ -110,9 +110,9 @@ test('should be able to update active session', async () => { test('should be able to update active session network', async () => { const { updateActiveSessionNetwork } = useAppSessionsStore.getState(); - updateActiveSessionNetwork({ host: UNISWAP_HOST, network: Network.base }); + updateActiveSessionNetwork({ host: UNISWAP_HOST, chainId: ChainId.base }); const activeSessionAddress = useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress; - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(Network.base); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(ChainId.base); }); test('should be able to update session network', async () => { @@ -121,9 +121,9 @@ test('should be able to update session network', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.zora, + chainId: ChainId.zora, }); - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(Network.zora); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(ChainId.zora); }); test('should be able to clear all sessions', async () => { @@ -139,14 +139,14 @@ test('should be able to check if host has an active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.mainnet, + chainId: ChainId.mainnet, }); const activeSession = getActiveSession({ host: UNISWAP_HOST }); expect(activeSession).toStrictEqual({ activeSessionAddress: ADDRESS_1, host: UNISWAP_HOST, sessions: { - '0x123': Network.mainnet, + '0x123': ChainId.mainnet, }, url: UNISWAP_URL, }); @@ -157,13 +157,13 @@ test('should be able to update session chain id', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - network: Network.arbitrum, + chainId: ChainId.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: Network.arbitrum }, + sessions: { [ADDRESS_1]: ChainId.arbitrum }, activeSessionAddress: ADDRESS_1, }, }); diff --git a/src/state/appSessions/index.ts b/src/state/appSessions/index.ts index 46ae89ee9b3..5a2ae7dd35c 100644 --- a/src/state/appSessions/index.ts +++ b/src/state/appSessions/index.ts @@ -1,25 +1,32 @@ import { Address } from 'viem'; -import { Network } from '@/networks/types'; +import { Network, ChainId, networkToIdMapping } from '@/networks/types'; import { createRainbowStore } from '../internal/createRainbowStore'; -export interface AppSession { +export interface AppSessionV0 { activeSessionAddress: Address; host: string; sessions: Record; url: string; } -export interface AppSessionsStore { +export interface AppSession { + activeSessionAddress: Address; + host: string; + sessions: Record; + url: string; +} + +export interface AppSessionsStore { appSessions: Record; getActiveSession: ({ host }: { host: string }) => AppSession; removeAddressSessions: ({ address }: { address: Address }) => void; - addSession: ({ host, address, network, url }: { host: string; address: Address; network: Network; url: string }) => void; - removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; network: Network } | null; + addSession: ({ host, address, chainId, url }: { host: string; address: Address; chainId: ChainId; url: string }) => void; + removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; chainId: ChainId } | null; removeAppSession: ({ host }: { host: string }) => void; updateActiveSession: ({ host, address }: { host: string; address: Address }) => void; - updateActiveSessionNetwork: ({ host, network }: { host: string; network: Network }) => void; - updateSessionNetwork: ({ address, host, network }: { address: Address; host: string; network: Network }) => void; + updateActiveSessionNetwork: ({ host, chainId }: { host: string; chainId: ChainId }) => void; + updateSessionNetwork: ({ address, host, chainId }: { address: Address; host: string; chainId: ChainId }) => void; clearSessions: () => void; } @@ -47,18 +54,18 @@ export const useAppSessionsStore = createRainbowStore { + addSession: ({ host, address, chainId, url }) => { const appSessions = get().appSessions; const existingSession = appSessions[host]; if (!existingSession || !existingSession.sessions) { appSessions[host] = { host, - sessions: { [address]: network }, + sessions: { [address]: chainId }, activeSessionAddress: address, url, }; } else { - appSessions[host].sessions[address] = network; + appSessions[host].sessions[address] = chainId; appSessions[host].activeSessionAddress = address; } set({ @@ -93,7 +100,7 @@ export const useAppSessionsStore = createRainbowStore { + updateActiveSessionNetwork: ({ host, chainId }) => { const appSessions = get().appSessions; const appSession = appSessions[host] || {}; set({ @@ -130,13 +137,13 @@ export const useAppSessionsStore = createRainbowStore { + updateSessionNetwork: ({ host, address, chainId }) => { const appSessions = get().appSessions; const appSession = appSessions[host]; if (!appSession) return; @@ -147,7 +154,7 @@ export const useAppSessionsStore = createRainbowStore { + if (version === 0) { + const oldState = persistedState as AppSessionsStore; + const appSessions: AppSessionsStore['appSessions'] = {}; + for (const [host, session] of Object.entries(oldState.appSessions)) { + const sessions = session.sessions; + const newSessions = Object.keys(sessions).reduce( + (acc, addr) => { + const address = addr as Address; + const network = sessions[address]; + acc[address] = networkToIdMapping[network]; + return acc as Record; + }, + {} as Record + ); + appSessions[host] = { + activeSessionAddress: session.activeSessionAddress, + host: session.host, + sessions: newSessions, + url: session.url, + }; + } + return { + ...oldState, + appSessions, + }; + } + return persistedState as AppSessionsStore; + }, } ); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 90834dde2e0..2a97a31f284 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,11 +1,12 @@ +import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; import { ETH_ADDRESS, SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; -import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; -import { swapsStore } from '../swaps/swapsStore'; +import { swapsStore } from '@/state/swaps/swapsStore'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '../connectedToHardhat'; const SEARCH_CACHE_MAX_ENTRIES = 50; @@ -66,7 +67,7 @@ function serializeUserAssetsState(state: Partial, version?: num version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to serialize state for user assets storage`), { error }); throw error; } } @@ -76,7 +77,7 @@ function deserializeUserAssetsState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to parse serialized state from user assets storage`), { error }); throw error; } @@ -88,7 +89,7 @@ function deserializeUserAssetsState(serializedState: string) { chainBalances = new Map(state.chainBalances); } } catch (error) { - logger.error(new RainbowError('Failed to convert chainBalances from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert chainBalances from user assets storage`), { error }); } let idsByChain = new Map(); @@ -97,7 +98,7 @@ function deserializeUserAssetsState(serializedState: string) { idsByChain = new Map(state.idsByChain); } } catch (error) { - logger.error(new RainbowError('Failed to convert idsByChain from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert idsByChain from user assets storage`), { error }); } let userAssetsData: Map = new Map(); @@ -106,7 +107,7 @@ function deserializeUserAssetsState(serializedState: string) { userAssetsData = new Map(state.userAssets); } } catch (error) { - logger.error(new RainbowError('Failed to convert userAssets from user assets storage'), { error }); + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert userAssets from user assets storage`), { error }); } return { @@ -179,7 +180,6 @@ export const userAssetsStore = createRainbowStore( return filteredIds; } }, - getHighestValueEth: () => { const preferredNetwork = swapsStore.getState().preferredNetwork; const assets = get().userAssets; @@ -278,7 +278,7 @@ export const userAssetsStore = createRainbowStore( }); // Ensure all supported chains are in the map with a fallback value of 0 - SUPPORTED_CHAIN_IDS({ testnetMode: false }).forEach(chainId => { + SUPPORTED_CHAIN_IDS({ testnetMode: useConnectedToHardhatStore.getState().connectedToHardhat }).forEach(chainId => { if (!unsortedChainBalances.has(chainId)) { unsortedChainBalances.set(chainId, 0); idsByChain.set(chainId, []); diff --git a/src/state/browser/browserStore.ts b/src/state/browser/browserStore.ts index f6c7340c3da..041913d506e 100644 --- a/src/state/browser/browserStore.ts +++ b/src/state/browser/browserStore.ts @@ -19,7 +19,7 @@ const lazyPersist = debounce( const serializedValue = serializeBrowserState(value.state, value.version ?? BROWSER_STORAGE_VERSION); browserStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError('Failed to serialize persisted browser data'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to serialize persisted browser data`), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -36,7 +36,7 @@ function serializeBrowserState(state: BrowserState, version: number): string { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to serialize state for browser storage`), { error }); throw error; } } @@ -46,7 +46,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to parse serialized state from browser storage`), { error }); throw error; } @@ -56,7 +56,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { tabsData = new Map(state.tabsData); } catch (error) { - logger.error(new RainbowError('Failed to convert tabsData from browser storage'), { error }); + logger.error(new RainbowError(`[browserStore]: Failed to convert tabsData from browser storage`), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState if (tabData) { tabData.url = persistedUrl; } else { - logger.warn(`No tabData found for tabId ${tabId} during URL restoration`); + logger.warn(`[browserStore]: No tabData found for tabId ${tabId} during URL restoration`); } } } diff --git a/src/state/connectedToHardhat/index.ts b/src/state/connectedToHardhat/index.ts new file mode 100644 index 00000000000..8d6b2c5a4ae --- /dev/null +++ b/src/state/connectedToHardhat/index.ts @@ -0,0 +1,28 @@ +import create from 'zustand'; +import { createRainbowStore } from '../internal/createRainbowStore'; + +export interface ConnectedToHardhatState { + connectedToHardhat: boolean; + setConnectedToHardhat: (connectedToHardhat: boolean) => void; + + connectedToHardhatOp: boolean; + setConnectedToHardhatOp: (connectedToHardhatOp: boolean) => void; +} + +export const useConnectedToHardhatStore = createRainbowStore( + set => ({ + connectedToHardhat: false, + setConnectedToHardhat: connectedToHardhat => { + set({ connectedToHardhat }); + }, + + connectedToHardhatOp: false, + setConnectedToHardhatOp: connectedToHardhatOp => { + set({ connectedToHardhatOp }); + }, + }), + { + storageKey: 'connectedToHardhat', + version: 0, + } +); diff --git a/src/state/featuredResults/featuredResults.ts b/src/state/featuredResults/featuredResults.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts index 3e504ced84e..df1a4d11df0 100644 --- a/src/state/internal/createRainbowStore.ts +++ b/src/state/internal/createRainbowStore.ts @@ -38,6 +38,11 @@ interface RainbowPersistConfig { * @default 0 */ version?: number; + /** + * A function to perform persisted state migration. + * This function will be called when persisted state versions mismatch with the one specified here. + */ + migrate?: (persistedState: unknown, version: number) => S | Promise; } /** @@ -46,7 +51,7 @@ interface RainbowPersistConfig { * @returns An object containing the persist storage and version. */ function createPersistStorage(config: RainbowPersistConfig) { - const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0 } = config; + const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0, migrate } = config; const persistStorage: PersistOptions>['storage'] = { getItem: (name: string) => { @@ -92,7 +97,7 @@ const lazyPersist = ({ name, serializer, storageKey, value }: LazyPersistPara const serializedValue = serializer(value.state, value.version ?? 0); rainbowStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError('Failed to serialize persisted store data'), { error }); + logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize persisted store data`), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -109,7 +114,7 @@ function defaultSerializeState(state: StorageValue>['state'], vers try { return JSON.stringify({ state, version }); } catch (error) { - logger.error(new RainbowError('Failed to serialize Rainbow store data'), { error }); + logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize Rainbow store data`), { error }); throw error; } } @@ -123,7 +128,7 @@ function defaultDeserializeState(serializedState: string): StorageValue( partialize: persistConfig.partialize || (state => state), storage: persistStorage, version, + migrate: persistConfig.migrate, }) ) ); diff --git a/src/state/nonces/index.ts b/src/state/nonces/index.ts index 8383d5ada1e..d9b41e79781 100644 --- a/src/state/nonces/index.ts +++ b/src/state/nonces/index.ts @@ -1,7 +1,7 @@ import create from 'zustand'; import { createStore } from '../internal/createStore'; -import { Network } from '@/networks/types'; -import { getProviderForNetwork } from '@/handlers/web3'; +import { Network, ChainId, networkToIdMapping } from '@/networks/types'; +import { getProvider } from '@/handlers/web3'; type NonceData = { currentNonce?: number; @@ -10,41 +10,49 @@ type NonceData = { type GetNonceArgs = { address: string; - network: Network; + chainId: ChainId; }; type UpdateNonceArgs = NonceData & GetNonceArgs; -export async function getNextNonce({ address, network }: { address: string; network: Network }) { +export async function getNextNonce({ address, chainId }: { address: string; chainId: ChainId }) { const { getNonce } = nonceStore.getState(); - const localNonceData = getNonce({ address, network }); + const localNonceData = getNonce({ address, chainId }); const localNonce = localNonceData?.currentNonce || 0; - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); const txCountIncludingPending = await provider.getTransactionCount(address, 'pending'); if (!localNonce && !txCountIncludingPending) return 0; const ret = Math.max(localNonce + 1, txCountIncludingPending); return ret; } -export interface CurrentNonceState { - nonces: Record>; - setNonce: ({ address, currentNonce, latestConfirmedNonce, network }: UpdateNonceArgs) => void; - getNonce: ({ address, network }: GetNonceArgs) => NonceData | null; +type NoncesV0 = { + [network in Network]: NonceData; +}; + +type Nonces = { + [chainId in ChainId]: NonceData; +}; + +export interface CurrentNonceState { + nonces: Record; + setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }: UpdateNonceArgs) => void; + getNonce: ({ address, chainId }: GetNonceArgs) => NonceData | null; clearNonces: () => void; } -export const nonceStore = createStore( +export const nonceStore = createStore>( (set, get) => ({ nonces: {}, - setNonce: ({ address, currentNonce, latestConfirmedNonce, network }) => { + setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }) => { const { nonces: oldNonces } = get(); - const addressAndChainIdNonces = oldNonces?.[address]?.[network] || {}; + const addressAndChainIdNonces = oldNonces?.[address]?.[chainId] || {}; set({ nonces: { ...oldNonces, [address]: { ...oldNonces[address], - [network]: { + [chainId]: { currentNonce: currentNonce ?? addressAndChainIdNonces?.currentNonce, latestConfirmedNonce: latestConfirmedNonce ?? addressAndChainIdNonces?.latestConfirmedNonce, }, @@ -52,9 +60,9 @@ export const nonceStore = createStore( }, }); }, - getNonce: ({ address, network }) => { + getNonce: ({ address, chainId }) => { const { nonces } = get(); - return nonces[address]?.[network] ?? null; + return nonces[address]?.[chainId] ?? null; }, clearNonces: () => { set({ nonces: {} }); @@ -63,7 +71,26 @@ export const nonceStore = createStore( { persist: { name: 'nonces', - version: 0, + version: 1, + migrate: (persistedState: unknown, version: number) => { + if (version === 0) { + const oldState = persistedState as CurrentNonceState; + const newNonces: CurrentNonceState['nonces'] = {}; + for (const [address, networkNonces] of Object.entries(oldState.nonces)) { + for (const [network, nonceData] of Object.entries(networkNonces)) { + if (!newNonces[address]) { + newNonces[address] = {} as Record; + } + newNonces[address][networkToIdMapping[network as Network]] = nonceData; + } + } + return { + ...oldState, + nonces: newNonces, + }; + } + return persistedState as CurrentNonceState; + }, }, } ); diff --git a/src/state/pendingTransactions/index.ts b/src/state/pendingTransactions/index.ts index fd5ac70063b..3eb8e2db2e5 100644 --- a/src/state/pendingTransactions/index.ts +++ b/src/state/pendingTransactions/index.ts @@ -2,8 +2,8 @@ import { RainbowTransaction, NewTransaction } from '@/entities/transactions'; import { createStore } from '../internal/createStore'; import create from 'zustand'; import { parseNewTransaction } from '@/parsers/transactions'; -import { Network } from '@/networks/types'; import { nonceStore } from '../nonces'; +import { ChainId } from '@/networks/types'; export interface PendingTransactionsState { pendingTransactions: Record; @@ -35,7 +35,7 @@ export const pendingTransactionsStore = createStore( ...currentPendingTransactions, [address]: [ ...addressPendingTransactions.filter(tx => { - if (tx.network === pendingTransaction.network) { + if (tx.chainId === pendingTransaction.chainId) { return tx.nonce !== pendingTransaction.nonce; } return true; @@ -70,11 +70,11 @@ export const usePendingTransactionsStore = create(pendingTransactionsStore); export const addNewTransaction = ({ address, - network, + chainId, transaction, }: { address: string; - network: Network; + chainId: ChainId; transaction: NewTransaction; }) => { const { addPendingTransaction } = pendingTransactionsStore.getState(); @@ -83,18 +83,18 @@ export const addNewTransaction = ({ addPendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - network, + chainId, currentNonce: transaction.nonce, }); }; export const updateTransaction = ({ address, - network, + chainId, transaction, }: { address: string; - network: Network; + chainId: ChainId; transaction: NewTransaction; }) => { const { updatePendingTransaction } = pendingTransactionsStore.getState(); @@ -103,7 +103,7 @@ export const updateTransaction = ({ updatePendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - network, + chainId, currentNonce: transaction.nonce, }); }; diff --git a/src/state/remoteCards/remoteCards.ts b/src/state/remoteCards/remoteCards.ts index bd354a10588..aff2b348b96 100644 --- a/src/state/remoteCards/remoteCards.ts +++ b/src/state/remoteCards/remoteCards.ts @@ -28,7 +28,7 @@ function serializeState(state: Partial, version?: number) { const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys?.id); if (state.cards && validCards.length < state.cards.size) { - logger.error(new RainbowError('remoteCardsStore: filtered cards without sys.id during serialization'), { + logger.error(new RainbowError(`[remoteCardsStore]: filtered cards without sys.id during serialization`), { filteredCount: state.cards.size - validCards.length, }); } @@ -44,7 +44,7 @@ function serializeState(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to serialize state for remote cards storage`), { error }); throw error; } } @@ -54,7 +54,7 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to parse serialized state from remote cards storage`), { error }); throw error; } @@ -66,7 +66,7 @@ function deserializeState(serializedState: string) { cardsByIdData = new Set(state.cardsById.filter(id => typeof id === 'string' && id.length > 0)); } } catch (error) { - logger.error(new RainbowError('Failed to convert cardsById from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cardsById from remote cards storage`), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeState(serializedState: string) { const validCards = state.cards.filter(([, card]) => card && card.sys?.id); if (validCards.length < state.cards.length) { - logger.error(new RainbowError('Filtered out cards without sys.id during deserialization'), { + logger.error(new RainbowError(`[remoteCardsStore]: Filtered out cards without sys.id during deserialization`), { filteredCount: state.cards.length - validCards.length, }); } @@ -84,7 +84,7 @@ function deserializeState(serializedState: string) { cardsData = new Map(validCards); } } catch (error) { - logger.error(new RainbowError('Failed to convert cards from remote cards storage'), { error }); + logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cards from remote cards storage`), { error }); throw error; } diff --git a/src/state/remotePromoSheets/remotePromoSheets.ts b/src/state/remotePromoSheets/remotePromoSheets.ts index 9e364bbf91e..1edd98a3a27 100644 --- a/src/state/remotePromoSheets/remotePromoSheets.ts +++ b/src/state/remotePromoSheets/remotePromoSheets.ts @@ -55,7 +55,7 @@ function serializeState(state: Partial, version?: number version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to serialize state for remote promo sheets storage`), { error }); throw error; } } @@ -65,7 +65,9 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to parse serialized state from remote promo sheets storage`), { + error, + }); throw error; } @@ -77,7 +79,7 @@ function deserializeState(serializedState: string) { sheetsByIdData = new Set(state.sheetsById); } } catch (error) { - logger.error(new RainbowError('Failed to convert sheetsById from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheetsById from remote promo sheets storage`), { error }); throw error; } @@ -87,7 +89,7 @@ function deserializeState(serializedState: string) { sheetsData = new Map(state.sheets); } } catch (error) { - logger.error(new RainbowError('Failed to convert sheets from remote promo sheets storage'), { error }); + logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheets from remote promo sheets storage`), { error }); throw error; } diff --git a/src/state/staleBalances/index.test.ts b/src/state/staleBalances/index.test.ts new file mode 100644 index 00000000000..b07e73acb7b --- /dev/null +++ b/src/state/staleBalances/index.test.ts @@ -0,0 +1,165 @@ +import { Address } from 'viem'; + +import { staleBalancesStore } from '.'; +import { DAI_ADDRESS, OP_ADDRESS } from '@/references'; +import { ETH_ADDRESS } from '@rainbow-me/swaps'; +import { ChainId } from '@/networks/types'; + +const TEST_ADDRESS_1 = '0xFOO'; +const TEST_ADDRESS_2 = '0xBAR'; +const THEN = Date.now() - 700000; +const WHEN = Date.now() + 60000; + +test('should be able to add asset information to the staleBalances object', async () => { + const { addStaleBalance, staleBalances } = staleBalancesStore.getState(); + expect(staleBalances).toStrictEqual({}); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [DAI_ADDRESS]: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should generate accurate stale balance query params and clear expired data - case #1', async () => { + const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + clearExpiredData(TEST_ADDRESS_1); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); + +test('should be able to remove expired stale balance and preserve unexpired data', async () => { + const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS as Address, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + clearExpiredData(TEST_ADDRESS_1); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should preserve data from other addresses when clearing expired data', async () => { + const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.mainnet, + info: { + address: DAI_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: THEN, + }, + }); + addStaleBalance({ + address: TEST_ADDRESS_2, + chainId: ChainId.mainnet, + info: { + address: ETH_ADDRESS as Address, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + clearExpiredData(TEST_ADDRESS_1); + const newStaleBalances = staleBalancesStore.getState().staleBalances; + expect(newStaleBalances).toStrictEqual({ + [TEST_ADDRESS_1]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + [TEST_ADDRESS_2]: { + [ChainId.mainnet]: { + [ETH_ADDRESS]: { + address: ETH_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }, + }, + }); +}); + +test('should generate accurate stale balance query params and clear expired data - case #2', async () => { + const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + clearExpiredData(TEST_ADDRESS_2); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_2); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); + +test('should generate accurate stale balance query params and clear expired data - case #3', async () => { + const { addStaleBalance, getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); + addStaleBalance({ + address: TEST_ADDRESS_1, + chainId: ChainId.optimism, + info: { + address: OP_ADDRESS, + transactionHash: '0xFOOBAR', + expirationTime: WHEN, + }, + }); + + clearExpiredData(TEST_ADDRESS_1); + const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); + expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}&token=${ChainId.optimism}.${OP_ADDRESS}`); + + clearExpiredData(TEST_ADDRESS_2); + const queryParam2 = getStaleBalancesQueryParam(TEST_ADDRESS_2); + expect(queryParam2).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); +}); diff --git a/src/state/staleBalances/index.ts b/src/state/staleBalances/index.ts new file mode 100644 index 00000000000..8a9928aaacb --- /dev/null +++ b/src/state/staleBalances/index.ts @@ -0,0 +1,99 @@ +import { createRainbowStore } from '../internal/createRainbowStore'; + +const TIME_TO_WATCH = 600000; + +interface StaleBalanceInfo { + address: string; + expirationTime?: number; + transactionHash: string; +} + +interface StaleBalances { + [key: string]: StaleBalanceInfo; +} +interface StaleBalancesByChainId { + [key: number]: StaleBalances; +} + +export interface StaleBalancesState { + addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => void; + clearExpiredData: (address: string) => void; + getStaleBalancesQueryParam: (address: string) => string; + staleBalances: Record; +} + +export const staleBalancesStore = createRainbowStore( + (set, get) => ({ + addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => { + set(state => { + const { staleBalances } = state; + const staleBalancesForUser = staleBalances[address] || {}; + const staleBalancesForChain = staleBalancesForUser[chainId] || {}; + const newStaleBalancesForChain = { + ...staleBalancesForChain, + [info.address]: { + ...info, + expirationTime: info.expirationTime || Date.now() + TIME_TO_WATCH, + }, + }; + const newStaleBalancesForUser = { + ...staleBalancesForUser, + [chainId]: newStaleBalancesForChain, + }; + return { + staleBalances: { + ...staleBalances, + [address]: newStaleBalancesForUser, + }, + }; + }); + }, + clearExpiredData: (address: string) => { + set(state => { + const { staleBalances } = state; + const staleBalancesForUser = staleBalances[address] || {}; + const newStaleBalancesForUser: StaleBalancesByChainId = { + ...staleBalancesForUser, + }; + for (const c of Object.keys(staleBalancesForUser)) { + const chainId = parseInt(c, 10); + const newStaleBalancesForChain = { + ...(staleBalancesForUser[chainId] || {}), + }; + for (const staleBalance of Object.values(newStaleBalancesForChain)) { + if (typeof staleBalance.expirationTime === 'number' && staleBalance.expirationTime <= Date.now()) { + delete newStaleBalancesForChain[staleBalance.address]; + } + } + newStaleBalancesForUser[chainId] = newStaleBalancesForChain; + } + return { + staleBalances: { + ...staleBalances, + [address]: newStaleBalancesForUser, + }, + }; + }); + }, + getStaleBalancesQueryParam: (address: string) => { + let queryStringFragment = ''; + const { staleBalances } = get(); + const staleBalancesForUser = staleBalances[address]; + for (const c of Object.keys(staleBalancesForUser)) { + const chainId = parseInt(c, 10); + const staleBalancesForChain = staleBalancesForUser[chainId]; + for (const staleBalance of Object.values(staleBalancesForChain)) { + if (typeof staleBalance.expirationTime === 'number') { + queryStringFragment += `&token=${chainId}.${staleBalance.address}`; + } + } + } + return queryStringFragment; + }, + staleBalances: {}, + }), + { + storageKey: 'staleBalances', + version: 0, + } +); diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index ac5fb83d92f..df49c0f32a2 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -2,7 +2,7 @@ import { MIN_FLASHBOTS_PRIORITY_FEE } from '@/__swaps__/screens/Swap/constants'; import { getCustomGasSettings, setCustomMaxPriorityFee } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { getSelectedGasSpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId } from '@/networks/types'; import { GasSpeed } from '@/__swaps__/types/gas'; import { RecentSwap } from '@/__swaps__/types/swap'; import { getCachedGasSuggestions } from '@/__swaps__/utils/meteorology'; @@ -66,7 +66,7 @@ function serialize(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError('Failed to serialize state for swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to serialize state for swaps storage`), { error }); throw error; } } @@ -76,7 +76,7 @@ function deserialize(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError('Failed to parse serialized state from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to parse serialized state from swaps storage`), { error }); throw error; } @@ -88,7 +88,7 @@ function deserialize(serializedState: string) { recentSwaps = new Map(state.recentSwaps); } } catch (error) { - logger.error(new RainbowError('Failed to convert recentSwaps from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to convert recentSwaps from swaps storage`), { error }); } let latestSwapAt: Map = new Map(); @@ -97,7 +97,7 @@ function deserialize(serializedState: string) { latestSwapAt = new Map(state.latestSwapAt); } } catch (error) { - logger.error(new RainbowError('Failed to convert latestSwapAt from swaps storage'), { error }); + logger.error(new RainbowError(`[swapsStore]: Failed to convert latestSwapAt from swaps storage`), { error }); } return { diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index f4573bb7834..4c8c379c775 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -1,23 +1,25 @@ -import { memo } from 'react'; import { Address } from 'viem'; import { useAccountSettings } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/__swaps__/types/chains'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; +import { ChainId } from '@/networks/types'; +import { useConnectedToHardhatStore } from '../connectedToHardhat'; -export const UserAssetsSync = memo(function UserAssetsSync() { +export const UserAssetsSync = function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const userAssetsWalletAddress = userAssetsStore(state => state.associatedWalletAddress); const isSwapsOpen = useSwapsStore(state => state.isSwapsOpen); + const { connectedToHardhat } = useConnectedToHardhatStore(); useUserAssets( { address: currentAddress as Address, currency: currentCurrency, + testnetMode: connectedToHardhat, }, { enabled: !isSwapsOpen || userAssetsWalletAddress !== currentAddress, @@ -41,4 +43,4 @@ export const UserAssetsSync = memo(function UserAssetsSync() { ); return null; -}); +}; diff --git a/src/storage/legacy.ts b/src/storage/legacy.ts index 2246c2bc2c4..64adfbec6e6 100644 --- a/src/storage/legacy.ts +++ b/src/storage/legacy.ts @@ -32,7 +32,7 @@ class LegacyStorage extends Storage; @@ -186,18 +187,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; let networkColors = { - arbitrum: '#2D374B', - base: '#0052FF', - goerli: '#f6c343', - gnosis: '#479E9C', - mainnet: '#25292E', - optimism: '#FF4040', - polygon: '#8247E5', - bsc: '#F0B90B', - zora: '#2B5DF0', - avalanche: '#E84142', - degen: '#A36EFD', - blast: '#25292E', + [ChainId.arbitrum]: '#2D374B', + [ChainId.base]: '#0052FF', + [ChainId.goerli]: '#f6c343', + [ChainId.gnosis]: '#479E9C', + [ChainId.mainnet]: '#25292E', + [ChainId.optimism]: '#FF4040', + [ChainId.polygon]: '#8247E5', + [ChainId.bsc]: '#F0B90B', + [ChainId.zora]: '#2B5DF0', + [ChainId.avalanche]: '#E84142', + [ChainId.degen]: '#A36EFD', + [ChainId.blast]: '#25292E', }; let gradients = { @@ -328,18 +329,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; networkColors = { - arbitrum: '#ADBFE3', - base: '#3979FF', - goerli: '#f6c343', - gnosis: '#479E9C', - mainnet: '#E0E8FF', - optimism: '#FF6A6A', - polygon: '#A275EE', - bsc: '#F0B90B', - zora: '#6183F0', - avalanche: '#FF5D5E', - degen: '#A36EFD', - blast: '#FCFC03', + [ChainId.arbitrum]: '#ADBFE3', + [ChainId.base]: '#3979FF', + [ChainId.goerli]: '#f6c343', + [ChainId.gnosis]: '#479E9C', + [ChainId.mainnet]: '#E0E8FF', + [ChainId.optimism]: '#FF6A6A', + [ChainId.polygon]: '#A275EE', + [ChainId.bsc]: '#F0B90B', + [ChainId.zora]: '#6183F0', + [ChainId.avalanche]: '#FF5D5E', + [ChainId.degen]: '#A36EFD', + [ChainId.blast]: '#FCFC03', }; } diff --git a/src/utils/actionsheet.ts b/src/utils/actionsheet.ts index 0b524e532d1..30b38eac3d9 100644 --- a/src/utils/actionsheet.ts +++ b/src/utils/actionsheet.ts @@ -3,10 +3,10 @@ import ActionSheet from 'react-native-action-sheet'; export default function showActionSheetWithOptions(...args: any[]) { if (ios) { - //@ts-ignore + // @ts-ignore ActionSheetIOS.showActionSheetWithOptions(...args); } else { - //@ts-ignore + // @ts-ignore ActionSheet.showActionSheetWithOptions(...args); } } diff --git a/src/utils/bluetoothPermissions.ts b/src/utils/bluetoothPermissions.ts index 7983fd1fc8f..48373a56cd3 100644 --- a/src/utils/bluetoothPermissions.ts +++ b/src/utils/bluetoothPermissions.ts @@ -60,7 +60,7 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { ]; const res = await checkForMultiplePermissions(ANDROID_BT_PERMISSION); - logger.debug('[Bluetooth] Android Permission status: ', { res }); + logger.debug('[bluetoothPermissions]: Android Permission status: ', { res }); const deniedPermissions: AndroidPermission[] = []; @@ -72,13 +72,13 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { } if (deniedPermissions.length === 0) { - logger.debug('[Bluetooth] Android Permissions all granted'); + logger.debug('[bluetoothPermissions]: Android Permissions all granted'); return true; } // if we're only missing one, only request one else if (deniedPermissions.length === 1) { const askResult = await requestPermission(deniedPermissions[0]); - logger.debug('[Bluetooth] Android Permission single askResult: ', { + logger.debug('[bluetoothPermissions]: Android Permission single askResult: ', { askResult, }); if (askResult === RESULTS.GRANTED) { @@ -97,7 +97,7 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { // else request in a group } else if (deniedPermissions.length > 1) { const askResults = await requestMultiplePermissions(deniedPermissions); - logger.debug('[Bluetooth] Android Bluetooth Permission multiple askResult: ', { askResults }); + logger.debug('[bluetoothPermissions]: Android Bluetooth Permission multiple askResult: ', { askResults }); const deniedOrBlockedPermissions: AndroidPermission[] = []; // check if we are missing any permissions diff --git a/src/utils/branch.ts b/src/utils/branch.ts index 474eaca32a6..f7c50bb79fe 100644 --- a/src/utils/branch.ts +++ b/src/utils/branch.ts @@ -8,7 +8,7 @@ import * as ls from '@/storage'; import { logger, RainbowError } from '@/logger'; export const branchListener = async (handleOpenLinkingURL: (url: any) => void) => { - logger.debug(`Branch: setting up listener`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: setting up listener`, {}, logger.DebugContext.deeplinks); /* * This is run every time the app is opened, whether from a cold start of from the background. @@ -19,30 +19,30 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = case 'Trouble reaching the Branch servers, please try again shortly.': break; default: - logger.error(new RainbowError('Branch: error when handling event'), { + logger.error(new RainbowError(`[branchListener]: error when handling event`), { error, }); } } - logger.debug(`Branch: handling event`, { params, uri }, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event`, { params, uri }, logger.DebugContext.deeplinks); if (!params && uri) { - logger.debug(`Branch: no params but we have a URI`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: no params but we have a URI`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } else if (!params) { // We got absolutely nothing to work with. - logger.warn(`Branch: received no params or URI when handling event`, { + logger.warn(`[branchListener]: received no params or URI when handling event`, { params, uri, }); } else if (params['+non_branch_link']) { const nonBranchUrl = params['+non_branch_link']; - logger.debug(`Branch: handling non-Branch link`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling non-Branch link`, {}, logger.DebugContext.deeplinks); if (typeof nonBranchUrl === 'string' && nonBranchUrl?.startsWith('rainbow://open')) { - logger.debug(`Branch: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); /** * This happens when the user hits the Branch-hosted fallback page in @@ -64,7 +64,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = handleOpenLinkingURL(url); } } else { - logger.debug(`Branch: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); /** * This can happen when the user clicks on a deeplink and we pass its handling on to Branch. @@ -80,7 +80,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * * No link was opened, so we don't typically need to do anything. */ - logger.debug(`Branch: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); if (IS_TESTING === 'true' && !!uri) { handleOpenLinkingURL(uri); @@ -91,14 +91,14 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * should use `params.uri`. This happens about 8k times per week, so it's * very expected. */ - logger.debug(`Branch: using preferred URI value from params`, { + logger.debug(`[branchListener]: using preferred URI value from params`, { params, uri, }); handleOpenLinkingURL(params.uri); } else if (uri) { - logger.debug(`Branch: handling event default case`, {}, logger.DebugContext.deeplinks); + logger.debug(`[branchListener]: handling event default case`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } @@ -112,7 +112,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = .getFirstReferringParams() .then(branchParams => branchParams) .catch(e => { - logger.error(new RainbowError('error calling branch.getFirstReferringParams()'), e); + logger.error(new RainbowError(`[branchListener]: error calling branch.getFirstReferringParams()`), e); return null; }); diff --git a/src/utils/contenthash.ts b/src/utils/contenthash.ts index 6ae3225ba73..948ad38ab13 100644 --- a/src/utils/contenthash.ts +++ b/src/utils/contenthash.ts @@ -12,7 +12,7 @@ export function encodeContenthash(text: string) { let encoded = ''; let error; if (text) { - let matched = matchProtocol(text); + const matched = matchProtocol(text); if (matched) { contentType = matched[1]; content = matched[2]; diff --git a/src/utils/deviceUtils.ts b/src/utils/deviceUtils.ts index efa5290c1f5..e4186a4366e 100644 --- a/src/utils/deviceUtils.ts +++ b/src/utils/deviceUtils.ts @@ -1,8 +1,10 @@ -import { Dimensions, PixelRatio, Platform } from 'react-native'; +import { Dimensions, PixelRatio, Platform, NativeModules } from 'react-native'; +const { NavbarHeight } = NativeModules; -import { IS_IOS } from '@/env'; +import { IS_ANDROID, IS_IOS } from '@/env'; const { height, width } = Dimensions.get('window'); +const scale = Dimensions.get('screen').scale; const deviceUtils = (function () { const iPhone15ProHeight = 852, @@ -39,5 +41,10 @@ const deviceUtils = (function () { export const DEVICE_WIDTH = deviceUtils.dimensions.width; export const DEVICE_HEIGHT = deviceUtils.dimensions.height; export const PIXEL_RATIO = PixelRatio.get(); - +export const NAVIGATION_BAR_HEIGHT = IS_ANDROID ? NavbarHeight.getNavigationBarHeight() / scale : 0; export default deviceUtils; + +export const isUsingButtonNavigation = () => { + if (!IS_ANDROID) return false; + return NAVIGATION_BAR_HEIGHT > 40; +}; diff --git a/src/utils/doesWalletsContainAddress.ts b/src/utils/doesWalletsContainAddress.ts index 4929399d672..088a7bb2953 100644 --- a/src/utils/doesWalletsContainAddress.ts +++ b/src/utils/doesWalletsContainAddress.ts @@ -5,7 +5,7 @@ export default function doesWalletsContainAddress({ address, wallets }: { addres for (let i = 0; i < Object.keys(wallets).length; i++) { const key = Object.keys(wallets)[i]; const someWallet = wallets[key]; - const found = someWallet.addresses.find((account: any) => account.visible && account.address !== address); + const found = someWallet.addresses?.find((account: any) => account.visible && account.address !== address); if (found) { return { key, wallet: found }; diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index f0f685191f4..00ac9f63577 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -24,8 +24,7 @@ import { SelectedGasFee, } from '@/entities'; import { getOnchainAssetBalance } from '@/handlers/assets'; -import { getIsHardhatConnected, getProviderForNetwork, isTestnetNetwork, toHex } from '@/handlers/web3'; -import { Network } from '@/helpers/networkTypes'; +import { getProvider, isTestnetChain, toHex } from '@/handlers/web3'; import { convertRawAmountToDecimalFormat, fromWei, greaterThan, isZero, subtract, add } from '@/helpers/utilities'; import { Navigation } from '@/navigation'; import { parseAssetNative } from '@/parsers'; @@ -43,32 +42,40 @@ import { import Routes from '@/navigation/routesNames'; import { logger, RainbowError } from '@/logger'; import { IS_IOS } from '@/env'; -import { RainbowNetworks, getNetworkObj, getNetworkObject } from '@/networks'; +import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; import { externalTokenQueryKey, FormattedExternalAsset, fetchExternalToken, useExternalToken, } from '@/resources/assets/externalAssetsQuery'; -import { ChainId } from '@/__swaps__/types/chains'; +import { ChainId, Network } from '@/networks/types'; +import { AddressOrEth } from '@/__swaps__/types/assets'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -const getNetworkNativeAsset = (chainId: ChainId): ParsedAddressAsset | undefined => { +const getNetworkNativeAsset = ({ chainId }: { chainId: ChainId }) => { const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId); return getAccountAsset(nativeAssetUniqueId); }; -export const getNativeAssetForNetwork = async (chainId: ChainId, address?: EthereumAddress): Promise => { - const network = getNetworkFromChainId(chainId); - const networkNativeAsset = getNetworkNativeAsset(chainId); +export const getNativeAssetForNetwork = async ({ + chainId, + address, +}: { + chainId: ChainId; + address?: EthereumAddress; +}): Promise => { + const networkNativeAsset = getNetworkNativeAsset({ chainId }); const { accountAddress, nativeCurrency } = store.getState().settings; const differentWallet = address?.toLowerCase() !== accountAddress?.toLowerCase(); let nativeAsset = differentWallet ? undefined : networkNativeAsset; // If the asset is on a different wallet, or not available in this wallet if (differentWallet || !nativeAsset) { - const mainnetAddress = getNetworkObject({ chainId })?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; - const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; + const networkObject = getNetworkObject({ chainId }); + const mainnetAddress = networkObject?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; + const nativeAssetAddress = networkObject.nativeCurrency.address as AddressOrEth; const externalAsset = await queryClient.fetchQuery( externalTokenQueryKey({ address: nativeAssetAddress, chainId, currency: nativeCurrency }), @@ -81,20 +88,20 @@ export const getNativeAssetForNetwork = async (chainId: ChainId, address?: Ether // @ts-ignore nativeAsset = { ...externalAsset, - network, - uniqueId: getUniqueId(getNetworkObject({ chainId }).nativeCurrency.address, chainId), - address: getNetworkObject({ chainId }).nativeCurrency.address, - decimals: getNetworkObject({ chainId }).nativeCurrency.decimals, - symbol: getNetworkObject({ chainId }).nativeCurrency.symbol, + network: networkObject.value, + uniqueId: getUniqueId(networkObject.nativeCurrency.address, chainId), + address: networkObject.nativeCurrency.address, + decimals: networkObject.nativeCurrency.decimals, + symbol: networkObject.nativeCurrency.symbol, }; } - const provider = getProviderForNetwork(network); + const provider = getProvider({ chainId }); if (nativeAsset) { nativeAsset.mainnet_address = mainnetAddress; - nativeAsset.address = getNetworkObject({ chainId }).nativeCurrency.address; + nativeAsset.address = networkObject.nativeCurrency.address; - const balance = await getOnchainAssetBalance(nativeAsset, address, network, provider); + const balance = await getOnchainAssetBalance(nativeAsset, address, chainId, provider); if (balance) { const assetWithBalance = { @@ -115,7 +122,7 @@ const getAsset = (accountAssets: Record, uniqueId: E const getUserAssetFromCache = (uniqueId: string) => { const { accountAddress, nativeCurrency } = store.getState().settings; - const connectedToHardhat = getIsHardhatConnected(); + const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; const cache = queryClient.getQueryCache(); @@ -168,11 +175,11 @@ const getAssetPrice = (address: EthereumAddress = ETH_ADDRESS): number => { }; export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { - let address = getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS; + let address = (getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS) as AddressOrEth; let internalChainId = ChainId.mainnet; const { nativeCurrency } = store.getState().settings; if (chainId === ChainId.avalanche || chainId === ChainId.degen) { - address = getNetworkObject({ chainId }).nativeCurrency?.address; + address = getNetworkObject({ chainId }).nativeCurrency?.address as AddressOrEth; internalChainId = chainId; } const { data: nativeAsset } = useExternalToken({ @@ -185,14 +192,14 @@ export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { }; // anotha 1 -const getPriceOfNativeAssetForNetwork = (network: Network) => { - if (network === Network.polygon) { +const getPriceOfNativeAssetForNetwork = ({ chainId }: { chainId: ChainId }) => { + if (chainId === ChainId.polygon) { return getMaticPriceUnit(); - } else if (network === Network.bsc) { + } else if (chainId === ChainId.bsc) { return getBnbPriceUnit(); - } else if (network === Network.avalanche) { + } else if (chainId === ChainId.avalanche) { return getAvaxPriceUnit(); - } else if (network === Network.degen) { + } else if (chainId === ChainId.degen) { return getDegenPriceUnit(); } return getEthPriceUnit(); @@ -274,7 +281,7 @@ const getDataString = (func: string, arrVals: string[]) => { * @param {Number} chainId */ export const getNetworkFromChainId = (chainId: ChainId): Network => { - return RainbowNetworks.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; + return RainbowNetworkObjects.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; }; /** @@ -282,7 +289,7 @@ export const getNetworkFromChainId = (chainId: ChainId): Network => { * @param {Number} chainId */ const getNetworkNameFromChainId = (chainId: ChainId): string => { - return RainbowNetworks.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; + return RainbowNetworkObjects.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; }; /** @@ -290,20 +297,20 @@ const getNetworkNameFromChainId = (chainId: ChainId): string => { * @param {String} network */ const getChainIdFromNetwork = (network?: Network): ChainId => { - return network ? getNetworkObj(network).id : ChainId.mainnet; + return RainbowNetworkObjects.find(networkObject => networkObject.value === network)?.id || ChainId.mainnet; }; /** * @desc get etherscan host from network string * @param {String} network */ -function getEtherscanHostForNetwork(chainId: ChainId): string { +function getEtherscanHostForNetwork({ chainId }: { chainId: ChainId }): string { const base_host = 'etherscan.io'; const networkObject = getNetworkObject({ chainId }); const blockExplorer = networkObject.blockExplorers?.default?.url; const network = networkObject.network as Network; - if (network && isTestnetNetwork(network)) { + if (network && isTestnetChain({ chainId })) { return `${network}.${base_host}`; } else { return blockExplorer || base_host; @@ -375,11 +382,11 @@ export const getFirstTransactionTimestamp = async (address: EthereumAddress): Pr return timestamp ? timestamp * 1000 : undefined; }; -function getBlockExplorer(chainId: ChainId) { +function getBlockExplorer({ chainId }: { chainId: ChainId }) { return getNetworkObject({ chainId }).blockExplorers?.default.name || 'etherscan'; } -function openAddressInBlockExplorer(address: EthereumAddress, chainId: ChainId) { +function openAddressInBlockExplorer({ address, chainId }: { address: EthereumAddress; chainId: ChainId }) { const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/address/${address}`); } @@ -425,7 +432,7 @@ async function parseEthereumUrl(data: string) { if (!functionName) { // Send native asset const chainId = getChainIdFromNetwork(network); - asset = getNetworkNativeAsset(chainId); + asset = getNetworkNativeAsset({ chainId }); // @ts-ignore if (!asset || asset?.balance.amount === 0) { @@ -469,17 +476,17 @@ export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) = export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => `${address}_${chainId}`; -export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: EthereumAddress; chainId: ChainId } => { +export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: AddressOrEth; chainId: ChainId } => { const parts = uniqueId.split('_'); // If the unique ID does not contain '_', it's a mainnet address if (parts.length === 1) { - return { address: parts[0], chainId: ChainId.mainnet }; + return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; } // If the unique ID contains '_', the last part is the network and the rest is the address const network = parts[1] as Network; // Assuming the last part is a valid Network enum value - const address = parts[0]; + const address = parts[0] as AddressOrEth; const chainId = getChainIdFromNetwork(network); return { address, chainId }; @@ -522,7 +529,7 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const l1FeeInWei = await OVM_GasPriceOracle.getL1Fee(serializedTx); return l1FeeInWei; } catch (e: any) { - logger.error(new RainbowError('error calculating l1 fee'), { + logger.error(new RainbowError(`[ethereumUtils]: error calculating l1 fee`), { message: e.message, }); } @@ -530,13 +537,13 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const getBasicSwapGasLimit = (chainId: ChainId) => { switch (chainId) { - case getChainIdFromNetwork(Network.arbitrum): + case ChainId.arbitrum: return ethUnits.basic_swap_arbitrum; - case getChainIdFromNetwork(Network.polygon): + case ChainId.polygon: return ethUnits.basic_swap_polygon; - case getChainIdFromNetwork(Network.bsc): + case ChainId.bsc: return ethUnits.basic_swap_bsc; - case getChainIdFromNetwork(Network.optimism): + case ChainId.optimism: return ethUnits.basic_swap_optimism; default: return ethUnits.basic_swap; diff --git a/src/utils/getTokenMetadata.ts b/src/utils/getTokenMetadata.ts deleted file mode 100644 index fe376441c80..00000000000 --- a/src/utils/getTokenMetadata.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TokenMetadata } from '@/entities/tokens'; -import { omitFlatten } from '@/helpers/utilities'; -import { rainbowTokenList } from '@/references'; - -export default function getTokenMetadata(tokenAddress: string | undefined): Omit | undefined { - return undefined; -} diff --git a/src/utils/getUrlForTrustIconFallback.ts b/src/utils/getUrlForTrustIconFallback.ts index c248639dfc9..49030949001 100644 --- a/src/utils/getUrlForTrustIconFallback.ts +++ b/src/utils/getUrlForTrustIconFallback.ts @@ -1,18 +1,19 @@ +import { ChainId } from '@/networks/types'; import { EthereumAddress } from '@/entities'; -import { Network } from '@/networks/types'; +import ethereumUtils from './ethereumUtils'; -export default function getUrlForTrustIconFallback(address: EthereumAddress, network: Network): string | null { +export default function getUrlForTrustIconFallback(address: EthereumAddress, chainId: ChainId): string | null { if (!address) return null; let networkPath = 'ethereum'; - switch (network) { - case Network.mainnet: + switch (chainId) { + case ChainId.mainnet: networkPath = 'ethereum'; break; - case Network.bsc: + case ChainId.bsc: networkPath = 'smartchain'; break; default: - networkPath = network; + networkPath = ethereumUtils.getNetworkFromChainId(chainId); } return `https://rainbowme-res.cloudinary.com/image/upload/assets/${networkPath}/${address}.png`; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 0ebd4ada6b7..aad92e7e346 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,14 +12,12 @@ export { default as ethereumUtils } from './ethereumUtils'; export { default as formatURLForDisplay } from './formatURLForDisplay'; export { default as gasUtils } from './gas'; export { default as getDominantColorFromImage } from './getDominantColorFromImage'; -export { default as getTokenMetadata } from './getTokenMetadata'; export { getUniqueTokenFormat, getUniqueTokenType } from './uniqueTokens'; export { default as getUrlForTrustIconFallback } from './getUrlForTrustIconFallback'; export { default as haptics } from './haptics'; export { default as isETH } from './isETH'; export { default as isLowerCaseMatch } from './isLowerCaseMatch'; export { default as labelhash } from './labelhash'; -export { default as logger } from './logger'; export { default as magicMemo } from './magicMemo'; export { default as measureText } from './measureText'; export { default as neverRerender } from './neverRerender'; diff --git a/src/utils/languageLocaleToCountry.ts b/src/utils/languageLocaleToCountry.ts new file mode 100644 index 00000000000..b8edace143b --- /dev/null +++ b/src/utils/languageLocaleToCountry.ts @@ -0,0 +1,16 @@ +import { Language } from '@/languages'; + +/** + * Converts a language locale to a country code. + * @param languageLocale - The language locale to convert. + * @returns The country code. + */ +export const languageLocaleToCountry = (languageLocale: keyof typeof Language) => { + const [languageCode, countryCode] = languageLocale.split('_'); + + // e.g. - ES_419 we want to return ES instead of 419 + if (Number(countryCode)) { + return languageCode; + } + return countryCode; +}; diff --git a/src/utils/ledger.ts b/src/utils/ledger.ts index af9bb6cd725..c7da0a7657f 100644 --- a/src/utils/ledger.ts +++ b/src/utils/ledger.ts @@ -25,14 +25,14 @@ export const ledgerErrorHandler = (error: Error) => { return LEDGER_ERROR_CODES.OFF_OR_LOCKED; } if (error.name.includes('Disconnected')) { - logger.error(new RainbowError('[Ledger] - Disconnected Error'), { + logger.error(new RainbowError(`[ledger]: Disconnected Error`), { name: error.name, message: error.message, }); return LEDGER_ERROR_CODES.DISCONNECTED; } - logger.error(new RainbowError('[LedgerConnect] - Unknown Error'), { + logger.error(new RainbowError(`[ledger]: Unknown Error`), { name: error.name, message: error.message, }); @@ -62,12 +62,12 @@ export const checkLedgerConnection = async ({ ethApp .getAddress(path) .then(res => { - logger.info('[checkLedgerConnection] - ledger is ready', {}); + logger.debug(`[ledger]: ledger is ready`, {}); successCallback?.(deviceId); }) .catch(e => { const errorType = ledgerErrorHandler(e); - logger.warn('[checkLedgerConnection] - ledger is not ready', { + logger.warn('[ledger] - ledger is not ready', { errorType: errorType, error: e, }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index c69ff91a70f..00000000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { captureException } from '@sentry/react-native'; -import { QUIET_OLD_LOGGER } from 'react-native-dotenv'; -import sentryUtils from './sentry'; - -/** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ -const Logger = { - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - debug(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}] ⚡⚡⚡ `); - console.log(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - error(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - console.error(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - log(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}]`); - console.log(...args); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - prettyLog() { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const allArgs = Array.prototype.slice.call(arguments).map(arg => { - try { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } else { - return arg; - } - } catch (e) { - return arg; - } - }); - console.log(allArgs.length > 0 ? allArgs : allArgs[0]); // eslint-disable-line no-console - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - sentry(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - const date = new Date().toLocaleTimeString(); - Array.prototype.unshift.call(args, `[${date}]`); - console.log(...args); // eslint-disable-line no-console - } - if (args.length === 1 && typeof args[0] === 'string') { - sentryUtils.addInfoBreadcrumb.apply(null, [args[0]]); - } else { - const safeData = safelyStringifyWithFormat(args[1]); - sentryUtils.addDataBreadcrumb(args[0], safeData); - } - }, - - /** - * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation - */ - warn(...args: any[]) { - if (QUIET_OLD_LOGGER) return; - if (__DEV__) { - console.warn(...args); // eslint-disable-line no-console - } - }, -}; - -const safelyStringifyWithFormat = (data: any) => { - try { - const seen: any = []; - const newData = JSON.stringify( - data, - // Required to ignore cyclic structures - (key, val) => { - if (val != null && typeof val == 'object') { - if (seen.indexOf(val) >= 0) { - return; - } - seen.push(val); - } - return val; - }, - 2 - ); - return { data: newData }; - } catch (e) { - captureException(e); - return {}; - } -}; - -export default Logger; diff --git a/src/utils/memoFn.ts b/src/utils/memoFn.ts index d279ae5feb6..da14394b00e 100644 --- a/src/utils/memoFn.ts +++ b/src/utils/memoFn.ts @@ -27,7 +27,7 @@ export function memoFn( // if no arguments used we just want the developer and run the function as is if (args.length === 0) { if (IS_DEV) { - logger.warn(`memoized function ${fn.name} was called with no arguments`); + logger.warn(`[memoFn]: memoized function ${fn.name} was called with no arguments`); } // Call it anyway to not break stuff @@ -41,7 +41,7 @@ export function memoFn( if (typeof arg !== 'number' && typeof arg !== 'boolean' && typeof arg !== 'string') { if (IS_DEV) { logger.warn( - `memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( + `[memoFn]: memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( args )}. Typeof of ${i + 1} argument is ${typeof arg}` ); @@ -57,8 +57,8 @@ export function memoFn( if (cache.has(key)) { // For debugging - // logger.debug(`Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); - // logger.debug('Total cached', cachedCalls++); + // logger.debug(`[memoFn]: Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); + // logger.debug('[memoFn]: Total cached', cachedCalls++); // return cached result return cache.get(key)!; // we did a check for that key already diff --git a/src/utils/methodRegistry.ts b/src/utils/methodRegistry.ts index 082b1b7c39c..21abc903164 100644 --- a/src/utils/methodRegistry.ts +++ b/src/utils/methodRegistry.ts @@ -1,5 +1,5 @@ import { Contract } from '@ethersproject/contracts'; -import { web3Provider } from '../handlers/web3'; +import { getProvider } from '../handlers/web3'; import namesOverrides from '../references/method-names-overrides.json'; import methodRegistryABI from '../references/method-registry-abi.json'; import { metadataClient } from '@/graphql'; @@ -17,7 +17,8 @@ export const methodRegistryLookupAndParse = async (methodSignatureBytes: any, ch if (data?.contractFunction?.text) { signature = data.contractFunction.text; } else { - const registry = new Contract(METHOD_REGISTRY_ADDRESS, methodRegistryABI, web3Provider); + const provider = getProvider({ chainId }); + const registry = new Contract(METHOD_REGISTRY_ADDRESS, methodRegistryABI, provider); signature = await registry.entries(methodSignatureBytes); } diff --git a/src/utils/poaps.ts b/src/utils/poaps.ts index 555a5a13da3..d0020a031ad 100644 --- a/src/utils/poaps.ts +++ b/src/utils/poaps.ts @@ -20,7 +20,7 @@ export const getPoapAndOpenSheetWithSecretWord = async (secretWord: string, goBa }); } } catch (e) { - logger.warn('Error getting POAP with secret word'); + logger.warn('[poaps]: Error getting POAP with secret word'); } }; @@ -39,6 +39,6 @@ export const getPoapAndOpenSheetWithQRHash = async (qrHash: string, goBack: bool }); } } catch { - logger.warn('Error getting POAP with qrHash'); + logger.warn('[poaps]: Error getting POAP with qrHash'); } }; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 8a6f193a480..52f5a2a334f 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -15,13 +15,15 @@ import { SEND_TRANSACTION } from './signingMethods'; import { handleSessionRequestResponse } from '@/walletConnect'; import ethereumUtils from './ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers'; -import { RainbowNetworks } from '@/networks'; +import { RainbowNetworkObjects } from '@/networks'; import { maybeSignUri } from '@/handlers/imgix'; import { getActiveRoute } from '@/navigation/Navigation'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; +import { Address } from 'viem'; +import { ChainId } from '@/networks/types'; export type RequestSource = 'walletconnect' | 'browser'; @@ -35,9 +37,13 @@ export interface DappConnectionData { address?: string; } -export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): Promise<{ chainId: number; address: string } | Error> => { +export const handleDappBrowserConnectionPrompt = ( + dappData: DappConnectionData +): Promise<{ chainId: ChainId; address: Address } | Error> => { return new Promise((resolve, reject) => { - const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( + network => network.id + ); const receivedTimestamp = Date.now(); const routeParams: WalletconnectApprovalSheetRouteParams = { receivedTimestamp, diff --git a/src/utils/reviewAlert.ts b/src/utils/reviewAlert.ts index 44b3113b6f8..4deef6eb9c9 100644 --- a/src/utils/reviewAlert.ts +++ b/src/utils/reviewAlert.ts @@ -30,7 +30,7 @@ export const numberOfTimesBeforePrompt: { }; export const handleReviewPromptAction = async (action: ReviewPromptAction) => { - logger.debug(`handleReviewPromptAction: ${action}`); + logger.debug(`[reviewAlert]: handleReviewPromptAction: ${action}`); if (IS_TESTING === 'true') { return; @@ -53,10 +53,10 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } const timeOfLastPrompt = ls.review.get(['timeOfLastPrompt']) || 0; - logger.debug(`timeOfLastPrompt: ${timeOfLastPrompt}`); + logger.debug(`[reviewAlert]: timeOfLastPrompt: ${timeOfLastPrompt}`); actionToDispatch.numOfTimesDispatched += 1; - logger.debug(`numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); + logger.debug(`[reviewAlert]: numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); const hasReachedAmount = actionToDispatch.numOfTimesDispatched >= numberOfTimesBeforePrompt[action]; @@ -66,7 +66,7 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } if (hasReachedAmount && timeOfLastPrompt + TWO_MONTHS <= Date.now()) { - logger.debug(`Prompting for review`); + logger.debug(`[reviewAlert]: Prompting for review`); actionToDispatch.numOfTimesDispatched = 0; ls.review.set(['timeOfLastPrompt'], Date.now()); promptForReview(); diff --git a/src/utils/simplifyChartData.ts b/src/utils/simplifyChartData.ts index a6397322946..d5d5e151116 100644 --- a/src/utils/simplifyChartData.ts +++ b/src/utils/simplifyChartData.ts @@ -3,13 +3,13 @@ import { maxBy, minBy } from 'lodash'; export default function simplifyChartData(data: any, destinatedNumberOfPoints: number) { if (!data) return null; - let allSegmentDividers: any = []; + const allSegmentDividers: any = []; let allSegmentsPoints: any = []; - let colors = []; - let lines = []; - let dividers = []; - let lastPoints = []; - let createdLastPoints: any = []; + const colors = []; + const lines = []; + const dividers = []; + const lastPoints = []; + const createdLastPoints: any = []; if (data.segments.length > 0) { for (let i = 0; i < 1; i++) { @@ -25,13 +25,13 @@ export default function simplifyChartData(data: any, destinatedNumberOfPoints: n } } if (allSegmentsPoints.length > destinatedNumberOfPoints) { - let destMul = allSegmentsPoints.length / destinatedNumberOfPoints; + const destMul = allSegmentsPoints.length / destinatedNumberOfPoints; const maxValue = maxBy(allSegmentsPoints, 'y'); const minValue = minBy(allSegmentsPoints, 'y'); const dataDiff = allSegmentsPoints[allSegmentsPoints.length - 1].x - allSegmentsPoints[0].x; const xMul = Math.floor(dataDiff / allSegmentsPoints.length); - let newData = []; + const newData = []; newData.push({ isImportant: true, x: allSegmentsPoints[0].x - xMul * 2, diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index b653aa9406e..4eee7e3a9c6 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -8,7 +8,7 @@ import { isAddress, getAddress } from '@ethersproject/address'; import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; -import { Core } from '@walletconnect/core'; +import WalletConnectCore, { Core } from '@walletconnect/core'; import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -37,16 +37,17 @@ import * as explain from '@/screens/Explain'; import { Box } from '@/design-system'; import { AuthRequestAuthenticateSignature, AuthRequestResponseErrorReason, RPCMethod, RPCPayload } from '@/walletConnect/types'; import { AuthRequest } from '@/walletConnect/sheets/AuthRequest'; -import { getProviderForNetwork } from '@/handlers/web3'; -import { RainbowNetworks } from '@/networks'; +import { getProvider } from '@/handlers/web3'; +import { RainbowNetworkObjects } from '@/networks'; import { uniq } from 'lodash'; import { fetchDappMetadata } from '@/resources/metadata/dapp'; import { DAppStatus } from '@/graphql/__generated__/metadata'; import { handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; import { PerformanceTracking } from '@/performance/tracking'; +import { ChainId } from '@/networks/types'; -const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworks.filter(({ features }) => features.walletconnect).map(({ id }) => id); +const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworkObjects.filter(({ features }) => features.walletconnect).map(({ id }) => id); const SUPPORTED_SESSION_EVENTS = ['chainChanged', 'accountsChanged']; @@ -69,7 +70,7 @@ let hasDeeplinkPendingRedirect = false; * listeners. BE CAREFUL WITH THIS. */ export function setHasPendingDeeplinkPendingRedirect(value: boolean) { - logger.info(`setHasPendingDeeplinkPendingRedirect`, { value }); + logger.debug(`[walletConnect]: setHasPendingDeeplinkPendingRedirect`, { value }); hasDeeplinkPendingRedirect = value; } @@ -95,32 +96,46 @@ export function maybeGoBackAndClearHasPendingRedirect({ delay = 0 }: { delay?: n */ let syncWeb3WalletClient: Awaited> | undefined; -const walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); - -const web3WalletClient = Web3Wallet.init({ - core: walletConnectCore, - metadata: { - name: '🌈 Rainbow', - description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', - url: 'https://rainbow.me', - icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], - redirect: { - native: 'rainbow://wc', - universal: 'https://rnbwapp.com/wc', - }, - }, -}); +let lastConnector: string | undefined = undefined; + +let walletConnectCore: WalletConnectCore | undefined; + +let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; let initPromise: ReturnType<(typeof Web3Wallet)['init']> | null = null; +export const initializeWCv2 = async () => { + walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); + + web3WalletClient = Web3Wallet.init({ + core: walletConnectCore, + metadata: { + name: '🌈 Rainbow', + description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', + url: 'https://rainbow.me', + icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], + redirect: { + native: 'rainbow://wc', + universal: 'https://rnbwapp.com/wc', + }, + }, + }); + return web3WalletClient; +}; + // this function ensures we only initialize the client once export async function getWeb3WalletClient() { if (!syncWeb3WalletClient) { if (!initPromise) { - initPromise = web3WalletClient.then(client => { - syncWeb3WalletClient = client; - return client; - }); + if (web3WalletClient) { + initPromise = web3WalletClient.then(client => { + syncWeb3WalletClient = client; + return client; + }); + } else { + await initializeWCv2(); + return getWeb3WalletClient(); + } } // Wait for the initialization promise to resolve return initPromise; @@ -149,7 +164,11 @@ export function parseRPCParams({ method, params }: RPCPayload): { decodedMessage = toUtf8String(message); } } catch (err) { - logger.debug('WC v2: parsing RPC params unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); + logger.debug( + `[walletConnect]: parsing RPC params unable to decode hex message to UTF8 string`, + {}, + logger.DebugContext.walletconnect + ); } return { @@ -216,7 +235,7 @@ export function getApprovedNamespaces(props: Parameters id === chainId && features.walletconnect); + return !!RainbowNetworkObjects.find(({ id, features }) => id === chainId && features.walletconnect); } /** @@ -300,7 +319,7 @@ async function rejectProposal({ proposal: Web3WalletTypes.SessionProposal; reason: Parameters[0]; }) { - logger.warn(`WC v2: session approval denied`, { + logger.warn(`[walletConnect]: session approval denied`, { reason, proposal, }); @@ -316,9 +335,23 @@ async function rejectProposal({ }); } -export async function pair({ uri, connector }: { uri: string; connector?: string }) { - logger.debug(`WC v2: pair`, { uri }, logger.DebugContext.walletconnect); +// listen for THIS topic pairing, and clear timeout if received +function trackTopicHandler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { + logger.debug(`[walletConnect]: pair: handler`, { proposal }); + const { metadata } = + (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; + + analytics.track(analytics.event.wcNewPairing, { + dappName: metadata.name, + dappUrl: metadata.url, + connector: lastConnector || 'unknown', + }); +} + +export async function pair({ uri, connector }: { uri: string; connector?: string }) { + logger.debug(`[walletConnect]: pair`, { uri }, logger.DebugContext.walletconnect); + lastConnector = connector; /** * Make sure this is cleared if we get multiple pairings in rapid succession */ @@ -326,27 +359,10 @@ export async function pair({ uri, connector }: { uri: string; connector?: string const { topic, ...rest } = parseUri(uri); const client = await getWeb3WalletClient(); - logger.debug(`WC v2: pair: parsed uri`, { topic, rest }); - - // listen for THIS topic pairing, and clear timeout if received - function handler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { - logger.debug(`WC v2: pair: handler`, { proposal }); - - const { metadata } = - (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; - analytics.track(analytics.event.wcNewPairing, { - dappName: metadata.name, - dappUrl: metadata.url, - connector, - }); - } - - // CAN get fired on subsequent pairs, so need to make sure we clean up - client.on('session_proposal', handler); - client.on('auth_request', handler); + logger.debug(`[walletConnect]: pair: parsed uri`, { topic, rest }); // init pairing - await client.core.pairing.pair({ uri }); + await client.pair({ uri }); } export async function initListeners() { @@ -357,13 +373,13 @@ export async function initListeners() { syncWeb3WalletClient = client; - logger.debug(`WC v2: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); client.on('session_proposal', onSessionProposal); client.on('session_request', onSessionRequest); client.on('auth_request', onAuthRequest); client.on('session_delete', () => { - logger.debug(`WC v2: session_delete`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_delete`, {}, logger.DebugContext.walletconnect); setTimeout(() => { events.emit('walletConnectV2SessionDeleted'); @@ -382,7 +398,7 @@ export async function initListeners() { /** * Ensure that if the FCM token changes we update the echo server */ - messaging().onTokenRefresh(async token => { + messaging().onTokenRefresh(async (token: string) => { await subscribeToEchoServer({ token, client_id }); }); } else { @@ -393,11 +409,11 @@ export async function initListeners() { * which could be due to network flakiness, SSL server error (has * happened), etc. Things out of our control. */ - logger.warn(`WC v2: FCM token not found, push notifications will not be received`); + logger.warn(`[walletConnect]: FCM token not found, push notifications will not be received`); } } } catch (e) { - logger.error(new RainbowError(`WC v2: initListeners failed`), { error: e }); + logger.error(new RainbowError(`[walletConnect]: initListeners failed`), { error: e }); } } @@ -417,7 +433,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; * should report these to Datadog, and we can leave this as a warn to * continue to monitor. */ - logger.warn(`WC v2: echo server subscription failed`, { + logger.warn(`[walletConnect]: echo server subscription failed`, { error: res.error, }); } @@ -425,7 +441,9 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposal) { try { - logger.debug(`WC v2: session_proposal`, { proposal }, logger.DebugContext.walletconnect); + trackTopicHandler(proposal); + + logger.debug(`[walletConnect]: session_proposal`, { proposal }, logger.DebugContext.walletconnect); const verifiedData = proposal.verifyContext.verified; const receivedTimestamp = Date.now(); @@ -465,7 +483,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa if (approved) { logger.debug( - `WC v2: session approved`, + `[walletConnect]: session approved`, { approved, approvedChainId, @@ -491,7 +509,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa }, }); - logger.debug(`WC v2: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); try { if (namespaces.success) { @@ -511,7 +529,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa // let the ConnectedDappsSheet know we've got a new one events.emit('walletConnectV2SessionCreated'); - logger.debug(`WC v2: session created`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session created`, {}, logger.DebugContext.walletconnect); analytics.track(analytics.event.wcNewSessionApproved, { dappName: proposer.metadata.name, @@ -553,7 +571,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa title: lang.t(lang.l.walletconnect.connection_failed), }); - logger.error(new RainbowError(`WC v2: session approval failed`), { + logger.error(new RainbowError(`[walletConnect]: session approval failed`), { error: (e as Error).message, }); } @@ -574,7 +592,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa ); } catch (error) { logger.error( - new RainbowError(`WC v2: session request catch all`, { + new RainbowError(`[walletConnect]: session request catch all`, { ...(error as Error), }) ); @@ -586,12 +604,12 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se setHasPendingDeeplinkPendingRedirect(true); const client = await getWeb3WalletClient(); - logger.debug(`WC v2: session_request`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_request`, {}, logger.DebugContext.walletconnect); const { id, topic } = event; const { method, params } = event.params.request; - logger.debug(`WC v2: session_request method`, { method, params }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: session_request method`, { method, params }, logger.DebugContext.walletconnect); // we allow eth sign for connections but we dont want to support actual singing if (method === RPCMethod.Sign) { @@ -614,19 +632,23 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se params, }); if (!address) { - logger.error(new RainbowError('No Address in the RPC Params')); + logger.error(new RainbowError('[walletConnect]: No Address in the RPC Params')); return; } const allWallets = store.getState().wallets.wallets; - logger.debug(`WC v2: session_request method is supported`, { method, params, address, message }, logger.DebugContext.walletconnect); + logger.debug( + `[walletConnect]: session_request method is supported`, + { method, params, address, message }, + logger.DebugContext.walletconnect + ); if (isSigningMethod) { - logger.debug(`WC v2: validating session_request signing method`); + logger.debug(`[walletConnect]: validating session_request signing method`); if (!address || !message) { - logger.error(new RainbowError(`WC v2: session_request exited, signing request had no address and/or messsage`), { + logger.error(new RainbowError(`[walletConnect]: session_request exited, signing request had no address and/or messsage`), { address, message, }); @@ -647,7 +669,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // for TS only, should never happen if (!allWallets) { - logger.error(new RainbowError(`WC v2: allWallets is null, this should never happen`)); + logger.error(new RainbowError(`[walletConnect]: allWallets is null, this should never happen`)); return; } @@ -655,7 +677,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const isReadOnly = selectedWallet?.type === WalletTypes.readOnly; if (!selectedWallet || isReadOnly) { - logger.error(new RainbowError(`WC v2: session_request exited, selectedWallet was falsy or read only`), { + logger.error(new RainbowError(`[walletConnect]: session_request exited, selectedWallet was falsy or read only`), { selectedWalletType: selectedWallet?.type, }); @@ -682,7 +704,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // mostly a TS guard, pry won't happen if (!session) { - logger.error(new RainbowError(`WC v2: session_request topic was not found`)); + logger.error(new RainbowError(`[walletConnect]: session_request topic was not found`)); await client.respondSessionRequest({ topic, @@ -695,9 +717,9 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const { nativeCurrency, network } = store.getState().settings; const chainId = Number(event.params.chainId.split(':')[1]); - logger.debug(`WC v2: getting session for topic`, { session }); + logger.debug(`[walletConnect]: getting session for topic`, { session }); - logger.debug(`WC v2: handling request`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handling request`, {}, logger.DebugContext.walletconnect); const dappNetwork = ethereumUtils.getNetworkFromChainId(chainId); const displayDetails = getRequestDisplayDetails(event.params.request, nativeCurrency, dappNetwork); @@ -749,7 +771,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); saveLocalRequests(updatedRequests, address, network); - logger.debug(`WC v2: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); handleWalletConnectRequest(request); @@ -759,7 +781,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); } } else { - logger.error(new RainbowError(`WC v2: received unsupported session_request RPC method`), { + logger.error(new RainbowError(`[walletConnect]: received unsupported session_request RPC method`), { method, }); @@ -769,7 +791,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se response: formatJsonRpcError(id, `Method ${method} not supported`), }); } catch (e) { - logger.error(new RainbowError(`WC v2: error rejecting session_request`), { + logger.error(new RainbowError(`[walletConnect]: error rejecting session_request`), { error: (e as Error).message, }); } @@ -794,7 +816,7 @@ export async function handleSessionRequestResponse( }, { result, error }: { result: string | null; error: any } ) { - logger.info(`WC v2: handleSessionRequestResponse`, { + logger.debug(`[walletConnect]: handleSessionRequestResponse`, { success: Boolean(result), }); @@ -805,14 +827,14 @@ export async function handleSessionRequestResponse( topic, response: formatJsonRpcResult(id, result), }; - logger.debug(`WC v2: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } else { const payload = { topic, response: formatJsonRpcError(id, error), }; - logger.debug(`WC v2: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } @@ -820,9 +842,11 @@ export async function handleSessionRequestResponse( } export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { + trackTopicHandler(event); + const client = await getWeb3WalletClient(); - logger.debug(`WC v2: auth_request`, { event }, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: auth_request`, { event }, logger.DebugContext.walletconnect); const authenticate: AuthRequestAuthenticateSignature = async ({ address }) => { try { @@ -855,11 +879,11 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { * encapsulate reused code. */ const loadWalletAndSignMessage = async () => { - const provider = getProviderForNetwork(); + const provider = getProvider({ chainId: ChainId.arbitrum }); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, provider }); if (!wallet) { - logger.error(new RainbowError(`WC v2: could not loadWallet to sign auth_request`)); + logger.error(new RainbowError(`[walletConnect]: could not loadWallet to sign auth_request`)); return undefined; } @@ -908,7 +932,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { return { success: true }; } catch (e: any) { - logger.error(new RainbowError(`WC v2: an unknown error occurred when signing auth_request`), { + logger.error(new RainbowError(`[walletConnect]: an unknown error occurred when signing auth_request`), { message: e.message, }); return { success: false, reason: AuthRequestResponseErrorReason.Unknown }; @@ -993,13 +1017,13 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres } } } else { - logger.error(new RainbowError(`WC v2: namespace is missing chains prop when updating`), { + logger.error(new RainbowError(`[walletConnect]: namespace is missing chains prop when updating`), { requiredNamespaces: session.requiredNamespaces, }); } } - logger.debug(`WC v2: updating session`, { + logger.debug(`[walletConnect]: updating session`, { namespaces, }); @@ -1008,7 +1032,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres namespaces, }); } catch (e: any) { - logger.error(new RainbowError(`WC v2: error adding account to session`), { + logger.error(new RainbowError(`[walletConnect]: error adding account to session`), { message: e.message, }); } @@ -1026,12 +1050,12 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { for (const value of Object.values(session.requiredNamespaces)) { if (!value.chains) { - logger.debug(`WC v2: changeAccount, no chains found for namespace`); + logger.debug(`[walletConnect]: changeAccount, no chains found for namespace`); continue; } for (const chainId of value.chains) { - logger.debug(`WC v2: changeAccount, updating accounts for chainId`, { + logger.debug(`[walletConnect]: changeAccount, updating accounts for chainId`, { chainId, }); @@ -1045,15 +1069,15 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { chainId, }); - logger.debug(`WC v2: changeAccount, updated accounts for chainId`, { + logger.debug(`[walletConnect]: changeAccount, updated accounts for chainId`, { chainId, }); } } - logger.debug(`WC v2: changeAccount complete`); + logger.debug(`[walletConnect]: changeAccount complete`); } catch (e: any) { - logger.error(new RainbowError(`WC v2: error changing account`), { + logger.error(new RainbowError(`[walletConnect]: error changing account`), { message: e.message, }); } diff --git a/tsconfig.json b/tsconfig.json index 2f2c192422a..9d8fe1cca5a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,6 @@ "@rainbow-me/model/*": ["src/model/*"], "@rainbow-me/navigation": ["src/navigation"], "@rainbow-me/navigation/*": ["src/navigation/*"], - "@rainbow-me/networkTypes": ["./src/helpers/networkTypes"], "@rainbow-me/parsers": ["src/parsers"], "@rainbow-me/raps": ["src/raps"], "@rainbow-me/react-query": ["./src/react-query"], diff --git a/yarn.lock b/yarn.lock index 94b70d87693..9d7df69de4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -115,6 +115,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/generator@npm:7.25.0" + dependencies: + "@babel/types": "npm:^7.25.0" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/d0e2dfcdc8bdbb5dded34b705ceebf2e0bc1b06795a1530e64fb6a3ccf313c189db7f60c1616effae48114e1a25adc75855bc4496f3779a396b3377bae718ce7 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.0.0, @babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" @@ -355,6 +367,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-replace-supers@npm:7.25.0" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" + "@babel/helper-optimise-call-expression": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-simple-access@npm:7.24.7" @@ -466,6 +491,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3": + version: 7.25.3 + resolution: "@babel/parser@npm:7.25.3" + dependencies: + "@babel/types": "npm:^7.25.2" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/874b01349aedb805d6694f867a752fdc7469778fad76aca4548d2cc6ce96087c3ba5fb917a6f8d05d2d1a74aae309b5f50f1a4dba035f5a2c9fcfe6e106d2c4e + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" @@ -963,7 +999,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": +"@babel/plugin-transform-class-properties@npm:^7.0.0-0, @babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" dependencies: @@ -1006,6 +1042,22 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-classes@npm:^7.0.0-0": + version: 7.25.0 + resolution: "@babel/plugin-transform-classes@npm:7.25.0" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-plugin-utils": "npm:^7.24.8" + "@babel/helper-replace-supers": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" + globals: "npm:^11.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4451dccf8a7979427ae042afe381233f30764a8072faf0de1337a4fc297c6d7cb40df9e28931ac096e5b56392d0cd97d3ce10aee68288150a8701624d362a791 + languageName: node + linkType: hard + "@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.21.5, @babel/plugin-transform-computed-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" @@ -1576,7 +1628,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": +"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.0.0-0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" dependencies: @@ -1887,6 +1939,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b + languageName: node + linkType: hard + "@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.2.3, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.0, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.4.5": version: 7.24.8 resolution: "@babel/traverse@npm:7.24.8" @@ -1905,6 +1968,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.0": + version: 7.25.3 + resolution: "@babel/traverse@npm:7.25.3" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.3" + "@babel/template": "npm:^7.25.0" + "@babel/types": "npm:^7.25.2" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/4c8a1966fa90b53a783a4afd2fcdaa6ab1a912e6621dca9fcc6633e80ccb9491620e88caf73b537da4e16cefd537b548c87d7087868d5b0066414dea375c0e9b + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.1.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.24.9 resolution: "@babel/types@npm:7.24.9" @@ -1916,6 +1994,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/types@npm:7.25.2" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/e489435856be239f8cc1120c90a197e4c2865385121908e5edb7223cfdff3768cba18f489adfe0c26955d9e7bbb1fb10625bc2517505908ceb0af848989bd864 + languageName: node + linkType: hard + "@bankify/react-native-animate-number@npm:0.2.1": version: 0.2.1 resolution: "@bankify/react-native-animate-number@npm:0.2.1" @@ -1943,13 +2032,13 @@ __metadata: languageName: node linkType: hard -"@candlefinance/faster-image@npm:1.5.0": - version: 1.5.0 - resolution: "@candlefinance/faster-image@npm:1.5.0" +"@candlefinance/faster-image@npm:1.6.2": + version: 1.6.2 + resolution: "@candlefinance/faster-image@npm:1.6.2" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/34666d7abb079d13209b9a16adf0758d6fb7d9041e8cb9a112dcca4d8c128f14017a5c34a33eeb93dd08c2f0e5802da134a185d40620225c10dc0049d08a5d79 + checksum: 10c0/d9d5a2ead7351fe5305fe9b298cddd7f7debe8c8d2524faf4e0dc66a5f1dd275f3ef73464b69cda792ce636578a3f5c8ef9a1b047f4e8ae9941001fb5b8f6f26 languageName: node linkType: hard @@ -5495,9 +5584,9 @@ __metadata: languageName: node linkType: hard -"@shopify/react-native-skia@npm:1.3.8": - version: 1.3.8 - resolution: "@shopify/react-native-skia@npm:1.3.8" +"@shopify/react-native-skia@npm:1.3.11": + version: 1.3.11 + resolution: "@shopify/react-native-skia@npm:1.3.11" dependencies: canvaskit-wasm: "npm:0.39.1" react-reconciler: "npm:0.27.0" @@ -5512,7 +5601,7 @@ __metadata: optional: true bin: setup-skia-web: scripts/setup-canvaskit.js - checksum: 10c0/df665797f0948265432a3c3786a9861b117ebf8945702fc2d552c4b6a4e48edb685584050165d382db162e2dcf84f5ee166f46737131342e4e9e93c5f95f1e6b + checksum: 10c0/b069d05ff1bf3599cc16eadc492614ab55b9e9130ba1aac22d83f19e6b7f37e57260471cf8bda86b9bbdebb71834bf4b83f33da2f7c0b18b31df104015b72fef languageName: node linkType: hard @@ -5730,7 +5819,7 @@ __metadata: languageName: node linkType: hard -"@stablelib/x25519@npm:1.0.3, @stablelib/x25519@npm:^1.0.3": +"@stablelib/x25519@npm:1.0.3": version: 1.0.3 resolution: "@stablelib/x25519@npm:1.0.3" dependencies: @@ -6140,27 +6229,7 @@ __metadata: languageName: node linkType: hard -"@types/eslint-scope@npm:^3.7.3": - version: 3.7.7 - resolution: "@types/eslint-scope@npm:3.7.7" - dependencies: - "@types/eslint": "npm:*" - "@types/estree": "npm:*" - checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc - languageName: node - linkType: hard - -"@types/eslint@npm:*": - version: 8.56.10 - resolution: "@types/eslint@npm:8.56.10" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 10c0/674349d6c342c3864d70f4d5a9965f96fb253801532752c8c500ad6a1c2e8b219e01ccff5dc8791dcb58b5483012c495708bb9f3ff929f5c9322b3da126c15d3 - languageName: node - linkType: hard - -"@types/estree@npm:*, @types/estree@npm:^1.0.5": +"@types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d @@ -6242,7 +6311,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -6338,11 +6407,11 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.40 - resolution: "@types/node@npm:18.19.40" + version: 18.19.45 + resolution: "@types/node@npm:18.19.45" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/e91c139cbfa7593d9634fc75fa73de787c35c0ca949f629bf65a66bb0909c6de508b974c5953bf3ab910474847287b92366bed632ac6bc14d78fa81b1ddf049f + checksum: 10c0/79c324176411dcfa92f76b0ffc0673aa4bd8da82d003b44633e927c9493cdc46c35f04c0873b096b23b12bab090a6bbdea21242b3bbb2ea5dc1d9bf72adaa04f languageName: node linkType: hard @@ -6989,28 +7058,27 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/core@npm:2.11.2" +"@walletconnect/core@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/core@npm:2.15.1" dependencies: - "@walletconnect/heartbeat": "npm:1.2.1" - "@walletconnect/jsonrpc-provider": "npm:1.0.13" - "@walletconnect/jsonrpc-types": "npm:1.0.3" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" - "@walletconnect/keyvaluestorage": "npm:^1.1.1" - "@walletconnect/logger": "npm:^2.0.1" - "@walletconnect/relay-api": "npm:^1.0.9" - "@walletconnect/relay-auth": "npm:^1.0.4" - "@walletconnect/safe-json": "npm:^1.0.2" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - events: "npm:^3.3.0" - isomorphic-unfetch: "npm:3.1.0" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:^3.1.0" - checksum: 10c0/a1bd4f028c08b668b5b5ddd9adbf136010b55dea90f1b7203b45c7149acdb7a8f03e1310f4c7bbd9580b6ba163962914a86a5a3360359a32f7932de0963e8cb3 + uint8arrays: "npm:3.1.0" + checksum: 10c0/3c831303bffcc360bb7d2f6e71b9928e73039e34e582e8da3f03dbc67e7876cf3ff89491ec9bad7ae31c3c3706ea6e44d2cd1be501349e882739a5184e917797 languageName: node linkType: hard @@ -7094,17 +7162,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/heartbeat@npm:1.2.1": - version: 1.2.1 - resolution: "@walletconnect/heartbeat@npm:1.2.1" - dependencies: - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/time": "npm:^1.0.2" - tslib: "npm:1.14.1" - checksum: 10c0/5ad46f26dcb7b9b3227f004cd74b18741d4cd32c21825a036eb03985c67a0cf859c285bc5635401966a99129e854d72de3458ff592370575ef7e52f5dd12ebbc - languageName: node - linkType: hard - "@walletconnect/heartbeat@npm:1.2.2, @walletconnect/heartbeat@npm:^1.2.1": version: 1.2.2 resolution: "@walletconnect/heartbeat@npm:1.2.2" @@ -7127,17 +7184,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/jsonrpc-provider@npm:1.0.13": - version: 1.0.13 - resolution: "@walletconnect/jsonrpc-provider@npm:1.0.13" - dependencies: - "@walletconnect/jsonrpc-utils": "npm:^1.0.8" - "@walletconnect/safe-json": "npm:^1.0.2" - tslib: "npm:1.14.1" - checksum: 10c0/9b5b2f0ce516d2ddebe2cd1a2c8ea18a6b765b0d068162caf39745c18534e264a0cc6198adb869ba8684d0efa563be30956a3b9a7cc82b80b9e263f6211e30ab - languageName: node - linkType: hard - "@walletconnect/jsonrpc-provider@npm:1.0.14": version: 1.0.14 resolution: "@walletconnect/jsonrpc-provider@npm:1.0.14" @@ -7149,16 +7195,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/jsonrpc-types@npm:1.0.3": - version: 1.0.3 - resolution: "@walletconnect/jsonrpc-types@npm:1.0.3" - dependencies: - keyvaluestorage-interface: "npm:^1.0.0" - tslib: "npm:1.14.1" - checksum: 10c0/a0fc8a88c62795bf4bf83d4e98a4e2cdd659ef70c73642582089fdf0994c54fd8050aa6cca85cfdcca6b77994e71334895e7a19649c325a8c822b059c2003884 - languageName: node - linkType: hard - "@walletconnect/jsonrpc-types@npm:1.0.4, @walletconnect/jsonrpc-types@npm:^1.0.2, @walletconnect/jsonrpc-types@npm:^1.0.3": version: 1.0.4 resolution: "@walletconnect/jsonrpc-types@npm:1.0.4" @@ -7192,7 +7228,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/keyvaluestorage@npm:1.1.1, @walletconnect/keyvaluestorage@npm:^1.1.1": +"@walletconnect/keyvaluestorage@npm:1.1.1": version: 1.1.1 resolution: "@walletconnect/keyvaluestorage@npm:1.1.1" dependencies: @@ -7233,16 +7269,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/logger@npm:2.0.1": - version: 2.0.1 - resolution: "@walletconnect/logger@npm:2.0.1" - dependencies: - pino: "npm:7.11.0" - tslib: "npm:1.14.1" - checksum: 10c0/1778686f608f03bc8a67fb560a2694e8aef74b392811508e98cc158d1839a1bb0a0256eb2ed719c4ee17e65a11543ddc4f9059d3bdd5dddcca6359ba1bab18bd - languageName: node - linkType: hard - "@walletconnect/logger@npm:2.1.2, @walletconnect/logger@npm:^2.0.1": version: 2.1.2 resolution: "@walletconnect/logger@npm:2.1.2" @@ -7265,13 +7291,13 @@ __metadata: languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/react-native-compat@npm:2.11.2" +"@walletconnect/react-native-compat@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/react-native-compat@npm:2.15.1" dependencies: events: "npm:3.3.0" - fast-text-encoding: "npm:^1.0.6" - react-native-url-polyfill: "npm:^2.0.0" + fast-text-encoding: "npm:1.0.6" + react-native-url-polyfill: "npm:2.0.0" peerDependencies: "@react-native-async-storage/async-storage": "*" "@react-native-community/netinfo": "*" @@ -7280,11 +7306,11 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: 10c0/b8a9d63de7e57078203c08a70f3b406d21582495cbea089160f5fd670d9c4db9afb9dee5507acb534507d4651cd6e1e38231930996cad6d8d5115d9b0d92124a + checksum: 10c0/996452a58797b811b4417c649943602decadb5cbe4dd2717f1aaaed42df0bebcbaeda81e43754d62c319d335702d9e1cb8161e9102ce1bd25354e41588b26b97 languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.10, @walletconnect/relay-api@npm:^1.0.9": +"@walletconnect/relay-api@npm:1.0.10": version: 1.0.10 resolution: "@walletconnect/relay-api@npm:1.0.10" dependencies: @@ -7293,7 +7319,16 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-auth@npm:1.0.4, @walletconnect/relay-auth@npm:^1.0.4": +"@walletconnect/relay-api@npm:1.0.11": + version: 1.0.11 + resolution: "@walletconnect/relay-api@npm:1.0.11" + dependencies: + "@walletconnect/jsonrpc-types": "npm:^1.0.2" + checksum: 10c0/2595d7e68d3a93e7735e0b6204811762898b0ce1466e811d78be5bcec7ac1cde5381637615a99104099165bf63695da5ef9381d6ded29924a57a71b10712a91d + languageName: node + linkType: hard + +"@walletconnect/relay-auth@npm:1.0.4": version: 1.0.4 resolution: "@walletconnect/relay-auth@npm:1.0.4" dependencies: @@ -7323,20 +7358,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/sign-client@npm:2.11.2" +"@walletconnect/sign-client@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/sign-client@npm:2.15.1" dependencies: - "@walletconnect/core": "npm:2.11.2" - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/heartbeat": "npm:1.2.1" + "@walletconnect/core": "npm:2.15.1" + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:^2.0.1" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - events: "npm:^3.3.0" - checksum: 10c0/c6ecf82bb247a5ae72e78a1f122c2458c5f7a36811125223bf394aaac92994db94782775cbbe3957a121c49c404cfd72ed09f648882407ccc1c180618ca6124c + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + events: "npm:3.3.0" + checksum: 10c0/4ff66239c2994cb501cd1830b2cbc2ecf854799cfc4d100cc6af3523f8524daead7c08daa7dba944def79640515ccabe2250f700e0be1a07dfee5c3668aa2dee languageName: node linkType: hard @@ -7360,20 +7395,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/types@npm:2.11.2" - dependencies: - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/heartbeat": "npm:1.2.1" - "@walletconnect/jsonrpc-types": "npm:1.0.3" - "@walletconnect/keyvaluestorage": "npm:^1.1.1" - "@walletconnect/logger": "npm:^2.0.1" - events: "npm:^3.3.0" - checksum: 10c0/3d0027e1b4bb6f281a237e4f887aa1ab36f17d713f8dce5987e5b5ca6890f50025d163fdf874d7057a554bdaaf19305c7afeefa7e7a6512037525263f64c2784 - languageName: node - linkType: hard - "@walletconnect/types@npm:2.13.3": version: 2.13.3 resolution: "@walletconnect/types@npm:2.13.3" @@ -7388,6 +7409,20 @@ __metadata: languageName: node linkType: hard +"@walletconnect/types@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/types@npm:2.15.1" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + events: "npm:3.3.0" + checksum: 10c0/0666874a4acd9326f2554542936a88f6df50ca958440254ab605dca81d57cdaf839b05cac983ad194f4ed44734d07d564f4277156556cadac07302e8dd86aa4d + languageName: node + linkType: hard + "@walletconnect/types@npm:^1.8.0": version: 1.8.0 resolution: "@walletconnect/types@npm:1.8.0" @@ -7395,47 +7430,47 @@ __metadata: languageName: node linkType: hard -"@walletconnect/utils@npm:2.11.2": - version: 2.11.2 - resolution: "@walletconnect/utils@npm:2.11.2" +"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": + version: 2.13.3 + resolution: "@walletconnect/utils@npm:2.13.3" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:^1.0.2" + "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:^1.0.3" - "@walletconnect/relay-api": "npm:^1.0.9" - "@walletconnect/safe-json": "npm:^1.0.2" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/window-getters": "npm:^1.0.1" - "@walletconnect/window-metadata": "npm:^1.0.1" + "@stablelib/x25519": "npm:1.0.3" + "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.13.3" + "@walletconnect/window-getters": "npm:1.0.1" + "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" - uint8arrays: "npm:^3.1.0" - checksum: 10c0/0adec7e6e88805d74b7bd44a12cf6765bae86678dd152dd23f120f5c183e10fad35d8257e1c5ae5a55f9b28fe0e2a9b07486e70280cd07b1267abb899b0a9ec1 + uint8arrays: "npm:3.1.0" + checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": - version: 2.13.3 - resolution: "@walletconnect/utils@npm:2.13.3" +"@walletconnect/utils@npm:2.15.1": + version: 2.15.1 + resolution: "@walletconnect/utils@npm:2.15.1" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/relay-api": "npm:1.0.11" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.3" + "@walletconnect/types": "npm:2.15.1" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad + checksum: 10c0/bde087f530f91502ba26c9553abd464234adb5738c7e10cfbdd275699d630a81601b5928abeeb77abe29e88238a4067a1bd1c5159f04b49178452066c82d99ae languageName: node linkType: hard @@ -7454,19 +7489,19 @@ __metadata: languageName: node linkType: hard -"@walletconnect/web3wallet@npm:1.10.2": - version: 1.10.2 - resolution: "@walletconnect/web3wallet@npm:1.10.2" +"@walletconnect/web3wallet@npm:1.14.1": + version: 1.14.1 + resolution: "@walletconnect/web3wallet@npm:1.14.1" dependencies: "@walletconnect/auth-client": "npm:2.1.2" - "@walletconnect/core": "npm:2.11.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.13" + "@walletconnect/core": "npm:2.15.1" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.0.1" - "@walletconnect/sign-client": "npm:2.11.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - checksum: 10c0/9f6950a1d49f8fbc14ba560bd98654f0d1cdaac5e2fd64560fe9cbfdc6fea98258c117fdf47c53754c44cae92a62dc29af24d2853afdca58d9d7e9e0a922ab0a + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.15.1" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + checksum: 10c0/1918cb3319036fef3ed5565a607b230e083ea37b12a067df22f1bc81ab34fcf9a48d7ff2f4eb9dbd16066432f0e53460db727466b976e009bab378db5b4da1f6 languageName: node linkType: hard @@ -7505,7 +7540,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5": +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: @@ -7591,7 +7626,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5": +"@webassemblyjs/wasm-edit@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: @@ -7632,7 +7667,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5": +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: @@ -7773,7 +7808,7 @@ __metadata: "@babel/runtime": "npm:7.22.0" "@bankify/react-native-animate-number": "npm:0.2.1" "@bradgarropy/use-countdown": "npm:1.4.1" - "@candlefinance/faster-image": "npm:1.5.0" + "@candlefinance/faster-image": "npm:1.6.2" "@capsizecss/core": "npm:3.0.0" "@ensdomains/address-encoder": "npm:0.2.16" "@ensdomains/content-hash": "npm:2.5.7" @@ -7834,7 +7869,7 @@ __metadata: "@rudderstack/rudder-sdk-react-native": "npm:1.12.1" "@sentry/react-native": "npm:5.22.0" "@shopify/flash-list": "npm:1.7.0" - "@shopify/react-native-skia": "npm:1.3.8" + "@shopify/react-native-skia": "npm:1.3.11" "@tanstack/query-async-storage-persister": "npm:4.2.1" "@tanstack/react-query": "npm:4.2.1" "@tanstack/react-query-persist-client": "npm:4.2.1" @@ -7861,12 +7896,12 @@ __metadata: "@unstoppabledomains/resolution": "npm:7.1.4" "@wagmi/chains": "npm:1.8.0" "@walletconnect/client": "npm:1.8.0" - "@walletconnect/core": "npm:2.11.2" + "@walletconnect/core": "npm:2.15.1" "@walletconnect/legacy-utils": "npm:2.0.0" - "@walletconnect/react-native-compat": "npm:2.11.2" - "@walletconnect/types": "npm:2.11.2" - "@walletconnect/utils": "npm:2.11.2" - "@walletconnect/web3wallet": "npm:1.10.2" + "@walletconnect/react-native-compat": "npm:2.15.1" + "@walletconnect/types": "npm:2.15.1" + "@walletconnect/utils": "npm:2.15.1" + "@walletconnect/web3wallet": "npm:1.14.1" assert: "npm:1.5.0" ast-parser: "npm:0.0.5" async-mutex: "npm:0.3.2" @@ -7876,7 +7911,7 @@ __metadata: babel-plugin-date-fns: "npm:2.0.0" babel-plugin-graphql-tag: "npm:2.5.0" babel-plugin-lodash: "npm:3.3.4" - babel-plugin-module-resolver: "npm:4.0.0" + babel-plugin-module-resolver: "npm:5.0.2" babel-plugin-rewire: "npm:1.2.0" babel-plugin-styled-components: "npm:1.11.1" babel-plugin-transform-remove-console: "npm:6.9.4" @@ -7941,7 +7976,6 @@ __metadata: multiformats: "npm:9.6.2" nanoid: "npm:3.2.0" node-vibrant: "npm:3.2.1-alpha.1" - p-queue: "npm:7.2.0" p-wait-for: "npm:4.1.0" pako: "npm:2.0.4" parse-ms: "npm:2.1.0" @@ -7969,7 +8003,7 @@ __metadata: react-native-branch: "npm:5.3.1" react-native-change-icon: "npm:4.0.0" react-native-circular-progress: "npm:1.3.8" - react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" + react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a" react-native-crypto: "npm:2.2.0" react-native-dark-mode: "npm:0.2.2" react-native-device-info: "npm:5.3.1" @@ -7977,7 +8011,7 @@ __metadata: react-native-extra-dimensions-android: "npm:1.2.2" react-native-fast-image: "npm:8.5.11" react-native-fs: "npm:2.16.6" - react-native-gesture-handler: "npm:2.17.1" + react-native-gesture-handler: "npm:2.18.1" react-native-get-random-values: "npm:1.5.0" react-native-haptic-feedback: "npm:2.2.0" react-native-image-crop-picker: "npm:0.41.0" @@ -7998,25 +8032,25 @@ __metadata: react-native-quick-md5: "npm:3.0.6" react-native-radial-gradient: "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c" react-native-randombytes: "npm:3.5.3" - react-native-reanimated: "npm:3.14.0" + react-native-reanimated: "npm:3.15.0" react-native-redash: "npm:16.3.0" react-native-restart: "npm:0.0.22" react-native-safe-area-context: "npm:4.10.1" react-native-safe-area-view: rainbow-me/react-native-safe-area-view react-native-screen-corner-radius: "npm:0.2.2" - react-native-screens: "npm:3.32.0" + react-native-screens: "npm:3.34.0" react-native-section-list-get-item-layout: "npm:2.2.3" react-native-share: "npm:8.2.1" react-native-sound: "npm:0.11.2" react-native-splash-screen: "npm:3.3.0" react-native-storage: "npm:1.0.1" - react-native-svg: "npm:15.3.0" + react-native-svg: "npm:15.6.0" react-native-tab-view: "npm:3.5.1" react-native-tcp: "npm:3.3.2" react-native-text-input-mask: "npm:2.0.0" react-native-text-size: "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a" react-native-tooltip: "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590" - react-native-tooltips: "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3" + react-native-tooltips: "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" react-native-udp: "npm:2.7.0" react-native-url-polyfill: "npm:2.0.0" react-native-version-number: "npm:0.3.6" @@ -8063,7 +8097,7 @@ __metadata: viem: "npm:2.9.16" vm-browserify: "npm:0.0.4" w2t: "npm:3.0.2" - webpack: "npm:5.90.3" + webpack: "npm:5.94.0" webpack-cli: "npm:5.1.4" zod: "npm:3.23.8" zustand: "npm:4.5.4" @@ -8173,12 +8207,12 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.9.0": - version: 1.9.0 - resolution: "acorn-import-assertions@npm:1.9.0" +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" peerDependencies: acorn: ^8 - checksum: 10c0/3b4a194e128efdc9b86c2b1544f623aba4c1aa70d638f8ab7dc3971a5b4aa4c57bd62f99af6e5325bb5973c55863b4112e708a6f408bad7a138647ca72283afe + checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d languageName: node linkType: hard @@ -8958,16 +8992,16 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:4.0.0": - version: 4.0.0 - resolution: "babel-plugin-module-resolver@npm:4.0.0" +"babel-plugin-module-resolver@npm:5.0.2": + version: 5.0.2 + resolution: "babel-plugin-module-resolver@npm:5.0.2" dependencies: - find-babel-config: "npm:^1.2.0" - glob: "npm:^7.1.6" + find-babel-config: "npm:^2.1.1" + glob: "npm:^9.3.3" pkg-up: "npm:^3.1.0" - reselect: "npm:^4.0.0" - resolve: "npm:^1.13.1" - checksum: 10c0/3b3eda8487aa56d768032763d653eab87eb7a3199e2cbf606e5c6883233f1b668c838bc751164bd28af5e12c566f9291bc928f845795b038805c227b2691e193 + reselect: "npm:^4.1.7" + resolve: "npm:^1.22.8" + checksum: 10c0/ccbb9e673c4219f68937349267521becb72be292cf30bf70b861c3e709d24fbfa589da0bf6c100a0def799d38199299171cb6eac3fb00b1ea740373e2c1fe54c languageName: node linkType: hard @@ -12226,7 +12260,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.15.0": +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0": version: 5.17.0 resolution: "enhanced-resolve@npm:5.17.0" dependencies: @@ -12236,6 +12270,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.17.1": + version: 5.17.1 + resolution: "enhanced-resolve@npm:5.17.1" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/81a0515675eca17efdba2cf5bad87abc91a528fc1191aad50e275e74f045b41506167d420099022da7181c8d787170ea41e4a11a0b10b7a16f6237daecb15370 + languageName: node + linkType: hard + "enquirer@npm:^2.3.0, enquirer@npm:^2.3.5, enquirer@npm:^2.3.6": version: 2.4.1 resolution: "enquirer@npm:2.4.1" @@ -13118,13 +13162,6 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.7": - version: 4.0.7 - resolution: "eventemitter3@npm:4.0.7" - checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b - languageName: node - linkType: hard - "events@npm:3.3.0, events@npm:^3.0.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -13395,7 +13432,7 @@ __metadata: languageName: node linkType: hard -"fast-text-encoding@npm:^1.0.6": +"fast-text-encoding@npm:1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" checksum: 10c0/e1d0381bda229c92c7906f63308f3b9caca8c78b732768b1ee16f560089ed21bc159bbe1434138ccd3815931ec8d4785bdade1ad1c45accfdf27ac6606ac67d2 @@ -13571,13 +13608,13 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^1.2.0": - version: 1.2.2 - resolution: "find-babel-config@npm:1.2.2" +"find-babel-config@npm:^2.1.1": + version: 2.1.1 + resolution: "find-babel-config@npm:2.1.1" dependencies: - json5: "npm:^1.0.2" - path-exists: "npm:^3.0.0" - checksum: 10c0/c82631323b055a3ea8d2dbc42593d243dddf39ec20e83bb6aad847d77676829f4a2bdf507c5177bc9d2d4509a5e239a6023631f1e8b8011ab16d44d227c65639 + json5: "npm:^2.2.3" + path-exists: "npm:^4.0.0" + checksum: 10c0/fb1f348027b83e9fe3b4163ec9c45719adab1205b2807708d660fe2a7beb5f812e0fabb6b21746cf0ef9c9fbf67bcc8e37f6cddb9d43229f0524767522d6110b languageName: node linkType: hard @@ -14319,6 +14356,18 @@ __metadata: languageName: node linkType: hard +"glob@npm:^9.3.3": + version: 9.3.5 + resolution: "glob@npm:9.3.5" + dependencies: + fs.realpath: "npm:^1.0.0" + minimatch: "npm:^8.0.2" + minipass: "npm:^4.2.4" + path-scurry: "npm:^1.6.1" + checksum: 10c0/2f6c2b9ee019ee21dc258ae97a88719614591e4c979cb4580b1b9df6f0f778a3cb38b4bdaf18dfa584637ea10f89a3c5f2533a5e449cf8741514ad18b0951f2e + languageName: node + linkType: hard + "global-modules@npm:^1.0.0": version: 1.0.0 resolution: "global-modules@npm:1.0.0" @@ -14426,7 +14475,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -18173,6 +18222,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^8.0.2": + version: 8.0.4 + resolution: "minimatch@npm:8.0.4" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/a0a394c356dd5b4cb7f821720841a82fa6f07c9c562c5b716909d1b6ec5e56a7e4c4b5029da26dd256b7d2b3a3f38cbf9ddd8680e887b9b5282b09c05501c1ca + languageName: node + linkType: hard + "minimist@npm:1.2.0": version: 1.2.0 resolution: "minimist@npm:1.2.0" @@ -18271,6 +18329,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^4.2.4": + version: 4.2.8 + resolution: "minipass@npm:4.2.8" + checksum: 10c0/4ea76b030d97079f4429d6e8a8affd90baf1b6a1898977c8ccce4701c5a2ba2792e033abc6709373f25c2c4d4d95440d9d5e9464b46b7b76ca44d2ce26d939ce + languageName: node + linkType: hard + "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -19517,17 +19582,7 @@ __metadata: languageName: node linkType: hard -"p-queue@npm:7.2.0": - version: 7.2.0 - resolution: "p-queue@npm:7.2.0" - dependencies: - eventemitter3: "npm:^4.0.7" - p-timeout: "npm:^5.0.2" - checksum: 10c0/0dad31488d6afe5c27a84ed00a703eef1ed4387338e0debe8155d36172808c6ae0451be5d88a12aa41f1deb4d3583ecd19e5f6ded5f06c937b01ff828d18c6cb - languageName: node - linkType: hard - -"p-timeout@npm:^5.0.0, p-timeout@npm:^5.0.2": +"p-timeout@npm:^5.0.0": version: 5.1.0 resolution: "p-timeout@npm:5.1.0" checksum: 10c0/1b026cf9d5878c64bec4341ca9cda8ec6b8b3aea8a57885ca0fe2b35753a20d767fb6f9d3aa41e1252f42bc95432c05ea33b6b18f271fb10bfb0789591850a41 @@ -19777,7 +19832,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": +"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -20828,10 +20883,10 @@ __metadata: languageName: node linkType: hard -"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a": - version: 2.6.1 - resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" - checksum: 10c0/74fe20b46f13a502176bb4bbc78ea24f5ec35d09c37a9404bc15a16c085d8459c184c948841bd8b0b1e5d649609fec2b8279ea31c18c70b24835609e34bbc253 +"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a": + version: 2.6.2 + resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=9b204615b76cf3d29bd86a9094dbd95d717b6a7a" + checksum: 10c0/db1c719b90475201aa1e1177209723598ac38689a827d387dd281ea5190ad09f3e6c8fee77caff70b46a228b6552459d9a9e73e4159c18a29d19d235e17d7907 languageName: node linkType: hard @@ -20926,9 +20981,9 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.17.1": - version: 2.17.1 - resolution: "react-native-gesture-handler@npm:2.17.1" +"react-native-gesture-handler@npm:2.18.1": + version: 2.18.1 + resolution: "react-native-gesture-handler@npm:2.18.1" dependencies: "@egjs/hammerjs": "npm:^2.0.17" hoist-non-react-statics: "npm:^3.3.0" @@ -20937,7 +20992,7 @@ __metadata: peerDependencies: react: "*" react-native: "*" - checksum: 10c0/01ea97f347df2505c58be8551c62e9fd0bae7814346a41f3367ec0b4836877fd8b9b710619f8bc10c6cdbb0e2670dd6c131124d27aabc4745383aab690dae6b3 + checksum: 10c0/6c0f69de1f31eb92cf858903223cbe37b5a71b9e943b70a284e564507067539c5381956b0f832a874da5e1185d01a0a56f06f11ea79985973235eaf3d469274b languageName: node linkType: hard @@ -21149,15 +21204,18 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:3.14.0": - version: 3.14.0 - resolution: "react-native-reanimated@npm:3.14.0" +"react-native-reanimated@npm:3.15.0": + version: 3.15.0 + resolution: "react-native-reanimated@npm:3.15.0" dependencies: "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" + "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" + "@babel/plugin-transform-classes": "npm:^7.0.0-0" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" + "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" "@babel/preset-typescript": "npm:^7.16.7" convert-source-map: "npm:^2.0.0" invariant: "npm:^2.2.4" @@ -21165,7 +21223,7 @@ __metadata: "@babel/core": ^7.0.0-0 react: "*" react-native: "*" - checksum: 10c0/a62ba3e4475c474bc7359d5175f192e86bbe86378a61041247b81beec6e93b4806cd8a5a8a61d1458d56eeef991803e5a128db69d260fa53d78050216b17d014 + checksum: 10c0/86420a616ed7d80404847b584c9fa8f31b516f05401274f32eee908727bcf3fdfa0f09e0b195cd1d11cd0efaa4264ee42d47e2d2000bdab6626f53a795edb6a8 languageName: node linkType: hard @@ -21227,16 +21285,16 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-screens@npm:3.32.0": - version: 3.32.0 - resolution: "react-native-screens@npm:3.32.0" +"react-native-screens@npm:3.34.0": + version: 3.34.0 + resolution: "react-native-screens@npm:3.34.0" dependencies: react-freeze: "npm:^1.0.0" warn-once: "npm:^0.1.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/38dc2e2bf3072c544d3bacceedbcc8ffaee5a588f4984c8d7186a236bd436f761c9b987a37fb3af8e0df2e5980db6155c5f6b6d4a3983001d0f45aa704a20b93 + checksum: 10c0/568af49a1d79797ebecf5b8c815d44b651b0affdd82d7a8024b9e5e71250a23e108d73562200ef571b806c6a9ebba0484510e710246165dbdc50ad18e4d77619 languageName: node linkType: hard @@ -21282,16 +21340,17 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-svg@npm:15.3.0": - version: 15.3.0 - resolution: "react-native-svg@npm:15.3.0" +"react-native-svg@npm:15.6.0": + version: 15.6.0 + resolution: "react-native-svg@npm:15.6.0" dependencies: css-select: "npm:^5.1.0" css-tree: "npm:^1.1.3" + warn-once: "npm:0.1.1" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/72ac639de834d943c3a52f1ac1703e0858eefe59f32d8db4c7e4736e117afea8b88efe8eef9fd1051367d601076090e733fcb93e70df0ec8c6ff6df4859874af + checksum: 10c0/213b6ee33651bb7d2dd17a51c62981264fc519283dc14493361e7816005e2a7b5f2784af11076129bd7402ae2dc0104478f5bd8f894d55f8d4c82c65060bbac6 languageName: node linkType: hard @@ -21347,12 +21406,10 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-tooltips@rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3": - version: 1.0.3 - resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=77338abadbef8225870aea5cfc0dacf94a1448e3" - dependencies: - deprecated-react-native-prop-types: "npm:2.2.0" - checksum: 10c0/b97f5891e583b15a37362f0b0bb2657a41728347bbf1bb4dc692a620db0ea87b02c11c633594e2c6dddcc3b116f6d186616761352f8f803768af3a9b98f9caab +"react-native-tooltips@rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa": + version: 1.0.4 + resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" + checksum: 10c0/7a1f79d8d381532b41eff2b6e8dbd818a4cbd05c21c28419cba8bfb63047555d5e10ee93687a56c2e093eff7921503971942f14a9b5584e6be93cd21ab334805 languageName: node linkType: hard @@ -21369,7 +21426,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-url-polyfill@npm:2.0.0, react-native-url-polyfill@npm:^2.0.0": +"react-native-url-polyfill@npm:2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0" dependencies: @@ -22072,7 +22129,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"reselect@npm:^4.0.0": +"reselect@npm:^4.1.7": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: 10c0/06a305a504affcbb67dd0561ddc8306b35796199c7e15b38934c80606938a021eadcf68cfd58e7bb5e17786601c37602a3362a4665c7bf0a96c1041ceee9d0b7 @@ -22156,7 +22213,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4": +"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -22191,7 +22248,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": +"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -24714,7 +24771,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"uint8arrays@npm:^3.0.0, uint8arrays@npm:^3.1.0": +"uint8arrays@npm:^3.0.0": version: 3.1.1 resolution: "uint8arrays@npm:3.1.1" dependencies: @@ -25376,7 +25433,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"warn-once@npm:^0.1.0": +"warn-once@npm:0.1.1, warn-once@npm:^0.1.0": version: 0.1.1 resolution: "warn-once@npm:0.1.1" checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7 @@ -25392,13 +25449,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"watchpack@npm:^2.4.0": - version: 2.4.1 - resolution: "watchpack@npm:2.4.1" +"watchpack@npm:^2.4.1": + version: 2.4.2 + resolution: "watchpack@npm:2.4.2" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/c694de0a61004e587a8a0fdc9cfec20ee692c52032d9ab2c2e99969a37fdab9e6e1bd3164ed506f9a13f7c83e65563d563e0d6b87358470cdb7309b83db78683 + checksum: 10c0/ec60a5f0e9efaeca0102fd9126346b3b2d523e01c34030d3fddf5813a7125765121ebdc2552981136dcd2c852deb1af0b39340f2fcc235f292db5399d0283577 languageName: node linkType: hard @@ -25488,25 +25545,24 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"webpack@npm:5.90.3": - version: 5.90.3 - resolution: "webpack@npm:5.90.3" +"webpack@npm:5.94.0": + version: 5.94.0 + resolution: "webpack@npm:5.94.0" dependencies: - "@types/eslint-scope": "npm:^3.7.3" "@types/estree": "npm:^1.0.5" - "@webassemblyjs/ast": "npm:^1.11.5" - "@webassemblyjs/wasm-edit": "npm:^1.11.5" - "@webassemblyjs/wasm-parser": "npm:^1.11.5" + "@webassemblyjs/ast": "npm:^1.12.1" + "@webassemblyjs/wasm-edit": "npm:^1.12.1" + "@webassemblyjs/wasm-parser": "npm:^1.12.1" acorn: "npm:^8.7.1" - acorn-import-assertions: "npm:^1.9.0" + acorn-import-attributes: "npm:^1.9.5" browserslist: "npm:^4.21.10" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.15.0" + enhanced-resolve: "npm:^5.17.1" es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.9" + graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" loader-runner: "npm:^4.2.0" mime-types: "npm:^2.1.27" @@ -25514,14 +25570,14 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: schema-utils: "npm:^3.2.0" tapable: "npm:^2.1.1" terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.0" + watchpack: "npm:^2.4.1" webpack-sources: "npm:^3.2.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10c0/f737aa871cadbbae89833eb85387f1bf9ee0768f039100a3c8134f2fdcc78c3230ca775c373b1aa467b272f74c6831e119f7a8a1c14dcac97327212be9c93eeb + checksum: 10c0/b4d1b751f634079bd177a89eef84d80fa5bb8d6fc15d72ab40fc2b9ca5167a79b56585e1a849e9e27e259803ee5c4365cb719e54af70a43c06358ec268ff4ebf languageName: node linkType: hard From cb4602b979259c2cd2679aa05e313bb4d42680ec Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 30 Aug 2024 14:54:23 -0300 Subject: [PATCH 07/64] Merge remote-tracking branch origin/develop into gregs/app-1756-sorting-nfts-on-android-is-broken --- .github/workflows/macstadium-android-e2e.yml | 94 +-- .github/workflows/macstadium-e2e.yml | 4 +- CHANGELOG.md | 10 - android/app/build.gradle | 55 +- .../main/java/me/rainbow/MainApplication.kt | 2 - .../NavbarHeight/NavbarHeightModule.java | 91 --- .../NavbarHeight/NavbarHeightPackage.java | 28 - android/build.gradle | 4 +- android/settings.gradle | 4 +- e2e/3_homeScreen.spec.ts | 14 +- e2e/9_swaps.spec.ts | 143 ----- e2e/helpers.ts | 39 +- ios/Gemfile.lock | 79 ++- ios/Podfile.lock | 84 +-- ios/Rainbow.xcodeproj/project.pbxproj | 428 +++++++------ ios/Rainbow/Info.plist | 2 - package.json | 47 +- .../@candlefinance+faster-image+1.5.0.patch | 29 + .../@candlefinance+faster-image+1.6.2.patch | 24 - ...react-native-gesture-handler+2.17.1.patch} | 0 ...h => react-native-reanimated+3.14.0.patch} | 0 .../react-native-text-input-mask+2.0.0.patch | 310 +-------- ...atch => react-native-tooltips+1.0.3.patch} | 0 scripts/codegen-translations.js | 2 +- shim.js | 8 +- src/App.tsx | 24 +- src/__swaps__/screens/Swap/Swap.tsx | 4 +- .../components/AnimatedChainImage.android.tsx | 2 +- .../components/AnimatedChainImage.ios.tsx | 2 +- .../screens/Swap/components/CoinRow.tsx | 26 +- .../Swap/components/FastSwapCoinIconImage.tsx | 88 +++ .../screens/Swap/components/FlipButton.tsx | 2 +- .../screens/Swap/components/GasButton.tsx | 2 +- .../screens/Swap/components/GasPanel.tsx | 2 +- .../NavigateToSwapSettingsTrigger.tsx | 29 - .../screens/Swap/components/ReviewPanel.tsx | 7 +- .../Swap/components/SwapActionButton.tsx | 16 +- .../Swap/components/SwapBackground.tsx | 11 +- .../Swap/components/SwapBottomPanel.tsx | 1 - .../screens/Swap/components/SwapCoinIcon.tsx | 2 +- .../Swap/components/SwapInputAsset.tsx | 12 +- .../Swap/components/SwapOutputAsset.tsx | 9 +- .../components/TokenList/ChainSelection.tsx | 6 +- .../components/TokenList/TokenToBuyList.tsx | 9 +- src/__swaps__/screens/Swap/constants.ts | 32 +- .../Swap/hooks/useAnimatedSwapStyles.ts | 2 +- .../screens/Swap/hooks/useAssetsToSell.ts | 4 - .../screens/Swap/hooks/useCustomGas.ts | 2 +- .../screens/Swap/hooks/useEstimatedGasFee.ts | 2 +- .../Swap/hooks/useNativeAssetForChain.ts | 6 +- .../Swap/hooks/useSearchCurrencyLists.ts | 5 +- .../screens/Swap/hooks/useSelectedGas.ts | 2 +- .../Swap/hooks/useSwapEstimatedGasLimit.ts | 2 +- .../Swap/hooks/useSwapInputsController.ts | 3 +- .../SyncSwapStateAndSharedValues.tsx | 2 +- .../screens/Swap/providers/swap-provider.tsx | 10 +- .../Swap/resources/_selectors/assets.ts | 2 +- .../screens/Swap/resources/assets/assets.ts | 4 +- .../Swap/resources/assets/userAssets.ts | 38 +- .../resources/assets/userAssetsByChain.ts | 4 +- .../Swap/resources/search/discovery.ts | 4 +- .../screens/Swap/resources/search/search.ts | 4 +- .../screens/Swap/resources/search/utils.ts | 2 +- src/__swaps__/types/assets.ts | 2 +- src/__swaps__/types/chains.ts | 211 +++++++ src/__swaps__/types/refraction.ts | 2 +- src/__swaps__/types/search.ts | 2 +- src/__swaps__/utils/address.ts | 2 +- src/__swaps__/utils/assets.ts | 3 +- src/__swaps__/utils/chains.ts | 2 +- src/__swaps__/utils/decimalFormatter.ts | 4 +- src/__swaps__/utils/gasUtils.ts | 2 +- src/__swaps__/utils/meteorology.ts | 6 +- src/__swaps__/utils/swaps.ts | 2 +- src/__swaps__/utils/userChains.ts | 2 +- src/analytics/event.ts | 3 +- src/analytics/index.ts | 10 +- src/analytics/utils.ts | 13 +- src/appIcons/appIcons.ts | 2 +- src/components/AddFundsInterstitial.js | 4 +- src/components/ChainLogo.js | 18 +- src/components/ContactRowInfoButton.js | 18 +- src/components/DappBrowser/Dimensions.ts | 4 +- src/components/DappBrowser/Homepage.tsx | 79 +-- .../control-panel/ControlPanel.tsx | 63 +- .../DappBrowser/handleProviderRequest.ts | 62 +- .../hooks/useScreenshotAndScrollTriggers.ts | 2 +- src/components/DappBrowser/screenshots.ts | 10 +- src/components/DappBrowser/utils.ts | 2 +- .../FeaturedResult/FeaturedResultCard.tsx | 60 -- .../FeaturedResult/FeaturedResultStack.tsx | 48 -- src/components/L2Disclaimer.js | 8 +- src/components/activity-list/ActivityList.js | 4 +- src/components/animations/animationConfigs.ts | 44 +- .../FastComponents/FastBalanceCoinRow.tsx | 14 +- .../FastComponents/FastCoinBadge.tsx | 2 +- .../FastCurrencySelectionRow.tsx | 2 +- .../RecyclerAssetList2/NFTEmptyState.tsx | 4 +- .../core/ExternalScrollView.tsx | 2 +- .../core/RawRecyclerList.tsx | 4 +- .../ProfileActionButtonsRow.tsx | 4 +- .../avatar-builder/EmojiContent.tsx | 6 +- .../backup/BackupChooseProviderStep.tsx | 8 +- src/components/backup/BackupManuallyStep.tsx | 4 +- src/components/backup/CloudBackupProvider.tsx | 12 +- src/components/backup/RestoreCloudStep.tsx | 9 +- src/components/cards/EthCard.tsx | 4 +- src/components/cards/FeaturedMintCard.tsx | 4 +- .../cards/MintsCard/CollectionCell.tsx | 5 +- src/components/cards/NFTOffersCard/Offer.tsx | 5 +- src/components/cards/OpRewardsCard.tsx | 3 +- src/components/change-wallet/WalletList.tsx | 6 +- src/components/coin-icon/ChainBadge.js | 3 +- src/components/coin-icon/ChainImage.tsx | 3 +- src/components/coin-icon/EthCoinIcon.tsx | 2 +- src/components/coin-icon/RainbowCoinIcon.tsx | 2 +- .../coin-icon/RequestVendorLogoIcon.js | 6 +- src/components/coin-icon/TwoCoinsIcon.tsx | 2 +- src/components/coin-row/CoinRowInfoButton.js | 6 +- .../coin-row/FastTransactionCoinRow.tsx | 5 +- src/components/coin-row/SendCoinRow.js | 6 +- src/components/contacts/ContactRow.js | 6 +- .../context-menu-buttons/ChainContextMenu.tsx | 2 +- .../discover/DiscoverSearchInput.js | 7 +- .../ActionButtons/ActionButtons.tsx | 2 +- .../ens-profile/ActionButtons/MoreButton.tsx | 8 +- src/components/error-boundary/Fallback.tsx | 2 +- .../exchange/ConfirmExchangeButton.js | 6 +- src/components/exchange/ExchangeAssetList.tsx | 1 + src/components/exchange/ExchangeField.tsx | 3 +- .../exchange/ExchangeInputField.tsx | 3 +- .../exchange/ExchangeOutputField.tsx | 2 +- src/components/exchange/ExchangeTokenRow.tsx | 12 +- src/components/exchange/NetworkSwitcher.js | 9 +- src/components/exchange/NetworkSwitcherv2.tsx | 18 +- .../exchangeAssetRowContextMenuProps.ts | 6 +- .../expanded-state/AvailableNetworks.js | 28 +- .../expanded-state/AvailableNetworksv2.tsx | 87 +-- .../expanded-state/ContactProfileState.js | 8 +- .../expanded-state/CustomGasState.js | 4 +- .../UniqueTokenExpandedState.tsx | 14 +- .../asset/ChartExpandedState.js | 31 +- .../expanded-state/asset/SocialLinks.js | 4 +- .../chart/ChartContextButton.js | 4 +- .../chart/ChartExpandedStateHeader.js | 6 +- .../expanded-state/custom-gas/FeesPanel.tsx | 14 +- .../swap-details/CurrencyTile.js | 2 +- .../swap-details/SwapDetailsContent.js | 2 +- .../swap-details/SwapDetailsContractRow.js | 12 +- .../swap-details/SwapDetailsExchangeRow.js | 5 +- .../swap-details/SwapDetailsRewardRow.tsx | 2 +- .../swap-settings/MaxToleranceInput.tsx | 256 ++++---- .../swap-settings/SwapSettingsState.js | 4 +- .../unique-token/NFTBriefTokenInfoRow.tsx | 2 +- .../UniqueTokenExpandedStateHeader.tsx | 12 +- .../unique-token/ZoomableWrapper.android.js | 4 +- src/components/gas/GasSpeedButton.js | 6 +- src/components/investment-cards/PoolValue.js | 4 +- src/components/list/NoResults.tsx | 2 +- src/components/positions/PositionsCard.tsx | 3 +- src/components/qr-code/QRCode.js | 2 +- .../check-fns/hasNonZeroAssetBalance.ts | 32 + .../check-fns/hasSwapTxn.ts | 4 +- .../remote-promo-sheet/check-fns/index.ts | 1 + .../remote-promo-sheet/checkForCampaign.ts | 22 +- .../remote-promo-sheet/localCampaignChecks.ts | 2 +- .../notificationsPromoCampaign.ts | 10 +- .../remote-promo-sheet/runChecks.ts | 2 +- .../secret-display/SecretDisplaySection.tsx | 5 +- .../sheet-action-buttons/SwapActionButton.tsx | 2 +- src/components/svg/SvgImage.js | 10 +- src/components/toasts/OfflineToast.js | 4 +- src/components/toasts/TestnetToast.js | 20 +- .../token-info/TokenInfoBalanceValue.js | 4 +- src/components/value-chart/Chart.js | 2 +- src/components/video/SimpleVideo.tsx | 4 +- .../WalletConnectListItem.js | 6 +- .../WalletConnectV2ListItem.tsx | 6 +- src/config/experimental.ts | 4 +- src/debugging/network.js | 36 +- src/debugging/useDependencyDebugger.ts | 4 +- src/design-system/components/Box/Box.tsx | 15 +- src/ens-avatar/src/utils.ts | 2 +- src/entities/tokens.ts | 17 +- src/entities/transactions/transaction.ts | 3 +- src/entities/uniqueAssets.ts | 3 +- src/featuresToUnlock/tokenGatedUtils.ts | 8 +- .../unlockableAppIconCheck.ts | 10 +- src/graphql/index.ts | 20 +- src/graphql/queries/arc.graphql | 40 -- src/graphql/queries/metadata.graphql | 13 +- src/handlers/LedgerSigner.ts | 6 +- src/handlers/__mocks__/web3.ts | 2 +- src/handlers/assets.ts | 14 +- src/handlers/authentication.ts | 6 +- src/handlers/cloudBackup.ts | 12 +- src/handlers/cloudinary.ts | 2 +- src/handlers/deeplinks.ts | 42 +- src/handlers/dispersion.ts | 8 +- src/handlers/ens.ts | 38 +- src/handlers/gasFees.ts | 5 +- src/handlers/imgix.ts | 9 +- src/handlers/localstorage/common.ts | 10 +- src/handlers/localstorage/globalSettings.ts | 8 +- src/handlers/localstorage/removeWallet.ts | 12 +- src/handlers/localstorage/theme.ts | 2 +- src/handlers/swap.ts | 51 +- src/handlers/tokenSearch.ts | 13 +- src/handlers/transactions.ts | 4 +- src/handlers/walletReadyEvents.ts | 10 +- src/handlers/web3.ts | 177 ++++-- src/helpers/RainbowContext.tsx | 28 +- src/helpers/accountInfo.ts | 2 +- src/helpers/ens.ts | 13 +- src/helpers/findWalletWithAccount.ts | 2 +- src/helpers/gas.ts | 12 +- src/helpers/index.ts | 1 + src/helpers/networkInfo.ts | 26 +- src/helpers/networkTypes.ts | 31 + src/helpers/signingWallet.ts | 15 +- src/helpers/statusBarHelper.ts | 5 + src/helpers/validators.ts | 6 +- src/helpers/walletConnectNetworks.ts | 11 +- src/hooks/charts/useChartInfo.ts | 46 +- src/hooks/charts/useChartThrottledPoints.ts | 3 +- src/hooks/useAccountAsset.ts | 3 +- src/hooks/useAccountTransactions.ts | 26 +- src/hooks/useAdditionalAssetData.ts | 20 +- src/hooks/useAppVersion.ts | 2 +- src/hooks/useAsset.ts | 5 +- src/hooks/useCloudBackups.ts | 12 +- src/hooks/useContacts.ts | 10 +- src/hooks/useDeleteWallet.ts | 2 +- src/hooks/useENSRegistrationActionHandler.ts | 37 +- src/hooks/useENSRegistrationCosts.ts | 9 +- src/hooks/useENSRegistrationStepHandler.tsx | 26 +- src/hooks/useENSSearch.ts | 6 +- src/hooks/useEffectDebugger.ts | 14 +- src/hooks/useGas.ts | 44 +- src/hooks/useHideSplashScreen.ts | 4 +- src/hooks/useImportingWallet.ts | 25 +- src/hooks/useInitializeAccountData.ts | 6 +- src/hooks/useInitializeWallet.ts | 32 +- src/hooks/useLedgerConnect.ts | 8 +- src/hooks/useLedgerImport.ts | 21 +- src/hooks/useLoadAccountData.ts | 4 +- src/hooks/useLoadAccountLateData.ts | 4 +- src/hooks/useLoadGlobalEarlyData.ts | 4 +- src/hooks/useLoadGlobalLateData.ts | 4 +- src/hooks/useManageCloudBackups.ts | 4 +- src/hooks/useOnAvatarPress.ts | 4 +- src/hooks/usePriceImpactDetails.ts | 5 +- src/hooks/useRainbowFee.js | 2 +- src/hooks/useRefreshAccountData.ts | 21 +- src/hooks/useSafeImageUri.ts | 2 +- src/hooks/useScanner.ts | 14 +- src/hooks/useSearchCurrencyList.ts | 13 +- src/hooks/useSendableUniqueTokens.ts | 2 +- src/hooks/useStepper.ts | 2 +- src/hooks/useSwapCurrencyHandlers.ts | 3 + src/hooks/useSwapCurrencyList.ts | 142 +++-- src/hooks/useSwapDerivedOutputs.ts | 44 +- src/hooks/useSwapInputHandlers.ts | 6 +- src/hooks/useSwapRefuel.ts | 28 +- src/hooks/useSwappableUserAssets.ts | 9 +- src/hooks/useUserAccounts.ts | 18 +- src/hooks/useWalletBalances.ts | 4 +- src/hooks/useWalletCloudBackup.ts | 18 +- src/hooks/useWalletManualBackup.ts | 8 +- src/hooks/useWalletsWithBalancesAndNames.ts | 2 +- src/hooks/useWatchPendingTxs.ts | 104 +-- src/hooks/useWatchWallet.ts | 13 +- src/hooks/useWebData.ts | 12 +- src/keychain/index.ts | 54 +- src/languages/ar_AR.json | 15 +- src/languages/en_US.json | 1 - src/languages/es_419.json | 13 +- src/languages/fr_FR.json | 13 +- src/languages/hi_IN.json | 15 +- src/languages/id_ID.json | 13 +- src/languages/ja_JP.json | 15 +- src/languages/ko_KR.json | 15 +- src/languages/pt_BR.json | 13 +- src/languages/ru_RU.json | 13 +- src/languages/th_TH.json | 15 +- src/languages/tr_TR.json | 13 +- src/languages/zh_CN.json | 13 +- src/logger/sentry.ts | 6 +- src/migrations/index.ts | 14 +- src/migrations/migrations/migrateFavorites.ts | 3 +- .../migrateRemotePromoSheetsToZustand.ts | 2 +- src/model/backup.ts | 30 +- src/model/migrations.ts | 123 ++-- src/model/preferences.ts | 11 +- src/model/remoteConfig.ts | 19 +- src/model/wallet.ts | 127 ++-- src/navigation/HardwareWalletTxNavigator.tsx | 6 +- src/navigation/SwipeNavigator.tsx | 17 +- .../views/BottomSheetBackground.tsx | 8 +- .../views/BottomSheetNavigatorView.tsx | 12 +- src/navigation/config.tsx | 7 +- src/navigation/types.ts | 3 - src/networks/README.md | 8 +- src/networks/arbitrum.ts | 5 +- src/networks/avalanche.ts | 5 +- src/networks/base.ts | 5 +- src/networks/blast.ts | 5 +- src/networks/bsc.ts | 5 +- src/networks/degen.ts | 5 +- src/networks/gnosis.ts | 5 +- src/networks/goerli.ts | 5 +- src/networks/index.ts | 64 +- src/networks/mainnet.ts | 10 +- src/networks/optimism.ts | 5 +- src/networks/polygon.ts | 5 +- src/networks/types.ts | 199 +----- src/networks/zora.ts | 5 +- src/notifications/NotificationsHandler.tsx | 13 +- src/notifications/foregroundHandler.ts | 4 +- src/notifications/permissions.ts | 6 +- src/notifications/settings/firebase.ts | 8 +- src/notifications/settings/initialization.ts | 4 +- src/notifications/tokens.ts | 4 +- src/parsers/accounts.js | 72 +++ src/parsers/accounts.ts | 32 - src/parsers/gas.ts | 4 +- src/parsers/index.ts | 2 +- src/parsers/requests.js | 8 +- src/parsers/transactions.ts | 10 +- src/raps/actions/claimBridge.ts | 16 +- src/raps/actions/crosschainSwap.ts | 22 +- src/raps/actions/ens.ts | 56 +- src/raps/actions/swap.ts | 21 +- src/raps/actions/unlock.ts | 38 +- src/raps/common.ts | 2 +- src/raps/execute.ts | 2 +- src/raps/references.ts | 2 +- src/raps/unlockAndSwap.ts | 2 +- src/raps/utils.ts | 3 +- .../interpolations/bSplineInterpolation.js | 14 +- .../createNativeStackNavigator.js | 4 +- src/redux/contacts.ts | 10 +- src/redux/ensRegistration.ts | 8 +- src/redux/explorer.ts | 10 +- src/redux/gas.ts | 153 ++--- src/redux/requests.ts | 8 +- src/redux/settings.ts | 45 +- src/redux/showcaseTokens.ts | 4 +- src/redux/swap.ts | 4 +- src/redux/walletconnect.ts | 40 +- src/redux/wallets.ts | 64 +- src/references/chain-assets.json | 25 +- src/references/gasUnits.ts | 2 +- src/references/index.ts | 32 +- src/references/rainbow-token-list/index.ts | 19 +- src/references/shitcoins.ts | 2 +- src/references/testnet-assets-by-chain.ts | 69 -- src/resources/assets/UserAssetsQuery.ts | 55 +- src/resources/assets/assetSelectors.ts | 6 +- src/resources/assets/assets.ts | 7 +- src/resources/assets/externalAssetsQuery.ts | 18 +- src/resources/assets/hardhatAssets.ts | 104 +-- src/resources/assets/types.ts | 4 +- src/resources/assets/useSortedUserAssets.ts | 4 +- src/resources/assets/useUserAsset.ts | 6 +- src/resources/assets/useUserAssetCount.ts | 4 +- src/resources/defi/PositionsQuery.ts | 4 +- src/resources/ens/ensAddressQuery.ts | 5 +- src/resources/favorites.ts | 3 +- .../_selectors/getFeaturedResultById.ts | 6 - .../_selectors/getFeaturedResultIds.ts | 5 - .../getFeaturedResultsForPlacement.ts | 6 - .../getFeaturedResultsForPlacementWithIds.ts | 13 - .../featuredResults/getFeaturedResults.ts | 30 - .../featuredResults/trackFeaturedResult.ts | 25 - src/resources/metadata/dapps.tsx | 2 +- src/resources/nfts/index.ts | 30 +- src/resources/nfts/simplehash/index.ts | 48 +- src/resources/nfts/simplehash/types.ts | 2 +- src/resources/nfts/simplehash/utils.ts | 3 +- src/resources/nfts/types.ts | 2 +- src/resources/nfts/utils.ts | 4 +- src/resources/reservoir/mints.ts | 17 +- src/resources/reservoir/utils.ts | 2 +- .../transactions/consolidatedTransactions.ts | 6 +- .../firstTransactionTimestampQuery.ts | 8 +- src/resources/transactions/transaction.ts | 37 +- .../AddCash/components/ProviderCard.tsx | 22 +- src/screens/AddCash/index.tsx | 2 +- .../AddCash/providers/Coinbase/index.tsx | 4 +- .../AddCash/providers/Moonpay/index.tsx | 4 +- src/screens/AddCash/providers/Ramp/index.tsx | 4 +- src/screens/AddCash/utils.ts | 16 +- src/screens/AddWalletSheet.tsx | 18 +- src/screens/ChangeWalletSheet.tsx | 12 +- src/screens/CheckIdentifierScreen.tsx | 6 +- src/screens/CurrencySelectModal.tsx | 25 +- .../Diagnostics/DiagnosticsItemRow.tsx | 2 +- .../helpers/createAndShareStateDumpFile.ts | 2 +- src/screens/Diagnostics/index.tsx | 2 +- src/screens/ENSConfirmRegisterSheet.tsx | 2 +- src/screens/ExchangeModal.tsx | 51 +- src/screens/ExplainSheet.js | 126 ++-- src/screens/MintsSheet/card/Card.tsx | 11 +- src/screens/NFTOffersSheet/OfferRow.tsx | 3 +- src/screens/NFTSingleOfferSheet/index.tsx | 22 +- src/screens/NotificationsPromoSheet/index.tsx | 6 +- src/screens/SendConfirmationSheet.tsx | 32 +- src/screens/SendSheet.js | 83 +-- .../components/AppIconSection.tsx | 8 +- .../components/Backups/ViewWalletBackup.tsx | 16 +- .../components/Backups/WalletsAndBackup.tsx | 12 +- .../components/CurrencySection.tsx | 2 +- .../SettingsSheet/components/DevSection.tsx | 33 +- .../components/GoogleAccountSection.tsx | 4 +- .../components/NetworkSection.tsx | 26 +- .../components/NotificationsSection.tsx | 6 +- .../components/PrivacySection.tsx | 4 +- .../components/SettingsSection.tsx | 21 + .../SettingsSheet/useVisibleWallets.ts | 2 +- src/screens/SignTransactionSheet.tsx | 90 +-- src/screens/SpeedUpAndCancelSheet.js | 73 +-- src/screens/WalletConnectApprovalSheet.js | 49 +- src/screens/WalletScreen/index.tsx | 31 +- src/screens/WelcomeScreen/index.tsx | 4 +- .../DiscoverFeaturedResultsCard.tsx | 38 -- .../{DiscoverHome.tsx => DiscoverHome.js} | 43 +- .../discover/components/DiscoverSearch.js | 5 +- .../PairHardwareWalletSigningSheet.tsx | 7 +- src/screens/mints/MintSheet.tsx | 33 +- .../points/claim-flow/ClaimRewardsPanel.tsx | 15 +- .../points/components/LeaderboardRow.tsx | 10 +- src/screens/points/content/PointsContent.tsx | 2 +- .../points/content/ReferralContent.tsx | 2 +- .../points/contexts/PointsProfileContext.tsx | 10 +- ...ransactionDetailsHashAndActionsSection.tsx | 2 +- .../TransactionDetailsValueAndFeeSection.tsx | 4 +- .../components/TransactionMasthead.tsx | 4 +- src/state/appSessions/index.test.ts | 38 +- src/state/appSessions/index.ts | 68 +- src/state/assets/userAssets.ts | 20 +- src/state/browser/browserStore.ts | 10 +- src/state/connectedToHardhat/index.ts | 28 - src/state/featuredResults/featuredResults.ts | 0 src/state/internal/createRainbowStore.ts | 14 +- src/state/nonces/index.ts | 61 +- src/state/pendingTransactions/index.ts | 16 +- src/state/remoteCards/remoteCards.ts | 12 +- .../remotePromoSheets/remotePromoSheets.ts | 10 +- src/state/staleBalances/index.test.ts | 165 ----- src/state/staleBalances/index.ts | 99 --- src/state/swaps/swapsStore.ts | 10 +- src/state/sync/UserAssetsSync.tsx | 10 +- src/storage/legacy.ts | 2 +- src/styles/colors.ts | 49 +- src/utils/actionsheet.ts | 4 +- src/utils/bluetoothPermissions.ts | 8 +- src/utils/branch.ts | 24 +- src/utils/contenthash.ts | 2 +- src/utils/deviceUtils.ts | 13 +- src/utils/doesWalletsContainAddress.ts | 2 +- src/utils/ethereumUtils.ts | 91 ++- src/utils/getTokenMetadata.ts | 7 + src/utils/getUrlForTrustIconFallback.ts | 13 +- src/utils/index.ts | 2 + src/utils/languageLocaleToCountry.ts | 16 - src/utils/ledger.ts | 8 +- src/utils/logger.ts | 117 ++++ src/utils/memoFn.ts | 8 +- src/utils/methodRegistry.ts | 5 +- src/utils/poaps.ts | 4 +- src/utils/requestNavigationHandlers.ts | 12 +- src/utils/reviewAlert.ts | 8 +- src/utils/simplifyChartData.ts | 16 +- src/walletConnect/index.tsx | 204 +++--- tsconfig.json | 1 + yarn.lock | 590 ++++++++---------- 477 files changed, 4364 insertions(+), 5737 deletions(-) delete mode 100644 android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java delete mode 100644 android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java delete mode 100644 e2e/9_swaps.spec.ts create mode 100644 patches/@candlefinance+faster-image+1.5.0.patch delete mode 100644 patches/@candlefinance+faster-image+1.6.2.patch rename patches/{react-native-gesture-handler+2.18.1.patch => react-native-gesture-handler+2.17.1.patch} (100%) rename patches/{react-native-reanimated+3.15.0.patch => react-native-reanimated+3.14.0.patch} (100%) rename patches/{react-native-tooltips+1.0.4.patch => react-native-tooltips+1.0.3.patch} (100%) create mode 100644 src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx delete mode 100644 src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx create mode 100644 src/__swaps__/types/chains.ts delete mode 100644 src/components/FeaturedResult/FeaturedResultCard.tsx delete mode 100644 src/components/FeaturedResult/FeaturedResultStack.tsx create mode 100644 src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts create mode 100644 src/helpers/networkTypes.ts create mode 100644 src/parsers/accounts.js delete mode 100644 src/parsers/accounts.ts delete mode 100644 src/references/testnet-assets-by-chain.ts delete mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultById.ts delete mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultIds.ts delete mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts delete mode 100644 src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts delete mode 100644 src/resources/featuredResults/getFeaturedResults.ts delete mode 100644 src/resources/featuredResults/trackFeaturedResult.ts delete mode 100644 src/screens/discover/components/DiscoverFeaturedResultsCard.tsx rename src/screens/discover/components/{DiscoverHome.tsx => DiscoverHome.js} (69%) delete mode 100644 src/state/connectedToHardhat/index.ts delete mode 100644 src/state/featuredResults/featuredResults.ts delete mode 100644 src/state/staleBalances/index.test.ts delete mode 100644 src/state/staleBalances/index.ts create mode 100644 src/utils/getTokenMetadata.ts delete mode 100644 src/utils/languageLocaleToCountry.ts create mode 100644 src/utils/logger.ts diff --git a/.github/workflows/macstadium-android-e2e.yml b/.github/workflows/macstadium-android-e2e.yml index facc0b7861a..44e6bdb75c4 100644 --- a/.github/workflows/macstadium-android-e2e.yml +++ b/.github/workflows/macstadium-android-e2e.yml @@ -1,82 +1,56 @@ +# This is a basic workflow to help you get started with Actions + name: Android e2e tests -on: [] +# Controls when the workflow will run +on: [pull_request, workflow_dispatch] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: + # This workflow contains a single job called "android-e2e" android-e2e: - runs-on: ["self-hosted"] - concurrency: + if: startsWith(github.head_ref, 'android-ci') + # The type of runner that the job will run on + runs-on: ["self-hosted", "ci-5"] + # Cancel current builds if there's a newer commit on the same branch + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: - contents: read - + # Steps represent a sequence of tasks that will be executed as part of the job steps: - - name: Checkout repo - uses: actions/checkout@v4 + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 - name: Set up github keys run: git config core.sshCommand "ssh -i ~/.ssh/id_ed25519 -F /dev/null" - - name: Clean Android app - run: yarn clean:android > /dev/null 2>&1 || true - - name: Set up ENV vars & scripts - env: - CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} run: | - source ~/.zshrc + # read local env vars + source ~/.bashrc + # fetch env vars git clone git@github.com:rainbow-me/rainbow-env.git + # unpack dotenv + mv rainbow-env/android/app/google-services.json android/app mv rainbow-env/dotenv .env && rm -rf rainbow-env - eval $CI_SCRIPTS_RN_UPGRADE - sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - - - name: Get Yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - - name: Cache Yarn dependencies - uses: actions/cache@v4 - with: - path: | - ${{ steps.yarn-cache-dir-path.outputs.dir }} - .yarn/cache - .yarn/install-state.gz - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: Install dependencies - run: | - yarn cache clean --all && yarn install && yarn setup - - - name: Check for frozen lockfile - run: ./scripts/check-lockfile.sh - - - name: Audit CI - run: yarn audit-ci --config audit-ci.jsonc - - - name: Lint - run: yarn lint:ci - - - name: Unit tests - run: yarn test + # run CI scripts + eval $CI_SCRIPTS + # tweak dotenv for e2e + sed -i''-e "s/\IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e + # set up password + cp android/keystores/debug.keystore android/keystores/rainbow-key.keystore + sed -i -e "s:rainbow-alias:androiddebugkey:g" android/app/build.gradle + export RAINBOW_KEY_ANDROID_PASSWORD=android + - name: Install deps via Yarn + run: yarn setup-ci - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache - - - name: Version debug - run: | - npx react-native info - - - name: Fix permissions - run: | - chmod -R +x node_modules/react-native/scripts - chmod -R +x node_modules/@sentry/react-native/scripts - name: Build the app in Release mode - run: yarn detox build --configuration android.emu.release + run: ./node_modules/.bin/detox build --configuration android.emu.release + + - name: Run Android e2e tests + run: ./node_modules/.bin/detox test -R 5 --configuration android.emu.release --forceExit - # change the '3' here to how many times you want the tests to rerun on failure - - name: Run iOS e2e tests with retry - run: ./scripts/run-retry-tests.sh 3 diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-e2e.yml index fd826523eaa..0ae2e69dd9d 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-e2e.yml @@ -23,12 +23,12 @@ jobs: - name: Set up ENV vars & scripts env: - CI_SCRIPTS: ${{ secrets.CI_SCRIPTS }} + CI_SCRIPTS_RN_UPGRADE: ${{ secrets.CI_SCRIPTS_RN_UPGRADE }} run: | source ~/.zshrc git clone git@github.com:rainbow-me/rainbow-env.git mv rainbow-env/dotenv .env && rm -rf rainbow-env - eval $CI_SCRIPTS + eval $CI_SCRIPTS_RN_UPGRADE sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e - name: Get Yarn cache directory path diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ffe98480f..4f2c3056133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,16 +15,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed -## [1.9.36] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.36) - -### Fixed - -- Fixed Sentry logging issues (#6012, #6018, #6019) -- Fixed issue in swaps where certain errors were not being handled (#6017) -- Fixed a bug with wrapping and unwrapping ETH (#6022, #6026) -- Fixed a crash that was happening on asset balance (#6025) -- Fixed missing pricing on swaps (#6023) - ## [1.9.35] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.35) ### Added diff --git a/android/app/build.gradle b/android/app/build.gradle index 7297b466b66..5cca331f4c6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,6 +3,7 @@ apply plugin: "org.jetbrains.kotlin.android" apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' + def getPassword(String currentUser, String keyChain) { def stdout = new ByteArrayOutputStream() def stderr = new ByteArrayOutputStream() @@ -29,9 +30,15 @@ def getPassword(String currentUser, String keyChain) { stdout.toString().trim() } + import com.android.build.OutputFile + + apply plugin: "com.facebook.react" + + + apply from: "../../node_modules/@sentry/react-native/sentry.gradle" react { @@ -107,9 +114,11 @@ def enableProguardInReleaseBuilds = false */ def jscFlavor = 'org.webkit:android-jsc:+' + + android { def envFile = project.file('../../.env') - def env = [:] + def env =[:] envFile.eachLine { if (it.contains('=') && (!it.startsWith("#"))) { def (key, value) = it.split('=') @@ -130,25 +139,25 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 228 - versionName "1.9.37" + versionCode 227 + versionName "1.9.36" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true multiDexEnabled true - testBuildType System.getProperty('testBuildType', 'debug') - missingDimensionStrategy 'detox', 'full' + testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' manifestPlaceholders = [ - BRANCH_KEY: env.get('BRANCH_KEY') - ] + BRANCH_KEY: env.get('BRANCH_KEY') + ] ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' } } + signingConfigs { debug { storeFile file('../keystores/debug.keystore') @@ -178,7 +187,7 @@ android { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" // Detox-specific additions to pro-guard - proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" + //proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } @@ -190,17 +199,8 @@ android { pickFirst '**/x86_64/libc++_shared.so' pickFirst '**/x86/libjsc.so' pickFirst '**/armeabi-v7a/libjsc.so' - // exclude - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/license.txt' - exclude 'META-INF/NOTICE' - exclude 'META-INF/NOTICE.txt' - exclude 'META-INF/notice.txt' - exclude 'META-INF/ASL2.0' - exclude("META-INF/*.kotlin_module") } + } dependencies { @@ -208,15 +208,7 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation("com.github.NovaCrypto:BIP39:0e7fa95f80") { - exclude group: "io.github.novacrypto", module: "ToRuntime" - exclude group: "io.github.novacrypto", module: "SHA256" - } - implementation("com.github.NovaCrypto:Sha256:57bed72da5") { - exclude group: "io.github.novacrypto", module: "ToRuntime" - } - implementation "com.github.NovaCrypto:ToRuntime:c3ae3080eb" - + implementation 'io.github.novacrypto:BIP39:2019.01.27' implementation 'com.google.android.play:review:2.0.1' implementation 'com.google.android.play:app-update:2.1.0' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' @@ -229,8 +221,8 @@ dependencies { } // DETOX - androidTestImplementation('com.wix:detox:+') { transitive = true } - androidTestImplementation(project(path: ":detox")) + //androidTestImplementation('com.wix:detox:+') + } // Run this once to be able to run the application with BUCK @@ -241,3 +233,6 @@ task copyDownloadableDepsToLibs(type: Copy) { } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) + + + diff --git a/android/app/src/main/java/me/rainbow/MainApplication.kt b/android/app/src/main/java/me/rainbow/MainApplication.kt index 08d0640db6a..c096db243ff 100644 --- a/android/app/src/main/java/me/rainbow/MainApplication.kt +++ b/android/app/src/main/java/me/rainbow/MainApplication.kt @@ -21,7 +21,6 @@ import me.rainbow.NativeModules.RNStartTime.RNStartTimePackage import me.rainbow.NativeModules.RNTextAnimatorPackage.RNTextAnimatorPackage import me.rainbow.NativeModules.RNZoomableButton.RNZoomableButtonPackage import me.rainbow.NativeModules.SystemNavigationBar.SystemNavigationBarPackage -import me.rainbow.NativeModules.NavbarHeight.NavbarHeightPackage class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { @@ -42,7 +41,6 @@ class MainApplication : Application(), ReactApplication { packages.add(KeychainPackage(KeychainModuleBuilder().withoutWarmUp())) packages.add(RNStartTimePackage(START_MARK)) packages.add(RNHapticsPackage()) - packages.add(NavbarHeightPackage()) return packages } diff --git a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java deleted file mode 100644 index 2a5884a4311..00000000000 --- a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightModule.java +++ /dev/null @@ -1,91 +0,0 @@ -package me.rainbow.NativeModules.NavbarHeight; - -import androidx.annotation.NonNull; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.module.annotations.ReactModule; -import android.graphics.Point; -import android.view.WindowManager; -import android.view.Display; -import java.lang.IllegalAccessException; -import java.lang.reflect.InvocationTargetException; -import java.lang.NoSuchMethodException; -import android.view.WindowInsets; -import android.os.Build; -import android.content.Context; - -@ReactModule(name = NavbarHeightModule.NAME) -public class NavbarHeightModule extends ReactContextBaseJavaModule { - public static final String NAME = "NavbarHeight"; - - public NavbarHeightModule(ReactApplicationContext reactContext) { - super(reactContext); - } - - @Override - @NonNull - public String getName() { - return NAME; - } - - // Example method - // See https://reactnative.dev/docs/native-modules-android - @ReactMethod - public double getNavigationBarHeightSync() { - Context context = getReactApplicationContext(); - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - if (Build.VERSION.SDK_INT >= 30) { - return windowManager - .getCurrentWindowMetrics() - .getWindowInsets() - .getInsets(WindowInsets.Type.navigationBars()) - .bottom; - } else { - Point appUsableSize = getAppUsableScreenSize(context); - Point realScreenSize = getRealScreenSize(context); - - // navigation bar on the side - if (appUsableSize.x < realScreenSize.x) { - return appUsableSize.y; - } - - // navigation bar at the bottom - if (appUsableSize.y < realScreenSize.y) { - return realScreenSize.y - appUsableSize.y; - } - - // navigation bar is not present - return 0; - } - } - public Point getAppUsableScreenSize(Context context) { - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = windowManager.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - return size; - } - public Point getRealScreenSize(Context context) { - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = windowManager.getDefaultDisplay(); - Point size = new Point(); - - if (Build.VERSION.SDK_INT >= 17) { - display.getRealSize(size); - } else if (Build.VERSION.SDK_INT >= 14) { - try { - size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display); - size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display); - } catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {} - } - - return size; - } - @ReactMethod(isBlockingSynchronousMethod = true) - public double getNavigationBarHeight() { - return getNavigationBarHeightSync(); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java b/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java deleted file mode 100644 index 0f4df602636..00000000000 --- a/android/app/src/main/java/me/rainbow/NativeModules/NavbarHeight/NavbarHeightPackage.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.rainbow.NativeModules.NavbarHeight; - -import androidx.annotation.NonNull; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class NavbarHeightPackage implements ReactPackage { - @NonNull - @Override - public List createNativeModules(@NonNull ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - modules.add(new NavbarHeightModule(reactContext)); - return modules; - } - - @NonNull - @Override - public List createViewManagers(@NonNull ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 1de07c21f7c..8199414d720 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,13 +22,11 @@ buildscript { url 'https://maven.fabric.io/public' } mavenCentral() - maven { - url("${rootProject.projectDir}/../node_modules/detox/Detox-android") - } } dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") +// classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" classpath("de.undercouch:gradle-download-task:5.0.1") classpath 'com.google.gms:google-services:4.3.15' diff --git a/android/settings.gradle b/android/settings.gradle index 9bc55e25785..b03e948f98f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -6,7 +6,7 @@ project(':react-native-palette-full').projectDir = new File(rootProject.projectD apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' -include ':detox' +/*include ':detox' project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') - +*/ includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/e2e/3_homeScreen.spec.ts b/e2e/3_homeScreen.spec.ts index 94fea2546f0..c090519ded0 100644 --- a/e2e/3_homeScreen.spec.ts +++ b/e2e/3_homeScreen.spec.ts @@ -5,9 +5,8 @@ import { checkIfExists, checkIfExistsByText, swipe, + waitAndTap, afterAllcleanApp, - tap, - delayTime, } from './helpers'; const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth'; @@ -42,20 +41,19 @@ describe('Home Screen', () => { }); it('tapping "Swap" opens the swap screen', async () => { - await tap('swap-button'); - await delayTime('long'); + await waitAndTap('swap-button'); await checkIfExists('swap-screen'); - await swipe('swap-screen', 'down', 'fast'); + await swipe('swap-screen', 'down', 'slow'); }); it('tapping "Send" opens the send screen', async () => { - await tap('send-button'); + await waitAndTap('send-button'); await checkIfVisible('send-asset-form-field'); - await swipe('send-asset-form-field', 'down', 'fast'); + await swipe('send-asset-form-field', 'down'); }); it('tapping "Copy" shows copy address toast', async () => { - await tap('receive-button'); + await waitAndTap('receive-button'); await checkIfVisible('address-copied-toast'); }); }); diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts deleted file mode 100644 index 4655c019275..00000000000 --- a/e2e/9_swaps.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * // Other tests to consider: - * - Flip assets - * - exchange button onPress - * - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged - * - swap execution - * - token search (both from userAssets and output token list) - * - custom gas panel - * - flashbots - * - slippage - * - explainer sheets - * - switching wallets inside of swap screen - */ - -import { - importWalletFlow, - sendETHtoTestWallet, - checkIfVisible, - beforeAllcleanApp, - afterAllcleanApp, - fetchElementAttributes, - tap, - delayTime, - swipeUntilVisible, - tapAndLongPress, - swipe, -} from './helpers'; - -import { expect } from '@jest/globals'; -import { WALLET_VARS } from './testVariables'; - -describe('Swap Sheet Interaction Flow', () => { - beforeAll(async () => { - await beforeAllcleanApp({ hardhat: true }); - }); - afterAll(async () => { - await afterAllcleanApp({ hardhat: true }); - }); - - it('Import a wallet and go to welcome', async () => { - await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK); - }); - - it('Should send ETH to test wallet', async () => { - // send 20 eth - await sendETHtoTestWallet(); - }); - - it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => { - await tap('dev-button-hardhat'); - await checkIfVisible('testnet-toast-Hardhat'); - - // doesn't work atm - // validate it has the expected funds of 20 eth - // const attributes = await fetchElementAttributes('fast-coin-info'); - // expect(attributes.label).toContain('Ethereum'); - // expect(attributes.label).toContain('20'); - }); - - it('Should open swap screen with 50% inputAmount for inputAsset', async () => { - await device.disableSynchronization(); - await tap('swap-button'); - await delayTime('long'); - - await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100); - await swipe('token-to-buy-list', 'up', 'slow', 0.1); - - await tap('token-to-buy-dai-1'); - await delayTime('medium'); - - const swapInput = await fetchElementAttributes('swap-asset-input'); - - expect(swapInput.label).toContain('10'); - expect(swapInput.label).toContain('ETH'); - }); - - it('Should be able to go to review and execute a swap', async () => { - await tap('swap-bottom-action-button'); - const inputAssetActionButton = await fetchElementAttributes('swap-input-asset-action-button'); - const outputAssetActionButton = await fetchElementAttributes('swap-output-asset-action-button'); - const holdToSwapButton = await fetchElementAttributes('swap-bottom-action-button'); - - expect(inputAssetActionButton.label).toBe('ETH 􀆏'); - expect(outputAssetActionButton.label).toBe('DAI 􀆏'); - expect(holdToSwapButton.label).toBe('􀎽 Hold to Swap'); - - await tapAndLongPress('swap-bottom-action-button', 1500); - - // TODO: This doesn't work so need to figure this out eventually... - // await checkIfVisible('profile-screen'); - }); - - it.skip('Should be able to verify swap is happening', async () => { - // await delayTime('very-long'); - // const activityListElements = await fetchElementAttributes('wallet-activity-list'); - // expect(activityListElements.label).toContain('ETH'); - // expect(activityListElements.label).toContain('DAI'); - // await tapByText('Swapping'); - // await delayTime('long'); - // const transactionSheet = await checkIfVisible('transaction-details-sheet'); - // expect(transactionSheet).toBeTruthy(); - }); - - it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * grab highest user asset balance from userAssetsStore - * expect inputAsset.uniqueId === highest user asset uniqueId - */ - }); - - it.skip('Should open swap screen from asset chart with that asset selected', async () => { - /** - * tap any user asset (store const uniqueId here) - * wait for Swap header to be visible - * expect inputAsset.uniqueId === const uniqueId ^^ - */ - }); - - it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * grab highest user asset balance from userAssetsStore - * expect inputAsset.uniqueId === highest user asset uniqueId - */ - }); - - it.skip('Should not be able to type in output amount if cross-chain quote', async () => { - /** - * tap swap button - * wait for Swap header to be visible - * select different chain in output list chain selector - * select any asset in output token list - * focus output amount - * attempt to type any number in the SwapNumberPad - * attempt to remove a character as well - * - * ^^ expect both of those to not change the outputAmount - */ - }); -}); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 7ecb9811fc3..55344f6052c 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -4,9 +4,8 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { expect, device, element, by, waitFor } from 'detox'; import { parseEther } from '@ethersproject/units'; -import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox'; -const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F'; +const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608'; const DEFAULT_TIMEOUT = 20_000; const android = device.getPlatform() === 'android'; @@ -71,16 +70,6 @@ export async function tap(elementId: string | RegExp) { } } -interface CustomElementAttributes { - elements: Array; -} - -type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes; - -export const fetchElementAttributes = async (testId: string): Promise => { - return (await element(by.id(testId)).getAttributes()) as ElementAttributes; -}; - export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) { await delayTime('medium'); try { @@ -199,17 +188,17 @@ export async function clearField(elementId: string | RegExp) { } } -export async function tapAndLongPress(elementId: string | RegExp, duration?: number) { +export async function tapAndLongPress(elementId: string | RegExp) { try { - return await element(by.id(elementId)).longPress(duration); + return await element(by.id(elementId)).longPress(); } catch (error) { throw new Error(`Error long-pressing element by id "${elementId}": ${error}`); } } -export async function tapAndLongPressByText(text: string | RegExp, duration?: number) { +export async function tapAndLongPressByText(text: string | RegExp) { try { - return await element(by.text(text)).longPress(duration); + return await element(by.text(text)).longPress(); } catch (error) { throw new Error(`Error long-pressing element by text "${text}": ${error}`); } @@ -274,22 +263,15 @@ export async function scrollTo(scrollviewId: string | RegExp, edge: Direction) { } } -export async function swipeUntilVisible( - elementId: string | RegExp, - scrollViewId: string, - direction: Direction, - percentageVisible?: number -) { +export async function swipeUntilVisible(elementId: string | RegExp, scrollViewId: string, direction: Direction, pctVisible = 75) { let stop = false; - while (!stop) { try { await waitFor(element(by.id(elementId))) - .toBeVisible(percentageVisible) + .toBeVisible(pctVisible) .withTimeout(500); - stop = true; - } catch (e) { + } catch { await swipe(scrollViewId, direction, 'slow', 0.2); } } @@ -472,11 +454,6 @@ export async function sendETHtoTestWallet() { to: TESTING_WALLET, value: parseEther('20'), }); - await delayTime('long'); - const balance = await provider.getBalance(TESTING_WALLET); - if (balance.lt(parseEther('20'))) { - throw Error('Error sending ETH to test wallet'); - } return true; } diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index 1af4554f95c..ca3dbe8fdae 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -1,40 +1,37 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.7) - base64 - nkf + CFPropertyList (3.0.6) rexml activesupport (7.0.5.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) - artifactory (3.0.17) + artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.969.0) - aws-sdk-core (3.202.0) + aws-partitions (1.882.0) + aws-sdk-core (3.190.3) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.9) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.88.0) - aws-sdk-core (~> 3, >= 3.201.0) - aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.159.0) - aws-sdk-core (~> 3, >= 3.201.0) + aws-sdk-kms (1.76.0) + aws-sdk-core (~> 3, >= 3.188.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.142.0) + aws-sdk-core (~> 3, >= 3.189.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.5) - aws-sigv4 (1.9.1) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - base64 (0.2.0) claide (1.1.0) cocoapods (1.14.3) addressable (~> 2.8) @@ -87,7 +84,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.111.0) + excon (0.109.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -109,22 +106,22 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.2) + faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.1) - fastlane (2.222.0) + fastimage (2.3.0) + fastlane (2.219.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored (~> 1.2) + colored commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -145,10 +142,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1, < 1.0.0) + optparse (>= 0.1.1) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.5) + security (= 0.1.3) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -157,7 +154,7 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + xcpretty-travis-formatter (>= 0.0.3) ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) @@ -178,12 +175,12 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.7.1) + google-cloud-core (1.6.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.4.0) + google-cloud-errors (1.3.1) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -199,44 +196,41 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.7) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.2) - jwt (2.8.2) - base64 - mini_magick (4.13.2) + json (2.7.1) + jwt (2.7.1) + mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.18.1) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.4.1) + multipart-post (2.3.0) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) - nkf (0.2.0) - optparse (0.5.0) + optparse (0.4.0) os (1.1.4) plist (3.7.1) public_suffix (4.0.7) - rake (13.2.1) + rake (13.1.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.6) - strscan + rexml (3.2.6) rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.5) - signet (0.19.0) + security (0.1.3) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -244,7 +238,6 @@ GEM simctl (1.6.10) CFPropertyList naturally - strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -260,13 +253,13 @@ GEM uber (0.1.0) unicode-display_width (2.5.0) word_wrap (1.0.0) - xcodeproj (1.25.0) + xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1c400b0f1c1..ea5bcba53fc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -5,13 +5,13 @@ PODS: - React-Core - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FasterImage (1.6.2): - - FasterImage/Nuke (= 1.6.2) - - FasterImage/NukeUI (= 1.6.2) + - FasterImage (1.5.0): + - FasterImage/Nuke (= 1.5.0) + - FasterImage/NukeUI (= 1.5.0) - React-Core - - FasterImage/Nuke (1.6.2): + - FasterImage/Nuke (1.5.0): - React-Core - - FasterImage/NukeUI (1.6.2): + - FasterImage/NukeUI (1.5.0): - React-Core - FBLazyVector (0.74.3) - Firebase (10.27.0): @@ -1169,9 +1169,9 @@ PODS: - Yoga - react-native-change-icon (4.0.0): - React-Core - - react-native-cloud-fs (2.6.2): + - react-native-cloud-fs (2.6.1): - React - - react-native-compat (2.15.1): + - react-native-compat (2.11.2): - DoubleConversion - glog - hermes-engine @@ -1241,7 +1241,7 @@ PODS: - React-Core - react-native-screen-corner-radius (0.2.2): - React - - react-native-skia (1.3.11): + - react-native-skia (1.3.8): - DoubleConversion - glog - hermes-engine @@ -1624,7 +1624,7 @@ PODS: - Yoga - RNFS (2.16.6): - React - - RNGestureHandler (2.18.1): + - RNGestureHandler (2.17.1): - DoubleConversion - glog - hermes-engine @@ -1670,51 +1670,7 @@ PODS: - React-Core - RNReactNativeHapticFeedback (2.2.0): - React-Core - - RNReanimated (3.15.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 3.15.0) - - RNReanimated/worklets (= 3.15.0) - - Yoga - - RNReanimated/reanimated (3.15.0): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - RNReanimated/worklets (3.15.0): + - RNReanimated (3.14.0): - DoubleConversion - glog - hermes-engine @@ -1738,7 +1694,7 @@ PODS: - RNRudderSdk (1.12.1): - React - Rudder (< 2.0.0, >= 1.24.1) - - RNScreens (3.34.0): + - RNScreens (3.32.0): - DoubleConversion - glog - hermes-engine @@ -1772,7 +1728,7 @@ PODS: - RNSound/Core (= 0.11.2) - RNSound/Core (0.11.2): - React-Core - - RNSVG (15.6.0): + - RNSVG (15.3.0): - React-Core - RNTextSize (4.0.0-rc.1): - React @@ -2258,7 +2214,7 @@ SPEC CHECKSUMS: BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 - FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee + FasterImage: 1f65cdb2966c47b2a7b83162e5fc712534e50149 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 FirebaseABTesting: d87f56707159bae64e269757a6e963d490f2eebe @@ -2321,8 +2277,8 @@ SPEC CHECKSUMS: react-native-branch: 960c897d57b9f4912b08b9d06a25284b6e9879da react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22 react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 - react-native-cloud-fs: c90379f513b8d7ad5cfed610ccf50f27d837016e - react-native-compat: 408a4b320fa4426f2c25ab74ed5764be6b3eb5aa + react-native-cloud-fs: e7103d1f693c57b481f820fa5e6b6b0522a5a31e + react-native-compat: 0468620f537a51ce0d3f4ba65bda6872240b9a0d react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 @@ -2337,7 +2293,7 @@ SPEC CHECKSUMS: react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d react-native-screen-corner-radius: 67064efbb78f2d48f01626713ae8142f6a20f925 - react-native-skia: 8da84ea9410504bf27f0db229539a43f6caabb6a + react-native-skia: 929b6d19b41e3483a33a410f5c1efdf002929230 react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 react-native-text-input-mask: 07227297075f9653315f43b0424d596423a01736 react-native-udp: 96a517e5a121cfe69f4b05eeeafefe00c623debf @@ -2382,7 +2338,7 @@ SPEC CHECKSUMS: RNFBRemoteConfig: a8325e2d3772127358dd2e7d5e39c9ad3e3379ac RNFlashList: e9b57a5553639f9b528cc50ab53f25831722ed62 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: efed690b8493a00b99654043daeb1335276ac4a2 + RNGestureHandler: 8dbcccada4a7e702e7dec9338c251b1cf393c960 RNImageCropPicker: 13eab07a785c7a8f8047a1146f7e59d1911c7bb8 RNInputMask: 815461ebdf396beb62cf58916c35cf6930adb991 RNKeyboard: 14793d75953d99c6d950090b8e9698b234c5d08c @@ -2391,13 +2347,13 @@ SPEC CHECKSUMS: RNOS: 31db6fa4a197d179afbba9e6b4d28d450a7f250b RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 - RNReanimated: 45553a3ae29a75a76269595f8554d07d4090e392 + RNReanimated: f4ff116e33e0afc3d127f70efe928847c7c66355 RNRudderSdk: 805d4b7064714f3295bf5f152d3812cc67f67a93 - RNScreens: aa943ad421c3ced3ef5a47ede02b0cbfc43a012e + RNScreens: 5aeecbb09aa7285379b6e9f3c8a3c859bb16401c RNSentry: 7ae2a06a5563de39ec09dcb1e751ac0c67f1883c RNShare: eaee3dd5a06dad397c7d3b14762007035c5de405 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 - RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b + RNSVG: a48668fd382115bc89761ce291a81c4ca5f2fd2e RNTextSize: 21c94a67819fbf2c358a219bf6d251e3be9e5f29 RSCrashReporter: 6b8376ac729b0289ebe0908553e5f56d8171f313 Rudder: 3cfcd9e6c5359cd6d49f411101ed9b894e3b64bd diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 2dcbfe439c8..fe4d0b3f373 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 0299CE7B2886202800B5C7E7 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0299CE7A2886202800B5C7E7 /* NotificationService.m */; }; 0299CE7F2886202800B5C7E7 /* ImageNotification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0299CE772886202800B5C7E7 /* ImageNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 0C2E322C01EE0F31C4776094 /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -35,7 +34,8 @@ 66A1FEB624AB641100C3F539 /* RNCMScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEB324AB641100C3F539 /* RNCMScreen.m */; }; 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */; }; 66A28EB024CAF1B500410A88 /* TestFlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A28EAF24CAF1B500410A88 /* TestFlight.m */; }; - A2AAF523E5B2B1EBC8137D09 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */; }; + 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */; }; + 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -68,6 +68,7 @@ B5CE8FFF29A5758100EB1EFA /* pooly@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */; }; B5D7F2F029E8D41E003D6A54 /* finiliar@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */; }; B5D7F2F129E8D41E003D6A54 /* finiliar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */; }; + B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */; }; C04D10F025AFC8C1003BEF7A /* Extras.json in Resources */ = {isa = PBXBuildFile; fileRef = C04D10EF25AFC8C1003BEF7A /* Extras.json */; }; C1038325273C2D0C00B18210 /* PriceWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF75272BA7AA00FF5C78 /* PriceWidgetView.swift */; }; C1038337273C5C4200B18210 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF62272BA6EF00FF5C78 /* PriceWidget.swift */; }; @@ -128,7 +129,6 @@ C1C61A932731A05700E5C0B3 /* RainbowTokenList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */; }; C1EB01302731B68400830E70 /* TokenDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EB012E2731B68400830E70 /* TokenDetails.swift */; }; C1EB01312731B68400830E70 /* TokenDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EB012E2731B68400830E70 /* TokenDetails.swift */; }; - C5931485B6F3169BD50DFD30 /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */; }; C72F456C99A646399192517D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 98AED33BAB4247CEBEF8464D /* libz.tbd */; }; C97EAD8D2BD6C6DF00322D53 /* RCTDeviceUUID.m in Sources */ = {isa = PBXBuildFile; fileRef = C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */; }; C9B378A22C5159880085E5D0 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */; }; @@ -140,7 +140,7 @@ C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; - F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */; }; + F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,14 +203,13 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; - 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; 0299CE772886202800B5C7E7 /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE792886202800B5C7E7 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; - 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; + 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; + 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -236,6 +235,9 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; + 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; + 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -248,16 +250,16 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; + 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; + 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; + 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; - 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; - 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; - 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; - 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; + 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; @@ -273,19 +275,14 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; - 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; - 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; + 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; - 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ButtonManager.m; sourceTree = ""; }; - A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; AA0B1CB82B00C5E100EAF77D /* SF-Mono-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Semibold.otf"; path = "../src/assets/fonts/SF-Mono-Semibold.otf"; sourceTree = ""; }; AA0B1CB92B00C5E100EAF77D /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Bold.otf"; path = "../src/assets/fonts/SF-Mono-Bold.otf"; sourceTree = ""; }; AA0B1CBA2B00C5E100EAF77D /* SF-Pro-Rounded-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Black.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Black.otf"; sourceTree = ""; }; @@ -296,8 +293,6 @@ AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; - B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; - B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; B52242E428B1B11F0024D19D /* smol@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "smol@2x.png"; sourceTree = ""; }; @@ -318,7 +313,6 @@ B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pooly@3x.png"; sourceTree = ""; }; B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@3x.png"; sourceTree = ""; }; B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@2x.png"; sourceTree = ""; }; - BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C04D10EF25AFC8C1003BEF7A /* Extras.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Extras.json; sourceTree = ""; }; C11640E7274DC10B00C9120A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; C1272389274EBBB6006AC743 /* CurrencyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyDetails.swift; sourceTree = ""; }; @@ -346,6 +340,7 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; + C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -358,10 +353,15 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; + E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; + E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; + FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */, + 9475ADDD95382DE974220A85 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - C5931485B6F3169BD50DFD30 /* libPods-Rainbow.a in Frameworks */, + B682BA1A808F4025E489BC5C /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 0C2E322C01EE0F31C4776094 /* libPods-PriceWidgetExtension.a in Frameworks */, + 9860014A299B7D280C6E1EEA /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - A2AAF523E5B2B1EBC8137D09 /* libPods-SelectTokenIntent.a in Frameworks */, + F2284B4A0163A10630FBF1A5 /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */, - 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */, - 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */, - 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */, + E45D36EDBB176CF5F4C3EC97 /* libPods-ImageNotification.a */, + 2FF0CE0C1AF2D6171259E837 /* libPods-PriceWidgetExtension.a */, + 4B0234CB425AEA2691C35B96 /* libPods-Rainbow.a */, + 81728AB6287F2E58118AF0D2 /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */, - 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */, - 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */, - 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */, - 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */, - 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */, - 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */, - A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */, - 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */, - 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */, - 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */, - B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */, - B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */, - EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */, - 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */, + F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */, + 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */, + C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */, + 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */, + E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */, + FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */, + 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */, + 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */, + E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */, + 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */, + 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */, + 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */, + 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */, + 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */, + D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */, + 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */, + DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */, + F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - E25F1DAB12B818820CCCC7DA /* [CP] Embed Pods Frameworks */, - 89D80C8A947BAC7AC4044C2B /* [CP] Copy Pods Resources */, - 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */, + 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */, + B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */, + 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */, + B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */, + 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */, + EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */, + E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -925,12 +925,12 @@ 0299CE762886202800B5C7E7 = { CreatedOnToolsVersion = 13.3.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; 13B07F861A680F5B00A75B9A = { DevelopmentTeam = L74NQAQB8H; LastSwiftMigration = 1120; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Push = { enabled = 1; @@ -943,22 +943,22 @@ C16DCF5B272BA6EF00FF5C78 = { CreatedOnToolsVersion = 12.5.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; C16DCF7E272BAB9500FF5C78 = { CreatedOnToolsVersion = 12.5.1; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; C9B3789F2C5159880085E5D0 = { CreatedOnToolsVersion = 15.4; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; C9B378B72C515A860085E5D0 = { CreatedOnToolsVersion = 15.4; DevelopmentTeam = L74NQAQB8H; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -1098,7 +1098,7 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */ = { + 0365769EBB9FBABB80CCDF47 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1111,7 +1111,7 @@ shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */ = { + 0D53D2C3A3AB878B3A7BD736 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1133,13 +1133,31 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */ = { + 25178C62F57F29E51E0A387F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 5F667106DDE5C887160B00CC /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1166,10 +1184,25 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */ = { + 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Upload Debug Symbols to Sentry"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; + }; + B0336C04C83B1700864F2C15 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1184,14 +1217,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 89D80C8A947BAC7AC4044C2B /* [CP] Copy Pods Resources */ = { + B9ABD906A256A6AB8B9D9882 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1247,13 +1280,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */ = { + DC9CDEBD91E5DA1969B01966 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1264,6 +1297,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1277,34 +1311,20 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + E7434C1DCF27BA15D34AAFEF /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - ); - name = "Upload Debug Symbols to Sentry"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; - }; - A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1315,7 +1335,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1329,14 +1348,13 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */ = { + EE272D1D0094FC65203B3A64 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1351,32 +1369,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E25F1DAB12B818820CCCC7DA /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */ = { + F94DBF8BE0F88653223EAF49 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1391,7 +1391,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = F945E191B6B6D2348A0CDB28 /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1602,8 +1602,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -1623,7 +1623,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1633,7 +1633,7 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 71467CB869A27C2DCB7D5593 /* Pods-ImageNotification.release.xcconfig */; + baseConfigurationReference = 12EE7C7D7372EBA1AAA4EFDB /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1652,8 +1652,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1671,7 +1671,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.ImageNotification"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1681,7 +1681,7 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0DD21FBAC7D56C7D954F4AE7 /* Pods-ImageNotification.localrelease.xcconfig */; + baseConfigurationReference = C4E236E36E66B1A888A4516C /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1700,8 +1700,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1719,7 +1719,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1729,7 +1729,7 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4EC548A22626AE26F665F38E /* Pods-ImageNotification.staging.xcconfig */; + baseConfigurationReference = 49D4B971711ABEECC6758379 /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1748,8 +1748,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -1767,7 +1767,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1777,7 +1777,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A6C0C57C6C7EE4B4A24F3850 /* Pods-Rainbow.debug.xcconfig */; + baseConfigurationReference = 18B86A94D81F3008026E29B6 /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1787,8 +1787,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = time; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/RainbowDebug.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; @@ -1833,7 +1833,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.37; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1843,7 +1843,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1854,7 +1854,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = E91B95BAB8E712532F1E3828 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1895,7 +1895,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.37; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = 0D552401824AA1283B36EA29 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1981,8 +1981,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = ""; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/Rainbow.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2011,7 +2011,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.37; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2021,7 +2021,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; SWIFT_VERSION = 5.0; @@ -2087,7 +2087,7 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 599A5384115FEFEF13D00D9A /* Pods-Rainbow.localrelease.xcconfig */; + baseConfigurationReference = 33DA83342FB9CB70585D92B7 /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2097,8 +2097,8 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = time; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/Rainbow.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2127,7 +2127,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.37; + MARKETING_VERSION = 1.9.36; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2137,7 +2137,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow"; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; SWIFT_VERSION = 5.0; @@ -2260,7 +2260,7 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9E3D92B05F3FA768E29E7A3C /* Pods-PriceWidgetExtension.debug.xcconfig */; + baseConfigurationReference = E6E3C023897A77DB5D8D7331 /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2282,8 +2282,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; @@ -2297,7 +2297,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -2309,7 +2309,7 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 010FF25289284C29C641D30F /* Pods-PriceWidgetExtension.release.xcconfig */; + baseConfigurationReference = FFE54B0CB0579BC976CDE218 /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2331,8 +2331,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2345,7 +2345,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2356,7 +2356,7 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0BF4990455B5D9113BEF5606 /* Pods-PriceWidgetExtension.localrelease.xcconfig */; + baseConfigurationReference = 19249CA6B7BEF5161E2335BD /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2378,8 +2378,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2392,7 +2392,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2403,7 +2403,7 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 563BF5960D163F0670167BF0 /* Pods-PriceWidgetExtension.staging.xcconfig */; + baseConfigurationReference = 17BE2AF42CEF7F91DDB765C5 /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2425,8 +2425,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2439,7 +2439,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2450,7 +2450,7 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B295DCB35322858F3C197E67 /* Pods-SelectTokenIntent.debug.xcconfig */; + baseConfigurationReference = 25F7920534BC44E62C20018F /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2470,8 +2470,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = L74NQAQB8H; ENABLE_TESTABILITY = YES; @@ -2485,7 +2485,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -2497,7 +2497,7 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B15D3037AC12E33A43C1EAAC /* Pods-SelectTokenIntent.release.xcconfig */; + baseConfigurationReference = 8F44C46DDEDC679EA01FEB7F /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2517,8 +2517,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2531,7 +2531,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2542,7 +2542,7 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EC702D07AE19DB5AA566838D /* Pods-SelectTokenIntent.localrelease.xcconfig */; + baseConfigurationReference = 62E368B8E073C738A14FCFD5 /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2562,8 +2562,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2576,7 +2576,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2587,7 +2587,7 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 87B20DF1029C0FACF89CC70B /* Pods-SelectTokenIntent.staging.xcconfig */; + baseConfigurationReference = D7500DBC345BBAA0F9245CF3 /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2607,8 +2607,8 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L74NQAQB8H; @@ -2621,7 +2621,7 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2652,11 +2652,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2678,6 +2679,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2709,12 +2711,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2730,6 +2733,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2760,12 +2764,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2781,6 +2786,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2811,12 +2817,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2832,6 +2839,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.OpenInRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2862,11 +2870,12 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2888,6 +2897,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2919,12 +2929,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2940,6 +2951,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2970,12 +2982,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -2991,6 +3004,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -3021,12 +3035,13 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; @@ -3042,6 +3057,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development me.rainbow.ShareWithRainbow"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/ios/Rainbow/Info.plist b/ios/Rainbow/Info.plist index 91736a5af8d..4d31e1cc358 100644 --- a/ios/Rainbow/Info.plist +++ b/ios/Rainbow/Info.plist @@ -187,8 +187,6 @@ NSAllowsArbitraryLoads - NSAllowsArbitraryLoadsInWebContent - NSAllowsLocalNetworking diff --git a/package.json b/package.json index 52c229cff23..e95e1737304 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.37-1", + "version": "1.9.36-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", @@ -21,13 +21,11 @@ "clean:packager": "watchman watch-del-all", "clean:node": "rm -rf node_modules", "nuke": "./scripts/nuke.sh", + "detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose", "detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release", - "detox:android:tests": "detox test -c android.emu.debug --maxWorkers 2 -- --bail 1", - "detox:android": "detox build -c android.emu.debug && yarn detox:android:tests", - "detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ", - "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1", - "detox:ios": "yarn detox:ios:build && yarn detox:ios:tests", - "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1", + "detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1", + "detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests", + "detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1", "ds:install": "cd src/design-system/docs && yarn install", "ds": "cd src/design-system/docs && yarn dev", "fast": "yarn install && yarn setup && yarn install-pods-fast", @@ -44,8 +42,8 @@ "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", - "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js", - "lint:js": "eslint --cache . --quiet", + "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js --quiet", + "lint:js": "eslint --cache .", "lint:ts": "yarn tsc --skipLibCheck --noEmit", "postinstall": "./scripts/postinstall.sh", "start": "react-native start", @@ -68,7 +66,7 @@ "dependencies": { "@bankify/react-native-animate-number": "0.2.1", "@bradgarropy/use-countdown": "1.4.1", - "@candlefinance/faster-image": "1.6.2", + "@candlefinance/faster-image": "1.5.0", "@capsizecss/core": "3.0.0", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", @@ -123,7 +121,7 @@ "@rudderstack/rudder-sdk-react-native": "1.12.1", "@sentry/react-native": "5.22.0", "@shopify/flash-list": "1.7.0", - "@shopify/react-native-skia": "1.3.11", + "@shopify/react-native-skia": "1.3.8", "@tanstack/query-async-storage-persister": "4.2.1", "@tanstack/react-query": "4.2.1", "@tanstack/react-query-persist-client": "4.2.1", @@ -136,12 +134,12 @@ "@unstoppabledomains/resolution": "7.1.4", "@wagmi/chains": "1.8.0", "@walletconnect/client": "1.8.0", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.11.2", "@walletconnect/legacy-utils": "2.0.0", - "@walletconnect/react-native-compat": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", - "@walletconnect/web3wallet": "1.14.1", + "@walletconnect/react-native-compat": "2.11.2", + "@walletconnect/types": "2.11.2", + "@walletconnect/utils": "2.11.2", + "@walletconnect/web3wallet": "1.10.2", "assert": "1.5.0", "async-mutex": "0.3.2", "asyncstorage-down": "4.2.0", @@ -193,6 +191,7 @@ "moti": "0.28", "multiformats": "9.6.2", "nanoid": "3.2.0", + "p-queue": "7.2.0", "p-wait-for": "4.1.0", "pako": "2.0.4", "parse-ms": "2.1.0", @@ -219,7 +218,7 @@ "react-native-branch": "5.3.1", "react-native-change-icon": "4.0.0", "react-native-circular-progress": "1.3.8", - "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a", + "react-native-cloud-fs": "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a", "react-native-crypto": "2.2.0", "react-native-dark-mode": "0.2.2", "react-native-device-info": "5.3.1", @@ -227,7 +226,7 @@ "react-native-extra-dimensions-android": "1.2.2", "react-native-fast-image": "8.5.11", "react-native-fs": "2.16.6", - "react-native-gesture-handler": "2.18.1", + "react-native-gesture-handler": "2.17.1", "react-native-get-random-values": "1.5.0", "react-native-haptic-feedback": "2.2.0", "react-native-image-crop-picker": "0.41.0", @@ -248,25 +247,25 @@ "react-native-quick-md5": "3.0.6", "react-native-radial-gradient": "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c", "react-native-randombytes": "3.5.3", - "react-native-reanimated": "3.15.0", + "react-native-reanimated": "3.14.0", "react-native-redash": "16.3.0", "react-native-restart": "0.0.22", "react-native-safe-area-context": "4.10.1", "react-native-safe-area-view": "rainbow-me/react-native-safe-area-view", "react-native-screen-corner-radius": "0.2.2", - "react-native-screens": "3.34.0", + "react-native-screens": "3.32.0", "react-native-section-list-get-item-layout": "2.2.3", "react-native-share": "8.2.1", "react-native-sound": "0.11.2", "react-native-splash-screen": "3.3.0", "react-native-storage": "1.0.1", - "react-native-svg": "15.6.0", + "react-native-svg": "15.3.0", "react-native-tab-view": "3.5.1", "react-native-tcp": "3.3.2", "react-native-text-input-mask": "2.0.0", "react-native-text-size": "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a", "react-native-tooltip": "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590", - "react-native-tooltips": "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa", + "react-native-tooltips": "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3", "react-native-udp": "2.7.0", "react-native-url-polyfill": "2.0.0", "react-native-version-number": "0.3.6", @@ -344,7 +343,7 @@ "babel-plugin-date-fns": "2.0.0", "babel-plugin-graphql-tag": "2.5.0", "babel-plugin-lodash": "3.3.4", - "babel-plugin-module-resolver": "5.0.2", + "babel-plugin-module-resolver": "4.0.0", "babel-plugin-rewire": "1.2.0", "babel-plugin-styled-components": "1.11.1", "babel-plugin-transform-remove-console": "6.9.4", @@ -371,7 +370,7 @@ "ts-migrate": "0.1.26", "typescript": "5.1.6", "typescript-coverage-report": "0.6.1", - "webpack": "5.94.0", + "webpack": "5.90.3", "webpack-cli": "5.1.4" }, "engines": { diff --git a/patches/@candlefinance+faster-image+1.5.0.patch b/patches/@candlefinance+faster-image+1.5.0.patch new file mode 100644 index 00000000000..f29f47af8db --- /dev/null +++ b/patches/@candlefinance+faster-image+1.5.0.patch @@ -0,0 +1,29 @@ +diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +index a0b0ac6..e093754 100644 +--- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift ++++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift +@@ -41,7 +41,10 @@ final class FasterImageView: UIView { + lazyImageView.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + lazyImageView.pipeline = .shared +- lazyImageView.priority = .high ++ // 🌈🌈 ++ lazyImageView.priority = .veryLow ++// lazyImageView.priority = .high ++ // 🌈🌈 + lazyImageView.onCompletion = { [weak self] result in + self?.completionHandler(with: result) + } +@@ -121,11 +124,7 @@ final class FasterImageView: UIView { + } + } + +- var showActivityIndicator = false { +- didSet { +- lazyImageView.placeholderView = UIActivityIndicatorView() +- } +- } ++ var showActivityIndicator = false + + var resizeMode = "contain" { + didSet { diff --git a/patches/@candlefinance+faster-image+1.6.2.patch b/patches/@candlefinance+faster-image+1.6.2.patch deleted file mode 100644 index 238dfac6712..00000000000 --- a/patches/@candlefinance+faster-image+1.6.2.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -index a8dee30..e8e38d3 100644 ---- a/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -+++ b/node_modules/@candlefinance/faster-image/ios/FasterImageViewManager.swift -@@ -233,17 +233,9 @@ final class FasterImageView: UIView { - } - } - -- var showActivityIndicator = false { -- didSet { -- let activity = UIActivityIndicatorView() -- if self.activityColor != nil { -- activity.color = self.activityColor -- } -- lazyImageView.placeholderView = activity -- } -- } -+ var showActivityIndicator = false - -- var activityColor: UIColor? -+ var activityColor: UIColor? = Color.clear - - var resizeMode = "contain" { - didSet { diff --git a/patches/react-native-gesture-handler+2.18.1.patch b/patches/react-native-gesture-handler+2.17.1.patch similarity index 100% rename from patches/react-native-gesture-handler+2.18.1.patch rename to patches/react-native-gesture-handler+2.17.1.patch diff --git a/patches/react-native-reanimated+3.15.0.patch b/patches/react-native-reanimated+3.14.0.patch similarity index 100% rename from patches/react-native-reanimated+3.15.0.patch rename to patches/react-native-reanimated+3.14.0.patch diff --git a/patches/react-native-text-input-mask+2.0.0.patch b/patches/react-native-text-input-mask+2.0.0.patch index 9ebb2b5fb33..b0dc705aa3c 100644 --- a/patches/react-native-text-input-mask+2.0.0.patch +++ b/patches/react-native-text-input-mask+2.0.0.patch @@ -1,310 +1,7 @@ diff --git a/node_modules/react-native-text-input-mask/.DS_Store b/node_modules/react-native-text-input-mask/.DS_Store new file mode 100644 -index 0000000..e69de29 -diff --git a/node_modules/react-native-text-input-mask/android/build.gradle b/node_modules/react-native-text-input-mask/android/build.gradle -index ba1e645..18630dc 100644 ---- a/node_modules/react-native-text-input-mask/android/build.gradle -+++ b/node_modules/react-native-text-input-mask/android/build.gradle -@@ -26,6 +26,6 @@ android { - - dependencies { - implementation 'com.facebook.react:react-native:+' -- implementation 'com.redmadrobot:inputmask:4.1.0' -+ implementation 'com.github.RedMadRobot:input-mask-android:4.1.0' - implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.21' - } -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin -new file mode 100644 -index 0000000..0d259dd ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/results.bin -@@ -0,0 +1 @@ -+o/classes -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex -new file mode 100644 -index 0000000..b937d77 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/3330fd484248ddd211455a7ebeca4def/transformed/classes/classes_dex/classes.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin -new file mode 100644 -index 0000000..5ff383e ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/results.bin -@@ -0,0 +1 @@ -+o/debug -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex -new file mode 100644 -index 0000000..530bd9a -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/BuildConfig.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex -new file mode 100644 -index 0000000..b42ab6e -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1$1.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex -new file mode 100644 -index 0000000..5b4c31c -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule$1.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex -new file mode 100644 -index 0000000..e4d045f -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskModule.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex -new file mode 100644 -index 0000000..6f7f156 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/debug_dex/com/RNTextInputMask/RNTextInputMaskPackage.dex differ -diff --git a/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin -new file mode 100644 -index 0000000..5cb003f -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/.transforms/558c105ee4e6b972a92b31d57bb15e87/transformed/debug/desugar_graph.bin differ -diff --git a/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java -new file mode 100644 -index 0000000..5187c84 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/generated/source/buildConfig/debug/com/RNTextInputMask/BuildConfig.java -@@ -0,0 +1,10 @@ -+/** -+ * Automatically generated file. DO NOT MODIFY -+ */ -+package com.RNTextInputMask; -+ -+public final class BuildConfig { -+ public static final boolean DEBUG = Boolean.parseBoolean("true"); -+ public static final String LIBRARY_PACKAGE_NAME = "com.RNTextInputMask"; -+ public static final String BUILD_TYPE = "debug"; -+} -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml -new file mode 100644 -index 0000000..32b79dc ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml -@@ -0,0 +1,7 @@ -+ -+ -+ -+ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json -new file mode 100644 -index 0000000..97e5bd9 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json -@@ -0,0 +1,18 @@ -+{ -+ "version": 3, -+ "artifactType": { -+ "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", -+ "kind": "Directory" -+ }, -+ "applicationId": "com.RNTextInputMask", -+ "variantName": "debug", -+ "elements": [ -+ { -+ "type": "SINGLE", -+ "filters": [], -+ "attributes": [], -+ "outputFile": "AndroidManifest.xml" -+ } -+ ], -+ "elementType": "File" -+} -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties -new file mode 100644 -index 0000000..1211b1e ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/aar_metadata/debug/aar-metadata.properties -@@ -0,0 +1,6 @@ -+aarFormatVersion=1.0 -+aarMetadataVersion=1.0 -+minCompileSdk=1 -+minCompileSdkExtension=0 -+minAndroidGradlePluginVersion=1.0.0 -+coreLibraryDesugaringEnabled=false -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json -new file mode 100644 -index 0000000..9e26dfe ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json -@@ -0,0 +1 @@ -+{} -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar -new file mode 100644 -index 0000000..1a5b0f8 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_library_classes_jar/debug/classes.jar differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar -new file mode 100644 -index 0000000..82ba69f -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_r_class_jar/debug/R.jar differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/compile_symbol_list/debug/R.txt -new file mode 100644 -index 0000000..e69de29 -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties -new file mode 100644 -index 0000000..e40d27b ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties -@@ -0,0 +1 @@ -+#Wed Aug 21 15:12:29 EDT 2024 -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml -new file mode 100644 -index 0000000..87dfc96 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml -@@ -0,0 +1,2 @@ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml -new file mode 100644 -index 0000000..b65d916 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml -@@ -0,0 +1,2 @@ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml -new file mode 100644 -index 0000000..85962a0 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/mergeDebugShaders/merger.xml -@@ -0,0 +1,2 @@ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml -new file mode 100644 -index 0000000..4f8b0d8 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/incremental/packageDebugAssets/merger.xml -@@ -0,0 +1,2 @@ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class -new file mode 100644 -index 0000000..c38e5af -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/BuildConfig.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class -new file mode 100644 -index 0000000..7aa7e43 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class -new file mode 100644 -index 0000000..55f4795 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule$1.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class -new file mode 100644 -index 0000000..2a9601e -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskModule.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class -new file mode 100644 -index 0000000..a7a3f00 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/javac/debug/classes/com/RNTextInputMask/RNTextInputMaskPackage.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt -new file mode 100644 -index 0000000..78ac5b8 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/local_only_symbol_list/debug/R-def.txt -@@ -0,0 +1,2 @@ -+R_DEF: Internal format may change without notice -+local -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt -new file mode 100644 -index 0000000..fbfd233 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt -@@ -0,0 +1,7 @@ -+1 -+2 -+4 -+5 -+6 -+7 -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml -new file mode 100644 -index 0000000..32b79dc ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml -@@ -0,0 +1,7 @@ -+ -+ -+ -+ -+ -+ -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json -new file mode 100644 -index 0000000..0637a08 ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/navigation_json/debug/navigation.json -@@ -0,0 +1 @@ -+[] -\ No newline at end of file -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class -new file mode 100644 -index 0000000..c38e5af -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/BuildConfig.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class -new file mode 100644 -index 0000000..7aa7e43 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1$1.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class -new file mode 100644 -index 0000000..55f4795 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule$1.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class -new file mode 100644 -index 0000000..2a9601e -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskModule.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class -new file mode 100644 -index 0000000..a7a3f00 -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_dir/debug/com/RNTextInputMask/RNTextInputMaskPackage.class differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar -new file mode 100644 -index 0000000..c69e81e -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar differ -diff --git a/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt -new file mode 100644 -index 0000000..345473c ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt -@@ -0,0 +1 @@ -+com.RNTextInputMask -diff --git a/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt -new file mode 100644 -index 0000000..3c35afc ---- /dev/null -+++ b/node_modules/react-native-text-input-mask/android/build/outputs/logs/manifest-merger-debug-report.txt -@@ -0,0 +1,17 @@ -+-- Merging decision tree log --- -+manifest -+ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 -+INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:1-3:12 -+ package -+ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:2:11-40 -+ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml -+ xmlns:android -+ ADDED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml:1:11-69 -+uses-sdk -+INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml reason: use-sdk injection requested -+INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml -+INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml -+ android:targetSdkVersion -+ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml -+ android:minSdkVersion -+ INJECTED from /Users/bruno/repos/rainbow/node_modules/react-native-text-input-mask/android/src/main/AndroidManifest.xml -diff --git a/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin -new file mode 100644 -index 0000000..dc8c43a -Binary files /dev/null and b/node_modules/react-native-text-input-mask/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin differ +index 0000000..5902d8c +Binary files /dev/null and b/node_modules/react-native-text-input-mask/.DS_Store differ diff --git a/node_modules/react-native-text-input-mask/index.js b/node_modules/react-native-text-input-mask/index.js index 408edad..3905a6f 100644 --- a/node_modules/react-native-text-input-mask/index.js @@ -364,4 +61,5 @@ index 408edad..3905a6f 100644 +export default forwardRef(ForwardedTextInputMask); diff --git a/node_modules/react-native-text-input-mask/ios/.DS_Store b/node_modules/react-native-text-input-mask/ios/.DS_Store new file mode 100644 -index 0000000..e69de29 +index 0000000..66750e8 +Binary files /dev/null and b/node_modules/react-native-text-input-mask/ios/.DS_Store differ diff --git a/patches/react-native-tooltips+1.0.4.patch b/patches/react-native-tooltips+1.0.3.patch similarity index 100% rename from patches/react-native-tooltips+1.0.4.patch rename to patches/react-native-tooltips+1.0.3.patch diff --git a/scripts/codegen-translations.js b/scripts/codegen-translations.js index 56631ab0f7f..3f18125d4dc 100644 --- a/scripts/codegen-translations.js +++ b/scripts/codegen-translations.js @@ -97,7 +97,7 @@ type ValidScope =${validTagsAsArrays.map(generateTypeForTag).join('')}; * keys. */ function pushNestedKeysAsArrays(keysArray, object, prefixArray) { - for (const key in object) { + for (let key in object) { const keyRepresentation = prefixArray.concat([key]); keysArray.push(keyRepresentation); diff --git a/shim.js b/shim.js index bc700f0239f..6a079f429a3 100644 --- a/shim.js +++ b/shim.js @@ -6,7 +6,7 @@ import ReactNative from 'react-native'; import Storage from 'react-native-storage'; // import { debugLayoutAnimations } from './src/config/debug'; import { mmkvStorageBackend } from '@/handlers/localstorage/mmkvStorageBackend'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; import 'fast-text-encoding'; import globalVariables from './globalVariables'; @@ -47,7 +47,7 @@ for (const [key, value] of Object.entries(globalVariables)) { Object.defineProperty(global, key, { get: () => value, set: () => { - logger.debug(`[shim]: Trying to override internal Rainbow var ${key}`); + logger.sentry(`Trying to override internal Rainbow var ${key}`); }, }); } @@ -108,7 +108,7 @@ ReactNative.LayoutAnimation.configureNext = () => null; // debugLayoutAnimations // ) { // ReactNative.LayoutAnimation.configureNext = (...args) => { -// logger.debug('[shim]: LayoutAnimation.configureNext', args); +// logger.sentry('LayoutAnimation.configureNext', args); // oldConfigureNext(...args); // }; // ReactNative.LayoutAnimation.configureNext.__shimmed = true; @@ -122,7 +122,7 @@ if (!ReactNative.InteractionManager._shimmed) { if (finishAutomatically) { setTimeout(() => { ReactNative.InteractionManager.clearInteractionHandle(handle); - logger.debug(`[shim]: Interaction finished automatically`); + logger.sentry(`Interaction finished automatically`); }, 3000); } return handle; diff --git a/src/App.tsx b/src/App.tsx index 02c836a846e..93ec4a4f782 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -79,17 +79,17 @@ function App({ walletReady }: AppProps) { const initialUrl = await Linking.getInitialURL(); branchListenerRef.current = await branchListener(url => { - logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: listener called`, {}, logger.DebugContext.deeplinks); try { handleDeeplink(url, initialRoute); } catch (error) { if (error instanceof Error) { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { + logger.error(new RainbowError('Error opening deeplink'), { message: error.message, url, }); } else { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { + logger.error(new RainbowError('Error opening deeplink'), { message: 'Unknown error', url, }); @@ -98,7 +98,7 @@ function App({ walletReady }: AppProps) { }); if (initialUrl) { - logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); + logger.debug(`App: has initial URL, opening with Branch`, { initialUrl }); branch.openURL(initialUrl); } }, [initialRoute]); @@ -140,7 +140,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (!__DEV__ && isTestFlight) { - logger.debug(`[App]: Test flight usage - ${isTestFlight}`); + logger.info(`Test flight usage - ${isTestFlight}`); } identifyFlow(); eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); @@ -162,7 +162,7 @@ function App({ walletReady }: AppProps) { useEffect(() => { if (walletReady) { - logger.debug(`[App]: ✅ Wallet ready!`); + logger.info('✅ Wallet ready!'); runWalletBackupStatusChecks(); } }, [walletReady]); @@ -249,7 +249,7 @@ function Root() { */ if (deviceIdWasJustCreated && !isReturningUser) { // on very first open, set some default data and fire event - logger.debug(`[App]: User opened application for the first time`); + logger.info(`User opened application for the first time`); const { width: screenWidth, height: screenHeight, scale: screenScale } = Dimensions.get('screen'); @@ -271,17 +271,13 @@ function Root() { initializeApplication() .then(() => { - logger.debug(`[App]: Application initialized with Sentry and analytics`); + logger.debug(`Application initialized with Sentry and analytics`); // init complete, load the rest of the app setInitializing(false); }) - .catch(error => { - logger.error(new RainbowError(`[App]: initializeApplication failed`), { - data: { - error, - }, - }); + .catch(() => { + logger.error(new RainbowError(`initializeApplication failed`)); // for failure, continue to rest of the app for now setInitializing(false); diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index b3b8f05f449..6634ee6b99b 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -18,7 +18,7 @@ import { SwapInputAsset } from '@/__swaps__/screens/Swap/components/SwapInputAss import { SwapNavbar } from '@/__swaps__/screens/Swap/components/SwapNavbar'; import { SwapOutputAsset } from '@/__swaps__/screens/Swap/components/SwapOutputAsset'; import { SwapSheetGestureBlocker } from '@/__swaps__/screens/Swap/components/SwapSheetGestureBlocker'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { parseSearchAsset } from '@/__swaps__/utils/assets'; import { AbsolutePortalRoot } from '@/components/AbsolutePortal'; @@ -28,7 +28,6 @@ import { useSwapsStore } from '@/state/swaps/swapsStore'; import { SwapWarning } from './components/SwapWarning'; import { clearCustomGasSettings } from './hooks/useCustomGas'; import { SwapProvider, useSwapContext } from './providers/swap-provider'; -import { NavigateToSwapSettingsTrigger } from './components/NavigateToSwapSettingsTrigger'; /** README * This prototype is largely driven by Reanimated and Gesture Handler, which @@ -86,7 +85,6 @@ export function SwapScreen() { - ); } diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx index 689f9374507..96873dce266 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx @@ -13,7 +13,7 @@ const AvalancheBadge = require('@/assets/badges/avalanche.png'); const BlastBadge = require('@/assets/badges/blast.png'); const DegenBadge = require('@/assets/badges/degen.png'); -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { globalColors } from '@/design-system'; import { PIXEL_RATIO } from '@/utils/deviceUtils'; import { useSwapsStore } from '@/state/swaps/swapsStore'; diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index 3ed30eca49b..ceae4f7d750 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -11,7 +11,7 @@ import ZoraBadge from '@/assets/badges/zora.png'; import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { useAnimatedProps } from 'react-native-reanimated'; import { AddressOrEth } from '@/__swaps__/types/assets'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx index 37c7feb897c..02e439c90db 100644 --- a/src/__swaps__/screens/Swap/components/CoinRow.tsx +++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx @@ -1,14 +1,14 @@ import { BalancePill } from '@/__swaps__/screens/Swap/components/BalancePill'; import { CoinRowButton } from '@/__swaps__/screens/Swap/components/CoinRowButton'; import { AddressOrEth, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset } from '@/__swaps__/types/search'; import { ButtonPressAnimation } from '@/components/animations'; import { ContextMenuButton } from '@/components/context-menu'; import { Box, Column, Columns, HitSlop, Inline, Text } from '@/design-system'; import { setClipboard } from '@/hooks/useClipboard'; import * as i18n from '@/languages'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { BASE_DEGEN_ADDRESS, DEGEN_CHAIN_DEGEN_ADDRESS, ETH_ADDRESS } from '@/references'; import { toggleFavorite } from '@/resources/favorites'; import { userAssetsStore } from '@/state/assets/userAssets'; @@ -52,7 +52,6 @@ interface InputCoinRowProps { onPress: (asset: ParsedSearchAsset | null) => void; output?: false | undefined; uniqueId: string; - testID?: string; } type PartialAsset = Pick; @@ -63,12 +62,11 @@ interface OutputCoinRowProps extends PartialAsset { output: true; nativePriceChange?: string; isTrending?: boolean; - testID?: string; } type CoinRowProps = InputCoinRowProps | OutputCoinRowProps; -export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) { +export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) { const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId))); const outputAsset = output ? (assetProps as PartialAsset) : undefined; @@ -118,7 +116,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...asse if (!address || !chainId) return null; return ( - + @@ -185,7 +183,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...asse } const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) => { - const networkObject = RainbowNetworkObjects.find(networkObject => networkObject.id === chainId)?.value; + const network = RainbowNetworks.find(network => network.id === chainId)?.value; const handleCopy = useCallback(() => { haptics.selection(); @@ -197,11 +195,11 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) title: i18n.t(i18n.l.exchange.coin_row.copy_contract_address), action: handleCopy, }, - ...(networkObject + ...(network ? { blockExplorer: { - title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })) }), - action: () => ethereumUtils.openAddressInBlockExplorer({ address, chainId }), + title: i18n.t(i18n.l.exchange.coin_row.view_on, { blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)) }), + action: () => ethereumUtils.openAddressInBlockExplorer(address, chainId), }, } : {}), @@ -217,7 +215,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) iconValue: 'doc.on.doc', }, }, - ...(networkObject + ...(network ? [ { actionKey: 'blockExplorer', @@ -236,7 +234,7 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const handlePressMenuItem = async ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => { if (actionKey === 'copyAddress') { options.copy.action(); - } else if (actionKey === 'blockExplorer' && networkObject) { + } else if (actionKey === 'blockExplorer' && network) { options.blockExplorer?.action(); } }; @@ -244,14 +242,14 @@ const InfoButton = ({ address, chainId }: { address: string; chainId: ChainId }) const onPressAndroid = () => showActionSheetWithOptions( { - options: [options.copy.title, ...(networkObject ? [options.blockExplorer?.title] : [])], + options: [options.copy.title, ...(network ? [options.blockExplorer?.title] : [])], showSeparators: true, }, (idx: number) => { if (idx === 0) { options.copy.action(); } - if (idx === 1 && networkObject) { + if (idx === 1 && network) { options.blockExplorer?.action(); } } diff --git a/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx b/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx new file mode 100644 index 00000000000..ed659118401 --- /dev/null +++ b/src/__swaps__/screens/Swap/components/FastSwapCoinIconImage.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { ImgixImage } from '@/components/images'; +import { Network } from '@/networks/types'; +import { getUrlForTrustIconFallback } from '@/utils'; + +export const FastSwapCoinIconImage = React.memo(function FastSwapCoinIconImage({ + address, + disableShadow = true, + network, + shadowColor, + size, +}: { + address: string; + children: () => React.ReactNode; + disableShadow?: boolean; + network: Network; + shadowColor: string; + size?: number; +}) { + const imageUrl = getUrlForTrustIconFallback(address, network); + + return ( + + + + ); +}); + +const sx = StyleSheet.create({ + coinIconContainer: { + alignItems: 'center', + borderRadius: 20, + height: 40, + justifyContent: 'center', + overflow: 'visible', + width: 40, + }, + coinIconFallback: { + borderRadius: 20, + height: 40, + overflow: 'hidden', + width: 40, + }, + container: { + elevation: 6, + height: 59, + overflow: 'visible', + paddingTop: 9, + }, + contract: { + height: 40, + width: 40, + }, + fallbackWrapper: { + left: 0, + position: 'absolute', + top: 0, + }, + reactCoinIconContainer: { + alignItems: 'center', + justifyContent: 'center', + }, + reactCoinIconImage: { + height: '100%', + width: '100%', + }, + withShadow: { + elevation: 6, + shadowOffset: { + height: 4, + width: 0, + }, + shadowOpacity: 0.2, + shadowRadius: 6, + }, +}); diff --git a/src/__swaps__/screens/Swap/components/FlipButton.tsx b/src/__swaps__/screens/Swap/components/FlipButton.tsx index 0b71487dfcf..1bc69c1df28 100644 --- a/src/__swaps__/screens/Swap/components/FlipButton.tsx +++ b/src/__swaps__/screens/Swap/components/FlipButton.tsx @@ -14,7 +14,7 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { GestureHandlerButton } from './GestureHandlerButton'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const FlipButton = () => { const { isDarkMode } = useColorMode(); diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 6f96e02db53..bdef2deda67 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -1,3 +1,4 @@ +import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { getCachedCurrentBaseFee, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -23,7 +24,6 @@ import { NavigationSteps, useSwapContext } from '../providers/swap-provider'; import { EstimatedSwapGasFee, EstimatedSwapGasFeeSlot } from './EstimatedSwapGasFee'; import { GestureHandlerButton } from './GestureHandlerButton'; import { UnmountOnAnimatedReaction } from './UnmountOnAnimatedReaction'; -import { ChainId } from '@/networks/types'; const { SWAP_GAS_ICONS } = gasUtils; const GAS_BUTTON_HIT_SLOP = 16; diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 85167b3e817..77f6a7dbf0a 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -4,7 +4,7 @@ import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, withDelay, wi import { MIN_FLASHBOTS_PRIORITY_FEE, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; import { diff --git a/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx b/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx deleted file mode 100644 index 912f20c8a3e..00000000000 --- a/src/__swaps__/screens/Swap/components/NavigateToSwapSettingsTrigger.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useNavigation } from '@/navigation'; -import { RootStackParamList } from '@/navigation/types'; -import { RouteProp, useRoute } from '@react-navigation/native'; -import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; -import { useSwapContext } from '../providers/swap-provider'; - -export const NavigateToSwapSettingsTrigger = () => { - const route = useRoute>(); - const { setParams } = useNavigation(); - const { SwapNavigation } = useSwapContext(); - - useAnimatedReaction( - () => route.params, - (current, previous) => { - if (!current || current === previous) return; - - if (current.action === 'open_swap_settings') { - SwapNavigation.handleShowSettings(); - runOnJS(setParams)({ - ...route.params, - action: undefined, - }); - } - }, - [route.params?.action, setParams] - ); - - return null; -}; diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 029407d9310..9441ba77056 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -2,7 +2,8 @@ import { AnimatedChainImage } from '@/__swaps__/screens/Swap/components/Animated import { ReviewGasButton } from '@/__swaps__/screens/Swap/components/GasButton'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { useNativeAssetForChain } from '@/__swaps__/screens/Swap/hooks/useNativeAssetForChain'; -import { ChainNameDisplay, ChainId } from '@/networks/types'; +import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; +import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { useEstimatedTime } from '@/__swaps__/utils/meteorology'; import { convertRawAmountToBalance, @@ -363,10 +364,10 @@ export function ReviewPanel() { }); const openGasExplainer = useCallback(async () => { - const nativeAsset = await getNativeAssetForNetwork({ chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet }); + const nativeAsset = await getNativeAssetForNetwork(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet); navigate(Routes.EXPLAIN_SHEET, { - chainId: swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet, + network: chainNameFromChainId(swapsStore.getState().inputAsset?.chainId ?? ChainId.mainnet), type: 'gas', nativeAsset, }); diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index 4530a28db5d..e407de019ab 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -34,7 +34,6 @@ function SwapButton({ disabled, opacity, children, - testID, }: { asset: DerivedValue; borderRadius?: number; @@ -48,7 +47,6 @@ function SwapButton({ disabled?: DerivedValue; opacity?: DerivedValue; children?: React.ReactNode; - testID?: string; }) { const { isDarkMode } = useColorMode(); const fallbackColor = useForegroundColor('label'); @@ -112,7 +110,6 @@ function SwapButton({ return ( - + {labelValue} @@ -235,7 +225,6 @@ export const SwapActionButton = ({ scaleTo, style, disabled, - testID, ...props }: { asset: DerivedValue; @@ -259,7 +248,6 @@ export const SwapActionButton = ({ style?: ViewStyle; disabled?: DerivedValue; opacity?: DerivedValue; - testID?: string; }) => { const disabledWrapper = useAnimatedStyle(() => { return { @@ -280,7 +268,7 @@ export const SwapActionButton = ({ style={[hugContent && feedActionButtonStyles.buttonWrapper, style]} > {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + {holdProgress && } diff --git a/src/__swaps__/screens/Swap/components/SwapBackground.tsx b/src/__swaps__/screens/Swap/components/SwapBackground.tsx index 594b33f61bc..8fc56cf4f0e 100644 --- a/src/__swaps__/screens/Swap/components/SwapBackground.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBackground.tsx @@ -1,11 +1,11 @@ import { Canvas, Rect, LinearGradient, vec, Paint } from '@shopify/react-native-skia'; import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet } from 'react-native'; import { useDerivedValue, withTiming } from 'react-native-reanimated'; import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { useColorMode } from '@/design-system'; -import { IS_ANDROID, IS_TEST } from '@/env'; +import { IS_ANDROID } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { getColorValueForThemeWorklet, getTintedBackgroundColor } from '@/__swaps__/utils/swaps'; import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils'; @@ -18,15 +18,12 @@ export const SwapBackground = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); const animatedTopColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedInputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig ); }); - const animatedBottomColor = useDerivedValue(() => { - if (IS_TEST) return getColorValueForThemeWorklet(DEFAULT_BACKGROUND_COLOR, isDarkMode, true); return withTiming( getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.tintedBackgroundColor || DEFAULT_BACKGROUND_COLOR, isDarkMode, true), TIMING_CONFIGS.slowFadeConfig @@ -37,10 +34,6 @@ export const SwapBackground = () => { return [animatedTopColor.value, animatedBottomColor.value]; }); - if (IS_TEST) { - return ; - } - return ( diff --git a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx index 87298a06eb8..d41f5d28733 100644 --- a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx @@ -98,7 +98,6 @@ export function SwapBottomPanel() { } style={styles.inputTextMask}> - + {SwapInputController.formattedInputAmount} @@ -131,7 +123,7 @@ export function SwapInputAsset() { return ( - + diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index b3546fef19c..ac2dc4a4aba 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -15,7 +15,7 @@ import { TokenList } from '@/__swaps__/screens/Swap/components/TokenList/TokenLi import { BASE_INPUT_WIDTH, INPUT_INNER_WIDTH, INPUT_PADDING, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { IS_ANDROID, IS_IOS } from '@/env'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; @@ -39,7 +39,6 @@ function SwapOutputActionButton() { return ( { if (isLoading) return null; - const getFormattedTestId = (name: string, chainId: ChainId) => { - return `token-to-buy-${name}-${chainId}`.toLowerCase().replace(/\s+/g, '-'); - }; - return ( - + } @@ -164,7 +160,6 @@ export const TokenToBuyList = () => { } return ( (config: T): T => { - if (!IS_TEST) return config; - return { - ...config, - duration: 0, - } as T; -}; - -export const buttonPressConfig = disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }); -export const caretConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }); -export const fadeConfig = disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }); -export const pulsingConfig = disableForTestingEnvironment({ duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }); -export const sliderConfig = disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }); -export const slowFadeConfig = disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }); -export const snappySpringConfig = disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }); -export const snappierSpringConfig = disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }); -export const springConfig = disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }); +export const buttonPressConfig = { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }; +export const caretConfig = { duration: 300, easing: Easing.bezier(0.87, 0, 0.13, 1) }; +export const fadeConfig = { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }; +export const pulsingConfig = { duration: 1000, easing: Easing.bezier(0.37, 0, 0.63, 1) }; +export const sliderConfig = { damping: 40, mass: 1.25, stiffness: 450 }; +export const slowFadeConfig = { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }; +export const snappySpringConfig = { damping: 100, mass: 0.8, stiffness: 275 }; +export const snappierSpringConfig = { damping: 42, mass: 0.8, stiffness: 800 }; +export const springConfig = { damping: 100, mass: 1.2, stiffness: 750 }; // // /---- END animation configs ----/ // diff --git a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts index 2c2c791ad81..b07a99dfdd1 100644 --- a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts @@ -23,7 +23,7 @@ import { safeAreaInsetValues } from '@/utils'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; import { DerivedValue, SharedValue, interpolate, useAnimatedStyle, useDerivedValue, withSpring, withTiming } from 'react-native-reanimated'; import { NavigationSteps } from './useSwapNavigation'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; const INSET_BOTTOM = IS_ANDROID ? getSoftMenuBarHeight() - 24 : safeAreaInsetValues.bottom + 16; diff --git a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts index 53efd2f9cc1..13f6d54e02d 100644 --- a/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts +++ b/src/__swaps__/screens/Swap/hooks/useAssetsToSell.ts @@ -10,7 +10,6 @@ import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; import { ParsedAssetsDictByChain, ParsedSearchAsset, UserAssetFilter } from '@/__swaps__/types/assets'; import { useAccountSettings, useDebounce } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const sortBy = (by: UserAssetFilter) => { switch (by) { @@ -29,13 +28,10 @@ export const useAssetsToSell = () => { const debouncedAssetToSellFilter = useDebounce(searchQuery, 200); - const { connectedToHardhat } = useConnectedToHardhatStore(); - const { data: userAssets = [] } = useUserAssets( { address: currentAddress as Address, currency: currentCurrency, - testnetMode: connectedToHardhat, }, { select: data => diff --git a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts index 13aa6388d50..b6cfa892e40 100644 --- a/src/__swaps__/screens/Swap/hooks/useCustomGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useCustomGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; export type EIP1159GasSettings = { diff --git a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts index 9f8af50f901..ab3729c448c 100644 --- a/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts +++ b/src/__swaps__/screens/Swap/hooks/useEstimatedGasFee.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { weiToGwei } from '@/__swaps__/utils/ethereum'; import { add, convertAmountToNativeDisplayWorklet, formatNumber, multiply } from '@/__swaps__/utils/numbers'; import { useNativeAsset } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index 95c886d31d0..1f03494ed2c 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SharedValue, runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import { ParsedAddressAsset } from '@/entities'; @@ -9,11 +9,11 @@ import { ethereumUtils } from '@/utils'; export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue }) => { const chainId = useDerivedValue(() => inputAsset.value?.chainId ?? ChainId.mainnet); - const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset({ chainId: chainId.value })); + const nativeAsset = useSharedValue(ethereumUtils.getNetworkNativeAsset(chainId.value)); const getNativeAssetForNetwork = useCallback( (chainId: ChainId) => { - const asset = ethereumUtils.getNetworkNativeAsset({ chainId }); + const asset = ethereumUtils.getNetworkNativeAsset(chainId); nativeAsset.value = asset; }, [nativeAsset] diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index a1ba4437ff2..5d37829f703 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -1,6 +1,6 @@ import { TokenSearchResult, useTokenSearch } from '@/__swaps__/screens/Swap/resources/search/search'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset, TokenSearchAssetKey, TokenSearchThreshold } from '@/__swaps__/types/search'; import { addHexPrefix } from '@/__swaps__/utils/hex'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; @@ -27,7 +27,6 @@ export interface AssetToBuySection { const MAX_UNVERIFIED_RESULTS = 8; const MAX_VERIFIED_RESULTS = 48; -const MAX_POPULAR_RESULTS = 3; const mergeAssetsFavoriteStatus = ({ assets, @@ -178,7 +177,7 @@ const buildListSectionsData = ({ assets: combinedData.popularAssets, recentSwaps: combinedData.recentSwaps, filteredBridgeAssetAddress, - }).slice(0, MAX_POPULAR_RESULTS); + }).slice(0, 6); addSection( 'popular', mergeAssetsFavoriteStatus({ diff --git a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts index 40f066fec79..3cf7f91cd48 100644 --- a/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts +++ b/src/__swaps__/screens/Swap/hooks/useSelectedGas.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { getCachedGasSuggestions, useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts index bffed48cf60..9149ee472bb 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapEstimatedGasLimit.ts @@ -2,7 +2,7 @@ import { CrosschainQuote, Quote, QuoteError, SwapType } from '@rainbow-me/swaps' import { useQuery } from '@tanstack/react-query'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { estimateUnlockAndCrosschainSwap } from '@/raps/unlockAndCrosschainSwap'; import { estimateUnlockAndSwap } from '@/raps/unlockAndSwap'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey } from '@/react-query'; diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 1953277fb87..2d1944a544b 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,7 +1,7 @@ import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { RequestNewQuoteParams, inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { valueBasedDecimalFormatter } from '@/__swaps__/utils/decimalFormatter'; import { getInputValuesForSliderPositionWorklet, updateInputValuesAfterFlip } from '@/__swaps__/utils/flipAssets'; @@ -872,6 +872,7 @@ export function useSwapInputsController({ } } ); + return { debouncedFetchQuote, formattedInputAmount, diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 090ec7a11a7..474f44943a7 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -10,7 +10,7 @@ import { toScaledIntegerWorklet, } from '@/__swaps__/safe-math/SafeMath'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { add } from '@/__swaps__/utils/numbers'; import { ParsedAddressAsset } from '@/entities'; import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset'; diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 7c069fc1a20..5ae640688a2 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -24,12 +24,12 @@ import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextSty import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; import { getDefaultSlippageWorklet, isUnwrapEthWorklet, isWrapEthWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; +import { getFlashbotsProvider, getIsHardhatConnected, getProvider, isHardHat } from '@/handlers/web3'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; @@ -56,7 +56,6 @@ import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisable import { SyncGasStateToSharedValues, SyncQuoteSharedValuesToState } from './SyncSwapStateAndSharedValues'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const swapping = i18n.t(i18n.l.swap.actions.swapping); const holdToSwap = i18n.t(i18n.l.swap.actions.hold_to_swap); @@ -200,7 +199,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { parameters.flashbots && getNetworkObject({ chainId: parameters.chainId }).features.flashbots ? await getFlashbotsProvider() : getProvider({ chainId: parameters.chainId }); - const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; + const providerUrl = provider?.connection?.url; + const connectedToHardhat = !!providerUrl && isHardHat(providerUrl); const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; const isDegenModeEnabled = swapsStore.getState().degenMode; @@ -258,7 +258,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }; } - const chainId = connectedToHardhat ? ChainId.hardhat : parameters.chainId; + const chainId = getIsHardhatConnected() ? ChainId.hardhat : parameters.chainId; const { errorMessage } = await performanceTracking.getState().executeFn({ fn: walletExecuteRap, screen: Screens.SWAPS, diff --git a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts index f8c829cc06b..9e2e29c05cf 100644 --- a/src/__swaps__/screens/Swap/resources/_selectors/assets.ts +++ b/src/__swaps__/screens/Swap/resources/_selectors/assets.ts @@ -1,5 +1,5 @@ import { ParsedAssetsDict, ParsedAssetsDictByChain, ParsedUserAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { deriveAddressAndChainWithUniqueId } from '@/__swaps__/utils/address'; import { add } from '@/__swaps__/utils/numbers'; diff --git a/src/__swaps__/screens/Swap/resources/assets/assets.ts b/src/__swaps__/screens/Swap/resources/assets/assets.ts index 85d800e5be5..81108a88786 100644 --- a/src/__swaps__/screens/Swap/resources/assets/assets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/assets.ts @@ -4,7 +4,7 @@ import { requestMetadata } from '@/graphql'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { AddressOrEth, AssetMetadata, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { chunkArray, createAssetQuery, parseAssetMetadata } from '@/__swaps__/utils/assets'; import { RainbowError, logger } from '@/logger'; export const ASSETS_TIMEOUT_DURATION = 10000; @@ -50,7 +50,7 @@ export async function assetsQueryFunction({ const parsedAssets = parseAssets(results, chainId, currency); return parsedAssets; } catch (e) { - logger.error(new RainbowError('[assetsQueryFunction]: Failed to fetch assets'), { + logger.error(new RainbowError('assetsQueryFunction: '), { message: (e as Error)?.message, }); return {}; diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts index 37dc8c9f185..cd0e6abdca8 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts @@ -4,17 +4,17 @@ import { Address } from 'viem'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; import { SupportedCurrencyKey, SUPPORTED_CHAIN_IDS } from '@/references'; import { ParsedAssetsDictByChain, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { parseUserAsset } from '@/__swaps__/utils/assets'; import { greaterThan } from '@/__swaps__/utils/numbers'; import { fetchUserAssetsByChain } from './userAssetsByChain'; -import { fetchHardhatBalances, fetchHardhatBalancesByChainId } from '@/resources/assets/hardhatAssets'; const addysHttp = new RainbowFetchClient({ baseURL: 'https://addys.p.rainbow.me/v3', @@ -81,32 +81,10 @@ export const userAssetsSetQueryData = ({ address, currency, userAssets, testnetM queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), userAssets); }; -async function userAssetsQueryFunction({ - queryKey: [{ address, currency, testnetMode }], -}: QueryFunctionArgs): Promise { +async function userAssetsQueryFunction({ queryKey: [{ address, currency, testnetMode }] }: QueryFunctionArgs) { if (!address) { return {}; } - if (testnetMode) { - const { assets, chainIdsInResponse } = await fetchHardhatBalancesByChainId(address); - const parsedAssets: Array<{ - asset: ZerionAsset; - quantity: string; - small_balances: boolean; - }> = Object.values(assets).map(asset => ({ - asset: asset.asset, - quantity: asset.quantity, - small_balances: false, - })); - - const parsedAssetsDict = await parseUserAssets({ - assets: parsedAssets, - chainIds: chainIdsInResponse, - currency, - }); - - return parsedAssetsDict; - } const cache = queryClient.getQueryCache(); const cachedUserAssets = (cache.find(userAssetsQueryKey({ address, currency, testnetMode }))?.state?.data || {}) as ParsedAssetsDictByChain; @@ -145,7 +123,7 @@ async function userAssetsQueryFunction({ } return cachedUserAssets; } catch (e) { - logger.error(new RainbowError('[userAssetsQueryFunction]: Failed to fetch user assets'), { + logger.error(new RainbowError('userAssetsQueryFunction: '), { message: (e as Error)?.message, }); return cachedUserAssets; @@ -191,7 +169,7 @@ async function userAssetsQueryFunctionRetryByChain({ } queryClient.setQueryData(userAssetsQueryKey({ address, currency, testnetMode }), cachedUserAssets); } catch (e) { - logger.error(new RainbowError('[userAssetsQueryFunctionRetryByChain]: Failed to retry fetching user assets'), { + logger.error(new RainbowError('userAssetsQueryFunctionRetryByChain: '), { message: (e as Error)?.message, }); } @@ -230,10 +208,12 @@ export async function parseUserAssets({ // Query Hook export function useUserAssets( - { address, currency, testnetMode }: UserAssetsArgs, + { address, currency }: UserAssetsArgs, config: QueryConfigWithSelect = {} ) { - return useQuery(userAssetsQueryKey({ address, currency, testnetMode }), userAssetsQueryFunction, { + const isHardhatConnected = getIsHardhatConnected(); + + return useQuery(userAssetsQueryKey({ address, currency, testnetMode: isHardhatConnected }), userAssetsQueryFunction, { ...config, refetchInterval: USER_ASSETS_REFETCH_INTERVAL, staleTime: process.env.IS_TESTING === 'true' ? 0 : 1000, diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts index b2b130aea98..9652ecf2ae4 100644 --- a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts +++ b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { SupportedCurrencyKey } from '@/references'; import { ParsedAssetsDictByChain, ParsedUserAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { AddressAssetsReceivedMessage } from '@/__swaps__/types/refraction'; import { RainbowError, logger } from '@/logger'; @@ -82,7 +82,7 @@ export async function userAssetsByChainQueryFunction({ return cachedDataForChain; } } catch (e) { - logger.error(new RainbowError(`[userAssetsByChainQueryFunction]: Failed to fetch user assets for ${chainId}`), { + logger.error(new RainbowError(`userAssetsByChainQueryFunction - chainId = ${chainId}:`), { message: (e as Error)?.message, }); return cachedDataForChain; diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts index 269f44441b9..b64c680ba04 100644 --- a/src/__swaps__/screens/Swap/resources/search/discovery.ts +++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; @@ -28,7 +28,7 @@ async function tokenSearchQueryFunction({ queryKey: [{ chainId }] }: QueryFuncti const tokenSearch = await tokenSearchHttp.get<{ data: SearchAsset[] }>(url); return parseTokenSearch(tokenSearch.data.data, chainId); } catch (e) { - logger.error(new RainbowError('[tokenSearchQueryFunction]: Token discovery failed'), { url }); + logger.error(new RainbowError('Token discovery failed'), { url }); return []; } } diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts index 8f29f7eb1d9..86c45602cdd 100644 --- a/src/__swaps__/screens/Swap/resources/search/search.ts +++ b/src/__swaps__/screens/Swap/resources/search/search.ts @@ -1,5 +1,5 @@ /* eslint-disable no-nested-ternary */ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '@/__swaps__/types/search'; import { RainbowError, logger } from '@/logger'; import { RainbowFetchClient } from '@/rainbow-fetch'; @@ -90,7 +90,7 @@ async function tokenSearchQueryFunction({ return parseTokenSearch(tokenSearch.data.data, chainId); } } catch (e) { - logger.error(new RainbowError('[tokenSearchQueryFunction]: Token search failed'), { url }); + logger.error(new RainbowError('Token search failed'), { url }); return []; } } diff --git a/src/__swaps__/screens/Swap/resources/search/utils.ts b/src/__swaps__/screens/Swap/resources/search/utils.ts index 4c649d04cae..0ed74aeb80e 100644 --- a/src/__swaps__/screens/Swap/resources/search/utils.ts +++ b/src/__swaps__/screens/Swap/resources/search/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { SearchAsset } from '@/__swaps__/types/search'; import { ARBITRUM_ETH_ADDRESS, diff --git a/src/__swaps__/types/assets.ts b/src/__swaps__/types/assets.ts index 73946574888..05f78261ff1 100644 --- a/src/__swaps__/types/assets.ts +++ b/src/__swaps__/types/assets.ts @@ -1,7 +1,7 @@ import type { Address } from 'viem'; import { ETH_ADDRESS } from '@/references'; -import { ChainId, ChainName } from '@/networks/types'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { SearchAsset } from '@/__swaps__/types/search'; import { ResponseByTheme } from '../utils/swaps'; diff --git a/src/__swaps__/types/chains.ts b/src/__swaps__/types/chains.ts new file mode 100644 index 00000000000..98105ad5628 --- /dev/null +++ b/src/__swaps__/types/chains.ts @@ -0,0 +1,211 @@ +import * as chain from 'viem/chains'; +import type { Chain } from 'viem/chains'; + +const HARDHAT_CHAIN_ID = 1337; +const HARDHAT_OP_CHAIN_ID = 1338; + +export const chainHardhat: Chain = { + id: HARDHAT_CHAIN_ID, + name: 'Hardhat', + nativeCurrency: { + decimals: 18, + name: 'Hardhat', + symbol: 'ETH', + }, + rpcUrls: { + public: { http: ['http://127.0.0.1:8545'] }, + default: { http: ['http://127.0.0.1:8545'] }, + }, + testnet: true, +}; + +export const chainHardhatOptimism: Chain = { + id: HARDHAT_OP_CHAIN_ID, + name: 'Hardhat OP', + nativeCurrency: { + decimals: 18, + name: 'Hardhat OP', + symbol: 'ETH', + }, + rpcUrls: { + public: { http: ['http://127.0.0.1:8545'] }, + default: { http: ['http://127.0.0.1:8545'] }, + }, + testnet: true, +}; + +export enum ChainName { + arbitrum = 'arbitrum', + arbitrumNova = 'arbitrum-nova', + arbitrumSepolia = 'arbitrum-sepolia', + avalanche = 'avalanche', + avalancheFuji = 'avalanche-fuji', + base = 'base', + blast = 'blast', + blastSepolia = 'blast-sepolia', + bsc = 'bsc', + celo = 'celo', + degen = 'degen', + gnosis = 'gnosis', + linea = 'linea', + manta = 'manta', + optimism = 'optimism', + polygon = 'polygon', + polygonZkEvm = 'polygon-zkevm', + rari = 'rari', + scroll = 'scroll', + zora = 'zora', + mainnet = 'mainnet', + holesky = 'holesky', + hardhat = 'hardhat', + hardhatOptimism = 'hardhat-optimism', + sepolia = 'sepolia', + optimismSepolia = 'optimism-sepolia', + bscTestnet = 'bsc-testnet', + polygonMumbai = 'polygon-mumbai', + baseSepolia = 'base-sepolia', + zoraSepolia = 'zora-sepolia', + polygonAmoy = 'polygon-amoy', +} + +export enum ChainId { + arbitrum = chain.arbitrum.id, + arbitrumNova = chain.arbitrumNova.id, + arbitrumSepolia = chain.arbitrumSepolia.id, + avalanche = chain.avalanche.id, + avalancheFuji = chain.avalancheFuji.id, + base = chain.base.id, + baseSepolia = chain.baseSepolia.id, + blast = chain.blast.id, + blastSepolia = chain.blastSepolia.id, + bsc = chain.bsc.id, + bscTestnet = chain.bscTestnet.id, + celo = chain.celo.id, + degen = chain.degen.id, + gnosis = chain.gnosis.id, + goerli = chain.goerli.id, + hardhat = HARDHAT_CHAIN_ID, + hardhatOptimism = chainHardhatOptimism.id, + holesky = chain.holesky.id, + linea = chain.linea.id, + mainnet = chain.mainnet.id, + manta = chain.manta.id, + optimism = chain.optimism.id, + optimismSepolia = chain.optimismSepolia.id, + polygon = chain.polygon.id, + polygonAmoy = chain.polygonAmoy.id, + polygonMumbai = chain.polygonMumbai.id, + polygonZkEvm = chain.polygonZkEvm.id, + rari = 1380012617, + scroll = chain.scroll.id, + sepolia = chain.sepolia.id, + zora = chain.zora.id, + zoraSepolia = chain.zoraSepolia.id, +} + +export const chainNameToIdMapping: { + [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; +} = { + ['ethereum']: ChainId.mainnet, + [ChainName.arbitrum]: ChainId.arbitrum, + [ChainName.arbitrumNova]: ChainId.arbitrumNova, + [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, + [ChainName.avalanche]: ChainId.avalanche, + [ChainName.avalancheFuji]: ChainId.avalancheFuji, + [ChainName.base]: ChainId.base, + [ChainName.bsc]: ChainId.bsc, + [ChainName.celo]: ChainId.celo, + [ChainName.degen]: ChainId.degen, + [ChainName.gnosis]: ChainId.gnosis, + [ChainName.linea]: ChainId.linea, + [ChainName.manta]: ChainId.manta, + [ChainName.optimism]: ChainId.optimism, + [ChainName.polygon]: ChainId.polygon, + [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, + [ChainName.rari]: ChainId.rari, + [ChainName.scroll]: ChainId.scroll, + [ChainName.zora]: ChainId.zora, + [ChainName.mainnet]: ChainId.mainnet, + [ChainName.holesky]: ChainId.holesky, + [ChainName.hardhat]: ChainId.hardhat, + [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, + ['ethereum-sepolia']: ChainId.sepolia, + [ChainName.sepolia]: ChainId.sepolia, + [ChainName.optimismSepolia]: ChainId.optimismSepolia, + [ChainName.bscTestnet]: ChainId.bscTestnet, + [ChainName.polygonMumbai]: ChainId.polygonMumbai, + [ChainName.baseSepolia]: ChainId.baseSepolia, + [ChainName.zoraSepolia]: ChainId.zoraSepolia, + [ChainName.blast]: ChainId.blast, + [ChainName.blastSepolia]: ChainId.blastSepolia, + [ChainName.polygonAmoy]: ChainId.polygonAmoy, +}; + +export const chainIdToNameMapping: { + [key in ChainId]: ChainName; +} = { + [ChainId.arbitrum]: ChainName.arbitrum, + [ChainId.arbitrumNova]: ChainName.arbitrumNova, + [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, + [ChainId.avalanche]: ChainName.avalanche, + [ChainId.avalancheFuji]: ChainName.avalancheFuji, + [ChainId.base]: ChainName.base, + [ChainId.blast]: ChainName.blast, + [ChainId.blastSepolia]: ChainName.blastSepolia, + [ChainId.bsc]: ChainName.bsc, + [ChainId.celo]: ChainName.celo, + [ChainId.degen]: ChainName.degen, + [ChainId.gnosis]: ChainName.gnosis, + [ChainId.linea]: ChainName.linea, + [ChainId.manta]: ChainName.manta, + [ChainId.optimism]: ChainName.optimism, + [ChainId.polygon]: ChainName.polygon, + [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, + [ChainId.rari]: ChainName.rari, + [ChainId.scroll]: ChainName.scroll, + [ChainId.zora]: ChainName.zora, + [ChainId.mainnet]: ChainName.mainnet, + [ChainId.holesky]: ChainName.holesky, + [ChainId.hardhat]: ChainName.hardhat, + [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, + [ChainId.sepolia]: ChainName.sepolia, + [ChainId.optimismSepolia]: ChainName.optimismSepolia, + [ChainId.bscTestnet]: ChainName.bscTestnet, + [ChainId.polygonMumbai]: ChainName.polygonMumbai, + [ChainId.baseSepolia]: ChainName.baseSepolia, + [ChainId.zoraSepolia]: ChainName.zoraSepolia, + [ChainId.polygonAmoy]: ChainName.polygonAmoy, +}; + +export const ChainNameDisplay = { + [ChainId.arbitrum]: 'Arbitrum', + [ChainId.arbitrumNova]: chain.arbitrumNova.name, + [ChainId.avalanche]: 'Avalanche', + [ChainId.avalancheFuji]: 'Avalanche Fuji', + [ChainId.base]: 'Base', + [ChainId.blast]: 'Blast', + [ChainId.blastSepolia]: 'Blast Sepolia', + [ChainId.bsc]: 'BSC', + [ChainId.celo]: chain.celo.name, + [ChainId.degen]: 'Degen Chain', + [ChainId.linea]: 'Linea', + [ChainId.manta]: 'Manta', + [ChainId.optimism]: 'Optimism', + [ChainId.polygon]: 'Polygon', + [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, + [ChainId.rari]: 'RARI Chain', + [ChainId.scroll]: chain.scroll.name, + [ChainId.zora]: 'Zora', + [ChainId.mainnet]: 'Ethereum', + [ChainId.hardhat]: 'Hardhat', + [ChainId.hardhatOptimism]: chainHardhatOptimism.name, + [ChainId.sepolia]: chain.sepolia.name, + [ChainId.holesky]: chain.holesky.name, + [ChainId.optimismSepolia]: chain.optimismSepolia.name, + [ChainId.bscTestnet]: 'BSC Testnet', + [ChainId.polygonMumbai]: chain.polygonMumbai.name, + [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, + [ChainId.baseSepolia]: chain.baseSepolia.name, + [ChainId.zoraSepolia]: 'Zora Sepolia', + [ChainId.polygonAmoy]: 'Polygon Amoy', +} as const; diff --git a/src/__swaps__/types/refraction.ts b/src/__swaps__/types/refraction.ts index 9afc8ce9cf3..e18be684eb0 100644 --- a/src/__swaps__/types/refraction.ts +++ b/src/__swaps__/types/refraction.ts @@ -1,5 +1,5 @@ import { ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/networks/types'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { PaginatedTransactionsApiResponse } from '@/resources/transactions/types'; /** diff --git a/src/__swaps__/types/search.ts b/src/__swaps__/types/search.ts index 9e0aff40e3d..5acf427de4e 100644 --- a/src/__swaps__/types/search.ts +++ b/src/__swaps__/types/search.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, AssetType, ParsedAsset, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { AssetToBuySectionId } from '../screens/Swap/hooks/useSearchCurrencyLists'; export type TokenSearchAssetKey = keyof ParsedAsset; diff --git a/src/__swaps__/utils/address.ts b/src/__swaps__/utils/address.ts index 1b56b2ba57e..39fd20091c1 100644 --- a/src/__swaps__/utils/address.ts +++ b/src/__swaps__/utils/address.ts @@ -1,7 +1,7 @@ import { Address } from 'viem'; import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export function truncateAddress(address?: AddressOrEth) { if (!address) return ''; diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts index 420aee6a9b8..8b888c36fc2 100644 --- a/src/__swaps__/utils/assets.ts +++ b/src/__swaps__/utils/assets.ts @@ -1,4 +1,5 @@ import { AddressZero } from '@ethersproject/constants'; +import isValidDomain from 'is-valid-domain'; import { ETH_ADDRESS, SupportedCurrencyKey } from '@/references'; import { @@ -12,7 +13,7 @@ import { ZerionAsset, ZerionAssetPrice, } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/networks/types'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import * as i18n from '@/languages'; import { SearchAsset } from '@/__swaps__/types/search'; diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts index 63f821fc1b7..1b9ca2bd496 100644 --- a/src/__swaps__/utils/chains.ts +++ b/src/__swaps__/utils/chains.ts @@ -1,7 +1,7 @@ import { celo, fantom, harmonyOne, moonbeam } from 'viem/chains'; import { NATIVE_ASSETS_PER_CHAIN } from '@/references'; import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/networks/types'; +import { ChainId, ChainName, ChainNameDisplay, chainIdToNameMapping, chainNameToIdMapping } from '@/__swaps__/types/chains'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; diff --git a/src/__swaps__/utils/decimalFormatter.ts b/src/__swaps__/utils/decimalFormatter.ts index fc0f9a943ec..afe646b1653 100644 --- a/src/__swaps__/utils/decimalFormatter.ts +++ b/src/__swaps__/utils/decimalFormatter.ts @@ -36,7 +36,7 @@ export function valueBasedDecimalFormatter({ maximumDecimalPlaces: number; } { const orderOfMagnitude = orderOfMagnitudeWorklet(amount); - const minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; + let minDecimalsForOneCent = nativePrice ? Math.round(Math.max(0, Math.log10(nativePrice / 0.01))) : MAXIMUM_SIGNIFICANT_DECIMALS; const significantDecimals = significantDecimalsWorklet(amount); @@ -82,7 +82,7 @@ export function valueBasedDecimalFormatter({ // Format the number to add separators and trim trailing zeros const numberFormatter = new Intl.NumberFormat('en-US', { minimumFractionDigits: minimumDecimalPlaces, - maximumFractionDigits: maximumDecimalPlaces || 0, + maximumFractionDigits: maximumDecimalPlaces, useGrouping: !stripSeparators, }); diff --git a/src/__swaps__/utils/gasUtils.ts b/src/__swaps__/utils/gasUtils.ts index 5039a93eef1..7e4ff881a7b 100644 --- a/src/__swaps__/utils/gasUtils.ts +++ b/src/__swaps__/utils/gasUtils.ts @@ -12,7 +12,7 @@ import { OVM_GAS_PRICE_ORACLE, gasUnits, supportedNativeCurrencies, optimismGasO import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/__swaps__/utils/meteorology'; import { ParsedAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { BlocksToConfirmation, GasFeeLegacyParams, GasFeeParam, GasFeeParams, GasSpeed } from '@/__swaps__/types/gas'; import { gweiToWei, weiToGwei } from '@/__swaps__/utils/ethereum'; diff --git a/src/__swaps__/utils/meteorology.ts b/src/__swaps__/utils/meteorology.ts index c12164ef6d4..de0740f4069 100644 --- a/src/__swaps__/utils/meteorology.ts +++ b/src/__swaps__/utils/meteorology.ts @@ -1,11 +1,12 @@ import { useQuery } from '@tanstack/react-query'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { rainbowMeteorologyGetData } from '@/handlers/gasFees'; import { abs, lessThan, subtract } from '@/helpers/utilities'; import { gweiToWei } from '@/parsers'; import { QueryConfig, QueryFunctionArgs, QueryFunctionResult, createQueryKey, queryClient } from '@/react-query'; import { useSwapsStore } from '@/state/swaps/swapsStore'; +import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { useCallback } from 'react'; import { MIN_FLASHBOTS_PRIORITY_FEE } from '../screens/Swap/constants'; import { GasSettings } from '../screens/Swap/hooks/useCustomGas'; @@ -71,7 +72,8 @@ type MeteorologyQueryKey = ReturnType; // Query Function async function meteorologyQueryFunction({ queryKey: [{ chainId }] }: QueryFunctionArgs) { - const parsedResponse = await rainbowMeteorologyGetData(chainId); + const network = getNetworkFromChainId(chainId); + const parsedResponse = await rainbowMeteorologyGetData(network); const meteorologyData = parsedResponse.data as MeteorologyResponse | MeteorologyLegacyResponse; return meteorologyData; } diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 10f55dae1ad..33071792138 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -9,7 +9,7 @@ import { SLIDER_WIDTH, STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; -import { ChainId, ChainName } from '@/networks/types'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { chainNameFromChainId, chainNameFromChainIdWorklet } from '@/__swaps__/utils/chains'; import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; import { globalColors } from '@/design-system'; diff --git a/src/__swaps__/utils/userChains.ts b/src/__swaps__/utils/userChains.ts index cff0ccf7f6e..0a9a35369c1 100644 --- a/src/__swaps__/utils/userChains.ts +++ b/src/__swaps__/utils/userChains.ts @@ -21,7 +21,7 @@ import { sepolia, } from 'viem/chains'; -import { ChainId, ChainNameDisplay } from '@/networks/types'; +import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; export const chainIdMap: Record< ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.base | ChainId.bsc | ChainId.zora | ChainId.avalanche, diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 22c3c8c5e93..ebb6849433e 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -1,12 +1,13 @@ import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { UnlockableAppIconKey } from '@/appIcons/appIcons'; import { CardType } from '@/components/cards/GenericCard'; import { LearnCategory } from '@/components/cards/utils/types'; import { FiatProviderName } from '@/entities/f2c'; +import { Network } from '@/networks/types'; import { RapSwapActionParameters } from '@/raps/references'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; diff --git a/src/analytics/index.ts b/src/analytics/index.ts index 6bbd722edb8..d0800a9cb67 100644 --- a/src/analytics/index.ts +++ b/src/analytics/index.ts @@ -19,9 +19,9 @@ export class Analytics { this.client = rudderClient; this.disabled = isTesting || !!device.get(['doNotTrack']); if (isTesting) { - logger.debug('[Analytics]: disabled for testing'); + logger.debug('Analytics is disabled for testing'); } else { - logger.debug('[Analytics]: client initialized'); + logger.debug('Analytics client initialized'); } } @@ -71,7 +71,7 @@ export class Analytics { dataPlaneUrl: RUDDERSTACK_DATA_PLANE_URL, }); } catch (error) { - logger.error(new RainbowError('[Analytics]: Unable to initialize Rudderstack'), { error }); + logger.error(new RainbowError('Unable to initialize Rudderstack'), { error }); } } @@ -80,7 +80,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setDeviceId(deviceId: string) { - logger.debug(`[Analytics]: Set deviceId on analytics instance`); + logger.debug(`Set deviceId on analytics instance`); this.deviceId = deviceId; } @@ -89,7 +89,7 @@ export class Analytics { * `identify()`, you must do that on your own. */ setCurrentWalletAddressHash(currentWalletAddressHash: string) { - logger.debug(`[Analytics]: Set currentWalletAddressHash on analytics instance`); + logger.debug(`Set currentWalletAddressHash on analytics instance`); this.currentWalletAddressHash = currentWalletAddressHash; } diff --git a/src/analytics/utils.ts b/src/analytics/utils.ts index 450ed5666ff..28f474d7faa 100644 --- a/src/analytics/utils.ts +++ b/src/analytics/utils.ts @@ -35,7 +35,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { const deviceIdFromStorage = ls.device.get(['id']); if (deviceIdFromStorage) { - logger.debug(`[getOrCreateDeviceId]: using existing deviceId from storage ${deviceIdFromStorage}`); + logger.debug(`getOrCreateDeviceId using existing deviceId from storage`); // if we have a ID in storage, we've already migrated return [deviceIdFromStorage, false]; } else { @@ -47,11 +47,12 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { // set ID ls.device.set(['id'], deviceId); + // log to Sentry if (hasExistingDeviceId) { - logger.debug(`[getOrCreateDeviceId]: migrating device ID from keychain to local storage`); + logger.info(`getOrCreateDeviceId migrating device ID from keychain to local storage`); } - logger.debug(`[getOrCreateDeviceId]: returned new deviceId ${deviceId}`); + logger.debug(`getOrCreateDeviceId returned new deviceId`); // if we had an old device id in keychain, `wasCreated` should be false return [deviceId, !hasExistingDeviceId]; @@ -60,7 +61,7 @@ export async function getOrCreateDeviceId(): Promise<[string, boolean]> { export function securelyHashWalletAddress(walletAddress: `0x${string}`): string | undefined { if (!SECURE_WALLET_HASH_KEY) { - logger.error(new RainbowError(`[securelyHashWalletAddress]: Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); + logger.error(new RainbowError(`Required .env variable SECURE_WALLET_HASH_KEY does not exist`)); } try { @@ -72,11 +73,11 @@ export function securelyHashWalletAddress(walletAddress: `0x${string}`): string walletAddress ); - logger.debug(`[securelyHashWalletAddress]: Wallet address securely hashed`); + logger.debug(`Wallet address securely hashed`); return hmac; } catch (e) { // could be an invalid hashing key, or trying to hash an ENS - logger.error(new RainbowError(`[securelyHashWalletAddress]: Wallet address hashing failed`)); + logger.error(new RainbowError(`Wallet address hashing failed`)); } } diff --git a/src/appIcons/appIcons.ts b/src/appIcons/appIcons.ts index 407b688f21b..025ed695e05 100644 --- a/src/appIcons/appIcons.ts +++ b/src/appIcons/appIcons.ts @@ -1,4 +1,5 @@ import { EthereumAddress } from '@/entities'; +import { Network } from '@/helpers'; import { ImageSourcePropType } from 'react-native'; import AppIconFiniliar from '@/assets/appIconFiniliar.png'; import AppIconGoldDoge from '@/assets/appIconGoldDoge.png'; @@ -14,7 +15,6 @@ import AppIconPoolboy from '@/assets/appIconPoolboy.png'; import AppIconAdworld from '@/assets/appIconAdworld.png'; import AppIconFarcaster from '@/assets/appIconFarcaster.png'; import { TokenGateCheckerNetwork } from '@/featuresToUnlock/tokenGatedUtils'; -import { Network } from '@/networks/types'; // optimism app icon unlocking NFTs const OPTIMISTIC_EXPLORER_NFT_ADDRESS: EthereumAddress = '0x81b30ff521D1fEB67EDE32db726D95714eb00637'; diff --git a/src/components/AddFundsInterstitial.js b/src/components/AddFundsInterstitial.js index 23f430fd577..34312f9026a 100644 --- a/src/components/AddFundsInterstitial.js +++ b/src/components/AddFundsInterstitial.js @@ -3,6 +3,7 @@ import lang from 'i18n-js'; import React, { Fragment, useCallback } from 'react'; import { Linking, View } from 'react-native'; import networkInfo from '../helpers/networkInfo'; +import networkTypes from '../helpers/networkTypes'; import showWalletErrorAlert from '../helpers/support'; import { useNavigation } from '../navigation/Navigation'; import { useTheme } from '../theme/ThemeContext'; @@ -19,7 +20,6 @@ import styled from '@/styled-thing'; import { padding, position } from '@/styles'; import ShadowStack from '@/react-native-shadow-stack'; import { useRoute } from '@react-navigation/native'; -import { Network } from '@/networks/types'; const ContainerWidth = 261; @@ -213,7 +213,7 @@ const AddFundsInterstitial = ({ network }) => { return ( - {network === Network.mainnet ? ( + {network === networkTypes.mainnet ? ( {ios ? lang.t('add_funds.to_get_started_ios') : lang.t('add_funds.to_get_started_android')} diff --git a/src/components/ChainLogo.js b/src/components/ChainLogo.js index ae21dddd071..ef03bb368c7 100644 --- a/src/components/ChainLogo.js +++ b/src/components/ChainLogo.js @@ -19,9 +19,9 @@ import BaseBadge from '../assets/badges/baseBadge.png'; import BaseBadgeDark from '../assets/badges/baseBadgeDark.png'; import BaseBadgeNoShadow from '../assets/badges/baseBadgeNoShadow.png'; import { Centered } from './layout'; +import networkTypes from '@/helpers/networkTypes'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { ChainId } from '@/networks/types'; const ChainIcon = styled(FastImage)({ height: ({ size }) => size, @@ -34,25 +34,25 @@ const Content = styled(Centered)(({ size }) => ({ overflow: 'visible', })); -export default function ChainLogo({ chainId, size = 44, withShadows = true, ...props }) { +export default function ChainLogo({ network, size = 44, withShadows = true, ...props }) { const { isDarkMode } = useTheme(); const source = useMemo(() => { let val = null; - if (chainId === ChainId.arbitrum) { + if (network === networkTypes.arbitrum) { val = withShadows ? (isDarkMode ? ArbitrumBadgeDark : ArbitrumBadge) : ArbitrumBadgeNoShadow; - } else if (chainId === ChainId.optimism) { + } else if (network === networkTypes.optimism) { val = withShadows ? (isDarkMode ? OptimismBadgeDark : OptimismBadge) : OptimismBadgeNoShadow; - } else if (chainId === ChainId.polygon) { + } else if (network === networkTypes.polygon) { val = withShadows ? (isDarkMode ? PolygonBadgeDark : PolygonBadge) : PolygonBadgeNoShadow; - } else if (chainId === ChainId.bsc) { + } else if (network === networkTypes.bsc) { val = withShadows ? (isDarkMode ? BscBadgeDark : BscBadge) : BscBadgeNoShadow; - } else if (chainId === ChainId.zora) { + } else if (network === networkTypes.zora) { val = withShadows ? (isDarkMode ? ZoraBadgeDark : ZoraBadge) : ZoraBadgeNoShadow; - } else if (chainId === ChainId.base) { + } else if (network === networkTypes.base) { val = withShadows ? (isDarkMode ? BaseBadgeDark : BaseBadge) : BaseBadgeNoShadow; } return val; - }, [isDarkMode, chainId, withShadows]); + }, [isDarkMode, network, withShadows]); if (!source) return null; diff --git a/src/components/ContactRowInfoButton.js b/src/components/ContactRowInfoButton.js index 28ce8a1f159..60c58c64a07 100644 --- a/src/components/ContactRowInfoButton.js +++ b/src/components/ContactRowInfoButton.js @@ -69,7 +69,7 @@ const ContactRowActions = { const buildBlockExplorerAction = chainId => { const blockExplorerText = lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { @@ -82,7 +82,7 @@ const buildBlockExplorerAction = chainId => { }; }; -const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { +const ContactRowInfoButton = ({ children, item, network, scaleTo }) => { const { setClipboard } = useClipboard(); const handleCopyAddress = useCallback( address => { @@ -93,7 +93,7 @@ const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId }))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( { @@ -107,14 +107,14 @@ const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { handleCopyAddress(item?.address); } if (idx === 1) { - ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); + ethereumUtils.openAddressInBlockExplorer(item?.address, network); } } ); - }, [item?.name, item?.address, handleCopyAddress, chainId]); + }, [item?.network, item?.name, item?.address, handleCopyAddress, network]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(chainId); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); return { menuItems: [ blockExplorerAction, @@ -125,17 +125,17 @@ const ContactRowInfoButton = ({ children, item, chainId, scaleTo }) => { ], menuTitle: `${item?.name}`, }; - }, [chainId, item?.address, item?.name]); + }, [item]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContactRowActionsEnum.copyAddress) { handleCopyAddress(item?.address); } else if (actionKey === ContactRowActionsEnum.blockExplorer) { - ethereumUtils.openAddressInBlockExplorer({ address: item?.address, chainId }); + ethereumUtils.openAddressInBlockExplorer(item?.address); } }, - [handleCopyAddress, item?.address, chainId] + [item, handleCopyAddress] ); const Container = children ? Centered : InfoButton; diff --git a/src/components/DappBrowser/Dimensions.ts b/src/components/DappBrowser/Dimensions.ts index 2cddb184f37..a367b0967aa 100644 --- a/src/components/DappBrowser/Dimensions.ts +++ b/src/components/DappBrowser/Dimensions.ts @@ -2,11 +2,11 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; import { StatusBar } from 'react-native'; -import { NAVIGATION_BAR_HEIGHT } from '@/utils/deviceUtils'; +import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; export const BOTTOM_BAR_HEIGHT = 88; export const TOP_INSET = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight ?? 40; -export const BOTTOM_INSET = IS_ANDROID ? NAVIGATION_BAR_HEIGHT - 8 : BOTTOM_BAR_HEIGHT; +export const BOTTOM_INSET = IS_ANDROID ? (isUsingButtonNavigation() ? 32 : 12) : BOTTOM_BAR_HEIGHT; export const WEBVIEW_HEIGHT = deviceUtils.dimensions.height - TOP_INSET - TAB_BAR_HEIGHT - BOTTOM_INSET; export const COLLAPSED_WEBVIEW_ASPECT_RATIO = 4 / 3; export const COLLAPSED_WEBVIEW_HEIGHT_UNSCALED = Math.min(WEBVIEW_HEIGHT, deviceUtils.dimensions.width * COLLAPSED_WEBVIEW_ASPECT_RATIO); diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index 4053671b59c..d4fe0a7c014 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -20,7 +20,7 @@ import { } from '@/design-system'; import { ImgixImage } from '@/components/images'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; +import { IS_ANDROID, IS_IOS } from '@/env'; import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; import { opacity } from '@/__swaps__/utils/swaps'; import { FavoritedSite, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; @@ -41,10 +41,6 @@ import { getNameFromFormattedUrl } from './utils'; import { useTrendingDApps } from '@/resources/metadata/trendingDapps'; import { DApp } from '@/graphql/__generated__/metadata'; import { DEFAULT_TAB_URL } from './constants'; -import { FeaturedResult } from '@/graphql/__generated__/arc'; -import { useRemoteConfig } from '@/model/remoteConfig'; -import { FEATURED_RESULTS, useExperimentalFlag } from '@/config'; -import { FeaturedResultStack } from '../FeaturedResult/FeaturedResultStack'; const HORIZONTAL_PAGE_INSET = 24; const MAX_RECENTS_TO_DISPLAY = 6; @@ -61,7 +57,7 @@ const NUM_CARDS = 2; const CARD_PADDING = 12; const CARD_HEIGHT = 137; const RAW_CARD_WIDTH = (DEVICE_WIDTH - HORIZONTAL_PAGE_INSET * 2 - (NUM_CARDS - 1) * CARD_PADDING) / NUM_CARDS; -export const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); +const CARD_WIDTH = IS_IOS ? RAW_CARD_WIDTH : Math.floor(RAW_CARD_WIDTH); export const Homepage = ({ tabId }: { tabId: string }) => { const { goToUrl } = useBrowserContext(); @@ -86,25 +82,6 @@ export const Homepage = ({ tabId }: { tabId: string }) => { ); }; -const DappBrowserFeaturedResults = () => { - const { goToUrl } = useBrowserContext(); - const { featured_results } = useRemoteConfig(); - const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; - - const onNavigate = useCallback( - (href: string) => { - goToUrl(href); - }, - [goToUrl] - ); - - if (!featuredResultsEnabled) { - return null; - } - - return ; -}; - const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { const { data } = useTrendingDApps(); @@ -132,7 +109,6 @@ const Trending = ({ goToUrl }: { goToUrl: (url: string) => void }) => { > - {data.dApps .filter((dApp): dApp is DApp => dApp !== null) .map((dApp, index) => ( @@ -499,57 +475,6 @@ const Card = memo(function Card({ ); }); -export const DappBrowserFeaturedResultsCard = memo(function Card({ - handlePress, - featuredResult, -}: { - handlePress: () => void; - featuredResult: FeaturedResult; -}) { - const { isDarkMode } = useColorMode(); - - return ( - - - - - - {IS_IOS && ( - - )} - - - ); -}); - const CardBackground = memo(function CardBackgroundOverlay({ imageUrl, isDarkMode, diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index 55de3b4bf14..c02f018636d 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -30,6 +30,7 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBalancesAndNames } from '@/hooks'; import { useSyncSharedValue } from '@/hooks/reanimated/useSyncSharedValue'; +import { Network } from '@/networks/types'; import { useBrowserStore } from '@/state/browser/browserStore'; import { colors } from '@/styles'; import { deviceUtils, watchingAlert } from '@/utils'; @@ -40,7 +41,7 @@ import { TOP_INSET } from '../Dimensions'; import { formatUrl } from '../utils'; import { RouteProp, useRoute } from '@react-navigation/native'; import { toHex } from 'viem'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import * as i18n from '@/languages'; import { useDispatch } from 'react-redux'; import store from '@/redux/store'; @@ -62,7 +63,7 @@ import { SWAPS_V2, useExperimentalFlag } from '@/config'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const PAGES = { HOME: 'home', @@ -101,7 +102,7 @@ export const ControlPanel = () => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - chainId: hostSessions.sessions[hostSessions.activeSessionAddress], + network: hostSessions.sessions[hostSessions.activeSessionAddress], } : null, [hostSessions] @@ -111,7 +112,7 @@ export const ControlPanel = () => { const [currentAddress, setCurrentAddress] = useState( currentSession?.address || hostSessions?.activeSessionAddress || accountAddress ); - const [currentChainId, setCurrentChainId] = useState(currentSession?.chainId || ChainId.mainnet); + const [currentNetwork, setCurrentNetwork] = useState(currentSession?.network || Network.mainnet); // listens to the current active tab and sets the account useEffect(() => { @@ -127,8 +128,8 @@ export const ControlPanel = () => { setCurrentAddress(accountAddress); } - if (currentSession?.chainId) { - setCurrentChainId(currentSession?.chainId); + if (currentSession?.network) { + setCurrentNetwork(currentSession?.network); } } }, [accountAddress, activeTabHost, currentSession]); @@ -141,7 +142,7 @@ export const ControlPanel = () => { const accountBalances: Record = {}; Object.values(walletsWithBalancesAndNames).forEach(wallet => { - (wallet.addresses || []) + wallet.addresses .filter(account => account.visible) .forEach(account => { const balanceText = account.balances ? account.balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); @@ -183,27 +184,28 @@ export const ControlPanel = () => { const { testnetsEnabled } = store.getState().settings; const allNetworkItems = useMemo(() => { - return RainbowNetworkObjects.filter( + return RainbowNetworks.filter( ({ networkType, features: { walletconnect } }) => walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => { return { IconComponent: , label: network.name, secondaryLabel: i18n.t( - isConnected && network.id === currentChainId + isConnected && network.value === currentNetwork ? i18n.l.dapp_browser.control_panel.connected : i18n.l.dapp_browser.control_panel.not_connected ), - uniqueId: String(network.id), - selected: network.id === currentChainId, + uniqueId: network.value, + selected: network.value === currentNetwork, + chainId: network.id, }; }); - }, [currentChainId, isConnected, testnetsEnabled]); + }, [currentNetwork, isConnected, testnetsEnabled]); const selectedWallet = allWalletItems.find(item => item.selected); const animatedAccentColor = useSharedValue(selectedWallet?.color || globalColors.blue10); - const selectedNetworkId = useSharedValue(currentChainId?.toString() || RainbowNetworkObjects[0].value); + const selectedNetworkId = useSharedValue(currentNetwork?.toString() || RainbowNetworks[0].value); const selectedWalletId = useSharedValue(selectedWallet?.uniqueId || accountAddress); const handleSwitchWallet = useCallback( @@ -211,21 +213,21 @@ export const ControlPanel = () => { const address = selectedItemId; updateActiveSession({ host: activeTabHost, address: address as `0x${string}` }); if (isConnected) { - updateActiveSessionNetwork({ host: activeTabHost, chainId: currentChainId }); + updateActiveSessionNetwork({ host: activeTabHost, network: currentNetwork }); // need to emit these events to the dapp activeTabRef.current?.injectJavaScript(`window.ethereum.emit('accountsChanged', ['${address}']); true;`); } setCurrentAddress(address); }, - [activeTabHost, activeTabRef, currentChainId, isConnected, updateActiveSession, updateActiveSessionNetwork] + [activeTabHost, activeTabRef, currentNetwork, isConnected, updateActiveSession, updateActiveSessionNetwork] ); const handleNetworkSwitch = useCallback( (selectedItemId: string) => { - updateActiveSessionNetwork({ host: activeTabHost, chainId: Number(selectedItemId) as ChainId }); - const chainId = RainbowNetworkObjects.find(({ id }) => id === (Number(selectedItemId) as ChainId))?.id as number; + updateActiveSessionNetwork({ host: activeTabHost, network: selectedItemId as Network }); + const chainId = RainbowNetworks.find(({ value }) => value === (selectedItemId as Network))?.id as number; activeTabRef.current?.injectJavaScript(`window.ethereum.emit('chainChanged', ${toHex(chainId)}); true;`); - setCurrentChainId(Number(selectedItemId) as ChainId); + setCurrentNetwork(selectedItemId as Network); }, [activeTabHost, activeTabRef, updateActiveSessionNetwork] ); @@ -233,21 +235,23 @@ export const ControlPanel = () => { const handleConnect = useCallback(async () => { const activeTabHost = getDappHost(activeTabUrl || ''); const address = selectedWalletId.value; - const chainId = Number(selectedNetworkId.value); + const network = selectedNetworkId.value as Network; addSession({ host: activeTabHost || '', address: address as `0x${string}`, - chainId, + network, url: activeTabUrl || '', }); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + activeTabRef.current?.injectJavaScript( `window.ethereum.emit('accountsChanged', ['${address}']); window.ethereum.emit('connect', { address: '${address}', chainId: '${toHex(chainId)}' }); true;` ); setIsConnected(true); setCurrentAddress(address); - setCurrentChainId(chainId); + setCurrentNetwork(network); }, [activeTabUrl, selectedWalletId.value, selectedNetworkId.value, addSession, activeTabRef]); const handleDisconnect = useCallback(() => { @@ -269,7 +273,7 @@ export const ControlPanel = () => { ; goToPage: (pageId: string) => void; - selectedChainId: ChainId; + selectedNetwork: string; selectedWallet: ControlPanelMenuItemProps | undefined; allNetworkItems: ControlPanelMenuItemProps[]; isConnected: boolean; @@ -389,8 +393,8 @@ const HomePanel = ({ const walletLabel = selectedWallet?.label || ''; const walletSecondaryLabel = selectedWallet?.secondaryLabel || ''; - const network = allNetworkItems.find(item => item.uniqueId === String(selectedChainId)); - const networkIcon = ; + const network = allNetworkItems.find(item => item.uniqueId === selectedNetwork); + const networkIcon = ; const networkLabel = network?.label || ''; const networkSecondaryLabel = network?.secondaryLabel || ''; @@ -416,7 +420,7 @@ const HomePanel = ({ /> ); - }, [allNetworkItems, animatedAccentColor, goToPage, selectedChainId, selectedWallet]); + }, [allNetworkItems, animatedAccentColor, goToPage, selectedNetwork, selectedWallet]); const runWalletChecksBeforeSwapOrBridge = useCallback(async () => { if (!selectedWallet || !wallets) return false; @@ -456,7 +460,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -484,7 +488,7 @@ const HomePanel = ({ return; } - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: selectedWallet?.uniqueId }); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, selectedWallet?.uniqueId); Navigation.handleAction(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { @@ -733,6 +737,7 @@ interface ControlPanelMenuItemProps { label: string; labelColor?: TextColor; imageUrl?: string; + chainId?: ChainId; color?: string; onPress?: () => void; secondaryLabel?: string; diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index d0ce9356952..c730eadafdb 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -4,18 +4,20 @@ import * as lang from '@/languages'; import { Provider } from '@ethersproject/providers'; -import { RainbowNetworkObjects, RainbowSupportedChainIds } from '@/networks'; -import { getProvider } from '@/handlers/web3'; +import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { getProviderForNetwork } from '@/handlers/web3'; +import ethereumUtils, { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { UserRejectedRequestError } from 'viem'; import { convertHexToString } from '@/helpers/utilities'; import { logger } from '@/logger'; import { ActiveSession } from '@rainbow-me/provider/dist/references/appSession'; +import { Network } from '@/helpers'; import { handleDappBrowserConnectionPrompt, handleDappBrowserRequest } from '@/utils/requestNavigationHandlers'; import { Tab } from '@rainbow-me/provider/dist/references/messengers'; import { getDappMetadata } from '@/resources/metadata/dapp'; import { useAppSessionsStore } from '@/state/appSessions'; import { BigNumber } from '@ethersproject/bignumber'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export type ProviderRequestPayload = RequestArguments & { id: number; @@ -121,51 +123,56 @@ const messengerProviderRequestFn = async (messenger: Messenger, request: Provide hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - chainId: hostSessions.sessions[hostSessions.activeSessionAddress], + network: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; + // Wait for response from the popup. + let response: unknown | null; + if (request.method === 'eth_requestAccounts') { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - chainId is not defined in the type const chainId = request.params?.[0]?.chainId ? BigNumber.from(request.params?.[0]?.chainId).toNumber() : undefined; - const response = await handleDappBrowserConnectionPrompt({ + response = await handleDappBrowserConnectionPrompt({ dappName: dappData?.appName || request.meta?.sender.title || '', dappUrl: request.meta?.sender.url || '', chainId, address: hostSessions?.activeSessionAddress || undefined, }); - if (!response || response instanceof Error) { - throw new UserRejectedRequestError(Error('User rejected the request.')); - } - useAppSessionsStore.getState().addSession({ host: getDappHost(request.meta?.sender.url) || '', - address: response?.address, - chainId: response.chainId, + // @ts-ignore + address: response.address, + // @ts-ignore + network: getNetworkFromChainId(response.chainId), + // @ts-ignore url: request.meta?.sender.url || '', }); - return response; } else { const dappData = await getDappMetadata({ url: getDappHost(request.meta?.sender.url) }); - const response = await handleDappBrowserRequest({ + response = await handleDappBrowserRequest({ dappName: dappData?.appName || request.meta?.sender.title || request.meta?.sender.url || '', imageUrl: dappData?.appLogo || '', address: appSession?.address || '', dappUrl: request.meta?.sender.url || '', payload: request, - chainId: appSession?.chainId ? appSession?.chainId : ChainId.mainnet, + chainId: appSession?.network ? ethereumUtils.getChainIdFromNetwork(appSession.network) : ChainId.mainnet, }); - return response as object; } + + if (!response) { + throw new UserRejectedRequestError(Error('User rejected the request.')); + } + return response; }; const isSupportedChainId = (chainId: number | string) => { const numericChainId = BigNumber.from(chainId).toNumber(); - return !!RainbowSupportedChainIds.find(chainId => chainId === numericChainId); + return !!RainbowNetworks.find(network => Number(network.id) === numericChainId); }; const getActiveSession = ({ host }: { host: string }): ActiveSession => { const hostSessions = useAppSessionsStore.getState().getActiveSession({ host }); @@ -173,19 +180,26 @@ const getActiveSession = ({ host }: { host: string }): ActiveSession => { hostSessions && hostSessions.sessions?.[hostSessions.activeSessionAddress] ? { address: hostSessions.activeSessionAddress, - chainId: hostSessions.sessions[hostSessions.activeSessionAddress], + network: hostSessions.sessions[hostSessions.activeSessionAddress], } : null; if (!appSession) return null; return { address: appSession?.address || '', - chainId: appSession.chainId, + chainId: getChainIdByNetwork(appSession.network), }; // return null; }; -const getChain = (chainId: number) => RainbowNetworkObjects.find(network => Number(network.id) === chainId); +const getChainIdByNetwork = (network: Network) => getNetworkObj(network).id; + +const getChain = (chainId: number) => RainbowNetworks.find(network => Number(network.id) === chainId); + +const getProvider = ({ chainId }: { chainId?: number | undefined }) => { + const network = getNetworkFromChainId(chainId || 1); + return getProviderForNetwork(network) as unknown as Provider; +}; const checkRateLimitFn = async (host: string) => { // try { @@ -270,13 +284,13 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }): { chainAlreadyAdded: boolean } => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { // TODO - Open add / switch ethereum chain return { chainAlreadyAdded: true }; } else { - logger.debug(`[handleProviderRequestApp]: Dapp requested unsupported chain ${chainId}`); + logger.info('[DAPPBROWSER]: NOT SUPPORTED CHAIN'); return { chainAlreadyAdded: false }; } }; @@ -327,14 +341,14 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: callbackOptions?: CallbackOptions; }) => { const { chainId } = proposedChain; - const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.id.toString()); + const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); const supportedChainId = supportedChains.includes(numericChainId); if (supportedChainId) { const host = getDappHost(callbackOptions?.sender.url) || ''; const activeSession = getActiveSession({ host }); if (activeSession) { - useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, chainId: Number(numericChainId) }); + useAppSessionsStore.getState().updateActiveSessionNetwork({ host: host, network: getNetworkFromChainId(Number(numericChainId)) }); messenger.send(`chainChanged:${host}`, Number(numericChainId)); } console.warn('PROVIDER TODO: TODO SEND NOTIFICATION'); @@ -350,7 +364,7 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: checkRateLimit, onSwitchEthereumChainNotSupported, onSwitchEthereumChainSupported, - getProvider: chainId => getProvider({ chainId: chainId as number }) as unknown as Provider, + getProvider, getActiveSession, getChain, }); diff --git a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts index 841ea58aab7..787ba5db987 100644 --- a/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts +++ b/src/components/DappBrowser/hooks/useScreenshotAndScrollTriggers.ts @@ -51,7 +51,7 @@ export function useScreenshotAndScrollTriggers() { saveScreenshotToFileSystem(uri, tabId, timestamp, pageUrl); }) .catch(error => { - logger.error(new RainbowError('[DappBrowser]: Failed to capture tab screenshot'), { + logger.error(new RainbowError('Failed to capture tab screenshot'), { error: error.message, }); }); diff --git a/src/components/DappBrowser/screenshots.ts b/src/components/DappBrowser/screenshots.ts index 42fe8082aa7..89a56b359c2 100644 --- a/src/components/DappBrowser/screenshots.ts +++ b/src/components/DappBrowser/screenshots.ts @@ -20,11 +20,11 @@ export const findTabScreenshot = (id: string, url?: string): ScreenshotType | nu if (!Array.isArray(screenshots)) { try { - logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — expected array'), { + logger.error(new RainbowError('Screenshot data is malformed — expected array'), { screenshots: JSON.stringify(screenshots, null, 2), }); } catch (e: any) { - logger.error(new RainbowError('[DappBrowser]: Screenshot data is malformed — error stringifying'), { + logger.error(new RainbowError('Screenshot data is malformed — error stringifying'), { message: e.message, }); } @@ -79,7 +79,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] const deletePromises = screenshotsToDelete.map(screenshot => { const filePath = `${RNFS.DocumentDirectoryPath}/${screenshot.uri}`; return RNFS.unlink(filePath).catch(e => { - logger.error(new RainbowError('[DappBrowser]: Error deleting screenshot file'), { + logger.error(new RainbowError('Error deleting screenshot file'), { message: e.message, filePath, screenshot: JSON.stringify(screenshot, null, 2), @@ -88,7 +88,7 @@ const deletePrunedScreenshotFiles = async (screenshotsToDelete: ScreenshotType[] }); await Promise.all(deletePromises); } catch (e: any) { - logger.error(new RainbowError('[DappBrowser]: Screenshot file pruning operation failed to complete'), { + logger.error(new RainbowError('Screenshot file pruning operation failed to complete'), { message: e.message, }); } @@ -118,7 +118,7 @@ export const saveScreenshot = async (tempUri: string, tabId: string, timestamp: // Set screenshot for display return screenshotWithRNFSPath; } catch (e: any) { - logger.error(new RainbowError('[DappBrowser]: Error saving tab screenshot to file system'), { + logger.error(new RainbowError('Error saving tab screenshot to file system'), { message: e.message, screenshotData: { tempUri, diff --git a/src/components/DappBrowser/utils.ts b/src/components/DappBrowser/utils.ts index 7d1d885311b..22c38a979fb 100644 --- a/src/components/DappBrowser/utils.ts +++ b/src/components/DappBrowser/utils.ts @@ -120,7 +120,7 @@ export async function handleShareUrl(url: string): Promise { await Share.share({ message: url }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - logger.error(new RainbowError('[DappBrowser]: Error sharing browser URL'), { + logger.error(new RainbowError('Error sharing browser URL'), { message: e.message, url, }); diff --git a/src/components/FeaturedResult/FeaturedResultCard.tsx b/src/components/FeaturedResult/FeaturedResultCard.tsx deleted file mode 100644 index 594449258ea..00000000000 --- a/src/components/FeaturedResult/FeaturedResultCard.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { FeaturedResultsVariables, useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; -import { getFeaturedResultById } from '@/resources/featuredResults/_selectors/getFeaturedResultById'; -import { useTrackFeaturedResult } from '@/resources/featuredResults/trackFeaturedResult'; -import { TrackFeaturedResultType } from '@/graphql/__generated__/arc'; -import React, { useCallback, useEffect } from 'react'; -import { FeaturedResultStackProps } from './FeaturedResultStack'; -import { logger } from '@/logger'; - -type FeaturedResultCardProps = FeaturedResultStackProps & - FeaturedResultsVariables & { - featuredResultId: string; - }; - -export const FeaturedResultCard = ({ featuredResultId, onNavigate, Card, ...props }: FeaturedResultCardProps) => { - const { data: featuredResult } = useFeaturedResults(props, { - select: data => getFeaturedResultById(data, featuredResultId), - }); - - const { mutateAsync: trackFeaturedResult } = useTrackFeaturedResult(); - - useEffect(() => { - (async () => { - if (!featuredResult) { - return; - } - - await trackFeaturedResult({ - featuredResultCreativeId: featuredResult.advertiserId, - placementId: featuredResult.placementSlug, - impressionId: featuredResult.impressionId, - type: TrackFeaturedResultType.Impression, - }); - })(); - }, [featuredResult, trackFeaturedResult]); - - const handlePress = useCallback(async () => { - try { - if (!featuredResult) return; - - const [cta] = featuredResult.ctas || []; - - await trackFeaturedResult({ - featuredResultCreativeId: featuredResult.advertiserId, - placementId: featuredResult.placementSlug, - impressionId: featuredResult.impressionId, - type: TrackFeaturedResultType.Click, - }); - - onNavigate(cta.href); - } catch (error) { - logger.warn(`[FeaturedResultCard] Error tracking featured result click`, { error }); - } - }, [featuredResult, trackFeaturedResult, onNavigate]); - - if (!featuredResult) { - return null; - } - - return ; -}; diff --git a/src/components/FeaturedResult/FeaturedResultStack.tsx b/src/components/FeaturedResult/FeaturedResultStack.tsx deleted file mode 100644 index 17649921268..00000000000 --- a/src/components/FeaturedResult/FeaturedResultStack.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { useAccountSettings } from '@/hooks'; -import { useFeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; -import { languageLocaleToCountry } from '@/utils/languageLocaleToCountry'; -import { getFeaturedResultsById } from '@/resources/featuredResults/_selectors/getFeaturedResultIds'; -import { useSharedValue } from 'react-native-reanimated'; -import { FeaturedResultCard } from '@/components/FeaturedResult/FeaturedResultCard'; -import { FeaturedResult } from '@/graphql/__generated__/arc'; - -export type FeaturedResultStackProps = { - onNavigate: (url: string) => void; - placementId: string; - Card: React.FC<{ handlePress: () => void; featuredResult: FeaturedResult }>; -}; - -export const FeaturedResultStack = ({ onNavigate, placementId, Card }: FeaturedResultStackProps) => { - const { accountAddress, language } = useAccountSettings(); - const currentIndex = useSharedValue(0); - - // @ts-expect-error - language is type string instead of typeof keyof Language - const country = languageLocaleToCountry(language); - const { data: featuredResultIds } = useFeaturedResults( - { - placementId, - walletAddress: accountAddress, - country, - }, - { - select: getFeaturedResultsById, - } - ); - - const featuredResultId = featuredResultIds?.[currentIndex.value]; - if (!featuredResultId) { - return null; - } - - return ( - - ); -}; diff --git a/src/components/L2Disclaimer.js b/src/components/L2Disclaimer.js index aa5361e6970..162084f1dec 100644 --- a/src/components/L2Disclaimer.js +++ b/src/components/L2Disclaimer.js @@ -7,13 +7,14 @@ import { Column, Row } from './layout'; import { Text } from './text'; import { padding, position } from '@/styles'; import { darkModeThemeColors } from '@/styles/colors'; +import { getNetworkObj } from '@/networks'; import * as lang from '@/languages'; import { isL2Chain } from '@/handlers/web3'; import { EthCoinIcon } from './coin-icon/EthCoinIcon'; -import { chainIdToNameMapping } from '@/networks/types'; +import { ethereumUtils } from '@/utils'; const L2Disclaimer = ({ - chainId, + network, colors, hideDivider, isNft = false, @@ -36,6 +37,7 @@ const L2Disclaimer = ({ }, }; + const chainId = ethereumUtils.getChainIdFromNetwork(network); const isL2 = isL2Chain({ chainId }); return ( @@ -57,7 +59,7 @@ const L2Disclaimer = ({ ? customText : lang.t(lang.l.expanded_state.asset.l2_disclaimer, { symbol, - network: chainIdToNameMapping[chainId], + network: getNetworkObj(network).name, })} diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index da1f88d403d..d829d4fa8dc 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -2,6 +2,7 @@ import * as lang from '@/languages'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SectionList, StyleSheet, View } from 'react-native'; import sectionListGetItemLayout from 'react-native-section-list-get-item-layout'; +import networkTypes from '../../helpers/networkTypes'; import ActivityIndicator from '../ActivityIndicator'; import Spinner from '../Spinner'; import { ButtonPressAnimation } from '../animations'; @@ -13,7 +14,6 @@ import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { useSectionListScrollToTopContext } from '@/navigation/SectionListScrollToTopContext'; import { safeAreaInsetValues } from '@/utils'; -import { Network } from '@/networks/types'; const sx = StyleSheet.create({ sectionHeader: { @@ -107,7 +107,7 @@ const ActivityList = ({ setScrollToTopRef(ref); }; - if (network === Network.mainnet) { + if (network === networkTypes.mainnet) { return ( remainingItemsLabel && } diff --git a/src/components/animations/animationConfigs.ts b/src/components/animations/animationConfigs.ts index 1e61b4e6698..b3505c762d9 100644 --- a/src/components/animations/animationConfigs.ts +++ b/src/components/animations/animationConfigs.ts @@ -1,48 +1,40 @@ -import { IS_TEST } from '@/env'; import { Easing, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'; function createSpringConfigs>(configs: T): T { return configs; } - function createTimingConfigs>(configs: T): T { return configs; } -type AnyConfig = WithSpringConfig | WithTimingConfig; - -export const disableForTestingEnvironment = (config: T): T => { - if (!IS_TEST) return config; - return { - ...config, - duration: 0, - } as T; -}; - // /---- 🍎 Spring Animations 🍎 ----/ // +// const springAnimations = createSpringConfigs({ - browserTabTransition: disableForTestingEnvironment({ dampingRatio: 0.82, duration: 800 }), - keyboardConfig: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 1000 }), - sliderConfig: disableForTestingEnvironment({ damping: 40, mass: 1.25, stiffness: 450 }), - slowSpring: disableForTestingEnvironment({ damping: 500, mass: 3, stiffness: 800 }), - snappierSpringConfig: disableForTestingEnvironment({ damping: 42, mass: 0.8, stiffness: 800 }), - snappySpringConfig: disableForTestingEnvironment({ damping: 100, mass: 0.8, stiffness: 275 }), - springConfig: disableForTestingEnvironment({ damping: 100, mass: 1.2, stiffness: 750 }), + browserTabTransition: { dampingRatio: 0.82, duration: 800 }, + keyboardConfig: { damping: 500, mass: 3, stiffness: 1000 }, + sliderConfig: { damping: 40, mass: 1.25, stiffness: 450 }, + slowSpring: { damping: 500, mass: 3, stiffness: 800 }, + snappierSpringConfig: { damping: 42, mass: 0.8, stiffness: 800 }, + snappySpringConfig: { damping: 100, mass: 0.8, stiffness: 275 }, + springConfig: { damping: 100, mass: 1.2, stiffness: 750 }, }); export const SPRING_CONFIGS: Record = springAnimations; +// // /---- END ----/ // // /---- ⏱️ Timing Animations ⏱️ ----/ // +// const timingAnimations = createTimingConfigs({ - buttonPressConfig: disableForTestingEnvironment({ duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }), - fadeConfig: disableForTestingEnvironment({ duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - fastFadeConfig: disableForTestingEnvironment({ duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowFadeConfig: disableForTestingEnvironment({ duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowerFadeConfig: disableForTestingEnvironment({ duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - slowestFadeConfig: disableForTestingEnvironment({ duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }), - tabPressConfig: disableForTestingEnvironment({ duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }), + buttonPressConfig: { duration: 160, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }, + fadeConfig: { duration: 200, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + fastFadeConfig: { duration: 100, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowFadeConfig: { duration: 300, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowerFadeConfig: { duration: 400, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + slowestFadeConfig: { duration: 500, easing: Easing.bezier(0.22, 1, 0.36, 1) }, + tabPressConfig: { duration: 800, easing: Easing.bezier(0.22, 1, 0.36, 1) }, }); export const TIMING_CONFIGS: Record = timingAnimations; +// // /---- END ----/ // diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index f6a4885e514..30b8915ed54 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -12,8 +12,6 @@ import Routes from '@/navigation/routesNames'; import { borders, colors, padding, shadow } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; -import { NativeCurrencyKey } from '@/entities'; -import { ChainId } from '@/networks/types'; interface CoinCheckButtonProps { isHidden: boolean; @@ -57,7 +55,7 @@ const formatPercentageString = (percentString?: string) => (percentString ? perc interface MemoizedBalanceCoinRowProps { uniqueId: string; - nativeCurrency: NativeCurrencyKey; + nativeCurrency: string; theme: any; navigate: any; nativeCurrencySymbol: string; @@ -67,7 +65,7 @@ interface MemoizedBalanceCoinRowProps { const MemoizedBalanceCoinRow = React.memo( ({ uniqueId, nativeCurrency, theme, navigate, nativeCurrencySymbol, isHidden, maybeCallback }: MemoizedBalanceCoinRowProps) => { - const item = useAccountAsset(uniqueId, nativeCurrency); + const item = useAccountAsset(uniqueId, nativeCurrency) as any; const handlePress = useCallback(() => { if (maybeCallback.current) { @@ -82,7 +80,7 @@ const MemoizedBalanceCoinRow = React.memo( } }, [navigate, item, maybeCallback]); - const percentChange = item?.native?.change || undefined; + const percentChange = item?.native?.change; const percentageChangeDisplay = formatPercentageString(percentChange); const isPositive = percentChange && percentageChangeDisplay.charAt(0) !== '-'; @@ -93,10 +91,10 @@ const MemoizedBalanceCoinRow = React.memo( const valueColor = nativeDisplay ? theme.colors.dark : theme.colors.blueGreyLight; - const chainId = item?.chainId || ChainId.mainnet; + const chainId = ethereumUtils.getChainIdFromNetwork(item?.network); return ( - + @@ -104,7 +102,7 @@ const MemoizedBalanceCoinRow = React.memo( size={40} icon={item?.icon_url} chainId={chainId} - symbol={item?.symbol || ''} + symbol={item?.symbol} theme={theme} colors={item?.colors} /> diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx index 01092ef5462..40262984048 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx @@ -18,7 +18,7 @@ import BlastBadge from '@/assets/badges/blastBadge.png'; import BlastBadgeDark from '@/assets/badges/blastBadgeDark.png'; import DegenBadge from '@/assets/badges/degenBadge.png'; import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; interface FastChainBadgeProps { chainId: ChainId; diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 0da7a18422e..15923fff565 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -13,7 +13,7 @@ import { colors, fonts, fontWithWidth, getFontSize } from '@/styles'; import { deviceUtils } from '@/utils'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const SafeRadialGradient = (IS_TESTING === 'true' ? View : RadialGradient) as typeof RadialGradient; diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index 26f131b8638..a23be055a48 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -13,6 +13,7 @@ import { StyleSheet } from 'react-native'; import { LIGHT_SEPARATOR_COLOR, SEPARATOR_COLOR } from '@/__swaps__/screens/Swap/constants'; import { analyticsV2 } from '@/analytics'; import { convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; +import { ethereumUtils } from '@/utils'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; type LaunchFeaturedMintButtonProps = { @@ -31,7 +32,8 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); + const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); } }, [featuredMint]); diff --git a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx index 1bf25cbc586..821f6be0487 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/ExternalScrollView.tsx @@ -42,7 +42,7 @@ const ExternalScrollViewWithRef = React.forwardRef ); diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index f1ce574bb24..3f063c6a23d 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -13,7 +13,7 @@ import rowRenderer from './RowRenderer'; import { BaseCellType, CellTypes, RecyclerListViewRef } from './ViewTypes'; import getLayoutProvider from './getLayoutProvider'; import useLayoutItemAnimator from './useLayoutItemAnimator'; -import { NativeCurrencyKey, UniqueAsset } from '@/entities'; +import { UniqueAsset } from '@/entities'; import { useRecyclerListViewScrollToTopContext } from '@/navigation/RecyclerListViewScrollToTopContext'; import { useAccountSettings, useCoinListEdited, useCoinListEditOptions, useWallets } from '@/hooks'; import { useNavigation } from '@/navigation'; @@ -29,7 +29,7 @@ const dataProvider = new DataProvider((r1, r2) => { export type ExtendedState = { theme: any; nativeCurrencySymbol: string; - nativeCurrency: NativeCurrencyKey; + nativeCurrency: string; navigate: any; isCoinListEdited: boolean; hiddenCoins: BooleanMap; diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx index 9dd8a5ba71c..2378b7c6f5d 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx @@ -20,7 +20,7 @@ import { useAccountAccentColor } from '@/hooks/useAccountAccentColor'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const ProfileActionButtonsRowHeight = 80; @@ -191,7 +191,7 @@ function SwapButton() { android && delayNext(); - const mainnetEth = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.mainnet, address: accountAddress }); + const mainnetEth = await ethereumUtils.getNativeAssetForNetwork(ChainId.mainnet, accountAddress); navigate(Routes.EXCHANGE_MODAL, { fromDiscover: true, params: { diff --git a/src/components/avatar-builder/EmojiContent.tsx b/src/components/avatar-builder/EmojiContent.tsx index da1cafba6de..0e7d75f1aa8 100644 --- a/src/components/avatar-builder/EmojiContent.tsx +++ b/src/components/avatar-builder/EmojiContent.tsx @@ -18,10 +18,10 @@ const EmojiContent = ({ data, columns, onEmojiSelect, cellSize, fontSize }: Prop const { colors } = useTheme(); const categoryEmojis = useMemo(() => { - const categoryEmojis = []; + let categoryEmojis = []; for (let i = 0; i < data.length; i += columns) { - const rowContent = []; - const touchableNet = []; + let rowContent = []; + let touchableNet = []; for (let j = 0; j < columns; j++) { if (i + j < data.length) { rowContent.push(charFromEmojiObject(data[i + j].emoji)); diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupChooseProviderStep.tsx index 38325639704..d18dbeb68f5 100644 --- a/src/components/backup/BackupChooseProviderStep.tsx +++ b/src/components/backup/BackupChooseProviderStep.tsx @@ -55,10 +55,8 @@ export default function BackupSheetSectionNoProvider() { } }); } catch (e) { - logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), { - error: e, - }); Alert.alert(lang.t(lang.l.back_up.errors.no_account_found)); + logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -88,9 +86,7 @@ export default function BackupSheetSectionNoProvider() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey - ? (selectedWallet.addresses || [])[0].label - : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/BackupManuallyStep.tsx b/src/components/backup/BackupManuallyStep.tsx index da18d73806a..211eda2d196 100644 --- a/src/components/backup/BackupManuallyStep.tsx +++ b/src/components/backup/BackupManuallyStep.tsx @@ -20,9 +20,7 @@ export default function BackupManuallyStep() { const onManualBackup = async () => { const title = - selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey - ? (selectedWallet.addresses || [])[0].label - : selectedWallet.name; + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; goBack(); navigate(Routes.SETTINGS_SHEET, { diff --git a/src/components/backup/CloudBackupProvider.tsx b/src/components/backup/CloudBackupProvider.tsx index 377e9d13a83..4819f22bf6a 100644 --- a/src/components/backup/CloudBackupProvider.tsx +++ b/src/components/backup/CloudBackupProvider.tsx @@ -32,7 +32,7 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { setIsFetching(true); const isAvailable = await isCloudBackupAvailable(); if (!isAvailable) { - logger.debug('[CloudBackupProvider]: Cloud backup is not available'); + logger.log('Cloud backup is not available'); setIsFetching(false); return; } @@ -44,20 +44,20 @@ export function CloudBackupProvider({ children }: PropsWithChildren) { } } - logger.debug('[CloudBackupProvider]: Syncing with cloud'); + logger.log('Syncing with cloud'); await syncCloud(); - logger.debug('[CloudBackupProvider]: Fetching user data'); + logger.log('Fetching user data'); const userData = await fetchUserDataFromCloud(); setUserData(userData); - logger.debug('[CloudBackupProvider]: Fetching all backups'); + logger.log('Fetching all backups'); const backups = await fetchAllBackups(); - logger.debug(`[CloudBackupProvider]: Retrieved ${backups.files.length} backup files`); + logger.log(`Retrieved ${backups.files.length} backup files`); setBackups(backups); } catch (e) { - logger.error(new RainbowError('[CloudBackupProvider]: Failed to fetch all backups'), { + logger.error(new RainbowError('Failed to fetch all backups'), { error: e, }); } diff --git a/src/components/backup/RestoreCloudStep.tsx b/src/components/backup/RestoreCloudStep.tsx index e8bd83aa7a3..cfbeea0072e 100644 --- a/src/components/backup/RestoreCloudStep.tsx +++ b/src/components/backup/RestoreCloudStep.tsx @@ -151,15 +151,14 @@ export default function RestoreCloudStep() { filename = normalizeAndroidBackupFilename(filename); } - logger.debug('[RestoreCloudStep]: Done updating backup state'); + logger.info('Done updating backup state'); // NOTE: Marking the restored wallets as backed up // @ts-expect-error TypeScript doesn't play nicely with Redux types here const walletIdsToUpdate = Object.keys(newWalletsState || {}).filter(walletId => !(prevWalletsState || {})[walletId]); - - logger.debug('[RestoreCloudStep]: Updating backup state of wallets with ids', { + logger.log('updating backup state of wallets with ids', { walletIds: JSON.stringify(walletIdsToUpdate), }); - logger.debug('[RestoreCloudStep]: Selected backup name', { + logger.log('backupSelected.name', { fileName: selectedBackup.name, }); @@ -183,7 +182,7 @@ export default function RestoreCloudStep() { const walletKeys = Object.keys(newWalletsState || {}); // @ts-expect-error TypeScript doesn't play nicely with Redux types here const firstWallet = walletKeys.length > 0 ? (newWalletsState || {})[walletKeys[0]] : undefined; - const firstAddress = firstWallet ? (firstWallet.addresses || [])[0].address : undefined; + const firstAddress = firstWallet ? firstWallet.addresses[0].address : undefined; const p1 = dispatch(walletsSetSelected(firstWallet)); const p2 = dispatch(addressSetSelected(firstAddress)); await Promise.all([p1, p2]); diff --git a/src/components/cards/EthCard.tsx b/src/components/cards/EthCard.tsx index 693ff97b0f8..0e11d03b334 100644 --- a/src/components/cards/EthCard.tsx +++ b/src/components/cards/EthCard.tsx @@ -22,9 +22,10 @@ import * as i18n from '@/languages'; import { ButtonPressAnimationTouchEvent } from '@/components/animations/ButtonPressAnimation/types'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import assetTypes from '@/entities/assetTypes'; -import { Network, ChainId } from '@/networks/types'; +import { Network } from '@/networks/types'; import { getUniqueId } from '@/utils/ethereumUtils'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; +import { ChainId } from '@/__swaps__/types/chains'; export const ETH_CARD_HEIGHT = 284.3; @@ -44,7 +45,6 @@ export const EthCard = () => { ...externalEthAsset, address: ETH_ADDRESS, network: Network.mainnet, - chainId: ChainId.mainnet, uniqueId: getUniqueId(ETH_ADDRESS, ChainId.mainnet), }), [externalEthAsset] diff --git a/src/components/cards/FeaturedMintCard.tsx b/src/components/cards/FeaturedMintCard.tsx index b2738022f5c..1b6e2e1cf12 100644 --- a/src/components/cards/FeaturedMintCard.tsx +++ b/src/components/cards/FeaturedMintCard.tsx @@ -27,6 +27,7 @@ import { Media } from '../Media'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; +import { ethereumUtils } from '@/utils'; const IMAGE_SIZE = 111; @@ -73,7 +74,8 @@ export function FeaturedMintCard() { mintsLastHour: featuredMint.totalMints, priceInEth: convertRawAmountToRoundedDecimal(featuredMint.mintStatus.price, 18, 6), }); - navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, featuredMint.chainId); + const network = ethereumUtils.getNetworkFromChainId(featuredMint.chainId); + navigateToMintCollection(featuredMint.contract, featuredMint.mintStatus.price, network); } }, [featuredMint]); diff --git a/src/components/cards/MintsCard/CollectionCell.tsx b/src/components/cards/MintsCard/CollectionCell.tsx index 072057e5ce3..01053b1a852 100644 --- a/src/components/cards/MintsCard/CollectionCell.tsx +++ b/src/components/cards/MintsCard/CollectionCell.tsx @@ -6,7 +6,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { useTheme } from '@/theme'; import { View } from 'react-native'; import { MintableCollection } from '@/graphql/__generated__/arc'; -import { useNativeAsset } from '@/utils/ethereumUtils'; +import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { analyticsV2 } from '@/analytics'; import * as i18n from '@/languages'; import { IS_IOS } from '@/env'; @@ -58,7 +58,8 @@ export function CollectionCell({ collection }: { collection: MintableCollection priceInEth: amount, }); - navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); + const network = ethereumUtils.getNetworkFromChainId(collection.chainId); + navigateToMintCollection(collection.contract, collection.mintStatus.price, network); }, [amount, collection.chainId, collection.contract, collection.contractAddress, collection.mintStatus.price]); return ( diff --git a/src/components/cards/NFTOffersCard/Offer.tsx b/src/components/cards/NFTOffersCard/Offer.tsx index b20da3382f8..42bde292b39 100644 --- a/src/components/cards/NFTOffersCard/Offer.tsx +++ b/src/components/cards/NFTOffersCard/Offer.tsx @@ -23,7 +23,6 @@ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useAccountSettings } from '@/hooks'; import { Network } from '@/networks/types'; import { ethereumUtils } from '@/utils'; -import { AddressOrEth } from '@/__swaps__/types/assets'; const TWO_HOURS_MS = 2 * 60 * 60 * 1000; export const CELL_HORIZONTAL_PADDING = 7; @@ -70,7 +69,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { const { nativeCurrency } = useAccountSettings(); const offerChainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address as AddressOrEth, + address: offer.paymentToken.address, chainId: offerChainId, currency: nativeCurrency, }); @@ -141,7 +140,7 @@ export const Offer = ({ offer }: { offer: NftOffer }) => { default: secondaryTextColor = 'labelTertiary'; secondaryText = ''; - logger.error(new RainbowError('[NFTOffersCard]: invalid sort criterion')); + logger.error(new RainbowError('NFTOffersCard: invalid sort criterion')); break; } diff --git a/src/components/cards/OpRewardsCard.tsx b/src/components/cards/OpRewardsCard.tsx index 89a307ff16e..d53a421d187 100644 --- a/src/components/cards/OpRewardsCard.tsx +++ b/src/components/cards/OpRewardsCard.tsx @@ -8,7 +8,6 @@ import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; -import { ChainId } from '@/networks/types'; const GRADIENT: Gradient = { colors: ['#520907', '#B22824'], @@ -24,7 +23,7 @@ export const OpRewardsCard: React.FC = () => { }; return ( - + { const wallet = allWallets[key]; - const filteredAccounts = (wallet.addresses || []).filter((account: any) => account.visible); + const filteredAccounts = wallet.addresses.filter((account: any) => account.visible); filteredAccounts.forEach((account: any) => { const row = { ...account, @@ -138,7 +138,7 @@ export default function WalletList({ isReadOnly: wallet.type === WalletTypes.readOnly, isLedger: wallet.type === WalletTypes.bluetooth, isSelected: accountAddress === account.address && (watchOnly || wallet?.id === currentWallet?.id), - label: network !== Network.mainnet && account.ens === account.label ? address(account.address, 6, 4) : account.label, + label: network !== networkTypes.mainnet && account.ens === account.label ? address(account.address, 6, 4) : account.label, onPress: () => onChangeAccount(wallet?.id, account.address), rowType: RowTypes.ADDRESS, walletId: wallet?.id, diff --git a/src/components/coin-icon/ChainBadge.js b/src/components/coin-icon/ChainBadge.js index b5d25a2f67f..19cce6700b8 100644 --- a/src/components/coin-icon/ChainBadge.js +++ b/src/components/coin-icon/ChainBadge.js @@ -40,7 +40,8 @@ import { Centered } from '../layout'; import styled from '@/styled-thing'; import { position as positions } from '@/styles'; import { ChainBadgeSizeConfigs } from '@/components/coin-icon/ChainBadgeSizeConfigs'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const ChainIcon = styled(FastImage)({ height: ({ containerSize }) => containerSize, diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx index 1b2184ceb56..844b96a2d18 100644 --- a/src/components/coin-icon/ChainImage.tsx +++ b/src/components/coin-icon/ChainImage.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/helpers'; import ArbitrumBadge from '@/assets/badges/arbitrum.png'; import BaseBadge from '@/assets/badges/base.png'; @@ -12,6 +12,7 @@ import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; import FastImage, { Source } from 'react-native-fast-image'; +import { ChainId } from '@/__swaps__/types/chains'; export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) { const source = useMemo(() => { diff --git a/src/components/coin-icon/EthCoinIcon.tsx b/src/components/coin-icon/EthCoinIcon.tsx index 53346a7afd0..4207a9e6333 100644 --- a/src/components/coin-icon/EthCoinIcon.tsx +++ b/src/components/coin-icon/EthCoinIcon.tsx @@ -3,7 +3,7 @@ import { useTheme } from '@/theme'; import { useNativeAsset } from '@/utils/ethereumUtils'; import RainbowCoinIcon from './RainbowCoinIcon'; import { ETH_SYMBOL } from '@/references'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; type EthCoinIconProps = { size?: number; diff --git a/src/components/coin-icon/RainbowCoinIcon.tsx b/src/components/coin-icon/RainbowCoinIcon.tsx index d01e0e78b42..bcd086c3f39 100644 --- a/src/components/coin-icon/RainbowCoinIcon.tsx +++ b/src/components/coin-icon/RainbowCoinIcon.tsx @@ -7,7 +7,7 @@ import { FallbackIcon as CoinIconTextFallback } from '@/utils'; import { FastFallbackCoinIconImage } from '../asset-list/RecyclerAssetList2/FastComponents/FastFallbackCoinIconImage'; import { FastChainBadge } from '../asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const fallbackTextStyles = { fontFamily: fonts.family.SFProRounded, diff --git a/src/components/coin-icon/RequestVendorLogoIcon.js b/src/components/coin-icon/RequestVendorLogoIcon.js index cedf992c177..d858f7522dd 100644 --- a/src/components/coin-icon/RequestVendorLogoIcon.js +++ b/src/components/coin-icon/RequestVendorLogoIcon.js @@ -1,7 +1,7 @@ import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { useTheme } from '../../theme/ThemeContext'; -import { initials } from '../../utils'; +import { ethereumUtils, initials } from '../../utils'; import ChainBadge from './ChainBadge'; import { Centered } from '../layout'; import { Text } from '../text'; @@ -33,7 +33,7 @@ export default function RequestVendorLogoIcon({ shouldPrioritizeImageLoading, showLargeShadow, size = CoinIconSize, - chainId, + network, ...props }) { const [error, setError] = useState(null); @@ -71,7 +71,7 @@ export default function RequestVendorLogoIcon({ )} - + ); } diff --git a/src/components/coin-icon/TwoCoinsIcon.tsx b/src/components/coin-icon/TwoCoinsIcon.tsx index d6827ad1942..a6537bb131c 100644 --- a/src/components/coin-icon/TwoCoinsIcon.tsx +++ b/src/components/coin-icon/TwoCoinsIcon.tsx @@ -4,7 +4,7 @@ import { ParsedAddressAsset } from '@/entities'; import { useTheme } from '@/theme'; import ChainBadge from './ChainBadge'; import RainbowCoinIcon from './RainbowCoinIcon'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export function TwoCoinsIcon({ size = 45, diff --git a/src/components/coin-row/CoinRowInfoButton.js b/src/components/coin-row/CoinRowInfoButton.js index 59ad34022ad..63a58a9aa5b 100644 --- a/src/components/coin-row/CoinRowInfoButton.js +++ b/src/components/coin-row/CoinRowInfoButton.js @@ -93,7 +93,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ); const onPressAndroid = useCallback(() => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(item?.chainId))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -115,7 +115,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) }, [item, handleCopyContractAddress]); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(item?.chainId); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(item?.network)); return { menuItems: [ blockExplorerAction, @@ -126,7 +126,7 @@ const CoinRowInfoButton = ({ item, onCopySwapDetailsText, showFavoriteButton }) ], menuTitle: `${item?.name} (${item?.symbol})`, }; - }, [item?.address, item?.chainId, item?.name, item?.symbol]); + }, [item?.address, item?.name, item?.network, item?.symbol]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index 5adaba45b02..9ee6cb3bb10 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -10,7 +10,7 @@ import Routes from '@rainbow-me/routes'; import { ImgixImage } from '../images'; import { CardSize } from '../unique-token/CardSize'; import { ChainBadge } from '../coin-icon'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/networks/types'; import { address } from '@/utils/abbreviations'; import { TransactionType } from '@/resources/transactions/types'; import { @@ -26,6 +26,7 @@ import Spinner from '../Spinner'; import * as lang from '@/languages'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { checkForPendingSwap } from '@/screens/transaction-details/helpers/checkForPendingSwap'; +import { ChainId } from '@/__swaps__/types/chains'; export const getApprovalLabel = ({ approvalAmount, asset, type }: Pick) => { if (!approvalAmount || !asset) return; @@ -407,7 +408,7 @@ export default React.memo(function TransactionCoinRow({ const [topValue] = activityValues(item, nativeCurrency) ?? []; return ( - + diff --git a/src/components/coin-row/SendCoinRow.js b/src/components/coin-row/SendCoinRow.js index 8a0515f2422..4795baafa14 100644 --- a/src/components/coin-row/SendCoinRow.js +++ b/src/components/coin-row/SendCoinRow.js @@ -3,7 +3,7 @@ import { TouchableWithoutFeedback } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { buildAssetUniqueIdentifier } from '../../helpers/assets'; import { useTheme } from '../../theme/ThemeContext'; -import { deviceUtils } from '../../utils'; +import { deviceUtils, ethereumUtils } from '../../utils'; import { ButtonPressAnimation } from '../animations'; import { Text } from '../text'; import CoinName from './CoinName'; @@ -105,7 +105,9 @@ const SendCoinRow = ({ const Wrapper = disablePressAnimation ? TouchableWithoutFeedback : ButtonPressAnimation; - const isL2 = useMemo(() => isL2Chain({ chainId: item?.chainId }), [item?.chainId]); + const isL2 = useMemo(() => { + return isL2Chain({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }); + }, [item?.network]); const containerSelectedStyles = { height: selectedHeight, diff --git a/src/components/contacts/ContactRow.js b/src/components/contacts/ContactRow.js index e52b5f8251e..b213d33dd23 100644 --- a/src/components/contacts/ContactRow.js +++ b/src/components/contacts/ContactRow.js @@ -58,7 +58,7 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const { width: deviceWidth } = useDimensions(); const { onAddOrUpdateContacts } = useContacts(); const { colors } = useTheme(); - const { accountType, balances, ens, image, label, onPress, showcaseItem, testID } = props; + const { accountType, balances, ens, image, label, network, onPress, showcaseItem, testID } = props; const balanceText = balances ? balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); @@ -80,12 +80,12 @@ const ContactRow = ({ address, color, nickname, symmetricalMargins, ...props }, const name = await fetchReverseRecord(address); if (name !== ensName) { setENSName(name); - onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, name); + onAddOrUpdateContacts(address, name && isENSAddressFormat(nickname) ? name : nickname, color, network, name); } }; fetchENSName(); } - }, [accountType, onAddOrUpdateContacts, address, color, ensName, nickname, profilesEnabled, setENSName]); + }, [accountType, onAddOrUpdateContacts, address, color, ensName, network, nickname, profilesEnabled, setENSName]); let cleanedUpLabel = null; if (label) { diff --git a/src/components/context-menu-buttons/ChainContextMenu.tsx b/src/components/context-menu-buttons/ChainContextMenu.tsx index 5f79b99c36e..09828c27eb0 100644 --- a/src/components/context-menu-buttons/ChainContextMenu.tsx +++ b/src/components/context-menu-buttons/ChainContextMenu.tsx @@ -3,7 +3,7 @@ import { ChainImage } from '@/components/coin-icon/ChainImage'; import { ContextMenuButton } from '@/components/context-menu'; import { Bleed, Box, Inline, Text, TextProps } from '@/design-system'; import * as i18n from '@/languages'; -import { ChainId, ChainNameDisplay } from '@/networks/types'; +import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; import { showActionSheetWithOptions } from '@/utils'; import { userAssetsStore } from '@/state/assets/userAssets'; import { chainNameForChainIdWithMainnetSubstitution } from '@/__swaps__/utils/chains'; diff --git a/src/components/discover/DiscoverSearchInput.js b/src/components/discover/DiscoverSearchInput.js index 99913ebfef1..b7284d3c7f2 100644 --- a/src/components/discover/DiscoverSearchInput.js +++ b/src/components/discover/DiscoverSearchInput.js @@ -11,9 +11,9 @@ import { analytics } from '@/analytics'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { margin, padding } from '@/styles'; -import { deviceUtils } from '@/utils'; +import { deviceUtils, ethereumUtils } from '@/utils'; import DiscoverSheetContext from '@/screens/discover/DiscoverScreenContext'; -import { chainIdToNameMapping } from '@/networks/types'; +import { getNetworkObj } from '@/networks'; export const ExchangeSearchHeight = 40; const ExchangeSearchWidth = deviceUtils.dimensions.width - 30; @@ -131,8 +131,9 @@ const ExchangeSearch = ( const placeholder = useMemo(() => { if (!currentChainId) return placeholderText; + const network = getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)); return lang.t('button.exchange_search_placeholder_network', { - network: chainIdToNameMapping[currentChainId], + network: network.name, }); }, [currentChainId, placeholderText]); diff --git a/src/components/ens-profile/ActionButtons/ActionButtons.tsx b/src/components/ens-profile/ActionButtons/ActionButtons.tsx index 87ae91aabac..bf10fe77235 100644 --- a/src/components/ens-profile/ActionButtons/ActionButtons.tsx +++ b/src/components/ens-profile/ActionButtons/ActionButtons.tsx @@ -19,7 +19,7 @@ export default function ActionButtons({ const isOwner = useMemo(() => { return Object.values(wallets || {}).some( - (wallet: any) => wallet.type !== 'readOnly' && (wallet.addresses || []).some(({ address }: any) => address === primaryAddress) + (wallet: any) => wallet.type !== 'readOnly' && wallet.addresses.some(({ address }: any) => address === primaryAddress) ); }, [primaryAddress, wallets]); diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index dd695ea4e9e..d8bd9130d39 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -12,7 +12,7 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils } from '@/utils'; import { formatAddressForDisplay } from '@/utils/abbreviations'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -34,9 +34,9 @@ export default function MoreButton({ address, ensName }: { address?: string; ens params: { setIsSearchModeEnabled }, } = useRoute(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); + const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); - return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); + return visibleWallet.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; @@ -112,7 +112,7 @@ export default function MoreButton({ address, ensName }: { address?: string; ens setClipboard(address!); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer({ address, chainId: ChainId.mainnet }); + ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/components/error-boundary/Fallback.tsx b/src/components/error-boundary/Fallback.tsx index 0ca291fddeb..ad57442ebc3 100644 --- a/src/components/error-boundary/Fallback.tsx +++ b/src/components/error-boundary/Fallback.tsx @@ -36,7 +36,7 @@ export default function Fallback({ resetError: () => void; }) { const handleRestart = () => { - logger.error(new RainbowError('[ErrorBoundary]: RainbowAppRestartFromErrorBoundary'), { + logger.error(new RainbowError('RainbowAppRestartFromErrorBoundary'), { data: { error: error.toString(), componentStack, diff --git a/src/components/exchange/ConfirmExchangeButton.js b/src/components/exchange/ConfirmExchangeButton.js index 455517866ee..05b438e0acd 100644 --- a/src/components/exchange/ConfirmExchangeButton.js +++ b/src/components/exchange/ConfirmExchangeButton.js @@ -14,12 +14,12 @@ import Routes from '@/navigation/routesNames'; import { lightModeThemeColors } from '@/styles'; import { useTheme } from '@/theme'; import handleSwapErrorCodes from '@/utils/exchangeErrorCodes'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj } from '@/networks'; const NOOP = () => null; export default function ConfirmExchangeButton({ - chainId, + currentNetwork, disabled, loading, isHighPriceImpact, @@ -95,7 +95,7 @@ export default function ConfirmExchangeButton({ label = lang.t('button.confirm_exchange.insufficient_funds'); } else if (isSufficientGas != null && !isSufficientGas) { label = lang.t('button.confirm_exchange.insufficient_token', { - tokenName: getNetworkObject({ chainId }).nativeCurrency.symbol, + tokenName: getNetworkObj(currentNetwork).nativeCurrency.symbol, }); } else if (!isValidGas && isGasReady) { label = lang.t('button.confirm_exchange.invalid_fee'); diff --git a/src/components/exchange/ExchangeAssetList.tsx b/src/components/exchange/ExchangeAssetList.tsx index ff7da7c343e..4fd91c50b27 100644 --- a/src/components/exchange/ExchangeAssetList.tsx +++ b/src/components/exchange/ExchangeAssetList.tsx @@ -201,6 +201,7 @@ const ExchangeAssetList: ForwardRefRenderFunction items.map(({ data, ...item }) => ({ diff --git a/src/components/exchange/ExchangeField.tsx b/src/components/exchange/ExchangeField.tsx index f72ed6fe138..a31dc8e749b 100644 --- a/src/components/exchange/ExchangeField.tsx +++ b/src/components/exchange/ExchangeField.tsx @@ -4,13 +4,14 @@ import { TokenSelectionButton } from '../buttons'; import { ChainBadge, CoinIconSize } from '../coin-icon'; import { EnDash } from '../text'; import ExchangeInput from './ExchangeInput'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/helpers'; import styled from '@/styled-thing'; import { borders } from '@/styles'; import { useTheme } from '@/theme'; import { AccentColorProvider, Box, Space } from '@/design-system'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; const ExchangeFieldHeight = android ? 64 : 38; const ExchangeFieldPadding: Space = android ? '15px (Deprecated)' : '19px (Deprecated)'; diff --git a/src/components/exchange/ExchangeInputField.tsx b/src/components/exchange/ExchangeInputField.tsx index 438955417c8..490cc202a04 100644 --- a/src/components/exchange/ExchangeInputField.tsx +++ b/src/components/exchange/ExchangeInputField.tsx @@ -4,9 +4,10 @@ import { ColumnWithMargins, Row } from '../layout'; import ExchangeField from './ExchangeField'; import ExchangeMaxButton from './ExchangeMaxButton'; import ExchangeNativeField from './ExchangeNativeField'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/helpers'; import styled from '@/styled-thing'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; const Container = styled(ColumnWithMargins).attrs({ margin: 5 })({ paddingTop: android ? 0 : 6, diff --git a/src/components/exchange/ExchangeOutputField.tsx b/src/components/exchange/ExchangeOutputField.tsx index 0ada42075e3..479862c16f6 100644 --- a/src/components/exchange/ExchangeOutputField.tsx +++ b/src/components/exchange/ExchangeOutputField.tsx @@ -3,7 +3,7 @@ import { TextInput } from 'react-native'; import ExchangeField from './ExchangeField'; import { Box } from '@rainbow-me/design-system'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; interface ExchangeOutputFieldProps { color: string; diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index 5a1f9254bf5..b1280c79fcc 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -9,8 +9,7 @@ import { IS_IOS } from '@/env'; import { FavStar, Info } from '../asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow'; import { View } from 'react-native'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; -import { ChainId } from '@/networks/types'; -import { ParsedAddressAsset } from '@/entities'; +import { ChainId } from '@/__swaps__/types/chains'; interface ExchangeTokenRowProps { item: any; @@ -49,6 +48,7 @@ export default React.memo(function ExchangeTokenRow({ {name ?? item?.name} - {showBalance && (item as ParsedAddressAsset)?.balance?.display && ( + {showBalance && item?.balance?.display && ( - {(item as ParsedAddressAsset)?.balance?.display ?? ''} + {item?.balance?.display ?? ''} )} {!showBalance && ( @@ -92,7 +92,7 @@ export default React.memo(function ExchangeTokenRow({ {showBalance && ( - {(item as ParsedAddressAsset)?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} + {item?.native?.balance?.display ?? `${nativeCurrencySymbol}0.00`} )} diff --git a/src/components/exchange/NetworkSwitcher.js b/src/components/exchange/NetworkSwitcher.js index e9b3a1812d7..44870fb6286 100644 --- a/src/components/exchange/NetworkSwitcher.js +++ b/src/components/exchange/NetworkSwitcher.js @@ -8,12 +8,11 @@ import { Column, Row } from '../layout'; import { Text } from '../text'; import { padding, position } from '@/styles'; import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks, getNetworkObj } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { chainIdToNameMapping } from '@/networks/types'; const networkMenuItems = () => { - return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => ({ + return RainbowNetworks.filter(network => network.features.swaps).map(network => ({ actionKey: network.value, actionTitle: network.name, icon: { @@ -23,7 +22,7 @@ const networkMenuItems = () => { })); }; const androidNetworkMenuItems = () => { - return RainbowNetworkObjects.filter(network => network.features.swaps).map(network => network.name); + return RainbowNetworks.filter(network => network.features.swaps).map(network => network.name); }; const NetworkSwitcherv1 = ({ @@ -94,7 +93,7 @@ const NetworkSwitcherv1 = ({ weight={prominent ? 'heavy' : 'bold'} > {lang.t('expanded_state.swap.network_switcher', { - network: chainIdToNameMapping[currentChainId], + network: getNetworkObj(ethereumUtils.getNetworkFromChainId(currentChainId)).name, })} diff --git a/src/components/exchange/NetworkSwitcherv2.tsx b/src/components/exchange/NetworkSwitcherv2.tsx index a16693c7cbb..2ee33c53529 100644 --- a/src/components/exchange/NetworkSwitcherv2.tsx +++ b/src/components/exchange/NetworkSwitcherv2.tsx @@ -4,11 +4,11 @@ import RadialGradient from 'react-native-radial-gradient'; import { ButtonPressAnimation } from '../animations'; import ChainBadge from '../coin-icon/ChainBadge'; import { Bleed, Box, Columns, Inline, Text } from '@/design-system'; +import { Network } from '@/helpers'; import { position } from '@rainbow-me/styles'; import { useTheme } from '@/theme'; import { sortNetworks } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/networks/types'; const NetworkSwitcherv2 = ({ currentChainId, @@ -31,10 +31,10 @@ const NetworkSwitcherv2 = ({ })); }, []); - const radialGradientProps = (chainId: ChainId) => { + const radialGradientProps = (network: Network) => { return { center: [0, 1], - colors: [colors.alpha(colors.networkColors[chainId], 0.1), colors.alpha(colors.networkColors[chainId], 0.02)], + colors: [colors.alpha(colors.networkColors[network], 0.1), colors.alpha(colors.networkColors[network], 0.02)], pointerEvents: 'none', style: { ...position.coverAsObject, @@ -55,7 +55,7 @@ const NetworkSwitcherv2 = ({ testID={'network-switcher-scroll-view'} > - {networkMenuItems.map(({ chainId, title }) => { + {networkMenuItems.map(({ chainId, title, network }) => { const isSelected = currentChainId === chainId; return ( setCurrentChainId(chainId)} padding="8px" - testID={`${testID}-${chainId}`} + testID={`${testID}-${network}`} > {isSelected && ( )} - {chainId === ChainId.mainnet ? ( + {network === Network.mainnet ? ( ) : ( )} {title} diff --git a/src/components/exchange/exchangeAssetRowContextMenuProps.ts b/src/components/exchange/exchangeAssetRowContextMenuProps.ts index e1de9563887..57d0c2dc12f 100644 --- a/src/components/exchange/exchangeAssetRowContextMenuProps.ts +++ b/src/components/exchange/exchangeAssetRowContextMenuProps.ts @@ -3,11 +3,11 @@ import { startCase } from 'lodash'; import { NativeSyntheticEvent } from 'react-native'; import { setClipboard } from '../../hooks/useClipboard'; import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const buildBlockExplorerAction = (chainId: ChainId) => { const blockExplorerText = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { actionKey: CoinRowActionsEnum.blockExplorer, @@ -43,7 +43,7 @@ export default function contextMenuProps(item: any, onCopySwapDetailsText: (addr }; const onPressAndroid = () => { - const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer({ chainId: ethereumUtils.getChainIdFromNetwork(item?.network) }))}`; + const blockExplorerText = `View on ${startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(item?.network)))}`; const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( diff --git a/src/components/expanded-state/AvailableNetworks.js b/src/components/expanded-state/AvailableNetworks.js index f6e96c439ab..72cba5b9315 100644 --- a/src/components/expanded-state/AvailableNetworks.js +++ b/src/components/expanded-state/AvailableNetworks.js @@ -3,9 +3,11 @@ import React from 'react'; import { Linking } from 'react-native'; import RadialGradient from 'react-native-radial-gradient'; import { Box } from '@/design-system'; +import networkInfo from '@/helpers/networkInfo'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { padding, position } from '@/styles'; +import { ethereumUtils } from '@/utils'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import { Column, Row } from '../layout'; @@ -13,11 +15,11 @@ import { ChainBadge } from '../coin-icon'; import Divider from '../Divider'; import { Text } from '../text'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, marginHorizontal = 19, prominent }) => { const { colors } = useTheme(); const { navigate } = useNavigation(); + const radialGradientProps = { center: [0, 1], colors: colors.gradients.lightGreyWhite, @@ -28,7 +30,9 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, }, }; - const availableChainIds = Object.keys(networks).map(network => Number(network)); + const availableNetworks = Object.keys(networks).map(network => { + return ethereumUtils.getNetworkFromChainId(Number(network)); + }); const linkToHop = useCallback(() => { Linking.openURL('https://app.hop.exchange/#/send'); @@ -36,12 +40,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, const handleAvailableNetworksPress = useCallback(() => { navigate(Routes.EXPLAIN_SHEET, { - chainIds: availableChainIds, + networks: availableNetworks, onClose: linkToHop, tokenSymbol: asset.symbol, type: 'availableNetworks', }); - }, [navigate, availableChainIds, linkToHop, asset.symbol]); + }, [navigate, availableNetworks, linkToHop, asset.symbol]); return ( <> @@ -49,12 +53,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, - {availableChainIds?.map((chainId, index) => { + {availableNetworks?.map((network, index) => { return ( - {chainId !== ChainId.mainnet ? ( - + {network !== 'mainnet' ? ( + ) : ( )} @@ -81,12 +85,12 @@ const AvailableNetworksv1 = ({ asset, networks, hideDivider, marginBottom = 24, size="smedium" weight={prominent ? 'heavy' : 'bold'} > - {availableChainIds?.length > 1 + {availableNetworks?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableChainIds?.length, + availableNetworks: availableNetworks?.length, }) : lang.t('expanded_state.asset.available_network', { - availableNetwork: chainIdToNameMapping[availableChainIds[0]]?.name, + availableNetwork: networkInfo[availableNetworks?.[0]]?.name, })} diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index b1345505800..496a8bacd3a 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -8,14 +8,14 @@ import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { position } from '@/styles'; import { ethereumUtils, watchingAlert } from '@/utils'; -import { CurrencySelectionTypes, ExchangeModalTypes } from '@/helpers'; +import { CurrencySelectionTypes, ExchangeModalTypes, Network } from '@/helpers'; import { useSwapCurrencyHandlers, useWallets } from '@/hooks'; import { RainbowToken } from '@/entities'; import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { implementation } from '@/entities/dispersion'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks, getNetworkObj } from '@/networks'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { SWAPS_V2, enableActionsOnReadOnlyWallet, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; @@ -25,8 +25,7 @@ import { AddressOrEth, AssetType } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { swapsStore } from '@/state/swaps/swapsStore'; import { InteractionManager } from 'react-native'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; -import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; const NOOP = () => null; @@ -57,11 +56,11 @@ const AvailableNetworksv2 = ({ }, }; - const availableChainIds = useMemo(() => { + const availableNetworks = useMemo(() => { // we dont want to show mainnet return Object.keys(networks) - .filter(chainId => Number(chainId) !== ChainId.mainnet) - .map(chainId => Number(chainId)); + .map(network => ethereumUtils.getNetworkFromChainId(Number(network))) + .filter(network => network !== Network.mainnet); }, [networks]); const { updateInputCurrency } = useSwapCurrencyHandlers({ @@ -69,7 +68,7 @@ const AvailableNetworksv2 = ({ type: ExchangeModalTypes.swap, }); const convertAssetAndNavigate = useCallback( - (chainId: ChainId) => { + (chosenNetwork: Network) => { if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return; @@ -78,14 +77,15 @@ const AvailableNetworksv2 = ({ const newAsset = asset; // we need to convert the mainnet asset to the selected network's - newAsset.mainnet_address = networks?.[ChainId.mainnet]?.address ?? asset.address; - newAsset.address = networks?.[chainId].address; - newAsset.chainId = chainId; + newAsset.mainnet_address = networks?.[ethereumUtils.getChainIdFromNetwork(Network.mainnet)]?.address ?? asset.address; + newAsset.address = networks?.[ethereumUtils.getChainIdFromNetwork(chosenNetwork)].address; + newAsset.network = chosenNetwork; goBack(); if (swapsV2Enabled || swaps_v2) { - const uniqueId = `${newAsset.address}_${asset.chainId}`; + const chainId = ethereumUtils.getChainIdFromNetwork(newAsset.network); + const uniqueId = `${newAsset.address}_${chainId}`; const userAsset = userAssetsStore.getState().userAssets.get(uniqueId); const parsedAsset = parseSearchAsset({ @@ -94,16 +94,16 @@ const AvailableNetworksv2 = ({ uniqueId, address: newAsset.address as AddressOrEth, type: newAsset.type as AssetType, - chainId: asset.chainId, - chainName: chainNameFromChainId(asset.chainId), + chainId, + chainName: chainNameFromChainId(chainId), isNativeAsset: false, native: {}, }, searchAsset: { ...newAsset, uniqueId, - chainId: asset.chainId, - chainName: chainNameFromChainId(asset.chainId), + chainId, + chainName: chainNameFromChainId(chainId), address: newAsset.address as AddressOrEth, highLiquidity: newAsset.highLiquidity ?? false, isRainbowCurated: newAsset.isRainbowCurated ?? false, @@ -118,7 +118,7 @@ const AvailableNetworksv2 = ({ const largestBalanceSameChainUserAsset = userAssetsStore .getState() .getUserAssets() - .find(userAsset => userAsset.chainId === asset.chainId && userAsset.address !== newAsset.address); + .find(userAsset => userAsset.chainId === chainId && userAsset.address !== newAsset.address); if (largestBalanceSameChainUserAsset) { swapsStore.setState({ inputAsset: largestBalanceSameChainUserAsset }); } else { @@ -133,8 +133,8 @@ const AvailableNetworksv2 = ({ return; } - newAsset.uniqueId = getUniqueId(asset.address, chainId); - newAsset.type = ethereumUtils.getNetworkFromChainId(chainId); + newAsset.uniqueId = `${asset.address}_${chosenNetwork}`; + newAsset.type = chosenNetwork; navigate(Routes.EXCHANGE_MODAL, { params: { @@ -151,35 +151,37 @@ const AvailableNetworksv2 = ({ screen: Routes.CURRENCY_SELECT_SCREEN, }); }, - [asset, goBack, isReadOnlyWallet, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] + [asset, goBack, navigate, networks, swapsV2Enabled, swaps_v2, updateInputCurrency] ); const handlePressContextMenu = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly - ({ nativeEvent: { actionKey: chainId } }) => { - convertAssetAndNavigate(chainId); + ({ nativeEvent: { actionKey: network } }) => { + convertAssetAndNavigate(network); }, [convertAssetAndNavigate] ); const handlePressButton = useCallback(() => { - convertAssetAndNavigate(availableChainIds[0]); - }, [availableChainIds, convertAssetAndNavigate]); + convertAssetAndNavigate(availableNetworks[0]); + }, [availableNetworks, convertAssetAndNavigate]); const networkMenuItems = useMemo(() => { - return RainbowNetworkObjects.filter(({ features, id }) => features.swaps && id !== ChainId.mainnet && !!networks[id]).map(network => ({ - actionKey: `${network.id}`, - actionTitle: network.name, - icon: { - iconType: 'ASSET', - iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, - }, - })); + return RainbowNetworks.filter(({ features, value, id }) => features.swaps && value !== Network.mainnet && !!networks[id]).map( + network => ({ + actionKey: network.value, + actionTitle: network.name, + icon: { + iconType: 'ASSET', + iconValue: `${network.networkType === 'layer2' ? `${network.value}BadgeNoShadow` : 'ethereumBadge'}`, + }, + }) + ); }, [networks]); - const MenuWrapper = availableChainIds.length > 1 ? ContextMenuButton : Box; + const MenuWrapper = availableNetworks.length > 1 ? ContextMenuButton : Box; - if (availableChainIds.length === 0) return null; + if (availableNetworks.length === 0) return null; return ( <> @@ -206,15 +208,16 @@ const AvailableNetworksv2 = ({ - {availableChainIds?.map((chainId, index) => { + {availableNetworks?.map((network, index) => { + const chainId = ethereumUtils.getChainIdFromNetwork(network); return ( @@ -230,18 +233,18 @@ const AvailableNetworksv2 = ({ - {availableChainIds?.length > 1 + {availableNetworks?.length > 1 ? lang.t('expanded_state.asset.available_networks', { - availableNetworks: availableChainIds?.length, + availableNetworks: availableNetworks?.length, }) : lang.t('expanded_state.asset.available_networkv2', { - availableNetwork: chainIdToNameMapping[availableChainIds[0]], + availableNetwork: getNetworkObj(availableNetworks?.[0])?.name, })} - {availableChainIds?.length > 1 ? '􀁱' : '􀯻'} + {availableNetworks?.length > 1 ? '􀁱' : '􀯻'} diff --git a/src/components/expanded-state/ContactProfileState.js b/src/components/expanded-state/ContactProfileState.js index 9a895e6dedc..b6bde10d0de 100644 --- a/src/components/expanded-state/ContactProfileState.js +++ b/src/components/expanded-state/ContactProfileState.js @@ -7,7 +7,7 @@ import { magicMemo } from '../../utils'; import ProfileModal from './profile/ProfileModal'; import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; -import { useContacts, useENSAvatar } from '@/hooks'; +import { useAccountSettings, useContacts, useENSAvatar } from '@/hooks'; import { addressHashedColorIndex, addressHashedEmoji } from '@/utils/profileUtils'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; @@ -24,14 +24,16 @@ const ContactProfileState = ({ address, color, contact, ens, nickname }) => { const colorIndex = useMemo(() => color || addressHashedColorIndex(address) || 0, [address, color]); + const { network } = useAccountSettings(); + const handleAddContact = useCallback(() => { const nickname = profilesEnabled ? value : (emoji ? `${emoji} ${value}` : value).trim(); if (value?.length > 0) { - onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], ens); + onAddOrUpdateContacts(address, nickname, colors.avatarBackgrounds[colorIndex || 0], network, ens); goBack(); } android && Keyboard.dismiss(); - }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, onAddOrUpdateContacts, profilesEnabled, value]); + }, [address, colorIndex, colors.avatarBackgrounds, emoji, ens, goBack, network, onAddOrUpdateContacts, profilesEnabled, value]); const handleCancel = useCallback(() => { goBack(); diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index d19af128e10..eed7e54ba95 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -40,7 +40,7 @@ export default function CustomGasState({ asset }) { const { height: deviceHeight } = useDimensions(); const keyboardHeight = useKeyboardHeight(); const colorForAsset = useColorForAsset(asset || {}, fallbackColor, false, true); - const { selectedGasFee, currentBlockParams, chainId } = useGas(); + const { selectedGasFee, currentBlockParams, txNetwork } = useGas(); const [canGoBack, setCanGoBack] = useState(true); const { tradeDetails } = useSelector(state => state.swap); @@ -93,7 +93,7 @@ export default function CustomGasState({ asset }) { { - switch (chainId) { - case ChainId.mainnet: +const getIsSupportedOnRainbowWeb = (network: Network) => { + switch (network) { + case Network.mainnet: return true; default: return false; @@ -251,7 +251,7 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE [offer] ); - const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.chainId); + const isSupportedOnRainbowWeb = getIsSupportedOnRainbowWeb(asset.network); const [isRefreshMetadataToastActive, setIsRefreshMetadataToastActive] = useState(false); const [isReportSpamToastActive, setIsReportSpamToastActive] = useState(false); @@ -548,10 +548,10 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE /> )} - {asset.chainId !== ChainId.mainnet ? ( + {asset.network !== Network.mainnet ? ( // @ts-expect-error JavaScript component isL2Chain({ chainId: asset?.chainId }), [asset?.chainId]); - const isTestnet = isTestnetChain({ chainId: currentChainId }); + const isL2 = useMemo(() => isL2Chain({ chainId: assetChainId }), [assetChainId]); + const isTestnet = isTestnetNetwork(currentNetwork); const { data, isLoading: additionalAssetDataLoading } = useAdditionalAssetData({ address: asset?.address, - chainId: asset?.chainId, + network: asset?.network, currency: nativeCurrency, }); @@ -235,13 +235,12 @@ export default function ChartExpandedState({ asset }) { const { colors } = useTheme(); const crosschainEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); - const AvailableNetworks = !crosschainEnabled ? AvailableNetworksv1 : AvailableNetworksv2; - const assetChainId = assetWithPrice.chainId; + const assetNetwork = assetWithPrice.network; const { swagg_enabled, f2c_enabled } = useRemoteConfig(); - const swapEnabled = swagg_enabled && getNetworkObject({ chainId: assetChainId }).features.swaps; + const swapEnabled = swagg_enabled && getNetworkObj(assetNetwork).features.swaps; const addCashEnabled = f2c_enabled; const format = useCallback( @@ -315,7 +314,7 @@ export default function ChartExpandedState({ asset }) { ) : null} {!data?.networks && isL2 && ( - + )} {data?.networks && !hasBalance && ( @@ -376,7 +375,7 @@ export default function ChartExpandedState({ asset }) { isNativeAsset={assetWithPrice?.isNativeAsset} links={data?.links} marginTop={!delayedDescriptions && 19} - chainId={asset?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(asset?.network)} /> diff --git a/src/components/expanded-state/asset/SocialLinks.js b/src/components/expanded-state/asset/SocialLinks.js index f5199f6480d..79d2706a75a 100644 --- a/src/components/expanded-state/asset/SocialLinks.js +++ b/src/components/expanded-state/asset/SocialLinks.js @@ -28,8 +28,8 @@ const CommunityLink = styled(Link).attrs({ }); export default function SocialLinks({ address, color, isNativeAsset, links, marginTop, chainId }) { - const etherscanURL = ethereumUtils.getEtherscanHostForNetwork({ chainId }); - const blockExplorerName = ethereumUtils.getBlockExplorer({ chainId }); + const etherscanURL = ethereumUtils.getEtherscanHostForNetwork(chainId); + const blockExplorerName = ethereumUtils.getBlockExplorer(chainId); return ( <> diff --git a/src/components/expanded-state/chart/ChartContextButton.js b/src/components/expanded-state/chart/ChartContextButton.js index 19044bc6fe1..8ce84794e84 100644 --- a/src/components/expanded-state/chart/ChartContextButton.js +++ b/src/components/expanded-state/chart/ChartContextButton.js @@ -46,12 +46,12 @@ export default function ChartContextButton({ asset, color }) { ? [] : [ `🔍 ${emojiSpacing}${lang.t('wallet.action.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), })}`, ]), ...(ios ? [lang.t('wallet.action.cancel')] : []), ], - [asset?.chainId, asset?.isNativeAsset, currentAction] + [asset?.isNativeAsset, asset?.network, currentAction] ); return ; diff --git a/src/components/expanded-state/chart/ChartExpandedStateHeader.js b/src/components/expanded-state/chart/ChartExpandedStateHeader.js index 2d23e3ca37c..624255db57e 100644 --- a/src/components/expanded-state/chart/ChartExpandedStateHeader.js +++ b/src/components/expanded-state/chart/ChartExpandedStateHeader.js @@ -11,6 +11,7 @@ import { useAccountSettings, useBooleanState } from '@/hooks'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { ethereumUtils } from '@/utils'; const noPriceData = lang.t('expanded_state.chart.no_price_data'); @@ -51,6 +52,9 @@ export default function ChartExpandedStateHeader({ }) { const theme = useTheme(); const color = givenColors || theme.colors.dark; + const tokens = useMemo(() => { + return isPool ? asset.tokens : [asset]; + }, [asset, isPool]); const { nativeCurrency } = useAccountSettings(); const tabularNums = useTabularNumsWhileScrubbing(); @@ -110,7 +114,7 @@ export default function ChartExpandedStateHeader({ (null); const [maxPriorityFeeError, setMaxPriorityFeeError] = useState(null); @@ -248,12 +248,12 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack ); const addMinerTip = useCallback(() => { - updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, chainId)); - }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); + updatePriorityFeePerGas(calculateMinerTipAddDifference(maxPriorityFee, txNetwork)); + }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); const substMinerTip = useCallback(() => { - updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, chainId)); - }, [maxPriorityFee, chainId, updatePriorityFeePerGas]); + updatePriorityFeePerGas(-calculateMinerTipSubstDifference(maxPriorityFee, txNetwork)); + }, [maxPriorityFee, txNetwork, updatePriorityFeePerGas]); const addMaxFee = useCallback(() => { updateFeePerGas(isL2 ? GAS_FEE_L2_INCREMENT : GAS_FEE_INCREMENT); diff --git a/src/components/expanded-state/swap-details/CurrencyTile.js b/src/components/expanded-state/swap-details/CurrencyTile.js index 62072cc8930..dee048e8fe9 100644 --- a/src/components/expanded-state/swap-details/CurrencyTile.js +++ b/src/components/expanded-state/swap-details/CurrencyTile.js @@ -78,7 +78,7 @@ export default function CurrencyTile({ { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(chainId)), }); return { actionKey: ContractActionsEnum.blockExplorer, @@ -105,7 +105,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const [menuVisible, setMenuVisible] = useState(false); const menuConfig = useMemo(() => { - const blockExplorerAction = buildBlockExplorerAction(asset?.chainId); + const blockExplorerAction = buildBlockExplorerAction(ethereumUtils.getChainIdFromNetwork(asset?.network)); return { menuItems: [ blockExplorerAction, @@ -116,14 +116,14 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . ], menuTitle: `${asset?.name} (${asset?.symbol})`, }; - }, [asset?.chainId, asset?.address, asset?.name, asset?.symbol]); + }, [asset?.address, asset?.name, asset?.symbol, asset?.network]); const handlePressMenuItem = useCallback( ({ nativeEvent: { actionKey } }) => { if (actionKey === ContractActionsEnum.copyAddress) { handleCopyContractAddress(asset?.address); } else if (actionKey === ContractActionsEnum.blockExplorer) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); } }, [asset, handleCopyContractAddress] @@ -131,7 +131,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . const onPressAndroid = useCallback(() => { const blockExplorerText = lang.t('expanded_state.swap.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId: asset?.chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(asset?.network))), }); const androidContractActions = [lang.t('wallet.action.copy_contract_address'), blockExplorerText, lang.t('button.cancel')]; showActionSheetWithOptions( @@ -146,7 +146,7 @@ export default function SwapDetailsContractRow({ asset, onCopySwapDetailsText, . handleCopyContractAddress(asset?.address); } if (idx === 1) { - ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.chainId); + ethereumUtils.openTokenEtherscanURL(asset?.address, asset?.network); } } ); diff --git a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js index 67efaa8db35..fb9a5852652 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js @@ -1,6 +1,7 @@ import lang from 'i18n-js'; import { capitalize } from 'lodash'; import React, { Fragment, useMemo } from 'react'; +import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { convertAmountToPercentageDisplay } from '../../../helpers/utilities'; import Pill from '../../Pill'; import { ButtonPressAnimation } from '../../animations'; @@ -10,10 +11,10 @@ import { usePrevious, useStepper } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { getExchangeIconUrl, magicMemo } from '@/utils'; import { SocketBridges } from '@/references/swap/bridges'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; const parseExchangeName = name => { - const networks = RainbowNetworkObjects.map(network => network.name.toLowerCase()); + const networks = RainbowNetworks.map(network => network.name.toLowerCase()); const removeNetworks = name => networks.some(network => name.toLowerCase().includes(network)) ? name.slice(name.indexOf('_') + 1, name.length) : name; diff --git a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx index 2dd81818208..657c529d1a5 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx +++ b/src/components/expanded-state/swap-details/SwapDetailsRewardRow.tsx @@ -9,7 +9,7 @@ import { ChainBadge } from '@/components/coin-icon'; import { getNetworkObject } from '@/networks'; import { useTheme } from '@/theme'; import * as i18n from '@/languages'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export function SwapDetailsRewardRow({ reward }: { reward: Reward }) { const { navigate } = useNavigation(); diff --git a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx index d2413fed5f1..98f5ceeb63d 100644 --- a/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx +++ b/src/components/expanded-state/swap-settings/MaxToleranceInput.tsx @@ -6,13 +6,13 @@ import { ButtonPressAnimation } from '../../animations'; import { Icon } from '../../icons'; import StepButtonInput from './StepButtonInput'; import { AccentColorProvider, Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; +import { Network } from '@/helpers'; import { add, convertNumberToString, greaterThan } from '@/helpers/utilities'; import { useMagicAutofocus, useSwapSettings } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { colors } from '@/styles'; import { ethereumUtils } from '@/utils'; -import { ChainId } from '@/networks/types'; const convertBipsToPercent = (bips: number) => (bips / 100).toString(); const convertPercentToBips = (percent: number) => (percent * 100).toString(); @@ -20,131 +20,133 @@ const convertPercentToBips = (percent: number) => (percent * 100).toString(); const SLIPPAGE_INCREMENT = 0.1; // eslint-disable-next-line react/display-name -export const MaxToleranceInput = forwardRef(({ colorForAsset, chainId }: { colorForAsset: string; chainId: ChainId }, ref) => { - const { slippageInBips, updateSwapSlippage } = useSwapSettings(); - const { navigate } = useNavigation(); - - const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); - - const slippageRef = useRef(null); - - const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); - - const { hasPriceImpact, priceImpactColor } = useMemo(() => { - const hasPriceImpact = Number(slippageValue) >= 3; - const priceImpactColor = hasPriceImpact ? colors.orange : null; - return { hasPriceImpact, priceImpactColor }; - }, [slippageValue]); - - useImperativeHandle(ref, () => ({ - blur: () => { - slippageRef?.current?.blur(); - }, - reset: () => { - const slippage = getDefaultSlippageFromConfig(chainId) as unknown as number; - onSlippageChange(convertBipsToPercent(slippage)); - }, - })); - - const updateSlippage = useCallback( - (increment: any) => { - const newSlippage = add(slippageValue, increment); - const newSlippageValue = convertNumberToString(newSlippage); - if (greaterThan(0, newSlippageValue)) return; - - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); - setSlippageValue(newSlippageValue); - }, - [slippageValue, updateSwapSlippage] - ); - - const addSlippage = useCallback(() => { - updateSlippage(SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const minusSlippage = useCallback(() => { - updateSlippage(-SLIPPAGE_INCREMENT); - }, [updateSlippage]); - - const onSlippageChange = useCallback( - (value: any) => { - // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. - updateSwapSlippage(convertPercentToBips(value)); - setSlippageValue(value); - }, - [updateSwapSlippage, setSlippageValue] - ); - - const openSlippageExplainer = () => { - Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - type: 'slippage', - }); - }; - - return ( - - - - - - - {`${lang.t('exchange.slippage_tolerance')} `} - {!hasPriceImpact && ( - - {' 􀅵'} - +export const MaxToleranceInput = forwardRef( + ({ colorForAsset, currentNetwork }: { colorForAsset: string; currentNetwork: Network }, ref) => { + const { slippageInBips, updateSwapSlippage } = useSwapSettings(); + const { navigate } = useNavigation(); + + const [slippageValue, setSlippageValue] = useState(convertBipsToPercent(slippageInBips)); + + const slippageRef = useRef(null); + + const { handleFocus } = useMagicAutofocus(slippageRef, undefined, true); + + const { hasPriceImpact, priceImpactColor } = useMemo(() => { + const hasPriceImpact = Number(slippageValue) >= 3; + const priceImpactColor = hasPriceImpact ? colors.orange : null; + return { hasPriceImpact, priceImpactColor }; + }, [slippageValue]); + + useImperativeHandle(ref, () => ({ + blur: () => { + slippageRef?.current?.blur(); + }, + reset: () => { + const slippage = getDefaultSlippageFromConfig(ethereumUtils.getChainIdFromNetwork(currentNetwork)) as unknown as number; + onSlippageChange(convertBipsToPercent(slippage)); + }, + })); + + const updateSlippage = useCallback( + (increment: any) => { + const newSlippage = add(slippageValue, increment); + const newSlippageValue = convertNumberToString(newSlippage); + if (greaterThan(0, newSlippageValue)) return; + + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(parseFloat(newSlippageValue))); + setSlippageValue(newSlippageValue); + }, + [slippageValue, updateSwapSlippage] + ); + + const addSlippage = useCallback(() => { + updateSlippage(SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const minusSlippage = useCallback(() => { + updateSlippage(-SLIPPAGE_INCREMENT); + }, [updateSlippage]); + + const onSlippageChange = useCallback( + (value: any) => { + // @ts-expect-error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + updateSwapSlippage(convertPercentToBips(value)); + setSlippageValue(value); + }, + [updateSwapSlippage, setSlippageValue] + ); + + const openSlippageExplainer = () => { + Keyboard.dismiss(); + navigate(Routes.EXPLAIN_SHEET, { + type: 'slippage', + }); + }; + + return ( + + + + + + + {`${lang.t('exchange.slippage_tolerance')} `} + {!hasPriceImpact && ( + + {' 􀅵'} + + )} + + {hasPriceImpact && ( + + + )} - - {hasPriceImpact && ( - - - - )} - - - {hasPriceImpact && ( - - - - - {lang.t('exchange.high')} - - - {` · ${lang.t('exchange.price_impact.label')}`} - + - )} - - - - - - - ); -}); + {hasPriceImpact && ( + + + + + {lang.t('exchange.high')} + + + {` · ${lang.t('exchange.price_impact.label')}`} + + + )} + + + + + + + ); + } +); diff --git a/src/components/expanded-state/swap-settings/SwapSettingsState.js b/src/components/expanded-state/swap-settings/SwapSettingsState.js index 7432ebece10..13d2ae8899e 100644 --- a/src/components/expanded-state/swap-settings/SwapSettingsState.js +++ b/src/components/expanded-state/swap-settings/SwapSettingsState.js @@ -32,7 +32,7 @@ function useAndroidDisableGesturesOnFocus() { export default function SwapSettingsState({ asset }) { const { flashbotsEnabled, settingsChangeFlashbotsEnabled } = useAccountSettings(); const { - params: { swapSupportsFlashbots = false, chainId }, + params: { swapSupportsFlashbots = false, network }, } = useRoute(); const { colors } = useTheme(); const { setParams, goBack } = useNavigation(); @@ -151,7 +151,7 @@ export default function SwapSettingsState({ asset }) { )} - + diff --git a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx index 8247be503b0..21f6e2cd2f5 100644 --- a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx +++ b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx @@ -54,7 +54,7 @@ export default function NFTBriefTokenInfoRow({ asset }: { asset: UniqueAsset }) const { data: listing } = useNFTListing({ contractAddress: asset?.asset_contract?.address ?? '', tokenId: asset?.id, - chainId: asset?.chainId, + network: asset?.network, }); const listingValue = listing && convertRawAmountToRoundedDecimal(listing?.price, listing?.payment_token?.decimals, 3); diff --git a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx index 205b88c4bff..08fb711b91e 100644 --- a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx +++ b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx @@ -9,6 +9,7 @@ import saveToCameraRoll from './saveToCameraRoll'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Bleed, Column, Columns, Heading, Inline, Inset, Space, Stack, Text } from '@/design-system'; import { UniqueAsset } from '@/entities'; +import { Network } from '@/helpers'; import { useClipboard, useDimensions, useHiddenTokens, useShowcaseTokens } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { useNavigation } from '@/navigation/Navigation'; @@ -22,7 +23,6 @@ import { refreshNFTContractMetadata, reportNFT } from '@/resources/nfts/simpleha import { ContextCircleButton } from '@/components/context-menu'; import { IS_ANDROID, IS_IOS } from '@/env'; import { MenuActionConfig, MenuConfig } from 'react-native-ios-context-menu'; -import { ChainId } from '@/networks/types'; const AssetActionsEnum = { copyTokenID: 'copyTokenID', @@ -36,7 +36,7 @@ const AssetActionsEnum = { report: 'report', } as const; -const getAssetActions = ({ chainId }: { chainId: ChainId }) => +const getAssetActions = (network: Network) => ({ [AssetActionsEnum.copyTokenID]: { actionKey: AssetActionsEnum.copyTokenID, @@ -57,7 +57,7 @@ const getAssetActions = ({ chainId }: { chainId: ChainId }) => [AssetActionsEnum.etherscan]: { actionKey: AssetActionsEnum.etherscan, actionTitle: lang.t('expanded_state.unique_expanded.view_on_block_explorer', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer({ chainId })), + blockExplorerName: startCase(ethereumUtils.getBlockExplorer(ethereumUtils.getChainIdFromNetwork(network))), }), icon: { iconType: 'SYSTEM', @@ -263,7 +263,7 @@ const UniqueTokenExpandedStateHeader = ({ const isPhotoDownloadAvailable = !isSVG && !isENS; const assetMenuConfig: MenuConfig = useMemo(() => { - const AssetActions = getAssetActions({ chainId: asset.chainId }); + const AssetActions = getAssetActions(asset.network); return { menuItems: [ @@ -309,7 +309,7 @@ const UniqueTokenExpandedStateHeader = ({ { ...AssetActions[AssetActionsEnum.etherscan], }, - ...(asset.chainId === ChainId.mainnet + ...(asset.network === Network.mainnet ? [ { menuTitle: lang.t('expanded_state.unique_expanded.view_on_marketplace'), @@ -320,7 +320,7 @@ const UniqueTokenExpandedStateHeader = ({ ], menuTitle: '', }; - }, [asset.id, asset.chainId, isModificationActionsEnabled, isHiddenAsset, isPhotoDownloadAvailable, isSupportedOnRainbowWeb]); + }, [asset.id, asset?.network, isPhotoDownloadAvailable, isHiddenAsset, isModificationActionsEnabled, isSupportedOnRainbowWeb]); const handlePressFamilyMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly diff --git a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js index b81a35f88c0..3b74a5f5a1d 100644 --- a/src/components/expanded-state/unique-token/ZoomableWrapper.android.js +++ b/src/components/expanded-state/unique-token/ZoomableWrapper.android.js @@ -186,8 +186,8 @@ export const ZoomableWrapper = ({ let targetScale = Math.min(scale.value, MAX_IMAGE_SCALE); // determine whether to snap to screen edges - const breakingScaleX = deviceWidth / fullSizeWidth; - const breakingScaleY = deviceHeight / fullSizeHeight; + let breakingScaleX = deviceWidth / fullSizeWidth; + let breakingScaleY = deviceHeight / fullSizeHeight; const maxDisplacementX = (deviceWidth * (Math.max(1, targetScale / breakingScaleX) - 1)) / 2 / zooming; const maxDisplacementY = (deviceHeight * (Math.max(1, targetScale / breakingScaleY) - 1)) / 2 / zooming; diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index a8851d7d387..8ba909abe83 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -28,7 +28,7 @@ import { getNetworkObject } from '@/networks'; import { IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const { GAS_EMOJIS, GAS_ICONS, GasSpeedOrder, CUSTOM, URGENT, NORMAL, FAST, getGasLabel } = gasUtils; @@ -310,9 +310,9 @@ const GasSpeedButton = ({ type: 'crossChainGas', }); } else { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId }); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId); navigate(Routes.EXPLAIN_SHEET, { - chainId, + network: ethereumUtils.getNetworkFromChainId(chainId), type: 'gas', nativeAsset, }); diff --git a/src/components/investment-cards/PoolValue.js b/src/components/investment-cards/PoolValue.js index cf1e6397ee8..b382a707989 100644 --- a/src/components/investment-cards/PoolValue.js +++ b/src/components/investment-cards/PoolValue.js @@ -29,7 +29,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { const { nativeCurrency } = useAccountSettings(); if (type === 'annualized_fees') { - const percent = parseFloat(value); + let percent = parseFloat(value); if (!percent || percent === 0) { formattedValue = '0%'; } @@ -42,7 +42,7 @@ export const PoolValue = ({ type, value, simple, ...props }) => { formattedValue = '< 0.0001%'; } - const fixedPercent = percent.toFixed(2); + let fixedPercent = percent.toFixed(2); if (fixedPercent === '0.00') { formattedValue = '0%'; } diff --git a/src/components/list/NoResults.tsx b/src/components/list/NoResults.tsx index ef610f7b36e..5086163a346 100644 --- a/src/components/list/NoResults.tsx +++ b/src/components/list/NoResults.tsx @@ -41,7 +41,7 @@ export const NoResults = ({ onL2, type }: { onL2?: boolean; type: NoResultsType break; default: title = lang.t('exchange.no_results.nothing_found'); - logger.warn('[NoResults]: unknown type, falling back to default message'); + logger.warn('NoResults: unknown type, falling back to default message'); break; } diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx index c1faa16a794..366e9c3577d 100644 --- a/src/components/positions/PositionsCard.tsx +++ b/src/components/positions/PositionsCard.tsx @@ -18,7 +18,6 @@ import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useAccountSettings } from '@/hooks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { ethereumUtils } from '@/utils'; -import { AddressOrEth } from '@/__swaps__/types/assets'; type PositionCardProps = { position: RainbowPosition; @@ -34,7 +33,7 @@ function CoinIconForStack({ token }: { token: CoinStackToken }) { const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const chainId = ethereumUtils.getChainIdFromNetwork(token.network); - const { data: externalAsset } = useExternalToken({ address: token.address as AddressOrEth, chainId, currency: nativeCurrency }); + const { data: externalAsset } = useExternalToken({ address: token.address, chainId, currency: nativeCurrency }); return ( = async ({ assetAddress, network }) => { + const { accountAddress, nativeCurrency } = store.getState().settings; + + const assets = await fetchUserAssets({ + address: accountAddress, + currency: nativeCurrency, + connectedToHardhat: false, + }); + if (!assets || Object.keys(assets).length === 0) return false; + + const desiredAsset = Object.values(assets).find(asset => { + if (!network) { + return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase(); + } + + return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase() && asset.network === network; + }); + if (!desiredAsset) return false; + + return Number(desiredAsset.balance?.amount) > 0; +}; diff --git a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts index 0c7fe5d7fa2..b6133f9f4af 100644 --- a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts +++ b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts @@ -1,5 +1,5 @@ import type { EthereumAddress, RainbowTransaction } from '@/entities'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import store from '@/redux/store'; import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; @@ -13,7 +13,7 @@ const isSwapTx = (tx: RainbowTransaction): boolean => tx.to?.toLowerCase() === R export const hasSwapTxn = async (): Promise => { const { accountAddress, nativeCurrency } = store.getState().settings; - const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, diff --git a/src/components/remote-promo-sheet/check-fns/index.ts b/src/components/remote-promo-sheet/check-fns/index.ts index b3fdb065edb..94ec08d36ec 100644 --- a/src/components/remote-promo-sheet/check-fns/index.ts +++ b/src/components/remote-promo-sheet/check-fns/index.ts @@ -1,4 +1,5 @@ export * from './hasNftOffers'; +export * from './hasNonZeroAssetBalance'; export * from './hasNonZeroTotalBalance'; export * from './hasSwapTxn'; export * from './isAfterCampaignLaunch'; diff --git a/src/components/remote-promo-sheet/checkForCampaign.ts b/src/components/remote-promo-sheet/checkForCampaign.ts index da6a7794a25..47b5bf34447 100644 --- a/src/components/remote-promo-sheet/checkForCampaign.ts +++ b/src/components/remote-promo-sheet/checkForCampaign.ts @@ -31,21 +31,21 @@ const timeBetweenPromoSheets = () => { }; export const checkForCampaign = async () => { - logger.debug('[checkForCampaign]: Running Checks'); + logger.debug('Campaigns: Running Checks'); if (timeBetweenPromoSheets() < TIMEOUT_BETWEEN_PROMOS) { - logger.debug('[checkForCampaign]: Time between promos has not exceeded timeout'); + logger.debug('Campaigns: Time between promos has not exceeded timeout'); return; } const isShown = remotePromoSheetsStore.getState().isShown; if (isShown) { - logger.debug('[checkForCampaign]: Another remote sheet is currently shown'); + logger.debug('Campaigns: Another remote sheet is currently shown'); return; } const isReturningUser = device.get(['isReturningUser']); if (!isReturningUser) { - logger.debug('[checkForCampaign]: First launch, not showing promo sheet'); + logger.debug('Campaigns: First launch, not showing promo sheet'); return; } @@ -55,10 +55,10 @@ export const checkForCampaign = async () => { for (const promo of promoSheetCollection?.items || []) { if (!promo) continue; - logger.debug(`[checkForCampaign]: Checking ${promo.sys.id}`); + logger.debug(`Campaigns: Checking ${promo.sys.id}`); const result = await shouldPromptCampaign(promo as PromoSheet); - logger.debug(`[checkForCampaign]: ${promo.sys.id} will show: ${result}`); + logger.debug(`Campaigns: ${promo.sys.id} will show: ${result}`); if (result) { const isShown = remotePromoSheetsStore.getState().isShown; if (!isShown) { @@ -69,7 +69,7 @@ export const checkForCampaign = async () => { }; export const triggerCampaign = async ({ campaignKey, sys: { id: campaignId } }: PromoSheet) => { - logger.debug(`[checkForCampaign]: Showing ${campaignKey} Promo`); + logger.debug(`Campaigns: Showing ${campaignKey} Promo`); setTimeout(() => { remotePromoSheetsStore.getState().showSheet(campaignId); @@ -93,14 +93,14 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise action.fn === 'isPreviewing'); const hasShown = remotePromoSheetsStore.getState().getSheet(id)?.hasBeenShown; // If the campaign has been viewed already or it's the first app launch, exit early if (hasShown && !isPreviewing) { - logger.debug(`[checkForCampaign]: User has already been shown ${campaignKey}`); + logger.debug(`Campaigns: User has already been shown ${campaignKey}`); return false; } @@ -113,9 +113,9 @@ export const shouldPromptCampaign = async (campaign: PromoSheet): Promise ${result === outcome}`); + logger.debug(`Campaigns: [${fn}] matches desired outcome: => ${result === outcome}`); if (result !== outcome) { shouldPrompt = false; diff --git a/src/components/remote-promo-sheet/localCampaignChecks.ts b/src/components/remote-promo-sheet/localCampaignChecks.ts index f91c7909c7b..9577f3f1bef 100644 --- a/src/components/remote-promo-sheet/localCampaignChecks.ts +++ b/src/components/remote-promo-sheet/localCampaignChecks.ts @@ -31,7 +31,7 @@ export interface Campaign { export const activeCampaigns: Campaign[] = [NotificationsPromoCampaign]; export const runLocalCampaignChecks = async (): Promise => { - logger.debug('[runLocalCampaignChecks]: Running Checks'); + logger.debug('Campaigns: Running Checks'); for (const campaign of activeCampaigns) { InteractionManager.runAfterInteractions(async () => { const response = await campaign.check(); diff --git a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts index 465ea2dddef..00aa7b65c77 100644 --- a/src/components/remote-promo-sheet/notificationsPromoCampaign.ts +++ b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts @@ -10,12 +10,12 @@ import { STORAGE_IDS } from '@/model/mmkv'; const mmkv = new MMKV(); export const notificationsCampaignAction = async () => { - logger.debug('[notificationsCampaignAction]: showing notifications promo'); + logger.debug('Notifications promo: showing notifications promo'); mmkv.set(CampaignKey.notificationsLaunch, true); setTimeout(() => { - logger.debug('[notificationsCampaignAction]: triggering notifications promo action'); + logger.debug(`Notifications promo: triggering notifications promo action`); Navigation.handleAction(Routes.NOTIFICATIONS_PROMO_SHEET, {}); }, 1000); @@ -25,7 +25,7 @@ export const notificationsCampaignCheck = async (): Promise { const runChecks = useCallback(() => { InteractionManager.runAfterInteractions(async () => { if (IS_TEST || !remotePromoSheets) { - logger.debug('[useRunChecks]: remote promo sheets is disabled'); + logger.debug('Campaigns: remote promo sheets is disabled'); return; } diff --git a/src/components/secret-display/SecretDisplaySection.tsx b/src/components/secret-display/SecretDisplaySection.tsx index 0ef93ba05e6..eaa38f59627 100644 --- a/src/components/secret-display/SecretDisplaySection.tsx +++ b/src/components/secret-display/SecretDisplaySection.tsx @@ -106,10 +106,11 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: onSecretLoaded?.(!!seedPhrase); } catch (error) { const message = (error as Error)?.message; - logger.error(new RainbowError('[SecretDisplaySection]: Error while trying to reveal secret'), { + logger.error(new RainbowError('Error while trying to reveal secret'), { error: message, }); setSectionState(message === createdWithBiometricError ? SecretDisplayStates.securedWithBiometrics : SecretDisplayStates.noSeed); + captureException(error); onSecretLoaded?.(false); } }, [onSecretLoaded, privateKeyAddress, onWalletTypeIdentified, walletId]); @@ -233,7 +234,7 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }: ); default: - logger.error(new RainbowError(`[SecretDisplaySection]: Secret display section state unknown ${sectionState}`)); + logger.error(new RainbowError('Secret display section tries to present an unknown state')); return null; } } diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx index af2a29de48b..810bb9e7890 100644 --- a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx +++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx @@ -85,7 +85,7 @@ function SwapActionButton({ asset, color: givenColor, inputType, label, fromDisc if (inputType === assetInputTypes.in) { swapsStore.setState({ inputAsset: userAsset || parsedAsset }); - const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork({ chainId }); + const nativeAssetForChain = await ethereumUtils.getNativeAssetForNetwork(chainId); if (nativeAssetForChain && !isSameAsset({ address: nativeAssetForChain.address as AddressOrEth, chainId }, parsedAsset)) { const userOutputAsset = userAssetsStore.getState().getUserAsset(`${nativeAssetForChain.address}_${chainId}`); diff --git a/src/components/svg/SvgImage.js b/src/components/svg/SvgImage.js index e55bd0be0b1..1960a973590 100644 --- a/src/components/svg/SvgImage.js +++ b/src/components/svg/SvgImage.js @@ -4,7 +4,7 @@ import { WebView } from 'react-native-webview'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; import { CardSize } from '../unique-token/CardSize'; const ImageTile = styled(ImgixImage)({ @@ -97,7 +97,7 @@ class SvgImage extends Component { } doFetch = async props => { - const uri = props.source && props.source.uri; + let uri = props.source && props.source.uri; if (uri) { props.onLoadStart && props.onLoadStart(); if (uri.match(/^data:image\/svg/)) { @@ -110,17 +110,17 @@ class SvgImage extends Component { if (text.toLowerCase().indexOf('/)) { - logger.debug('[SvgImage]: foreignObject tag not supported', { text, uri }); + logger.log('foreignObject tag not supported', { text, uri }); // return w/o error so we can fallback to png return; } this.mounted && this.setState({ fetchingUrl: uri, svgContent: text }); } else { - logger.debug('[SvgImage]: invalid svg', { text, uri }); + logger.log('invalid svg', { text, uri }); this.mounted && props.onError && props.onError('invalid svg'); } } catch (err) { - logger.debug('[SvgImage]: error loading remote svg image', err); + logger.log('error loading remote svg image', err); this.mounted && props.onError && props.onError('error loading remote svg image'); } } diff --git a/src/components/toasts/OfflineToast.js b/src/components/toasts/OfflineToast.js index ec4565c57e9..defab4a11ee 100644 --- a/src/components/toasts/OfflineToast.js +++ b/src/components/toasts/OfflineToast.js @@ -1,15 +1,15 @@ import lang from 'i18n-js'; import React from 'react'; import { web3Provider } from '../../handlers/web3'; +import networkTypes from '../../helpers/networkTypes'; import Toast from './Toast'; import { useAccountSettings, useInternetStatus } from '@/hooks'; -import { Network } from '@/networks/types'; const OfflineToast = () => { const isConnected = useInternetStatus(); const { network } = useAccountSettings(); const providerUrl = web3Provider?.connection?.url; - const isMainnet = network === Network.mainnet && !providerUrl?.startsWith('http://'); + const isMainnet = network === networkTypes.mainnet && !providerUrl?.startsWith('http://'); return ; }; diff --git a/src/components/toasts/TestnetToast.js b/src/components/toasts/TestnetToast.js index acbf9afb4ab..ec370b3e51b 100644 --- a/src/components/toasts/TestnetToast.js +++ b/src/components/toasts/TestnetToast.js @@ -1,22 +1,22 @@ import React, { useEffect, useState } from 'react'; +import networkTypes from '../../helpers/networkTypes'; import { Icon } from '../icons'; import { Nbsp, Text } from '../text'; import Toast from './Toast'; +import { isHardHat } from '@/handlers/web3'; import { useInternetStatus } from '@/hooks'; -import { getNetworkObject } from '@/networks'; -import { ChainId } from '@/networks/types'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; +import { getNetworkObj } from '@/networks'; -const TestnetToast = ({ chainId }) => { - const { connectedToHardhat } = useConnectedToHardhatStore(); +const TestnetToast = ({ network, web3Provider }) => { const isConnected = useInternetStatus(); - const { name, colors: networkColors } = getNetworkObject({ chainId }); - const [visible, setVisible] = useState(chainId !== ChainId.mainnet); + const providerUrl = web3Provider?.connection?.url; + const { name, colors: networkColors } = getNetworkObj(network); + const [visible, setVisible] = useState(!network === networkTypes.mainnet); const [networkName, setNetworkName] = useState(name); useEffect(() => { - if (chainId === ChainId.mainnet) { - if (connectedToHardhat) { + if (network === networkTypes.mainnet) { + if (isHardHat(providerUrl)) { setVisible(true); setNetworkName('Hardhat'); } else { @@ -26,7 +26,7 @@ const TestnetToast = ({ chainId }) => { setVisible(true); setNetworkName(name + (isConnected ? '' : ' (offline)')); } - }, [name, isConnected, chainId, connectedToHardhat]); + }, [name, network, providerUrl, isConnected]); const { colors, isDarkMode } = useTheme(); diff --git a/src/components/token-info/TokenInfoBalanceValue.js b/src/components/token-info/TokenInfoBalanceValue.js index f9e42c44412..e69519c8811 100644 --- a/src/components/token-info/TokenInfoBalanceValue.js +++ b/src/components/token-info/TokenInfoBalanceValue.js @@ -3,7 +3,7 @@ import { RowWithMargins } from '../layout'; import TokenInfoValue from './TokenInfoValue'; import { useColorForAsset } from '@/hooks'; import styled from '@/styled-thing'; -import { magicMemo } from '@/utils'; +import { ethereumUtils, magicMemo } from '@/utils'; import RainbowCoinIcon from '../coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; import { View } from 'react-native'; @@ -28,7 +28,7 @@ const TokenInfoBalanceValue = ({ align, asset, ...props }) => { { - const networkObj = getNetworkObject({ chainId }); + const networkObj = getNetworkObj(ethereumUtils.getNetworkFromChainId(Number(chainId))); return { chainId, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, @@ -195,7 +195,7 @@ export default function WalletConnectListItem({ account, chainId, dappIcon, dapp > - + parseInt(chain.split(':')[1]))?.filter(isSupportedChain) ?? []) as ChainId[]; if (!address) { - const e = new RainbowError(`[WalletConnectV2ListItem]: could not parse address`); + const e = new RainbowError(`WalletConnectV2ListItem: could not parse address`); logger.error(e); // defensive, just for types, should never happen diff --git a/src/config/experimental.ts b/src/config/experimental.ts index b380ec0b0ac..77c87bd58a4 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -28,7 +28,6 @@ export const SWAPS_V2 = 'SwapsV2'; export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; -export const FEATURED_RESULTS = 'Featured Results'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -62,10 +61,9 @@ export const defaultConfig: Record = { [REMOTE_CARDS]: { settings: true, value: false }, [POINTS_NOTIFICATIONS_TOGGLE]: { settings: true, value: false }, [DAPP_BROWSER]: { settings: true, value: !!IS_TEST }, - [SWAPS_V2]: { settings: true, value: !!IS_TEST }, + [SWAPS_V2]: { settings: true, value: false }, [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, - [FEATURED_RESULTS]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/debugging/network.js b/src/debugging/network.js index 989aa3365b2..716faef9a23 100644 --- a/src/debugging/network.js +++ b/src/debugging/network.js @@ -1,5 +1,5 @@ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; let internalCounter = 0; @@ -21,10 +21,10 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses }; const emptyLine = () => { - logger.debug(''); + logger.log(''); }; const separator = () => { - logger.debug(`----------------------------------------`); + logger.log(`----------------------------------------`); }; if (showNetworkRequests) { @@ -35,21 +35,21 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.debug(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); + logger.log(`${PREFIX} ➡️ REQUEST #${xhr._trackingName} - ${xhr._method} ${xhr._url}`); emptyLine(); if (data) { emptyLine(); - logger.debug(' PARAMETERS: '); + logger.log(' PARAMETERS: '); emptyLine(); try { const dataObj = JSON.parse(data); - logger.debug(' {'); + logger.log(' {'); Object.keys(dataObj).forEach(key => { - logger.debug(` ${key} : `, dataObj[key]); + logger.log(` ${key} : `, dataObj[key]); }); - logger.debug(' }'); + logger.log(' }'); } catch (e) { - logger.error(new RainbowError(`Error parsing data: ${e}`), { data }); + logger.log(data); } } emptyLine(); @@ -72,33 +72,33 @@ export default function monitorNetwork(showNetworkRequests, showNetworkResponses separator(); emptyLine(); - logger.debug(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); + logger.log(`${PREFIX} ${getEmojiForStatusCode(status)} RESPONSE #${rid} - ${xhr._method} ${url}`); emptyLine(); if (timeout && status > 400) { - logger.debug(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); + logger.log(` ⚠️ ⚠️ TIMEOUT! ⚠️ ⚠️ `); } if (status) { - logger.debug(` Status: ${status}`); + logger.log(` Status: ${status}`); } if (time) { - logger.debug(` Completed in: ${time / 1000} s`); + logger.log(` Completed in: ${time / 1000} s`); } if (response) { emptyLine(); - logger.debug(' RESPONSE: '); + logger.log(' RESPONSE: '); emptyLine(); try { const responseObj = JSON.parse(response); - logger.debug(' {'); + logger.log(' {'); Object.keys(responseObj).forEach(key => { - logger.debug(` ${key} : `, responseObj[key]); + logger.log(` ${key} : `, responseObj[key]); }); - logger.debug(' }'); + logger.log(' }'); } catch (e) { - logger.error(new RainbowError(`Error parsing response: ${e}`), { data: response }); + logger.log(response); } } emptyLine(); diff --git a/src/debugging/useDependencyDebugger.ts b/src/debugging/useDependencyDebugger.ts index b3966e3c1da..4080b3bffc5 100644 --- a/src/debugging/useDependencyDebugger.ts +++ b/src/debugging/useDependencyDebugger.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { logger } from '@/logger'; +import { logger } from '../utils'; const usePrevious = (value: T): T => { const ref = useRef(value); @@ -50,6 +50,6 @@ export const useDependencyDebugger = (dependencies: unknown[] | Record Component instanceof _C) ? View : Component; - const isView = ComponentToUse === View || ComponentToUse === Animated.View; + const isView = Component === View || Component === Animated.View; const shadowStylesExist = !!styleProp && @@ -278,7 +273,7 @@ export const Box = forwardRef(function Box( {({ backgroundColor, backgroundStyle }) => ( - + {children} {borderColor || borderWidth ? ( ) : null} - + )} ) : ( - + {children} {borderColor || borderWidth ? ( ) : null} - + ); }) as PolymorphicBox; diff --git a/src/ens-avatar/src/utils.ts b/src/ens-avatar/src/utils.ts index 1b765ea0c8a..3e5a7a77b06 100644 --- a/src/ens-avatar/src/utils.ts +++ b/src/ens-avatar/src/utils.ts @@ -39,7 +39,7 @@ export function isCID(hash: any) { } } -export function parseNFT(uri: string, seperator = '/') { +export function parseNFT(uri: string, seperator: string = '/') { // parse valid nft spec (CAIP-22/CAIP-29) // @see: https://github.com/ChainAgnostic/CAIPs/tree/master/CAIPs try { diff --git a/src/entities/tokens.ts b/src/entities/tokens.ts index 75fab380118..3de13f36507 100644 --- a/src/entities/tokens.ts +++ b/src/entities/tokens.ts @@ -1,7 +1,8 @@ import { EthereumAddress } from '.'; import { Chain } from '@wagmi/chains'; -import { Network, ChainId } from '@/networks/types'; +import { Network } from '@/networks/types'; import { TokenColors } from '@/graphql/__generated__/metadata'; +import { ChainId } from '@/__swaps__/types/chains'; export interface ZerionAssetPrice { value: number; @@ -53,7 +54,7 @@ export interface ParsedAddressAsset extends Asset, Partial { - const chainId = ethereumUtils.getChainIdFromNetwork(network); - const p = await getProvider({ chainId }); + const p = await getProviderForNetwork(network); const contractInstance = new Contract(TOKEN_GATE_CHECKER_ADDRESS[network], tokenGateCheckerAbi, p); diff --git a/src/featuresToUnlock/unlockableAppIconCheck.ts b/src/featuresToUnlock/unlockableAppIconCheck.ts index b2d87d261ae..f2c9b72ea29 100644 --- a/src/featuresToUnlock/unlockableAppIconCheck.ts +++ b/src/featuresToUnlock/unlockableAppIconCheck.ts @@ -22,7 +22,7 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w const handled = unlockableAppIconStorage.getBoolean(appIconKey); - logger.debug(`[unlockableAppIconCheck]: ${appIconKey} was handled? ${handled}`); + logger.debug(`${appIconKey} was handled? ${handled}`); if (handled) return false; @@ -32,13 +32,13 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w (Object.keys(appIcon.unlockingNFTs) as TokenGateCheckerNetwork[]).map(async network => { const nfts = appIcon.unlockingNFTs[network]; if (!nfts) return; - logger.debug(`[unlockableAppIconCheck]: Checking ${appIconKey} on network ${network}`); + logger.debug(`Checking ${appIconKey} on network ${network}`); return await checkIfWalletsOwnNft(nfts, network, walletsToCheck); }) ) ).some(result => !!result); - logger.debug(`[unlockableAppIconCheck]: ${appIconKey} check result: ${found}`); + logger.debug(`${appIconKey} check result: ${found}`); // We open the sheet with a setTimeout 1 sec later to make sure we can return first // so we can abort early if we're showing a sheet to prevent 2+ sheets showing at the same time @@ -46,14 +46,14 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w setTimeout(() => { if (found) { unlockableAppIconStorage.set(appIconKey, true); - logger.debug(`[unlockableAppIconCheck]: Feature check ${appIconKey} set to true. Wont show up anymore!`); + logger.debug(`Feature check ${appIconKey} set to true. Wont show up anymore!`); Navigation.handleAction(Routes.APP_ICON_UNLOCK_SHEET, { appIconKey }); return true; } }, 1000); return found; } catch (e) { - logger.error(new RainbowError('[unlockableAppIconCheck]: UnlockableAppIconCheck blew up'), { e }); + logger.error(new RainbowError('UnlockableAppIconCheck blew up'), { e }); } return false; }; diff --git a/src/graphql/index.ts b/src/graphql/index.ts index 2675fa9a603..b3ababa3bbd 100644 --- a/src/graphql/index.ts +++ b/src/graphql/index.ts @@ -15,25 +15,7 @@ export const ensClient = getEnsSdk(getFetchRequester(config.ens)); export const metadataClient = getMetadataSdk(metadataRequester); export const metadataPOSTClient = getMetadataSdk(getFetchRequester(config.metadataPOST)); export const arcClient = IS_PROD ? getArcSdk(getFetchRequester(config.arc)) : getArcDevSdk(getFetchRequester(config.arcDev)); -export const arcPOSTClient = IS_PROD - ? getArcSdk( - getFetchRequester({ - ...config.arc, - schema: { - ...config.arc.schema, - method: 'POST', - }, - }) - ) - : getArcDevSdk( - getFetchRequester({ - ...config.arcDev, - schema: { - ...config.arcDev.schema, - method: 'POST', - }, - }) - ); + export const requestMetadata = (q: string, options?: Pick) => metadataRequester( gql` diff --git a/src/graphql/queries/arc.graphql b/src/graphql/queries/arc.graphql index 69648355a7c..4b8d55ebb58 100644 --- a/src/graphql/queries/arc.graphql +++ b/src/graphql/queries/arc.graphql @@ -473,43 +473,3 @@ query getNFTs($walletAddress: String!, $sortBy: NFTCollectionSortCriterion, $sor } } } - -query getFeaturedResults($placementId: String!, $walletAddress: String!, $country: String, $limit: Int) { - featuredResults(placementId: $placementId, walletAddress: $walletAddress, country: $country, limit: $limit) { - items { - id - type - impressionId - advertiserId - placementSlug - title - context { - text - } - description - imageUrl - category - imageAltText - ctas { - title - href - } - } - } -} - -mutation trackFeaturedResult( - $type: TrackFeaturedResultType! - $placementId: String! - $impressionId: String! - $featuredResultCreativeId: String! -) { - trackFeaturedResult( - type: $type - placementId: $placementId - impressionId: $impressionId - featuredResultCreativeId: $featuredResultCreativeId - ) { - message - } -} diff --git a/src/graphql/queries/metadata.graphql b/src/graphql/queries/metadata.graphql index 0b38946e07f..e398cbdd2ea 100644 --- a/src/graphql/queries/metadata.graphql +++ b/src/graphql/queries/metadata.graphql @@ -571,17 +571,8 @@ query tokenMetadata($address: String!, $chainId: Int!, $currency: String) { } } -query priceChart( - $chainId: Int! - $address: String! - $currency: String - $day: Boolean! - $hour: Boolean! - $week: Boolean! - $month: Boolean! - $year: Boolean! -) { - token(chainID: $chainId, address: $address, currency: $currency) { +query priceChart($chainId: Int!, $address: String!, $day: Boolean!, $hour: Boolean!, $week: Boolean!, $month: Boolean!, $year: Boolean!) { + token(chainID: $chainId, address: $address) { priceCharts { day @include(if: $day) { points diff --git a/src/handlers/LedgerSigner.ts b/src/handlers/LedgerSigner.ts index af5a511c81d..7e86770d4d4 100644 --- a/src/handlers/LedgerSigner.ts +++ b/src/handlers/LedgerSigner.ts @@ -57,14 +57,14 @@ export class LedgerSigner extends Signer { return new Promise(async (resolve, reject) => { if (timeout && timeout > 0) { setTimeout(() => { - logger.debug('[LedgerSigner]: Signer timeout', {}, logger.DebugContext.ledger); + logger.debug('Ledger: Signer timeout', {}, logger.DebugContext.ledger); return reject(new RainbowError('Ledger: Signer timeout')); }, timeout); } const eth = await this._eth; if (!eth) { - logger.debug('[LedgerSigner]: Eth app not open', {}, logger.DebugContext.ledger); + logger.debug('Ledger: Eth app not open', {}, logger.DebugContext.ledger); return reject(new Error('Ledger: Eth app not open')); } @@ -74,7 +74,7 @@ export class LedgerSigner extends Signer { const result = await callback(eth); return resolve(result); } catch (error: any) { - logger.error(new RainbowError('[LedgerSigner]: Transport error'), error); + logger.error(new RainbowError('Ledger: Transport error'), error); // blind signing isnt enabled if (error.name === 'EthAppPleaseEnableContractData') diff --git a/src/handlers/__mocks__/web3.ts b/src/handlers/__mocks__/web3.ts index 4b6ff17ecf0..dbd6cbf1714 100644 --- a/src/handlers/__mocks__/web3.ts +++ b/src/handlers/__mocks__/web3.ts @@ -1,3 +1,3 @@ import { jest } from '@jest/globals'; -export const getProvider = jest.fn(); +export const getProviderForNetwork = jest.fn(); diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 164ae1c436a..48044fac0a0 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -2,16 +2,22 @@ import { Contract } from '@ethersproject/contracts'; import { erc20ABI } from '@/references'; import { convertAmountToBalanceDisplay, convertRawAmountToDecimalFormat } from '@/helpers/utilities'; -import { getNetworkObject } from '@/networks'; -import { ChainId } from '@/networks/types'; +import { getNetworkObj, getNetworkObject } from '@/networks'; +import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; +import { ethereumUtils } from '@/utils'; + +export function isL2Asset(network: Network) { + return getNetworkObj(network).networkType === 'layer2'; +} export function isNativeAsset(address: string, chainId: ChainId) { return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } -export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: any, provider: any) { +export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, network: any, provider: any) { // Check if it's the native chain asset - if (isNativeAsset(address, chainId)) { + if (isNativeAsset(address, ethereumUtils.getChainIdFromNetwork(network))) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); } return getOnchainTokenBalance({ address, decimals, symbol }, userAddress, provider); diff --git a/src/handlers/authentication.ts b/src/handlers/authentication.ts index 75ddcb3f0ae..df12fa72059 100644 --- a/src/handlers/authentication.ts +++ b/src/handlers/authentication.ts @@ -17,9 +17,7 @@ export async function getExistingPIN(): Promise { return userPIN as string; } } catch (error) { - logger.error(new RainbowError('[getExistingPIN]: Error while trying to get existing PIN code.'), { - message: (error as Error).message, - }); + logger.error(new RainbowError('Error while trying to get existing PIN code.'), { message: (error as Error).message }); } return; } @@ -41,7 +39,7 @@ export async function savePIN(pin: string | undefined) { await keychain.saveString(pinKey, encryptedPin); } } catch (error) { - logger.error(new RainbowError('[savePIN]: savePin error'), { + logger.error(new RainbowError('savePin error'), { message: (error as Error).message, }); } diff --git a/src/handlers/cloudBackup.ts b/src/handlers/cloudBackup.ts index 1eb3f5be795..0f500a4bb46 100644 --- a/src/handlers/cloudBackup.ts +++ b/src/handlers/cloudBackup.ts @@ -115,14 +115,16 @@ export async function encryptAndSaveDataToCloud(data: any, password: any, filena ); if (!exists) { + logger.info('Backup doesnt exist after completion'); const error = new Error(CLOUD_BACKUP_ERRORS.INTEGRITY_CHECK_FAILED); + logger.error(new RainbowError(error.message)); throw error; } await RNFS.unlink(path); return filename; } catch (e: any) { - logger.error(new RainbowError('[cloudBackup]: Error during encryptAndSaveDataToCloud'), { + logger.error(new RainbowError('Error during encryptAndSaveDataToCloud'), { message: e.message, }); throw new Error(CLOUD_BACKUP_ERRORS.GENERAL_ERROR); @@ -171,7 +173,7 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n } if (!document) { - logger.error(new RainbowError('[cloudBackup]: No backup found with that name!'), { + logger.error(new RainbowError('No backup found with that name!'), { filename, }); const error = new Error(CLOUD_BACKUP_ERRORS.SPECIFIC_BACKUP_NOT_FOUND); @@ -184,19 +186,19 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n const encryptedData = ios ? await getICloudDocument(filename) : await getGoogleDriveDocument(document.id); if (encryptedData) { - logger.debug(`[cloudBackup]: Got cloud document ${filename}`); + logger.info('Got cloud document ', { filename }); const backedUpDataStringified = await encryptor.decrypt(backupPassword, encryptedData); if (backedUpDataStringified) { const backedUpData = JSON.parse(backedUpDataStringified); return backedUpData; } else { - logger.error(new RainbowError('[cloudBackup]: We couldnt decrypt the data')); + logger.error(new RainbowError('We couldnt decrypt the data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA); throw error; } } - logger.error(new RainbowError('[cloudBackup]: We couldnt get the encrypted data')); + logger.error(new RainbowError('We couldnt get the encrypted data')); const error = new Error(CLOUD_BACKUP_ERRORS.ERROR_GETTING_ENCRYPTED_DATA); throw error; } diff --git a/src/handlers/cloudinary.ts b/src/handlers/cloudinary.ts index 5a3521a7d52..244a4b2575b 100644 --- a/src/handlers/cloudinary.ts +++ b/src/handlers/cloudinary.ts @@ -13,7 +13,7 @@ type CloudinaryConfig = { const PixelRatios = [1, 1.5, 2, 2.625, 2.75, 3, 3.5]; // popular ratios. const IconsSizes = [40, 36]; // Remove 36 with TopMover const allowedIconSizes = PixelRatios.reduce((acc, ratio) => { - for (const size of IconsSizes) { + for (let size of IconsSizes) { acc.push(size * ratio); } return acc; diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index c8914e691bd..7c2f95be5fd 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -28,7 +28,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; export default async function handleDeeplink(url: string, initialRoute: any = null) { if (!url) { - logger.warn(`[handleDeeplink]: No url provided`); + logger.warn(`handleDeeplink: No url provided`); return; } @@ -36,13 +36,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * We need to wait till the wallet is ready to handle any deeplink */ while (!store.getState().appState.walletReady) { - logger.debug(`[handleDeeplink]: Waiting for wallet to be ready`); + logger.info(`handleDeeplink: Waiting for wallet to be ready`); await delay(50); } const { protocol, host, pathname, query } = new URL(url, true); - logger.debug(`[handleDeeplink]: handling url`, { + logger.info(`handleDeeplink: handling url`, { url, protocol, host, @@ -54,13 +54,13 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * Handling send deep links */ - logger.debug(`[handleDeeplink]: ethereum:// protocol`); + logger.info(`handleDeeplink: ethereum:// protocol`); ethereumUtils.parseEthereumUrl(url); } else if (protocol === 'https:' || protocol === 'rainbow:') { /** * Any native iOS deep link OR universal links via HTTPS */ - logger.debug(`[handleDeeplink]: https:// or rainbow:// protocol`); + logger.info(`handleDeeplink: https:// or rainbow:// protocol`); /** * The first path following the host (universal link) or protocol @@ -75,7 +75,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * tap "Rainbow" in Web3Modal and it hits this handler */ case 'wc': { - logger.debug(`[handleDeeplink]: wc`); + logger.info(`handleDeeplink: wc`); handleWalletConnect(query.uri, query.connector); break; } @@ -84,7 +84,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Links from website to an individual token */ case 'token': { - logger.debug(`[handleDeeplink]: token`); + logger.info(`handleDeeplink: token`); const { addr } = query; const address = (addr as string)?.toLowerCase() ?? ''; @@ -120,12 +120,12 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * should contain metadata about the transaction, if we have it. */ case 'f2c': { - logger.debug(`[handleDeeplink]: f2c`); + logger.info(`handleDeeplink: f2c`); const { provider, sessionId } = query; if (!provider || !sessionId) { - logger.warn(`[handleDeeplink]: Received FWC deeplink with invalid params`, { + logger.warn('Received FWC deeplink with invalid params', { url, query, }); @@ -166,12 +166,11 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu * Ratio's onramp SDK. */ case 'plaid': { - logger.debug(`[handleDeeplink]: handling Plaid redirect`, { url }); + logger.log('handleDeeplink: handling Plaid redirect', { url }); break; } case 'poap': { - logger.debug(`[handleDeeplink]: handling POAP`, { url }); const secretWordOrHash = pathname?.split('/')?.[1]; await getPoapAndOpenSheetWithSecretWord(secretWordOrHash, false); await getPoapAndOpenSheetWithQRHash(secretWordOrHash, false); @@ -179,7 +178,6 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } case 'points': { - logger.debug(`[handleDeeplink]: handling points`, { url }); const referralCode = query?.ref; if (referralCode) { analyticsV2.track(analyticsV2.event.pointsReferralCodeDeeplinkOpened); @@ -193,7 +191,6 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu case 'dapp': { const { url } = query; - logger.debug(`[handleDeeplink]: handling dapp`, { url }); if (url) { Navigation.handleAction(Routes.DAPP_BROWSER_SCREEN, { url }); } @@ -201,7 +198,6 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } default: { - logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; /** * This handles ENS profile links on mobile i.e. @@ -219,7 +215,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu fromRoute: 'Deeplink', }); } else { - logger.warn(`[handleDeeplink]: invalid address or ENS provided`, { + logger.warn(`handleDeeplink: invalid address or ENS provided`, { url, protocol, host, @@ -232,7 +228,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu /** * This is a catch-all for any other deep links that we don't handle */ - logger.warn(`[handleDeeplink]: invalid or unknown deeplink`, { + logger.warn(`handleDeeplink: invalid or unknown deeplink`, { url, protocol, host, @@ -244,7 +240,7 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu } // Android uses normal deeplinks } else if (protocol === 'wc:') { - logger.debug(`[handleDeeplink]: wc:// protocol`); + logger.info(`handleDeeplink: wc:// protocol`); handleWalletConnect(url, query.connector); } } @@ -271,21 +267,21 @@ const walletConnectURICache = new Set(); function handleWalletConnect(uri?: string, connector?: string) { if (!uri) { - logger.debug(`[handleWalletConnect]: skipping uri empty`); + logger.debug(`handleWalletConnect: skipping uri empty`, {}); return; } const cacheKey = JSON.stringify({ uri }); if (walletConnectURICache.has(cacheKey)) { - logger.debug(`[handleWalletConnect]: skipping duplicate event`); + logger.debug(`handleWalletConnect: skipping duplicate event`, {}); return; } const { query } = new URL(uri); const parsedUri = uri ? parseUri(uri) : null; - logger.debug(`[handleWalletConnect]: handling event`, { + logger.debug(`handleWalletConnect: handling event`, { uri, query, parsedUri, @@ -299,7 +295,7 @@ function handleWalletConnect(uri?: string, connector?: string) { store.dispatch(walletConnectSetPendingRedirect()); store.dispatch( walletConnectOnSessionRequest(uri, connector, (status: any, dappScheme: any) => { - logger.debug(`[walletConnectOnSessionRequest] callback`, { + logger.debug(`walletConnectOnSessionRequest callback`, { status, dappScheme, }); @@ -308,12 +304,12 @@ function handleWalletConnect(uri?: string, connector?: string) { }) ); } else if (parsedUri.version === 2) { - logger.debug(`[handleWalletConnect]: handling v2`, { uri }); + logger.debug(`handleWalletConnect: handling v2`, { uri }); setHasPendingDeeplinkPendingRedirect(true); pairWalletConnect({ uri, connector }); } } else { - logger.debug(`[handleWalletConnect]: handling fallback`, { uri }); + logger.debug(`handleWalletConnect: handling fallback`, { uri }); // This is when we get focused by WC due to a signing request // Don't add this URI to cache setHasPendingDeeplinkPendingRedirect(true); diff --git a/src/handlers/dispersion.ts b/src/handlers/dispersion.ts index f2f1686f794..c2281dadf71 100644 --- a/src/handlers/dispersion.ts +++ b/src/handlers/dispersion.ts @@ -1,7 +1,7 @@ import { RainbowFetchClient } from '../rainbow-fetch'; import { EthereumAddress, IndexToken, RainbowToken } from '@/entities'; import UniswapAssetsCache from '@/utils/uniswapAssetsCache'; -import { logger, RainbowError } from '@/logger'; +import { logger } from '@/logger'; const dispersionApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -25,7 +25,7 @@ export const getUniswapV2Tokens = async (addresses: EthereumAddress[]): Promise< return res?.data?.tokens ?? null; } } catch (e: any) { - logger.error(new RainbowError(`[getUniswapV2Tokens]: error fetching uniswap v2 tokens`), { + logger.warn(`dispersionApi: error fetching uniswap v2 tokens`, { message: e.message, }); } @@ -37,7 +37,7 @@ export const getTrendingAddresses = async (): Promise const res = await dispersionApi.get('/dispersion/v1/trending'); return res?.data?.data?.trending ?? null; } catch (e: any) { - logger.error(new RainbowError(`[getTrendingAddresses]: error fetching trending addresses`), { + logger.warn(`dispersionApi: error fetching trending addresses`, { message: e.message, }); return null; @@ -49,7 +49,7 @@ export const getAdditionalAssetData = async (address: EthereumAddress, chainId = const res = await dispersionApi.get(`/dispersion/v1/expanded/${chainId}/${address}`); return res?.data?.data ?? null; } catch (e: any) { - logger.error(new RainbowError(`[getAdditionalAssetData]: error fetching additional asset data`), { + logger.warn(`dispersionApi: error fetching additional asset data`, { message: e.message, }); return null; diff --git a/src/handlers/ens.ts b/src/handlers/ens.ts index aa039d13a05..e29076a878f 100644 --- a/src/handlers/ens.ts +++ b/src/handlers/ens.ts @@ -1,30 +1,33 @@ import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder'; import { getAddress } from '@ethersproject/address'; import { Resolver } from '@ethersproject/providers'; +import { captureException } from '@sentry/react-native'; import { Duration, sub } from 'date-fns'; import { isValidAddress, isZeroAddress } from 'ethereumjs-util'; import { BigNumber } from '@ethersproject/bignumber'; import { debounce, isEmpty, sortBy } from 'lodash'; -import { fetchENSAvatar } from '../hooks/useENSAvatar'; +import { fetchENSAvatar, prefetchENSAvatar } from '../hooks/useENSAvatar'; import { prefetchENSCover } from '../hooks/useENSCover'; import { prefetchENSRecords } from '../hooks/useENSRecords'; import { ENSActionParameters, ENSRapActionType } from '@/raps/common'; import { getENSData, getNameFromLabelhash, saveENSData } from './localstorage/ens'; -import { estimateGasWithPadding, getProvider, TokenStandard } from './web3'; +import { estimateGasWithPadding, getProviderForNetwork, TokenStandard } from './web3'; import { ENSRegistrationRecords, Records, UniqueAsset } from '@/entities'; +import { Network } from '@/helpers'; import { ENS_DOMAIN, ENS_RECORDS, ENSRegistrationTransactionType, generateSalt, getENSExecutionDetails, getNameOwner } from '@/helpers/ens'; import { add } from '@/helpers/utilities'; import { ImgixImage } from '@/components/images'; import { ENS_NFT_CONTRACT_ADDRESS, ethUnits } from '@/references'; -import { labelhash, profileUtils } from '@/utils'; +import { labelhash, logger, profileUtils } from '@/utils'; import { AvatarResolver } from '@/ens-avatar/src'; import { ensClient } from '@/graphql'; import { prefetchFirstTransactionTimestamp } from '@/resources/transactions/firstTransactionTimestampQuery'; import { prefetchENSAddress } from '@/resources/ens/ensAddressQuery'; +import { ENS_MARQUEE_QUERY_KEY } from '@/resources/metadata/ensMarqueeQuery'; +import { queryClient } from '@/react-query'; +import { EnsMarqueeAccount } from '@/graphql/__generated__/metadata'; import { MimeType, handleNFTImages } from '@/utils/handleNFTImages'; import store from '@/redux/store'; -import { logger, RainbowError } from '@/logger'; -import { ChainId, Network } from '@/networks/types'; const DUMMY_RECORDS = { description: 'description', @@ -51,7 +54,6 @@ const buildEnsToken = ({ }); return { acquisition_date: undefined, - chainId: ChainId.mainnet, animation_url: null, asset_contract: { address: contractAddress, @@ -135,9 +137,8 @@ export const fetchMetadata = async ({ const image_url = `https://metadata.ens.domains/mainnet/${contractAddress}/${tokenId}/image`; return { image_url, name }; } catch (error) { - logger.error(new RainbowError(`[ENS]: fetchMetadata failed`), { - error, - }); + logger.sentry('ENS: Error getting ENS metadata', error); + captureException(new Error('ENS: Error getting ENS metadata')); throw error; } }; @@ -174,9 +175,8 @@ export const fetchEnsTokens = async ({ .filter((token: TToken | null | undefined): token is TToken => !!token) || [] ); } catch (error) { - logger.error(new RainbowError(`[ENS]: fetchEnsTokens failed`), { - error, - }); + logger.sentry('ENS: Error getting ENS unique tokens', error); + captureException(new Error('ENS: Error getting ENS unique tokens')); return []; } }; @@ -322,7 +322,7 @@ export const fetchAccountDomains = async (address: string) => { export const fetchImage = async (imageType: 'avatar' | 'header', ensName: string) => { let imageUrl; - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); try { const avatarResolver = new AvatarResolver(provider); imageUrl = await avatarResolver.getImage(ensName, { @@ -347,7 +347,7 @@ export const fetchRecords = async (ensName: string, { supportedOnly = true }: { const data = response.domains[0] || {}; const rawRecordKeys = data.resolver?.texts || []; - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const resolver = await provider.getResolver(ensName); const supportedRecords = Object.values(ENS_RECORDS); const recordKeys = (rawRecordKeys as ENS_RECORDS[]).filter(key => (supportedOnly ? supportedRecords.includes(key) : true)); @@ -369,7 +369,7 @@ export const fetchCoinAddresses = async ( const response = await ensClient.getCoinTypesByName({ name: ensName }); const data = response.domains[0] || {}; const supportedRecords = Object.values(ENS_RECORDS); - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const resolver = await provider.getResolver(ensName); const rawCoinTypes: number[] = data.resolver?.coinTypes || []; const rawCoinTypesNames: string[] = rawCoinTypes.map(type => formatsByCoinType[type].name); @@ -402,7 +402,7 @@ export const fetchCoinAddresses = async ( }; export const fetchContenthash = async (ensName: string) => { - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const resolver = await provider.getResolver(ensName); const contenthash = await resolver?.getContentHash(); return contenthash; @@ -449,7 +449,7 @@ export const fetchRegistration = async (ensName: string) => { }; export const fetchPrimary = async (ensName: string) => { - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const address = await provider.resolveName(ensName); return { address, @@ -888,7 +888,7 @@ export const getRapActionTypeForTxType = (txType: ENSRegistrationTransactionType export const fetchReverseRecord = async (address: string) => { try { const checksumAddress = getAddress(address); - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const reverseRecord = await provider.lookupAddress(checksumAddress); return reverseRecord ?? ''; } catch (e) { @@ -898,7 +898,7 @@ export const fetchReverseRecord = async (address: string) => { export const fetchResolver = async (ensName: string) => { try { - const provider = await getProvider({ chainId: ChainId.mainnet }); + const provider = await getProviderForNetwork(); const resolver = await provider.getResolver(ensName); return resolver ?? ({} as Resolver); } catch (e) { diff --git a/src/handlers/gasFees.ts b/src/handlers/gasFees.ts index 6d85e0d1b59..aeffb8cddac 100644 --- a/src/handlers/gasFees.ts +++ b/src/handlers/gasFees.ts @@ -1,5 +1,5 @@ +import { Network } from '@/helpers'; import { RainbowFetchClient } from '../rainbow-fetch'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; const rainbowMeteorologyApi = new RainbowFetchClient({ baseURL: 'https://metadata.p.rainbow.me', @@ -10,5 +10,4 @@ const rainbowMeteorologyApi = new RainbowFetchClient({ timeout: 30000, // 30 secs }); -export const rainbowMeteorologyGetData = (chainId: ChainId) => - rainbowMeteorologyApi.get(`/meteorology/v1/gas/${chainIdToNameMapping[chainId]}`, {}); +export const rainbowMeteorologyGetData = (network: Network) => rainbowMeteorologyApi.get(`/meteorology/v1/gas/${network}`, {}); diff --git a/src/handlers/imgix.ts b/src/handlers/imgix.ts index d07591839a8..5df0d639337 100644 --- a/src/handlers/imgix.ts +++ b/src/handlers/imgix.ts @@ -6,6 +6,7 @@ import { Source } from 'react-native-fast-image'; import parse from 'url-parse'; import { isCloudinaryStorageIconLink, signCloudinaryIconUrl } from '@/handlers/cloudinary'; import { logger, RainbowError } from '@/logger'; +import { GOOGLE_USER_CONTENT_URL } from '@/utils/getFullResUrl'; const shouldCreateImgixClient = (): ImgixClient | null => { if (typeof domain === 'string' && !!domain.length && typeof secureURLToken === 'string' && !!secureURLToken.length) { @@ -31,7 +32,7 @@ const staticImgixClient = shouldCreateImgixClient(); // This might be conditional based upon either the runtime // hardware or the number of unique tokens a user may have. const capacity = 1024; -export const staticSignatureLRU: LRUCache = new LRUCache(capacity); +export let staticSignatureLRU: LRUCache = new LRUCache(capacity); interface ImgOptions { w?: number; @@ -116,7 +117,11 @@ const isPossibleToSignUri = (externalImageUri: string | undefined): boolean => { return false; }; -export const maybeSignUri = (externalImageUri: string | undefined, options?: ImgOptions, skipCaching = false): string | undefined => { +export const maybeSignUri = ( + externalImageUri: string | undefined, + options?: ImgOptions, + skipCaching: boolean = false +): string | undefined => { // If the image has already been signed, return this quickly. const signature = `${externalImageUri}-${options?.w}-${options?.fm}`; if (typeof externalImageUri === 'string' && staticSignatureLRU.has(signature as string) && !skipCaching) { diff --git a/src/handlers/localstorage/common.ts b/src/handlers/localstorage/common.ts index 591b9e47d36..12aa8a41735 100644 --- a/src/handlers/localstorage/common.ts +++ b/src/handlers/localstorage/common.ts @@ -1,4 +1,4 @@ -/* global storage*/ +/*global storage*/ import { legacy } from '@/storage/legacy'; import { logger, RainbowError } from '@/logger'; @@ -17,7 +17,7 @@ export const saveLocal = (key = '', data = {}) => { try { legacy.set([key], data); } catch (error) { - logger.error(new RainbowError('[localstorage/common]: saveLocal error')); + logger.error(new RainbowError('Legacy Storage: saveLocal error')); } }; @@ -51,7 +51,7 @@ export const deprecatedSaveLocal = async (key = '', data = {}, version = default key, }); } catch (error) { - logger.error(new RainbowError('[localstorage/common]: deprecatedSaveLocal error')); + logger.error(new RainbowError('Storage: deprecatedSaveLocal error')); } }; @@ -90,7 +90,7 @@ export const deprecatedGetLocal = async (key = '', version = defaultVersion) => case 'ExpiredError': break; default: - logger.error(new RainbowError('[localstorage/common]: deprecatedGetLocal error')); + logger.error(new RainbowError('Storage: deprecatedGetLocal error')); } return null; @@ -109,7 +109,7 @@ export const deprecatedRemoveLocal = (key = '') => { // @ts-expect-error ts-migrate(2552) FIXME: Cannot find name 'storage'. Did you mean 'Storage'... Remove this comment to see the full error message storage.remove({ key }); } catch (error) { - logger.error(new RainbowError('[localstorage/common]: deprecatedRemoveLocal error')); + logger.error(new RainbowError('Storage: deprecatedRemoveLocal error')); } }; diff --git a/src/handlers/localstorage/globalSettings.ts b/src/handlers/localstorage/globalSettings.ts index c54370a7cd1..a9e3769bfbf 100644 --- a/src/handlers/localstorage/globalSettings.ts +++ b/src/handlers/localstorage/globalSettings.ts @@ -1,6 +1,6 @@ -import { ChainId } from '@/networks/types'; import { getGlobal, saveGlobal } from './common'; import { NativeCurrencyKeys } from '@/entities'; +import networkTypes from '@/helpers/networkTypes'; import { Language } from '@/languages'; export const IMAGE_METADATA = 'imageMetadata'; @@ -8,7 +8,7 @@ const KEYBOARD_HEIGHT = 'keyboardHeight'; const APP_ICON = 'appIcon'; const LANGUAGE = 'language'; const NATIVE_CURRENCY = 'nativeCurrency'; -const CHAIN_ID = 'chainId'; +const NETWORK = 'network'; const KEYCHAIN_INTEGRITY_STATE = 'keychainIntegrityState'; const AUTH_TIMELOCK = 'authTimelock'; const PIN_AUTH_ATTEMPTS_LEFT = 'pinAuthAttemptsLeft'; @@ -32,9 +32,9 @@ export const getLanguage = () => getGlobal(LANGUAGE, Language.EN_US); export const saveLanguage = (language: any) => saveGlobal(LANGUAGE, language); -export const getChainId = () => getGlobal(CHAIN_ID, ChainId.mainnet); +export const getNetwork = () => getGlobal(NETWORK, networkTypes.mainnet); -export const saveChainId = (chainId: ChainId) => saveGlobal(CHAIN_ID, chainId); +export const saveNetwork = (network: any) => saveGlobal(NETWORK, network); export const getKeyboardHeight = () => getGlobal(KEYBOARD_HEIGHT, null); diff --git a/src/handlers/localstorage/removeWallet.ts b/src/handlers/localstorage/removeWallet.ts index 1a2934127be..fe450683a61 100644 --- a/src/handlers/localstorage/removeWallet.ts +++ b/src/handlers/localstorage/removeWallet.ts @@ -1,25 +1,23 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { keys } from 'lodash'; +import NetworkTypes from '../../helpers/networkTypes'; import { accountLocalKeys } from './accountLocal'; import { getKey } from './common'; import { walletConnectAccountLocalKeys } from './walletconnectRequests'; import { logger, RainbowError } from '@/logger'; import { removeNotificationSettingsForWallet } from '@/notifications/settings'; -import { Network } from '@/networks/types'; export const removeWalletData = async (accountAddress: any) => { - logger.debug('[localstorage/removeWallet]: removing wallet data', { accountAddress }); + logger.debug('[remove wallet]', { accountAddress }); const allPrefixes = accountLocalKeys.concat(walletConnectAccountLocalKeys); - logger.debug('[localstorage/removeWallet]: all prefixes', { allPrefixes }); - const networks = keys(Network); + logger.debug('[remove wallet] - all prefixes', { allPrefixes }); + const networks = keys(NetworkTypes); const allKeysWithNetworks = allPrefixes.map(prefix => networks.map(network => getKey(prefix, accountAddress, network))); const allKeys = allKeysWithNetworks.flat(); try { await AsyncStorage.multiRemove(allKeys); } catch (error) { - logger.error(new RainbowError('[localstorage/removeWallet]: Error removing wallet data from storage'), { - error, - }); + logger.error(new RainbowError('Error removing wallet data from storage')); } removeNotificationSettingsForWallet(accountAddress); }; diff --git a/src/handlers/localstorage/theme.ts b/src/handlers/localstorage/theme.ts index a18f805da04..32011a99809 100644 --- a/src/handlers/localstorage/theme.ts +++ b/src/handlers/localstorage/theme.ts @@ -2,7 +2,7 @@ import { IS_ANDROID } from '@/env'; import { getGlobal, saveGlobal } from './common'; import { NativeModules } from 'react-native'; import { colors } from '@/styles'; -import { isUsingButtonNavigation } from '@/utils/deviceUtils'; +import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; import { Themes, ThemesType } from '@/theme'; const { NavigationBar } = NativeModules; diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index d68138be447..55a9dd87b5a 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -15,14 +15,13 @@ import { Contract } from '@ethersproject/contracts'; import { MaxUint256 } from '@ethersproject/constants'; import { IS_TESTING } from 'react-native-dotenv'; import { Token } from '../entities/tokens'; -import { estimateGasWithPadding, getProvider, toHexNoLeadingZeros } from './web3'; +import { estimateGasWithPadding, getProviderForNetwork, toHexNoLeadingZeros } from './web3'; import { getRemoteConfig } from '@/model/remoteConfig'; import { Asset } from '@/entities'; import { add, convertRawAmountToDecimalFormat, divide, lessThan, multiply, subtract } from '@/helpers/utilities'; import { erc20ABI, ethUnits } from '@/references'; -import { ethereumUtils } from '@/utils'; -import { logger, RainbowError } from '@/logger'; -import { ChainId } from '@/networks/types'; +import { ethereumUtils, logger } from '@/utils'; +import { ChainId } from '@/__swaps__/types/chains'; export enum Field { INPUT = 'INPUT', @@ -138,9 +137,7 @@ export const getStateDiff = async (provider: StaticJsonRpcProvider, tradeDetails return formattedStateDiff; } } - logger.debug('[swap]: Couldnt get stateDiff...', { - trace, - }); + logger.log('Couldnt get stateDiff...', JSON.stringify(trace, null, 2)); }; export const getSwapGasLimitWithFakeApproval = async ( @@ -171,26 +168,20 @@ export const getSwapGasLimitWithFakeApproval = async ( try { await provider.send('eth_call', [...callParams, stateDiff]); - logger.debug('[swap]: Estimate worked with gasLimit', { - gas, - }); + logger.log(`Estimate worked with gasLimit: `, gas); return true; } catch (e) { - logger.debug('[swap]: Estimate failed with gasLimit', { - gas, - }); + logger.log(`Estimate failed with gasLimit ${gas}. Trying with different amounts...`); return false; } }); if (gasLimit && gasLimit >= ethUnits.basic_swap) { return gasLimit; } else { - logger.debug('[swap]: Could not find a gas estimate'); + logger.log('Could not find a gas estimate'); } } catch (e) { - logger.error(new RainbowError('[swap]: Blew up trying to get state diff. Falling back to defaults'), { - error: e, - }); + logger.log(`Blew up trying to get state diff. Falling back to defaults`, e); } return getDefaultGasLimitForTrade(tradeDetails, chainId); }; @@ -205,7 +196,7 @@ export const isUnwrapNative = ({ buyTokenAddress: string; }) => { return ( - sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() && + sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() && buyTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() ); }; @@ -221,7 +212,7 @@ export const isWrapNative = ({ }) => { return ( sellTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() && - buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() + buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId].toLowerCase() ); }; @@ -234,7 +225,8 @@ export const estimateSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: Quote | null; }): Promise => { - const provider = await getProvider({ chainId }); + const network = ethereumUtils.getNetworkFromChainId(chainId); + const provider = await getProviderForNetwork(network); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } @@ -279,14 +271,10 @@ export const estimateSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug('[swap]: Got gasLimitWithFakeApproval!', { - gasLimitWithFakeApproval, - }); + logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); return gasLimitWithFakeApproval; } catch (e) { - logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { - error: e, - }); + logger.debug('Error estimating swap gas limit with approval', e); } } @@ -310,7 +298,8 @@ export const estimateCrosschainSwapGasLimit = async ({ requiresApprove?: boolean; tradeDetails: CrosschainQuote; }): Promise => { - const provider = await getProvider({ chainId }); + const network = ethereumUtils.getNetworkFromChainId(chainId); + const provider = await getProviderForNetwork(network); if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } @@ -319,14 +308,10 @@ export const estimateCrosschainSwapGasLimit = async ({ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') { try { const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails); - logger.debug('[swap]: Got gasLimitWithFakeApproval!', { - gasLimitWithFakeApproval, - }); + logger.debug(' ✅ Got gasLimitWithFakeApproval!', gasLimitWithFakeApproval); return gasLimitWithFakeApproval; } catch (e) { - logger.error(new RainbowError('[swap]: Error estimating swap gas limit with approval'), { - error: e, - }); + logger.debug('Error estimating swap gas limit with approval', e); } } diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index 00a54669b06..023e7100617 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -52,7 +52,7 @@ export const swapSearch = async (searchParams: { const tokenSearch = await tokenSearchApi.get(url); return { ...tokenSearch.data?.data, chainId: searchParams.chainId }; } catch (e: any) { - logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { + logger.error(new RainbowError(`An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -93,18 +93,17 @@ export const tokenSearch = async (searchParams: { return tokenSearch.data.data.map(token => { const networkKeys = Object.keys(token.networks); - const chainId = Number(networkKeys[0]); - const network = ethereumUtils.getNetworkFromChainId(chainId); + const network = ethereumUtils.getNetworkFromChainId(Number(networkKeys[0])); return { ...token, - chainId, - address: token.networks['1']?.address || token.networks[chainId]?.address, + address: token.networks['1']?.address || token.networks[Number(networkKeys[0])]?.address, network, + chainId: searchParams.chainId, mainnet_address: token.networks['1']?.address, }; }); } catch (e: any) { - logger.error(new RainbowError(`[tokenSearch]: An error occurred while searching for query`), { + logger.error(new RainbowError(`An error occurred while searching for query`), { query: searchParams.query, message: e.message, }); @@ -122,7 +121,7 @@ export const walletFilter = async (params: { addresses: EthereumAddress[]; fromC }); return filteredAddresses?.data?.data || []; } catch (e: any) { - logger.error(new RainbowError(`[tokenSearch]: An error occurred while filter wallet addresses`), { + logger.error(new RainbowError(`An error occurred while filter wallet addresses`), { toChainId: params.toChainId, fromChainId: params.fromChainId, message: e.message, diff --git a/src/handlers/transactions.ts b/src/handlers/transactions.ts index f2cf11f164e..949e03a5990 100644 --- a/src/handlers/transactions.ts +++ b/src/handlers/transactions.ts @@ -128,12 +128,12 @@ export const getTransactionSocketStatus = async (pendingTransaction: RainbowTran pending = false; } } else if (socketResponse.error) { - logger.warn('[getTransactionSocketStatus]: transaction check failed', socketResponse.error); + logger.warn('getTransactionSocketStatus transaction check failed', socketResponse.error); status = TransactionStatus.failed; pending = false; } } catch (e) { - logger.error(new RainbowError('[getTransactionSocketStatus]: transaction check caught')); + logger.error(new RainbowError('getTransactionSocketStatus transaction check caught')); if (IS_TEST) { status = swap?.isBridge ? TransactionStatus.bridged : TransactionStatus.swapped; pending = false; diff --git a/src/handlers/walletReadyEvents.ts b/src/handlers/walletReadyEvents.ts index 1cfa62be144..9f02a762c36 100644 --- a/src/handlers/walletReadyEvents.ts +++ b/src/handlers/walletReadyEvents.ts @@ -53,10 +53,10 @@ export const runWalletBackupStatusChecks = () => { if (!rainbowWalletsNotBackedUp.length) return; - logger.debug('[walletReadyEvents]: there is a rainbow wallet not backed up'); + logger.debug('there is a rainbow wallet not backed up'); const hasSelectedWallet = rainbowWalletsNotBackedUp.find(notBackedUpWallet => notBackedUpWallet.id === selected!.id); - logger.debug('[walletReadyEvents]: rainbow wallet not backed up that is selected?', { + logger.debug('rainbow wallet not backed up that is selected?', { hasSelectedWallet, }); @@ -70,7 +70,7 @@ export const runWalletBackupStatusChecks = () => { } setTimeout(() => { - logger.debug(`[walletReadyEvents]: showing ${stepType} backup sheet for selected wallet`); + logger.debug(`showing ${stepType} backup sheet for selected wallet`); triggerOnSwipeLayout(() => Navigation.handleAction(Routes.BACKUP_SHEET, { step: stepType, @@ -99,11 +99,11 @@ export const runFeatureUnlockChecks = async (): Promise => { } }); - logger.debug('[walletReadyEvents]: WALLETS TO CHECK', { walletsToCheck }); + logger.debug('WALLETS TO CHECK', { walletsToCheck }); if (!walletsToCheck.length) return false; - logger.debug('[walletReadyEvents]: Feature Unlocks: Running Checks'); + logger.debug('Feature Unlocks: Running Checks'); // short circuits once the first feature is unlocked for (const featureUnlockCheck of featureUnlockChecks) { diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 13416a8aceb..95822333253 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -10,6 +10,7 @@ import { startsWith } from 'lodash'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AssetType, NewTransaction, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; +import { Network } from '@/helpers/networkTypes'; import { isUnstoppableAddressFormat } from '@/helpers/validators'; import { ARBITRUM_ETH_ADDRESS, @@ -35,16 +36,17 @@ import { import { ethereumUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; import { IS_IOS, RPC_PROXY_API_KEY, RPC_PROXY_BASE_URL } from '@/env'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj, getNetworkObject } from '@/networks'; import store from '@/redux/store'; -import { ChainId } from '@/networks/types'; +import { getNetworkFromChainId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; export enum TokenStandard { ERC1155 = 'ERC1155', ERC721 = 'ERC721', } -export const chainsProviders = new Map(); +export const networkProviders = new Map(); /** * Creates an rpc endpoint for a given chain id using the Rainbow rpc proxy. @@ -71,29 +73,30 @@ export const proxyRpcEndpoint = (chainId: number, customEndpoint?: string) => { }`; } else { if (customEndpoint) return customEndpoint; - switch (chainId) { - case ChainId.arbitrum: + const network = ethereumUtils.getNetworkFromChainId(chainId); + switch (network) { + case Network.arbitrum: return arbitrum_mainnet_rpc; - case ChainId.goerli: + case Network.goerli: return ethereum_goerli_rpc; - case ChainId.optimism: + case Network.optimism: return optimism_mainnet_rpc; - case ChainId.polygon: + case Network.polygon: return polygon_mainnet_rpc; - case ChainId.base: + case Network.base: return base_mainnet_rpc; - case ChainId.bsc: + case Network.bsc: return bsc_mainnet_rpc; - case ChainId.zora: + case Network.zora: return zora_mainnet_rpc; - case ChainId.avalanche: + case Network.avalanche: return avalanche_mainnet_rpc; - case ChainId.blast: + case Network.blast: return blast_mainnet_rpc; - case ChainId.degen: + case Network.degen: return degen_mainnet_rpc; - case ChainId.gnosis: - case ChainId.mainnet: + case Network.gnosis: + case Network.mainnet: default: return ethereum_mainnet_rpc; } @@ -116,7 +119,7 @@ type GasParamsInput = { gasPrice: BigNumberish } & { /** * The input data provied to `getTxDetails`. */ -type TransactionDetailsInput = Pick & +type TransactionDetailsInput = Pick & Pick & GasParamsInput; @@ -127,7 +130,7 @@ type TransactionDetailsReturned = { data?: TransactionRequest['data']; from?: TransactionRequest['from']; gasLimit?: string; - chainId?: ChainId | string; + network?: Network | string; to?: TransactionRequest['to']; value?: TransactionRequest['value']; nonce?: TransactionRequest['nonce']; @@ -147,13 +150,23 @@ type NewTransactionNonNullable = { */ export let web3Provider: StaticJsonRpcProvider = null as unknown as StaticJsonRpcProvider; +/** + * @desc Checks whether or not a `Network | string` union type should be + * treated as a `Network` based on its prefix, as opposed to a `string` type. + * @param network The network to check. + * @return A type predicate of `network is Network`. + */ +const isNetworkEnum = (network: Network | string): network is Network => { + return !network.startsWith('http://'); +}; + /** * @desc Sets a different web3 provider. * @param network The network to set. * @return A promise that resolves with an Ethers Network when the provider is ready. */ -export const web3SetHttpProvider = async (chainId: ChainId): Promise => { - web3Provider = await getProvider({ chainId }); +export const web3SetHttpProvider = async (network: Network | string): Promise => { + web3Provider = await getProviderForNetwork(network); return web3Provider.ready; }; @@ -166,42 +179,92 @@ export const isL2Chain = ({ chainId }: { chainId: ChainId }): boolean => { return getNetworkObject({ chainId }).networkType === 'layer2'; }; +/** + * @desc Checks whether a provider is HardHat. + * @param providerUrl The provider URL. + * @return Whether or not the provider is HardHat. + */ +export const isHardHat = (providerUrl: string): boolean => { + return providerUrl?.startsWith('http://') && providerUrl?.endsWith('8545'); +}; + /** * @desc Checks if the given network is a testnet. * @param network The network to check. * @return Whether or not the network is a testnet. */ -export const isTestnetChain = ({ chainId }: { chainId: ChainId }): boolean => { - return getNetworkObject({ chainId }).networkType === 'testnet'; +export const isTestnetNetwork = (network: Network): boolean => { + return getNetworkObj(network as Network).networkType === 'testnet'; }; // shoudl figure out better way to include this in networks export const getFlashbotsProvider = async () => { return new StaticJsonRpcProvider( proxyRpcEndpoint( - ChainId.mainnet, + 1, 'https://rpc.flashbots.net/?hint=hash&builder=flashbots&builder=f1b.io&builder=rsync&builder=beaverbuild.org&builder=builder0x69&builder=titan&builder=eigenphi&builder=boba-builder' - ) + ), + Network.mainnet ); }; -export const getCachedProviderForNetwork = (chainId: ChainId = ChainId.mainnet): StaticJsonRpcProvider | undefined => { - return chainsProviders.get(chainId); +export const getCachedProviderForNetwork = (network: Network = Network.mainnet): StaticJsonRpcProvider | undefined => { + return networkProviders.get(network); }; -export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { - const cachedProvider = chainsProviders.get(chainId); +/** + * @desc Gets or constructs a web3 provider for the specified network. + * @param network The network as a `Network` or string. + * @return The provider for the network. + */ +export const getProviderForNetwork = (network: Network | string = Network.mainnet): StaticJsonRpcProvider => { + const isSupportedNetwork = isNetworkEnum(network); + const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; - const networkObject = getNetworkObject({ chainId }); + if (isSupportedNetwork && cachedProvider) { + return cachedProvider; + } + + if (!isSupportedNetwork) { + const provider = new StaticJsonRpcProvider(network, Network.mainnet); + networkProviders.set(Network.mainnet, provider); + return provider; + } else { + const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); + networkProviders.set(network, provider); + return provider; + } +}; + +export const getProvider = ({ chainId }: { chainId: number }): StaticJsonRpcProvider => { + const network = getNetworkFromChainId(chainId); + const isSupportedNetwork = isNetworkEnum(network); + const cachedProvider = isSupportedNetwork ? networkProviders.get(network) : undefined; - if (cachedProvider && cachedProvider?.connection.url === networkObject.rpc()) { + if (isSupportedNetwork && cachedProvider) { return cachedProvider; } - const provider = new StaticJsonRpcProvider(networkObject.rpc(), networkObject.id); - chainsProviders.set(chainId, provider); + if (!isSupportedNetwork) { + const provider = new StaticJsonRpcProvider(network, Network.mainnet); + networkProviders.set(Network.mainnet, provider); + return provider; + } else { + const provider = new StaticJsonRpcProvider(getNetworkObj(network).rpc(), getNetworkObj(network).id); + networkProviders.set(network, provider); + return provider; + } +}; - return provider; +/** + * @desc Checks if the active network is Hardhat. + * @returns boolean: `true` if connected to Hardhat. + */ +export const getIsHardhatConnected = (): boolean => { + const currentNetwork = store.getState().settings.network; + const currentProviderUrl = getCachedProviderForNetwork(currentNetwork)?.connection?.url; + const connectedToHardhat = !!currentProviderUrl && isHardHat(currentProviderUrl); + return connectedToHardhat; }; /** @@ -351,16 +414,16 @@ export async function estimateGasWithPadding( const code = to ? await p.getCode(to) : undefined; // 2 - if it's not a contract AND it doesn't have any data use the default gas limit if ((!contractCallEstimateGas && !to) || (to && !data && (!code || code === '0x'))) { - logger.debug('[web3]: ⛽ Skipping estimates, using default', { + logger.debug('⛽ Skipping estimates, using default', { ethUnits: ethUnits.basic_tx.toString(), }); return ethUnits.basic_tx.toString(); } - logger.debug('[web3]: ⛽ Calculating safer gas limit for last block'); + logger.debug('⛽ Calculating safer gas limit for last block'); // 3 - If it is a contract, call the RPC method `estimateGas` with a safe value const saferGasLimit = fraction(gasLimit.toString(), 19, 20); - logger.debug('[web3]: ⛽ safer gas limit for last block is', { saferGasLimit }); + logger.debug('⛽ safer gas limit for last block is', { saferGasLimit }); txPayloadToEstimate[contractCallEstimateGas ? 'gasLimit' : 'gas'] = toHex(saferGasLimit); @@ -372,7 +435,7 @@ export async function estimateGasWithPadding( const lastBlockGasLimit = addBuffer(gasLimit.toString(), 0.9); const paddedGas = addBuffer(estimatedGas.toString(), paddingFactor.toString()); - logger.debug('[web3]: ⛽ GAS CALCULATIONS!', { + logger.debug('⛽ GAS CALCULATIONS!', { estimatedGas: estimatedGas.toString(), gasLimit: gasLimit.toString(), lastBlockGasLimit: lastBlockGasLimit, @@ -381,24 +444,24 @@ export async function estimateGasWithPadding( // If the safe estimation is above the last block gas limit, use it if (greaterThan(estimatedGas.toString(), lastBlockGasLimit)) { - logger.debug('[web3]: ⛽ returning orginal gas estimation', { + logger.debug('⛽ returning orginal gas estimation', { esimatedGas: estimatedGas.toString(), }); return estimatedGas.toString(); } // If the estimation is below the last block gas limit, use the padded estimate if (greaterThan(lastBlockGasLimit, paddedGas)) { - logger.debug('[web3]: ⛽ returning padded gas estimation', { paddedGas }); + logger.debug('⛽ returning padded gas estimation', { paddedGas }); return paddedGas; } // otherwise default to the last block gas limit - logger.debug('[web3]: ⛽ returning last block gas limit', { lastBlockGasLimit }); + logger.debug('⛽ returning last block gas limit', { lastBlockGasLimit }); return lastBlockGasLimit; } catch (e) { /* * Reported ~400x per day, but if it's not actionable it might as well be a warning. */ - logger.warn('[web3]: Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); + logger.warn('Error calculating gas limit with padding', { message: e instanceof Error ? e.message : 'Unknown error' }); return null; } } @@ -433,8 +496,8 @@ export const getTransactionCount = async (address: string): Promise & GasParamsInput): GasParamsReturned => { - return getNetworkObject({ chainId: transaction.chainId }).gas.gasType === 'legacy' +export const getTransactionGasParams = (transaction: Pick & GasParamsInput): GasParamsReturned => { + return getNetworkObj(transaction.network).gas.gasType === 'legacy' ? { gasPrice: toHex(transaction.gasPrice), } @@ -489,7 +552,7 @@ export const resolveUnstoppableDomain = async (domain: string): Promise { - logger.error(new RainbowError(`[web3]: resolveUnstoppableDomain error`), { + logger.error(new RainbowError(`resolveUnstoppableDomain error`), { message: error.message, }); return null; @@ -510,7 +573,7 @@ export const resolveNameOrAddress = async (nameOrAddress: string): Promise ): Promise => { const recipient = await resolveNameOrAddress(transaction.to); @@ -545,7 +608,7 @@ export const getTransferNftTransaction = async ( data, from, gasLimit: transaction.gasLimit?.toString(), - chainId: transaction.chainId, + network: transaction.network, nonce, to: contractAddress, ...gasParams, @@ -561,7 +624,7 @@ export const getTransferNftTransaction = async ( export const getTransferTokenTransaction = async ( transaction: Pick< NewTransactionNonNullable, - 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'chainId' | 'maxFeePerGas' | 'maxPriorityFeePerGas' + 'asset' | 'from' | 'to' | 'amount' | 'gasPrice' | 'gasLimit' | 'network' | 'maxFeePerGas' | 'maxPriorityFeePerGas' > ): Promise => { const value = convertAmountToRawAmount(transaction.amount, transaction.asset.decimals); @@ -572,7 +635,7 @@ export const getTransferTokenTransaction = async ( data, from: transaction.from, gasLimit: transaction.gasLimit?.toString(), - chainId: transaction.chainId, + network: transaction.network, to: transaction.asset.address, ...gasParams, }; @@ -645,10 +708,10 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd const lowercasedContractAddress = asset.asset_contract.address.toLowerCase(); const standard = asset.asset_contract?.schema_name; let data: string | undefined; - if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { + if (lowercasedContractAddress === CRYPTO_KITTIES_NFT_ADDRESS && asset.network === Network.mainnet) { const transferMethod = smartContractMethods.token_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); - } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.chainId === ChainId.mainnet) { + } else if (lowercasedContractAddress === CRYPTO_PUNKS_NFT_ADDRESS && asset.network === Network.mainnet) { const transferMethod = smartContractMethods.punk_transfer; data = ethereumUtils.getDataString(transferMethod.hash, [ethereumUtils.removeHexPrefix(to), convertStringToHex(asset.id)]); } else if (standard === TokenStandard.ERC1155) { @@ -677,7 +740,7 @@ export const getDataForNftTransfer = (from: string, to: string, asset: ParsedAdd * @param [{address, amount, asset, gasLimit, recipient}] The transaction * initialization details. * @param provider The RCP provider to use. - * @param chainId The chainId for the transaction + * @param network The network for the transaction * @return The transaction request. */ export const buildTransaction = async ( @@ -695,7 +758,7 @@ export const buildTransaction = async ( gasLimit?: string; }, provider: StaticJsonRpcProvider | null, - chainId: ChainId + network: Network ): Promise => { const _amount = amount && Number(amount) ? convertAmountToRawAmount(amount, asset.decimals) : estimateAssetBalancePortion(asset); const value = _amount.toString(); @@ -714,7 +777,7 @@ export const buildTransaction = async ( from: address, to: contractAddress, }; - } else if (!isNativeAsset(asset.address, chainId)) { + } else if (!isNativeAsset(asset.address, ethereumUtils.getChainIdFromNetwork(network))) { const transferData = getDataForTokenTransfer(value, _recipient); txData = { data: transferData, @@ -734,7 +797,7 @@ export const buildTransaction = async ( * to `false`. * @param provider If provided, a provider to use instead of the default * cached `web3Provider`. - * @param chainId The chainId to use, defaulting to `ChainId.mainnet`. + * @param network The network to use, defaulting to `Network.mainnet`. * @returns The estimated gas limit. */ export const estimateGasLimit = async ( @@ -751,9 +814,9 @@ export const estimateGasLimit = async ( }, addPadding = false, provider: StaticJsonRpcProvider | null = null, - chainId: ChainId = ChainId.mainnet + network: Network = Network.mainnet ): Promise => { - const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, chainId); + const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, network); if (addPadding) { return estimateGasWithPadding(estimateGasData, null, null, provider); diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index c0734b50313..055210d6fd8 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -3,18 +3,31 @@ import { MMKV } from 'react-native-mmkv'; import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; -import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; +import { + showReloadButton, + showSwitchModeButton, + // @ts-ignore + showConnectToHardhatButton, +} from '../config/debug'; import { defaultConfig } from '../config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; import { STORAGE_IDS } from '@/model/mmkv'; -import { IS_TESTING } from 'react-native-dotenv'; +import { + // @ts-ignore + HARDHAT_URL_ANDROID, + // @ts-ignore + HARDHAT_URL_IOS, + // @ts-ignore + IS_TESTING, +} from 'react-native-dotenv'; +import { web3SetHttpProvider } from '@/handlers/web3'; import { logger, RainbowError } from '@/logger'; +import networkTypes from '@/helpers/networkTypes'; import { explorerInit } from '@/redux/explorer'; import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const RainbowContext = createContext({}); const storageKey = 'config'; @@ -27,7 +40,6 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { // This value is hold here to prevent JS VM from shutting down // on unmounting all shared values. useSharedValue(0); - const { setConnectedToHardhat } = useConnectedToHardhatStore(); const [config, setConfig] = useState>( Object.entries(defaultConfig).reduce((acc, [key, { value }]) => ({ ...acc, [key]: value }), {}) ); @@ -63,17 +75,17 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) { const connectToHardhat = useCallback(async () => { try { - setConnectedToHardhat(true); - logger.debug('connected to hardhat'); + const ready = await web3SetHttpProvider('http://127.0.0.1:8545'); + logger.debug('connected to hardhat', { ready }); } catch (e: any) { - setConnectedToHardhat(false); + await web3SetHttpProvider(networkTypes.mainnet); logger.error(new RainbowError('error connecting to hardhat'), { message: e.message, }); } dispatch(explorerInit()); Navigation.handleAction(Routes.WALLET_SCREEN, {}); - }, [dispatch, setConnectedToHardhat]); + }, [dispatch]); return ( diff --git a/src/helpers/accountInfo.ts b/src/helpers/accountInfo.ts index d694523cb71..a3005dde802 100644 --- a/src/helpers/accountInfo.ts +++ b/src/helpers/accountInfo.ts @@ -17,7 +17,7 @@ export function getAccountProfileInfo(selectedWallet: any, walletNames: any, acc const accountENS = walletNames?.[accountAddress]; - const selectedAccount = selectedWallet.addresses?.find((account: any) => account.address === accountAddress); + const selectedAccount = selectedWallet.addresses.find((account: any) => account.address === accountAddress); if (!selectedAccount) { return {}; diff --git a/src/helpers/ens.ts b/src/helpers/ens.ts index f6be6fdf771..8a1e81f8e7d 100644 --- a/src/helpers/ens.ts +++ b/src/helpers/ens.ts @@ -8,7 +8,7 @@ import { atom } from 'recoil'; import { InlineFieldProps } from '../components/inputs/InlineField'; import { add, addBuffer, convertAmountAndPriceToNativeDisplay, divide, fromWei, handleSignificantDecimals, multiply } from './utilities'; import { ENSRegistrationRecords, EthereumAddress } from '@/entities'; -import { getProvider, toHex } from '@/handlers/web3'; +import { getProviderForNetwork, toHex } from '@/handlers/web3'; import { gweiToWei } from '@/parsers'; import { ENSBaseRegistrarImplementationABI, @@ -25,7 +25,6 @@ import { import { colors } from '@/styles'; import { labelhash } from '@/utils'; import { encodeContenthash, isValidContenthash } from '@/utils/contenthash'; -import { ChainId } from '@/networks/types'; export const ENS_SECONDS_WAIT = 60; export const ENS_SECONDS_PADDING = 5; @@ -368,27 +367,27 @@ export const deprecatedTextRecordFields = { export const ENS_DOMAIN = '.eth'; const getENSRegistrarControllerContract = async (wallet?: Signer, registrarAddress?: string) => { - const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); + const signerOrProvider = wallet || (await getProviderForNetwork()); return new Contract(registrarAddress || ensETHRegistrarControllerAddress, ENSETHRegistrarControllerABI, signerOrProvider); }; const getENSPublicResolverContract = async (wallet?: Signer, resolverAddress?: EthereumAddress) => { - const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); + const signerOrProvider = wallet || (await getProviderForNetwork()); return new Contract(resolverAddress || ensPublicResolverAddress, ENSPublicResolverABI, signerOrProvider); }; const getENSReverseRegistrarContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); + const signerOrProvider = wallet || (await getProviderForNetwork()); return new Contract(ensReverseRegistrarAddress, ENSReverseRegistrarABI, signerOrProvider); }; const getENSBaseRegistrarImplementationContract = async (wallet?: Signer) => { - const signerOrProvider = wallet || (await getProvider({ chainId: ChainId.mainnet })); + const signerOrProvider = wallet || (await getProviderForNetwork()); return new Contract(ensBaseRegistrarImplementationAddress, ENSBaseRegistrarImplementationABI, signerOrProvider); }; const getENSRegistryContract = async (wallet?: Signer) => { - const signerOrProvider = wallet ?? (await getProvider({ chainId: ChainId.mainnet })); + const signerOrProvider = wallet ?? (await getProviderForNetwork()); return new Contract(ensRegistryAddress, ENSRegistryWithFallbackABI, signerOrProvider); }; diff --git a/src/helpers/findWalletWithAccount.ts b/src/helpers/findWalletWithAccount.ts index 109371d2b82..85b06a2bd8a 100644 --- a/src/helpers/findWalletWithAccount.ts +++ b/src/helpers/findWalletWithAccount.ts @@ -5,7 +5,7 @@ export function findWalletWithAccount(wallets: { [key: string]: RainbowWallet }, let walletWithAccount: RainbowWallet | undefined; sortedKeys.forEach(key => { const wallet = wallets[key]; - const found = wallet.addresses?.find((account: any) => account.address === accountAddress); + const found = wallet.addresses.find((account: any) => account.address === accountAddress); if (found) { walletWithAccount = wallet; } diff --git a/src/helpers/gas.ts b/src/helpers/gas.ts index 69b7a786578..540cf9752ad 100644 --- a/src/helpers/gas.ts +++ b/src/helpers/gas.ts @@ -1,7 +1,7 @@ +import { Network } from '@/networks/types'; import { memoFn } from '../utils/memoFn'; import { gasUtils } from '@/utils'; -import { getNetworkObject } from '@/networks'; -import { ChainId } from '@/networks/types'; +import { getNetworkObj } from '@/networks'; const { GasTrends } = gasUtils; const { FALLING, NO_TREND, RISING, STABLE, SURGING } = GasTrends; @@ -25,8 +25,8 @@ export const getTrendKey = memoFn((trend: number) => { return NO_TREND; }); -export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { - const networkObject = getNetworkObject({ chainId }); +export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { + const networkObject = getNetworkObj(txNetwork); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; @@ -38,8 +38,8 @@ export const calculateMinerTipAddDifference = memoFn((maxPriorityFee: string, ch } }); -export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, chainId: ChainId) => { - const networkObject = getNetworkObject({ chainId }); +export const calculateMinerTipSubstDifference = memoFn((maxPriorityFee: string, txNetwork: Network) => { + const networkObject = getNetworkObj(txNetwork); const isL2 = networkObject.networkType === 'layer2'; const FEE_INCREMENT = isL2 ? PRIORITY_FEE_L2_INCREMENT : PRIORITY_FEE_INCREMENT; const FEE_THRESHOLD = isL2 ? PRIORITY_FEE_L2_THRESHOLD : PRIORITY_FEE_THRESHOLD; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 4a38d19659d..eb9b7a803b2 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -5,5 +5,6 @@ export { default as CurrencySelectionTypes } from './currencySelectionTypes'; export { default as ExchangeModalTypes } from './exchangeModalTypes'; export { default as isKeyboardOpen } from './isKeyboardOpen'; export { default as isReanimatedAvailable } from './isReanimatedAvailable'; +export { default as NetworkTypes, Network } from './networkTypes'; export { default as TokenSectionTypes } from './tokenSectionTypes'; export { StatusBarHelper }; diff --git a/src/helpers/networkInfo.ts b/src/helpers/networkInfo.ts index be81a8bad48..2bda8506ad8 100644 --- a/src/helpers/networkInfo.ts +++ b/src/helpers/networkInfo.ts @@ -1,8 +1,8 @@ -import { Network } from '@/networks/types'; +import networkTypes from './networkTypes'; // TODO: networkInfo is DEPRECATED after the new network support changes const networkInfo = { - [`${Network.mainnet}`]: { + [`${networkTypes.mainnet}`]: { balance_checker_contract_address: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', color: '#0E76FD', disabled: false, @@ -10,9 +10,9 @@ const networkInfo = { faucet_url: null, name: 'Ethereum', gasToken: 'ETH', - value: Network.mainnet, + value: networkTypes.mainnet, }, - [`${Network.goerli}`]: { + [`${networkTypes.goerli}`]: { balance_checker_contract_address: '0xf3352813b612a2d198e437691557069316b84ebe', color: '#f6c343', disabled: false, @@ -21,9 +21,9 @@ const networkInfo = { name: 'Goerli', gasToken: 'ETH', testnet: true, - value: Network.goerli, + value: networkTypes.goerli, }, - [`${Network.arbitrum}`]: { + [`${networkTypes.arbitrum}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#2D374B', disabled: false, @@ -32,9 +32,9 @@ const networkInfo = { layer2: true, name: 'Arbitrum', gasToken: 'ETH', - value: Network.arbitrum, + value: networkTypes.arbitrum, }, - [`${Network.optimism}`]: { + [`${networkTypes.optimism}`]: { balance_checker_contract_address: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', color: '#FF4040', disabled: false, @@ -43,9 +43,9 @@ const networkInfo = { layer2: true, name: 'Optimism', gasToken: 'ETH', - value: Network.optimism, + value: networkTypes.optimism, }, - [`${Network.polygon}`]: { + [`${networkTypes.polygon}`]: { balance_checker_contract_address: '0x54A4E5800345c01455a7798E0D96438364e22723', color: '#8247E5', disabled: false, @@ -55,9 +55,9 @@ const networkInfo = { longName: 'Polygon (Matic)', name: 'Polygon', gasToken: 'MATIC', - value: Network.polygon, + value: networkTypes.polygon, }, - [`${Network.bsc}`]: { + [`${networkTypes.bsc}`]: { balance_checker_contract_address: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', color: '#F0B90B', disabled: false, @@ -67,7 +67,7 @@ const networkInfo = { longName: 'Binance Smart Chain', name: 'BSC', gasToken: 'BNB', - value: Network.bsc, + value: networkTypes.bsc, }, }; diff --git a/src/helpers/networkTypes.ts b/src/helpers/networkTypes.ts new file mode 100644 index 00000000000..62e425cda73 --- /dev/null +++ b/src/helpers/networkTypes.ts @@ -0,0 +1,31 @@ +export enum Network { + arbitrum = 'arbitrum', + goerli = 'goerli', + mainnet = 'mainnet', + optimism = 'optimism', + polygon = 'polygon', + base = 'base', + bsc = 'bsc', + zora = 'zora', + gnosis = 'gnosis', + avalanche = 'avalanche', + blast = 'blast', + degen = 'degen', +} + +// We need to keep this one until +// we have typescript everywhere +export default { + arbitrum: 'arbitrum' as Network, + goerli: 'goerli' as Network, + mainnet: 'mainnet' as Network, + optimism: 'optimism' as Network, + polygon: 'polygon' as Network, + base: 'base' as Network, + bsc: 'bsc' as Network, + zora: 'zora' as Network, + gnosis: 'gnosis' as Network, + avalanche: 'avalanche' as Network, + blast: 'blast' as Network, + degen: 'degen' as Network, +}; diff --git a/src/helpers/signingWallet.ts b/src/helpers/signingWallet.ts index 7d41fc0e074..5722ea9c0ee 100644 --- a/src/helpers/signingWallet.ts +++ b/src/helpers/signingWallet.ts @@ -8,8 +8,9 @@ import { signingWalletAddress, signingWallet as signingWalletKeychain } from '.. import { EthereumAddress } from '@/entities'; import AesEncryptor from '@/handlers/aesEncryption'; import { addHexPrefix } from '@/handlers/web3'; +import { logger } from '@/utils'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger, RainbowError } from '@/logger'; +import { logger as Logger, RainbowError } from '@/logger'; export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): Promise { let alreadyExistingWallet = await loadString(signingWalletAddress); @@ -19,7 +20,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P const { wallet, address } = await deriveAccountFromWalletInput(walletSeed); if (!wallet || !address) { - logger.error(new RainbowError('[signingWallet]: wallet or address undefined')); + Logger.error(new RainbowError('signingWallet - wallet or address undefined')); // @ts-ignore need to handle types in case wallet or address are null return null; } @@ -34,7 +35,7 @@ export async function getPublicKeyOfTheSigningWalletAndCreateWalletIfNeeded(): P await saveString(signingWalletAddress, address, publicAccessControlOptions); alreadyExistingWallet = address; } - logger.debug('[signingWallet]: Signing wallet already existing'); + logger.log('Signing wallet already existing'); return alreadyExistingWallet; } @@ -47,7 +48,7 @@ export async function getSignatureForSigningWalletAndCreateSignatureIfNeeded(add if (address === verifyMessage(publicKeyForTheSigningWallet, decryptedSignature)) { return decryptedSignature; } else { - logger.debug('[signingWallet]: Signature does not match. Creating a new one.'); + logger.log('Signature does not match. Creating a new one.'); alreadyExistingEncodedSignature = null; return createSignature(address); } @@ -60,14 +61,14 @@ export async function signWithSigningWallet(messageToSign: string): Promise { StatusBar.setTranslucent(translucent); @@ -29,3 +30,7 @@ export const setDarkContent = (isAnimated = true) => { barStyle: 'dark-content', }); }; + +export const isUsingButtonNavigation = () => { + return getSoftMenuBarHeight() > 95; +}; diff --git a/src/helpers/validators.ts b/src/helpers/validators.ts index 3016e5c7950..72aceda6c26 100644 --- a/src/helpers/validators.ts +++ b/src/helpers/validators.ts @@ -1,8 +1,8 @@ import { isValidAddress } from 'ethereumjs-util'; import { memoFn } from '../utils/memoFn'; -import { getProvider, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; +import { Network } from './networkTypes'; +import { getProviderForNetwork, isHexStringIgnorePrefix, isValidMnemonic, resolveUnstoppableDomain } from '@/handlers/web3'; import { sanitizeSeedPhrase } from '@/utils/formatters'; -import { ChainId } from '@/networks/types'; // Currently supported Top Level Domains from Unstoppable Domains const supportedUnstoppableDomains = ['888', 'bitcoin', 'blockchain', 'coin', 'crypto', 'dao', 'nft', 'wallet', 'x', 'zil']; @@ -68,7 +68,7 @@ export const checkIsValidAddressOrDomainFormat = (address: any) => { * @return {Boolean} */ export const checkIsValidAddressOrDomain = async (address: any) => { - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(Network.mainnet); if (isENSAddressFormat(address)) { try { const resolvedAddress = await provider.resolveName(address); diff --git a/src/helpers/walletConnectNetworks.ts b/src/helpers/walletConnectNetworks.ts index cb02ab79f2c..326399fd897 100644 --- a/src/helpers/walletConnectNetworks.ts +++ b/src/helpers/walletConnectNetworks.ts @@ -1,12 +1,12 @@ -import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; +import { RainbowNetworks, getNetworkObj } from '@/networks'; +import { Network } from '@/networks/types'; import store from '@/redux/store'; import { showActionSheetWithOptions } from '@/utils'; import * as i18n from '@/languages'; -import { ChainId } from '@/networks/types'; const androidNetworkActions = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworkObjects.filter( + return RainbowNetworks.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => network.name); }; @@ -15,7 +15,7 @@ export const NETWORK_MENU_ACTION_KEY_FILTER = 'switch-to-network-'; export const networksMenuItems = () => { const { testnetsEnabled } = store.getState().settings; - return RainbowNetworkObjects.filter( + return RainbowNetworks.filter( ({ features, networkType }) => features.walletconnect && (testnetsEnabled || networkType !== 'testnet') ).map(network => ({ actionKey: `${NETWORK_MENU_ACTION_KEY_FILTER}${network.value}`, @@ -76,8 +76,7 @@ export const androidShowNetworksActionSheet = (callback: any) => { (idx: any) => { if (idx !== undefined) { const networkActions = androidNetworkActions(); - const networkObj = - RainbowNetworkObjects.find(network => network.name === networkActions[idx]) || getNetworkObject({ chainId: ChainId.mainnet }); + const networkObj = RainbowNetworks.find(network => network.name === networkActions[idx]) || getNetworkObj(Network.mainnet); callback({ chainId: networkObj.id, network: networkObj.value }); } } diff --git a/src/hooks/charts/useChartInfo.ts b/src/hooks/charts/useChartInfo.ts index e2402eb2e9f..05efb97f142 100644 --- a/src/hooks/charts/useChartInfo.ts +++ b/src/hooks/charts/useChartInfo.ts @@ -1,11 +1,18 @@ import { useNavigation, useRoute } from '@react-navigation/native'; -import { useCallback } from 'react'; +import { isEmpty } from 'lodash'; +import { useCallback, useEffect, useState } from 'react'; +import isEqual from 'react-fast-compare'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import { useCallbackOne } from 'use-memo-one'; +import { disableCharts } from '../../config/debug'; import { DEFAULT_CHART_TYPE } from '../../redux/charts'; import { metadataClient } from '@/graphql'; import { useQuery } from '@tanstack/react-query'; import { createQueryKey } from '@/react-query'; -import { SupportedCurrencyKey } from '@/references'; -import { ChainId } from '@/networks/types'; +import { getNetworkObj } from '@/networks'; +import { NetworkProperties } from '@/networks/types'; +import { Network } from '@/helpers'; const chartTimes = ['hour', 'day', 'week', 'month', 'year'] as const; type ChartTime = (typeof chartTimes)[number]; @@ -16,19 +23,9 @@ const getChartTimeArg = (selected: ChartTime) => export type ChartData = { x: number; y: number }; -const fetchPriceChart = async ({ - address, - chainId, - currency, - time, -}: { - address: string; - chainId: ChainId; - currency: SupportedCurrencyKey; - time: ChartTime; -}) => { +const fetchPriceChart = async (time: ChartTime, chainId: NetworkProperties['id'], address: string) => { const priceChart = await metadataClient - .priceChart({ address, chainId, currency, ...getChartTimeArg(time) }) + .priceChart({ address, chainId, ...getChartTimeArg(time) }) .then(d => d.token?.priceCharts[time] as PriceChartTimeData); return priceChart?.points?.reduce((result, point) => { result.push({ x: point[0], y: point[1] }); @@ -36,17 +33,7 @@ const fetchPriceChart = async ({ }, [] as ChartData[]); }; -export const usePriceChart = ({ - mainnetAddress, - address, - currency, - chainId, -}: { - mainnetAddress?: string; - address: string; - currency: SupportedCurrencyKey; - chainId: ChainId; -}) => { +export const usePriceChart = ({ mainnetAddress, address, network }: { mainnetAddress?: string; address: string; network: Network }) => { const { setParams } = useNavigation(); const updateChartType = useCallback( (type: ChartTime) => { @@ -60,11 +47,12 @@ export const usePriceChart = ({ params: any; }>(); const chartType = params?.chartType ?? DEFAULT_CHART_TYPE; + const chainId = getNetworkObj(network).id; + const mainnetChainId = getNetworkObj(Network.mainnet).id; const query = useQuery({ queryFn: async () => { - const chart = await fetchPriceChart({ address, chainId, currency, time: chartType }); - if (!chart && mainnetAddress) - return fetchPriceChart({ address: mainnetAddress, chainId: ChainId.mainnet, currency, time: chartType }); + const chart = await fetchPriceChart(chartType, chainId, address); + if (!chart && mainnetAddress) return fetchPriceChart(chartType, mainnetChainId, mainnetAddress); return chart || null; }, queryKey: createQueryKey('price chart', { address, chainId, chartType }), diff --git a/src/hooks/charts/useChartThrottledPoints.ts b/src/hooks/charts/useChartThrottledPoints.ts index 3586efe49c6..a88dc3b111c 100644 --- a/src/hooks/charts/useChartThrottledPoints.ts +++ b/src/hooks/charts/useChartThrottledPoints.ts @@ -98,9 +98,8 @@ export default function useChartThrottledPoints({ updateChartType, } = usePriceChart({ address: asset.address, - chainId: asset.chainId, + network: asset.network, mainnetAddress: asset?.mainnet_address || asset?.mainnetAddress, - currency: nativeCurrency, }); const [throttledPoints, setThrottledPoints] = useState(() => traverseData({ nativePoints: [], points: [] }, chart)); diff --git a/src/hooks/useAccountAsset.ts b/src/hooks/useAccountAsset.ts index 2d2bccc2699..146e6412ff9 100644 --- a/src/hooks/useAccountAsset.ts +++ b/src/hooks/useAccountAsset.ts @@ -1,10 +1,9 @@ -import { NativeCurrencyKey } from '@/entities'; import useAccountSettings from './useAccountSettings'; import { parseAssetNative } from '@/parsers'; import { useUserAsset } from '@/resources/assets/useUserAsset'; // this is meant to be used for assets contained in the current wallet -export default function useAccountAsset(uniqueId: string, nativeCurrency: NativeCurrencyKey | undefined = undefined) { +export default function useAccountAsset(uniqueId: string, nativeCurrency: string | undefined = undefined) { const { data: accountAsset } = useUserAsset(uniqueId); // this is temporary for FastBalanceCoinRow to make a tiny bit faster diff --git a/src/hooks/useAccountTransactions.ts b/src/hooks/useAccountTransactions.ts index 9fab4b41ae9..f7dc48cdac1 100644 --- a/src/hooks/useAccountTransactions.ts +++ b/src/hooks/useAccountTransactions.ts @@ -8,9 +8,9 @@ import { useTheme } from '@/theme'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import { RainbowTransaction } from '@/entities'; import { pendingTransactionsStore, usePendingTransactionsStore } from '@/state/pendingTransactions'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; +import { Network } from '@/networks/types'; import { nonceStore } from '@/state/nonces'; -import { ChainId } from '@/networks/types'; export const NOE_PAGE = 30; @@ -34,16 +34,16 @@ export default function useAccountTransactions() { .filter(t => t.from?.toLowerCase() === accountAddress?.toLowerCase()) .reduce( (latestTxMap, currentTx) => { - const currentChainId = currentTx?.chainId; - if (currentChainId) { - const latestTx = latestTxMap.get(currentChainId); + const currentNetwork = currentTx?.network; + if (currentNetwork) { + const latestTx = latestTxMap.get(currentNetwork); if (!latestTx) { - latestTxMap.set(currentChainId, currentTx); + latestTxMap.set(currentNetwork, currentTx); } } return latestTxMap; }, - new Map(RainbowNetworkObjects.map(chain => [chain.id, null as RainbowTransaction | null])) + new Map(RainbowNetworks.map(chain => [chain.value, null as RainbowTransaction | null])) ); watchForPendingTransactionsReportedByRainbowBackend({ currentAddress: accountAddress, @@ -56,17 +56,17 @@ export default function useAccountTransactions() { latestTransactions, }: { currentAddress: string; - latestTransactions: Map; + latestTransactions: Map; }) { const { setNonce } = nonceStore.getState(); const { setPendingTransactions, pendingTransactions: storePendingTransactions } = pendingTransactionsStore.getState(); const pendingTransactions = storePendingTransactions[currentAddress] || []; - const networks = RainbowNetworkObjects.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); + const networks = RainbowNetworks.filter(({ enabled, networkType }) => enabled && networkType !== 'testnet'); for (const network of networks) { - const latestTxConfirmedByBackend = latestTransactions.get(network.id); + const latestTxConfirmedByBackend = latestTransactions.get(network.value); if (latestTxConfirmedByBackend) { const latestNonceConfirmedByBackend = latestTxConfirmedByBackend.nonce || 0; - const [latestPendingTx] = pendingTransactions.filter(tx => tx?.chainId === network.id); + const [latestPendingTx] = pendingTransactions.filter(tx => tx?.network === network.value); let currentNonce; if (latestPendingTx) { @@ -79,7 +79,7 @@ export default function useAccountTransactions() { setNonce({ address: currentAddress, - chainId: network.id, + network: network.value, currentNonce, latestConfirmedNonce: latestNonceConfirmedByBackend, }); @@ -88,7 +88,7 @@ export default function useAccountTransactions() { const updatedPendingTransactions = pendingTransactions?.filter(tx => { const txNonce = tx.nonce || 0; - const latestTx = latestTransactions.get(tx.chainId); + const latestTx = latestTransactions.get(tx.network); const latestTxNonce = latestTx?.nonce || 0; // still pending or backend is not returning confirmation yet // if !latestTx means that is the first tx of the wallet diff --git a/src/hooks/useAdditionalAssetData.ts b/src/hooks/useAdditionalAssetData.ts index df16a1dcd74..cf8b8569890 100644 --- a/src/hooks/useAdditionalAssetData.ts +++ b/src/hooks/useAdditionalAssetData.ts @@ -1,8 +1,9 @@ import { useQuery } from '@tanstack/react-query'; import { NativeCurrencyKey } from '@/entities'; +import { Network } from '@/networks/types'; import { metadataClient } from '@/graphql'; +import { ethereumUtils } from '@/utils'; import { Token } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/networks/types'; // Types type TokenMetadata = Pick< @@ -13,20 +14,21 @@ type TokenMetadata = Pick< // Types for the query arguments type AdditionalAssetDataArgs = { address: string; - chainId: ChainId; + network: Network; currency: NativeCurrencyKey; }; // Query Key function -const createAdditionalAssetDataQueryKey = ({ address, chainId, currency }: AdditionalAssetDataArgs) => [ +const createAdditionalAssetDataQueryKey = ({ address, network, currency }: AdditionalAssetDataArgs) => [ 'additionalAssetData', address, - chainId, + network, currency, ]; // Refactor the getAdditionalAssetData function to accept the new parameters -async function getAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs): Promise { +async function getAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs): Promise { + const chainId = ethereumUtils.getChainIdFromNetwork(network); const data = await metadataClient.tokenMetadata({ address, chainId, @@ -40,12 +42,12 @@ async function getAdditionalAssetData({ address, chainId, currency }: Additional } // Usage of the useQuery hook -export default function useAdditionalAssetData({ address, chainId, currency }: AdditionalAssetDataArgs) { +export default function useAdditionalAssetData({ address, network, currency }: AdditionalAssetDataArgs) { return useQuery( - createAdditionalAssetDataQueryKey({ address, chainId, currency }), - () => getAdditionalAssetData({ address, chainId, currency }), + createAdditionalAssetDataQueryKey({ address, network, currency }), + () => getAdditionalAssetData({ address, network, currency }), { - enabled: !!address && !!chainId && !!currency, // Ensure all parameters are provided + enabled: !!address && !!network && !!currency, // Ensure all parameters are provided } ); } diff --git a/src/hooks/useAppVersion.ts b/src/hooks/useAppVersion.ts index c0ce735f36c..b6a660c970c 100644 --- a/src/hooks/useAppVersion.ts +++ b/src/hooks/useAppVersion.ts @@ -1,7 +1,7 @@ import VersionNumber from 'react-native-version-number'; import { useState } from 'react'; function formatAppVersion(appVersion = VersionNumber.appVersion) { - const version = `${appVersion} (${VersionNumber.buildVersion})`; + let version = `${appVersion} (${VersionNumber.buildVersion})`; return version; } diff --git a/src/hooks/useAsset.ts b/src/hooks/useAsset.ts index 91a77b25563..9a5e3180397 100644 --- a/src/hooks/useAsset.ts +++ b/src/hooks/useAsset.ts @@ -4,12 +4,11 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { useSelector } from 'react-redux'; import { AppState } from '@/redux/store'; -import { ChainId } from '@/networks/types'; -import { Address } from 'viem'; +import { ChainId } from '@/__swaps__/types/chains'; // To fetch an asset from account assets, // generic assets, and uniqueTokens -export default function useAsset({ address, chainId }: { address: Address; chainId: ChainId }) { +export default function useAsset({ address, chainId }: { address: string; chainId: ChainId }) { const nativeCurrency = useSelector((state: AppState) => state.settings.nativeCurrency); const uniqueId = getUniqueId(address, chainId); const accountAsset = useAccountAsset(uniqueId); diff --git a/src/hooks/useCloudBackups.ts b/src/hooks/useCloudBackups.ts index 506e669c682..a9146462af5 100644 --- a/src/hooks/useCloudBackups.ts +++ b/src/hooks/useCloudBackups.ts @@ -26,31 +26,31 @@ export default function useCloudBackups() { setIsFetching(true); const isAvailable = isCloudBackupAvailable(); if (!isAvailable) { - logger.debug('[useCloudBackups]: Cloud backup is not available'); + logger.log('Cloud backup is not available'); setIsFetching(false); setStep(CloudBackupStep.IDLE); return; } setStep(CloudBackupStep.SYNCING); - logger.debug('[useCloudBackups]: Syncing with cloud'); + logger.log('Syncing with cloud'); await syncCloud(); setStep(CloudBackupStep.FETCHING_USER_DATA); - logger.debug('[useCloudBackups]: Fetching user data'); + logger.log('Fetching user data'); const userData = await fetchUserDataFromCloud(); setUserData(userData); setStep(CloudBackupStep.FETCHING_ALL_BACKUPS); - logger.debug('[useCloudBackups]: Fetching all backups'); + logger.log('Fetching all backups'); const backups = await fetchAllBackups(); - logger.debug(`[useCloudBackups]: Retrieved ${backups.files.length} backup files`); + logger.log(`Retrieved ${backups.files.length} backup files`); setBackups(backups); setStep(CloudBackupStep.IDLE); } catch (e) { setStep(CloudBackupStep.FAILED); - logger.error(new RainbowError('[useCloudBackups]: Failed to fetch all backups'), { + logger.error(new RainbowError('Failed to fetch all backups'), { error: e, }); } diff --git a/src/hooks/useContacts.ts b/src/hooks/useContacts.ts index 73aaac697f7..277a5f77e1c 100644 --- a/src/hooks/useContacts.ts +++ b/src/hooks/useContacts.ts @@ -2,6 +2,7 @@ import { sortBy, values } from 'lodash'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { createSelector } from 'reselect'; +import networkTypes from '../helpers/networkTypes'; import { contactsAddOrUpdate, removeContact } from '../redux/contacts'; import { AppState } from '@/redux/store'; @@ -15,6 +16,9 @@ const contactsSelector = createSelector( export default function useContacts() { const dispatch = useDispatch(); + const { network } = useSelector(({ settings: { network } }: AppState) => ({ + network, + })); const { contacts, sortedContacts } = useSelector(contactsSelector); const onAddOrUpdateContacts = useCallback( @@ -25,9 +29,13 @@ export default function useContacts() { const onRemoveContact = useCallback((data: string) => dispatch(removeContact(data)), [dispatch]); + const filteredContacts = sortedContacts.filter(contact => + contact.network === network || (!contact.network && network === networkTypes.mainnet) ? contact : false + ); + return { contacts, - filteredContacts: sortedContacts, + filteredContacts, onAddOrUpdateContacts, onRemoveContact, sortedContacts, diff --git a/src/hooks/useDeleteWallet.ts b/src/hooks/useDeleteWallet.ts index bbe54e5b587..4613034e530 100644 --- a/src/hooks/useDeleteWallet.ts +++ b/src/hooks/useDeleteWallet.ts @@ -13,7 +13,7 @@ export default function useDeleteWallet({ address: primaryAddress }: { address?: const [watchingWalletId] = useMemo(() => { return ( Object.entries(wallets || {}).find(([_, wallet]: [string, RainbowWallet]) => - (wallet.addresses || []).some(({ address }: RainbowAccount) => address === primaryAddress) + wallet.addresses.some(({ address }: RainbowAccount) => address === primaryAddress) ) || ['', ''] ); }, [primaryAddress, wallets]); diff --git a/src/hooks/useENSRegistrationActionHandler.ts b/src/hooks/useENSRegistrationActionHandler.ts index 6216645ff4c..65c94689835 100644 --- a/src/hooks/useENSRegistrationActionHandler.ts +++ b/src/hooks/useENSRegistrationActionHandler.ts @@ -12,20 +12,19 @@ import { Records, RegistrationParameters } from '@/entities'; import { fetchResolver } from '@/handlers/ens'; import { saveNameFromLabelhash } from '@/handlers/localstorage/ens'; import { uploadImage } from '@/handlers/pinata'; -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork } from '@/handlers/web3'; import { ENS_DOMAIN, generateSalt, getRentPrice, REGISTRATION_STEPS } from '@/helpers/ens'; import { loadWallet } from '@/model/wallet'; import { timeUnits } from '@/references'; import Routes from '@/navigation/routesNames'; -import { labelhash } from '@/utils'; +import { labelhash, logger } from '@/utils'; import { getNextNonce } from '@/state/nonces'; +import { Network } from '@/networks/types'; import { Hex } from 'viem'; import { executeENSRap } from '@/raps/actions/ens'; import store from '@/redux/store'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { noop } from 'lodash'; -import { logger, RainbowError } from '@/logger'; -import { ChainId } from '@/networks/types'; // Generic type for action functions type ActionFunction

= (...params: P) => Promise; @@ -115,7 +114,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step }; (() => { - provider = getProvider({ chainId: ChainId.mainnet }); + provider = getProviderForNetwork(); provider.on('block', updateAvatars); })(); return () => { @@ -128,7 +127,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { updateAvatarsOnNextBlock.current = true; - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -139,7 +138,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const salt = generateSalt(); const [nonce, rentPrice] = await Promise.all([ - getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), + getNextNonce({ network: Network.mainnet, address: accountAddress }), getRentPrice(registrationParameters.name.replace(ENS_DOMAIN, ''), duration), ]); @@ -186,7 +185,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name, duration } = registrationParameters as RegistrationParameters; - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -196,7 +195,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, rentPrice, changedRecords] = await Promise.all([ - getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), + getNextNonce({ network: Network.mainnet, address: accountAddress }), getRentPrice(name.replace(ENS_DOMAIN, ''), duration), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, @@ -225,7 +224,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -234,7 +233,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); const rentPrice = await getRentPrice(name.replace(ENS_DOMAIN, ''), duration); const registerEnsRegistrationParameters: ENSActionParameters = { @@ -253,7 +252,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async (callback = noop) => { const { name } = registrationParameters as RegistrationParameters; - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -262,7 +261,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); const registerEnsRegistrationParameters: ENSActionParameters = { ...formatENSActionParams(registrationParameters), @@ -278,7 +277,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step const setRecordsAction: ActionTypes[typeof REGISTRATION_STEPS.EDIT] = useCallback( async (callback = noop) => { - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -288,7 +287,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step } const [nonce, changedRecords, resolver] = await Promise.all([ - getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }), + getNextNonce({ network: Network.mainnet, address: accountAddress }), uploadRecordImages(registrationParameters.changedRecords, { avatar: avatarMetadata, header: coverMetadata, @@ -316,7 +315,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step async ({ clearRecords, records, name, setAddress, toAddress, transferControl, wallet: walletOverride }, callback = noop) => { let wallet = walletOverride; if (!wallet) { - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); wallet = await loadWallet({ showErrorIfNotLoaded: false, provider, @@ -326,7 +325,7 @@ const useENSRegistrationActionHandler: UseENSRegistrationActionHandler = ({ step return; } - const nonce = await getNextNonce({ chainId: ChainId.mainnet, address: accountAddress }); + const nonce = await getNextNonce({ network: Network.mainnet, address: accountAddress }); const transferEnsParameters: ENSActionParameters = { ...formatENSActionParams({ @@ -380,9 +379,7 @@ async function uploadRecordImages(records: Partial | undefined, imageMe }); return url; } catch (error) { - logger.error(new RainbowError('[useENSRegistrationActionHandler]: Failed to upload image.'), { - error, - }); + logger.sentry('[uploadRecordImages] Failed to upload image.', error); return undefined; } } diff --git a/src/hooks/useENSRegistrationCosts.ts b/src/hooks/useENSRegistrationCosts.ts index 75fc975a023..d1143975b3e 100644 --- a/src/hooks/useENSRegistrationCosts.ts +++ b/src/hooks/useENSRegistrationCosts.ts @@ -24,10 +24,11 @@ import { REGISTRATION_MODES, REGISTRATION_STEPS, } from '@/helpers/ens'; +import { Network } from '@/helpers/networkTypes'; import { add, addBuffer, addDisplay, fromWei, greaterThanOrEqualTo, multiply } from '@/helpers/utilities'; import { ethUnits, timeUnits } from '@/references'; import { ethereumUtils, gasUtils } from '@/utils'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; enum QUERY_KEYS { GET_COMMIT_GAS_LIMIT = 'GET_COMMIT_GAS_LIMIT', @@ -92,7 +93,7 @@ export default function useENSRegistrationCosts({ const rentPriceInWei = rentPrice?.wei?.toString(); const checkIfSufficientEth = useCallback((wei: string) => { - const nativeAsset = ethereumUtils.getNetworkNativeAsset({ chainId: ChainId.mainnet }); + const nativeAsset = ethereumUtils.getNetworkNativeAsset(ChainId.mainnet); const balanceAmount = nativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(wei); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); @@ -247,7 +248,7 @@ export default function useENSRegistrationCosts({ ); const estimatedFee = useMemo(() => { - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); const { gasFeeParamsBySpeed, currentBaseFee } = gasFeeParams; let estimatedGasLimit = ''; @@ -333,7 +334,7 @@ export default function useENSRegistrationCosts({ const data = useMemo(() => { const rentPricePerYearInWei = rentPrice?.perYear?.wei?.toString(); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); if (rentPricePerYearInWei) { const rentPriceInWei = multiply(rentPricePerYearInWei, yearsDuration); diff --git a/src/hooks/useENSRegistrationStepHandler.tsx b/src/hooks/useENSRegistrationStepHandler.tsx index f53c8ece33a..77653dca970 100644 --- a/src/hooks/useENSRegistrationStepHandler.tsx +++ b/src/hooks/useENSRegistrationStepHandler.tsx @@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux'; import usePrevious from './usePrevious'; import { useENSRegistration, useInterval } from '.'; import { RegistrationParameters } from '@/entities'; -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork, isHardHat, web3Provider } from '@/handlers/web3'; import { ENS_SECONDS_PADDING, ENS_SECONDS_WAIT, @@ -14,20 +14,18 @@ import { REGISTRATION_STEPS, } from '@/helpers/ens'; import { updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; -import { ChainId } from '@/networks/types'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const checkRegisterBlockTimestamp = async ({ registrationParameters, secondsSinceCommitConfirmed, - connectedToHardhat, + isTestingHardhat, }: { registrationParameters: RegistrationParameters; secondsSinceCommitConfirmed: number; - connectedToHardhat: boolean; + isTestingHardhat: boolean; }) => { try { - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const block = await provider.getBlock('latest'); const msBlockTimestamp = getBlockMsTimestamp(block); const secs = differenceInSeconds(msBlockTimestamp, registrationParameters?.commitTransactionConfirmedAt || msBlockTimestamp); @@ -35,7 +33,7 @@ const checkRegisterBlockTimestamp = async ({ (secs > ENS_SECONDS_WAIT_WITH_PADDING && secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_WITH_PADDING) || // sometimes the provider.getBlock('latest) takes a long time to update to newest block secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_PROVIDER_PADDING || - connectedToHardhat + isTestingHardhat ) { return true; } @@ -62,12 +60,12 @@ export default function useENSRegistrationStepHandler(observer = true) { -1 ); - const { connectedToHardhat } = useConnectedToHardhatStore(); + const isTestingHardhat = useMemo(() => isHardHat(web3Provider.connection.url), []); const [readyToRegister, setReadyToRegister] = useState(secondsSinceCommitConfirmed > ENS_SECONDS_WAIT); // flag to wait 10 secs before we get the tx block, to be able to simulate not confirmed tx when testing - const shouldLoopForConfirmation = useRef(connectedToHardhat); + const shouldLoopForConfirmation = useRef(isTestingHardhat); const registrationStep = useMemo(() => { if (mode === REGISTRATION_MODES.EDIT) return REGISTRATION_STEPS.EDIT; @@ -92,7 +90,7 @@ export default function useENSRegistrationStepHandler(observer = true) { const watchCommitTransaction = useCallback(async () => { if (observer) return; - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); let confirmed = false; const tx = await provider.getTransaction(commitTransactionHash || ''); if (!tx?.blockHash) return confirmed; @@ -101,7 +99,7 @@ export default function useENSRegistrationStepHandler(observer = true) { const now = Date.now(); const msBlockTimestamp = getBlockMsTimestamp(block); // hardhat block timestamp is behind - const timeDifference = connectedToHardhat ? now - msBlockTimestamp : 0; + const timeDifference = isTestingHardhat ? now - msBlockTimestamp : 0; const commitTransactionConfirmedAt = msBlockTimestamp + timeDifference; const secs = differenceInSeconds(now, commitTransactionConfirmedAt); setSecondsSinceCommitConfirmed(secondsSinceCommitConfirmed < 0 ? 0 : secs); @@ -115,7 +113,7 @@ export default function useENSRegistrationStepHandler(observer = true) { shouldLoopForConfirmation.current = false; } return confirmed; - }, [observer, commitTransactionHash, connectedToHardhat, secondsSinceCommitConfirmed, dispatch]); + }, [observer, commitTransactionHash, isTestingHardhat, secondsSinceCommitConfirmed, dispatch]); const startPollingWatchCommitTransaction = useCallback(async () => { if (observer) return; @@ -168,7 +166,7 @@ export default function useENSRegistrationStepHandler(observer = true) { if (!observer && secondsSinceCommitConfirmed % 2 === 0 && secondsSinceCommitConfirmed >= ENS_SECONDS_WAIT && !readyToRegister) { const checkIfReadyToRegister = async () => { const readyToRegister = await checkRegisterBlockTimestamp({ - connectedToHardhat, + isTestingHardhat, registrationParameters, secondsSinceCommitConfirmed, }); @@ -176,7 +174,7 @@ export default function useENSRegistrationStepHandler(observer = true) { }; checkIfReadyToRegister(); } - }, [connectedToHardhat, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]); + }, [isTestingHardhat, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]); useEffect( () => () => { diff --git a/src/hooks/useENSSearch.ts b/src/hooks/useENSSearch.ts index b492ad0749f..b99378a9f0c 100644 --- a/src/hooks/useENSSearch.ts +++ b/src/hooks/useENSSearch.ts @@ -4,11 +4,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useAccountSettings, useENSLocalTransactions } from '.'; import { fetchRegistrationDate } from '@/handlers/ens'; import { ENS_DOMAIN, formatRentPrice, getAvailable, getENSRegistrarControllerContract, getNameExpires, getRentPrice } from '@/helpers/ens'; +import { Network } from '@/helpers/networkTypes'; import { timeUnits } from '@/references'; import { ethereumUtils, validateENS } from '@/utils'; -import { ChainId } from '@/networks/types'; -const formatTime = (timestamp: string, abbreviated = true) => { +const formatTime = (timestamp: string, abbreviated: boolean = true) => { const style = abbreviated ? 'MMM d, y' : 'MMMM d, y'; return format(new Date(Number(timestamp) * 1000), style); }; @@ -51,7 +51,7 @@ export default function useENSSearch({ yearsDuration = 1, name: inputName }: { y } const [isAvailable, rentPrice] = await Promise.all([getAvailable(name, contract), getRentPrice(name, duration, contract)]); - const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork({ chainId: ChainId.mainnet }); + const nativeAssetPrice = ethereumUtils.getPriceOfNativeAssetForNetwork(Network.mainnet); const formattedRentPrice = formatRentPrice(rentPrice, yearsDuration, nativeCurrency, nativeAssetPrice); if (isAvailable) { diff --git a/src/hooks/useEffectDebugger.ts b/src/hooks/useEffectDebugger.ts index 7294d1b1e5d..85e742c5f57 100644 --- a/src/hooks/useEffectDebugger.ts +++ b/src/hooks/useEffectDebugger.ts @@ -1,12 +1,12 @@ -import { logger } from '@/logger'; +import logger from '@/utils/logger'; const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { // Edge-case: different array lengths if (oldInputs.length !== newInputs.length) { // Not helpful to compare item by item, so just output the whole array - logger.debug(`[useEffectDebugger]: ${prefix} - Inputs have a different length`, oldInputs, newInputs); - logger.debug(`[useEffectDebugger]: Old inputs:`, oldInputs); - logger.debug(`[useEffectDebugger]: New inputs:`, newInputs); + logger.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs); + logger.log('Old inputs:', oldInputs); + logger.log('New inputs:', newInputs); return; } @@ -14,9 +14,9 @@ const compareInputs = (oldInputs: any, newInputs: any, prefix: any) => { oldInputs.forEach((oldInput: any, index: any) => { const newInput = newInputs[index]; if (oldInput !== newInput) { - logger.debug(`[useEffectDebugger]: ${prefix} - The input changed in position ${index}`); - logger.debug(`[useEffectDebugger]: Old value:`, oldInput); - logger.debug(`[useEffectDebugger]: New value:`, newInput); + logger.log(`${prefix} - The input changed in position ${index}`); + logger.log('Old value:', oldInput); + logger.log('New value:', newInput); } }); }; diff --git a/src/hooks/useGas.ts b/src/hooks/useGas.ts index 4c6c7f3c2ce..224e25ebc8e 100644 --- a/src/hooks/useGas.ts +++ b/src/hooks/useGas.ts @@ -13,6 +13,7 @@ import { ParsedAddressAsset, SelectedGasFee, } from '@/entities'; +import networkTypes, { Network } from '@/helpers/networkTypes'; import { fromWei, greaterThan, greaterThanOrEqualTo } from '@/helpers/utilities'; import { gasPricesStartPolling, @@ -23,24 +24,25 @@ import { gasUpdateTxFee, } from '@/redux/gas'; import { ethereumUtils } from '@/utils'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj } from '@/networks'; import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { BNB_MAINNET_ADDRESS, ETH_ADDRESS, MATIC_MAINNET_ADDRESS } from '@/references'; import useAccountSettings from './useAccountSettings'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; -const checkSufficientGas = (txFee: LegacyGasFee | GasFee, chainId: ChainId, nativeAsset?: ParsedAddressAsset) => { - const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; +const checkSufficientGas = (txFee: LegacyGasFee | GasFee, network: Network, nativeAsset?: ParsedAddressAsset) => { + const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; - const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset({ chainId }); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + const networkNativeAsset = nativeAsset || ethereumUtils.getNetworkNativeAsset(chainId); const balanceAmount = networkNativeAsset?.balance?.amount || 0; const txFeeAmount = fromWei(txFeeValue?.value?.amount); const isSufficientGas = greaterThanOrEqualTo(balanceAmount, txFeeAmount); return isSufficientGas; }; -const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { - const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; +const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { + const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -48,8 +50,8 @@ const checkValidGas = (selectedGasParams: LegacyGasFeeParams | GasFeeParams, cha return isValidGas; }; -const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, chainId: ChainId) => { - const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; +const checkGasReady = (txFee: LegacyGasFee | GasFee, selectedGasParams: LegacyGasFeeParams | GasFeeParams, network: Network) => { + const isLegacyGasNetwork = getNetworkObj(network).gas.gasType === 'legacy'; const gasValue = isLegacyGasNetwork ? (selectedGasParams as LegacyGasFeeParams)?.gasPrice : (selectedGasParams as GasFeeParams)?.maxBaseFee; @@ -86,7 +88,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit: string; selectedGasFee: SelectedGasFee; selectedGasFeeOption: string; - chainId: ChainId; + txNetwork: Network; l1GasFeeOptimism: string; } = useSelector( ({ @@ -98,7 +100,7 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss gasLimit, l1GasFeeOptimism, selectedGasFee, - chainId, + txNetwork, }, }: AppState) => ({ currentBlockParams, @@ -109,29 +111,29 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss l1GasFeeOptimism, selectedGasFee, selectedGasFeeOption: selectedGasFee.option, - chainId, + txNetwork, }) ); const prevSelectedGasFee = usePrevious(gasData?.selectedGasFee); const isSufficientGas = useMemo( - () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset), - [gasData?.selectedGasFee?.gasFee, gasData?.chainId, nativeAsset] + () => checkSufficientGas(gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset), + [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork, nativeAsset] ); const isValidGas = useMemo( - () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), - [gasData?.selectedGasFee, gasData?.chainId] + () => checkValidGas(gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), + [gasData?.selectedGasFee, gasData?.txNetwork] ); const isGasReady = useMemo( - () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId), - [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.chainId] + () => checkGasReady(gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork), + [gasData?.selectedGasFee?.gasFee, gasData?.selectedGasFee?.gasFeeParams, gasData?.txNetwork] ); const startPollingGasFees = useCallback( - (chainId = ChainId.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(chainId, flashbots)), + (network = networkTypes.mainnet, flashbots = false) => dispatch(gasPricesStartPolling(network, flashbots)), [dispatch] ); const stopPollingGasFees = useCallback(() => dispatch(gasPricesStopPolling()), [dispatch]); @@ -151,12 +153,12 @@ export default function useGas({ nativeAsset }: { nativeAsset?: ParsedAddressAss const getTotalGasPrice = useCallback(() => { const txFee = gasData?.selectedGasFee?.gasFee; - const isLegacyGasNetwork = getNetworkObject({ chainId: gasData?.chainId }).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObj(gasData?.txNetwork).gas.gasType === 'legacy'; const txFeeValue = isLegacyGasNetwork ? (txFee as LegacyGasFee)?.estimatedFee : (txFee as GasFee)?.maxFee; const txFeeAmount = fromWei(txFeeValue?.value?.amount); return txFeeAmount; - }, [gasData?.selectedGasFee?.gasFee, gasData?.chainId]); + }, [gasData?.selectedGasFee?.gasFee, gasData?.txNetwork]); return { isGasReady, diff --git a/src/hooks/useHideSplashScreen.ts b/src/hooks/useHideSplashScreen.ts index aff7dd0c43a..d9383a7b482 100644 --- a/src/hooks/useHideSplashScreen.ts +++ b/src/hooks/useHideSplashScreen.ts @@ -55,12 +55,12 @@ export default function useHideSplashScreen() { if (appIcon === 'poolboy') { const sound = new Sound(require('../assets/sounds/RainbowSega.mp3'), (error: any) => { if (error) { - logger.error(new RainbowError('[useHideSplashScreen]: Error playing poolboy sound')); + logger.error(new RainbowError('Error playing poolboy sound')); return; } sound.play((success: any) => { - logger.debug('[useHideSplashScreen]: playing poolboy sound'); + logger.debug('playing poolboy sound'); }); }); } diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 1523bae55c4..0e8c5368f5f 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -17,7 +17,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { PROFILES, useExperimentalFlag } from '@/config'; import { fetchReverseRecord } from '@/handlers/ens'; -import { getProvider, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; +import { getProviderForNetwork, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; import { isENSAddressFormat, isUnstoppableAddressFormat, isValidWallet } from '@/helpers/validators'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { walletInit } from '@/model/wallet'; @@ -25,13 +25,13 @@ import { Navigation, useNavigation } from '@/navigation'; import { walletsLoadState } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; import { sanitizeSeedPhrase } from '@/utils'; +import logger from '@/utils/logger'; import { deriveAccountFromWalletInput } from '@/utils/wallet'; -import { logger, RainbowError } from '@/logger'; +import { logger as Logger, RainbowError } from '@/logger'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils'; import walletBackupTypes from '@/helpers/walletBackupTypes'; -import { ChainId } from '@/networks/types'; export default function useImportingWallet({ showImportModal = true } = {}) { const { accountAddress } = useAccountSettings(); @@ -46,8 +46,8 @@ export default function useImportingWallet({ showImportModal = true } = {}) { const [name, setName] = useState(null); const [image, setImage] = useState(null); const [busy, setBusy] = useState(false); - const [checkedWallet, setCheckedWallet] = useState> | null>(null); - const [resolvedAddress, setResolvedAddress] = useState(null); + const [checkedWallet, setCheckedWallet] = useState(null); + const [resolvedAddress, setResolvedAddress] = useState(null); const wasImporting = usePrevious(isImporting); const { updateWalletENSAvatars } = useWalletENSAvatar(); const profilesEnabled = useExperimentalFlag(PROFILES); @@ -123,9 +123,9 @@ export default function useImportingWallet({ showImportModal = true } = {}) { // Validate ENS if (isENSAddressFormat(input)) { try { - const provider = getProvider({ chainId: ChainId.mainnet }); + const web3Provider = getProviderForNetwork(); const [address, avatar] = await Promise.all([ - provider.resolveName(input), + web3Provider.resolveName(input), !avatarUrl && profilesEnabled && fetchENSAvatar(input, { swallowError: true }), ]); if (!address) { @@ -133,6 +133,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_ens_name')); return; } + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; avatarUrl = avatarUrl || avatar?.imageUrl; @@ -156,6 +157,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { Alert.alert(lang.t('wallet.invalid_unstoppable_name')); return; } + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; setBusy(false); @@ -185,7 +187,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { input, }); } catch (e) { - logger.error(new RainbowError(`[useImportingWallet]: Error resolving ENS during wallet import: ${e}`)); + logger.log(`Error resolving ENS during wallet import`, e); } setBusy(false); // @ts-expect-error ts-migrate(2554) FIXME: Expected 4 arguments, but got 3. @@ -194,9 +196,10 @@ export default function useImportingWallet({ showImportModal = true } = {}) { try { setTimeout(async () => { const walletResult = await deriveAccountFromWalletInput(input); + // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ address: string; isHDWallet: b... Remove this comment to see the full error message setCheckedWallet(walletResult); if (!walletResult.address) { - logger.error(new RainbowError('[useImportingWallet]: walletResult address is undefined')); + Logger.error(new RainbowError('useImportingWallet - walletResult address is undefined')); return null; } const ens = await fetchReverseRecord(walletResult.address); @@ -217,7 +220,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { }); }, 100); } catch (error) { - logger.error(new RainbowError(`[useImportingWallet]: Error looking up ENS for imported HD type wallet: ${error}`)); + logger.log('Error looking up ENS for imported HD type wallet', error); setBusy(false); } } @@ -334,7 +337,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { .catch(error => { handleSetImporting(false); android && handleSetImporting(false); - logger.error(new RainbowError(`[useImportingWallet]: Error importing seed phrase: ${error}`)); + logger.error('error importing seed phrase: ', error); setTimeout(() => { inputRef.current?.focus(); // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 0. diff --git a/src/hooks/useInitializeAccountData.ts b/src/hooks/useInitializeAccountData.ts index 205d8fa42fa..0ac4db6d733 100644 --- a/src/hooks/useInitializeAccountData.ts +++ b/src/hooks/useInitializeAccountData.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { InteractionManager } from 'react-native'; import { useDispatch } from 'react-redux'; import { explorerInit } from '../redux/explorer'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; export default function useInitializeAccountData() { const dispatch = useDispatch(); @@ -11,11 +11,11 @@ export default function useInitializeAccountData() { const initializeAccountData = useCallback(async () => { try { InteractionManager.runAfterInteractions(() => { - logger.debug('[useInitializeAccountData]: Initialize account data'); + logger.sentry('Initialize account data'); dispatch(explorerInit()); }); } catch (error) { - logger.error(new RainbowError(`[useInitializeAccountData]: Error initializing account data: ${error}`)); + logger.sentry('Error initializing account data'); captureException(error); } }, [dispatch]); diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts index 4ece4a1d2d5..c702ac8d61a 100644 --- a/src/hooks/useInitializeWallet.ts +++ b/src/hooks/useInitializeWallet.ts @@ -60,19 +60,19 @@ export default function useInitializeWallet() { ) => { try { PerformanceTracking.startMeasuring(PerformanceMetrics.useInitializeWallet); - logger.debug('[useInitializeWallet]: Start wallet setup'); + logger.debug('Start wallet setup'); await resetAccountState(); - logger.debug('[useInitializeWallet]: resetAccountState ran ok'); + logger.debug('resetAccountState ran ok'); const isImporting = !!seedPhrase; - logger.debug(`[useInitializeWallet]: isImporting? ${isImporting}`); + logger.debug('isImporting? ' + isImporting); if (shouldRunMigrations && !seedPhrase) { - logger.debug('[useInitializeWallet]: shouldRunMigrations && !seedPhrase? => true'); + logger.debug('shouldRunMigrations && !seedPhrase? => true'); await dispatch(walletsLoadState(profilesEnabled)); - logger.debug('[useInitializeWallet]: walletsLoadState call #1'); + logger.debug('walletsLoadState call #1'); await runMigrations(); - logger.debug('[useInitializeWallet]: done with migrations'); + logger.debug('done with migrations'); } setIsSmallBalancesOpen(false); @@ -82,7 +82,7 @@ export default function useInitializeWallet() { const { isNew, walletAddress } = await walletInit(seedPhrase, color, name, overwrite, checkedWallet, network, image, silent); - logger.debug('[useInitializeWallet]: walletInit returned', { + logger.debug('walletInit returned', { isNew, walletAddress, }); @@ -94,12 +94,12 @@ export default function useInitializeWallet() { } if (seedPhrase || isNew) { - logger.debug('[useInitializeWallet]: walletsLoadState call #2'); + logger.debug('walletLoadState call #2'); await dispatch(walletsLoadState(profilesEnabled)); } if (isNil(walletAddress)) { - logger.debug('[useInitializeWallet]: walletAddress is nil'); + logger.debug('walletAddress is nil'); Alert.alert(lang.t('wallet.import_failed_invalid_private_key')); if (!isImporting) { dispatch(appStateUpdate({ walletReady: true })); @@ -109,18 +109,18 @@ export default function useInitializeWallet() { if (!(isNew || isImporting)) { await loadGlobalEarlyData(); - logger.debug('[useInitializeWallet]: loaded global data...'); + logger.debug('loaded global data...'); } await dispatch(settingsUpdateAccountAddress(walletAddress)); - logger.debug('[useInitializeWallet]: updated settings address', { + logger.debug('updated settings address', { walletAddress, }); // Newly created / imported accounts have no data in localstorage if (!(isNew || isImporting)) { await loadAccountData(); - logger.debug('[useInitializeWallet]: loaded account data', { + logger.debug('loaded account data', { network, }); } @@ -128,7 +128,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { + logger.error(new RainbowError('Error while hiding splash screen'), { error: err, }); } @@ -136,7 +136,7 @@ export default function useInitializeWallet() { initializeAccountData(); dispatch(appStateUpdate({ walletReady: true })); - logger.debug('[useInitializeWallet]: 💰 Wallet initialized'); + logger.debug('💰 Wallet initialized'); PerformanceTracking.finishMeasuring(PerformanceMetrics.useInitializeWallet, { walletStatus: getWalletStatusForPerformanceMetrics(isNew, isImporting), @@ -145,7 +145,7 @@ export default function useInitializeWallet() { return walletAddress; } catch (error) { PerformanceTracking.clearMeasure(PerformanceMetrics.useInitializeWallet); - logger.error(new RainbowError('[useInitializeWallet]: Error while initializing wallet'), { + logger.error(new RainbowError('Error while initializing wallet'), { error, }); // TODO specify error states more granular @@ -156,7 +156,7 @@ export default function useInitializeWallet() { try { hideSplashScreen(); } catch (err) { - logger.error(new RainbowError('[useInitializeWallet]: Error while hiding splash screen'), { + logger.error(new RainbowError('Error while hiding splash screen'), { error: err, }); } diff --git a/src/hooks/useLedgerConnect.ts b/src/hooks/useLedgerConnect.ts index 103370bacb0..4ac4888839d 100644 --- a/src/hooks/useLedgerConnect.ts +++ b/src/hooks/useLedgerConnect.ts @@ -33,13 +33,13 @@ export function useLedgerConnect({ if (isReady) return; if (errorType === LEDGER_ERROR_CODES.DISCONNECTED) { setReadyForPolling(false); - logger.debug('[useLedgerConnect]: Device Disconnected - Attempting Reconnect', {}); + logger.info('[LedgerConnect] - Device Disconnected - Attempting Reconnect', {}); transport.current = undefined; try { transport.current = await TransportBLE.open(deviceId); setReadyForPolling(true); } catch (e) { - logger.error(new RainbowError('[useLedgerConnect]: Reconnect Error'), { + logger.error(new RainbowError('[LedgerConnect] - Reconnect Error'), { error: (e as Error).message, }); // temp removing this to see if it fixes an issue @@ -67,7 +67,7 @@ export function useLedgerConnect({ const pollerCleanup = (poller: NodeJS.Timer | undefined) => { try { if (poller) { - logger.debug('[useLedgerConnect]: polling tear down', {}); + logger.debug('[LedgerConnect] - polling tear down', {}); clearInterval(poller); poller?.unref(); timer.current = undefined; @@ -78,7 +78,7 @@ export function useLedgerConnect({ }; useEffect(() => { if (readyForPolling && (!timer.current || triggerPollerCleanup)) { - logger.debug('[useLedgerConnect]: init device polling', {}); + logger.debug('[LedgerConnect] - init device polling', {}); setTriggerPollerCleanup(false); timer.current = setInterval(async () => { if (transport.current) { diff --git a/src/hooks/useLedgerImport.ts b/src/hooks/useLedgerImport.ts index 074e4319a96..611e39f0c99 100644 --- a/src/hooks/useLedgerImport.ts +++ b/src/hooks/useLedgerImport.ts @@ -1,8 +1,9 @@ import TransportBLE from '@ledgerhq/react-native-hw-transport-ble'; -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { DebugContext } from '@/logger/debugContext'; import { logger, RainbowError } from '@/logger'; import { Subscription } from '@ledgerhq/hw-transport'; +import { Alert } from 'react-native'; import { checkAndRequestAndroidBluetooth, showBluetoothPermissionsAlert, showBluetoothPoweredOffAlert } from '@/utils/bluetoothPermissions'; import { IS_ANDROID, IS_IOS } from '@/env'; import { ledgerErrorHandler, LEDGER_ERROR_CODES } from '@/utils/ledger'; @@ -29,7 +30,7 @@ export function useLedgerImport({ */ const handlePairError = useCallback( (error: Error) => { - logger.error(new RainbowError('[useLedgerImport]: Pairing Error'), { + logger.error(new RainbowError('[LedgerImport] - Pairing Error'), { error, }); errorCallback?.(ledgerErrorHandler(error)); @@ -42,7 +43,7 @@ export function useLedgerImport({ */ const handlePairSuccess = useCallback( (deviceId: string) => { - logger.debug('[useLedgerImport]: Pairing Success', {}, DebugContext.ledger); + logger.debug('[LedgerImport] - Pairing Success', {}, DebugContext.ledger); successCallback?.(deviceId); handleCleanUp(); }, @@ -58,15 +59,15 @@ export function useLedgerImport({ const newObserver = TransportBLE.observeState({ // havnt seen complete or error fire yet but its in the docs so keeping for reporting purposes complete: (e: any) => { - logger.debug('[useLedgerImport]: Observer complete', { e }, DebugContext.ledger); + logger.debug('[LedgerImport] Observer complete', { e }, DebugContext.ledger); }, error: (e: any) => { - logger.debug('[useLedgerImport]: Observer error ', { e }, DebugContext.ledger); + logger.debug('[LedgerImport] Observer error ', { e }, DebugContext.ledger); }, next: async (e: any) => { // App is not authorized to use Bluetooth if (e.type === 'Unauthorized') { - logger.debug('[useLedgerImport]: Bluetooth Unauthorized', {}, DebugContext.ledger); + logger.debug('[LedgerImport] - Bluetooth Unauthorized', {}, DebugContext.ledger); if (IS_IOS) { await showBluetoothPermissionsAlert(); } else { @@ -75,14 +76,14 @@ export function useLedgerImport({ } // Bluetooth is turned off if (e.type === 'PoweredOff') { - logger.debug('[useLedgerImport]: Bluetooth Powered Off', {}, DebugContext.ledger); + logger.debug('[LedgerImport] - Bluetooth Powered Off', {}, DebugContext.ledger); await showBluetoothPoweredOffAlert(); } if (e.available) { const newListener = TransportBLE.listen({ complete: () => {}, error: error => { - logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (error as Error).message }); + logger.error(new RainbowError('[Ledger Import] - Error Pairing'), { errorMessage: (error as Error).message }); }, next: async e => { if (e.type === 'add') { @@ -118,10 +119,10 @@ export function useLedgerImport({ useEffect(() => { const asyncFn = async () => { - logger.debug('[useLedgerImport]: init device polling', {}, DebugContext.ledger); + logger.debug('[LedgerImport] - init device polling', {}, DebugContext.ledger); const isBluetoothEnabled = IS_ANDROID ? await checkAndRequestAndroidBluetooth() : true; - logger.debug('[useLedgerImport]: bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); + logger.debug('[LedgerImport] - bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger); if (isBluetoothEnabled) { searchAndPair(); diff --git a/src/hooks/useLoadAccountData.ts b/src/hooks/useLoadAccountData.ts index bb74891d0c7..ecfd441cf48 100644 --- a/src/hooks/useLoadAccountData.ts +++ b/src/hooks/useLoadAccountData.ts @@ -5,12 +5,12 @@ import { requestsLoadState } from '../redux/requests'; import { showcaseTokensLoadState } from '../redux/showcaseTokens'; import { walletConnectLoadState } from '../redux/walletconnect'; import { promiseUtils } from '../utils'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; export default function useLoadAccountData() { const dispatch = useDispatch(); const loadAccountData = useCallback(async () => { - logger.debug('[useLoadAccountData]: Load wallet account data'); + logger.sentry('Load wallet account data'); await dispatch(showcaseTokensLoadState()); await dispatch(hiddenTokensLoadState()); const promises = []; diff --git a/src/hooks/useLoadAccountLateData.ts b/src/hooks/useLoadAccountLateData.ts index e438b081072..68d84bd34f2 100644 --- a/src/hooks/useLoadAccountLateData.ts +++ b/src/hooks/useLoadAccountLateData.ts @@ -3,7 +3,7 @@ import { promiseUtils } from '../utils'; import { prefetchAccountENSDomains } from './useAccountENSDomains'; import useAccountSettings from './useAccountSettings'; import useWallets from './useWallets'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; import { ensRegistrationsLoadState } from '@/redux/ensRegistration'; import { useDispatch } from 'react-redux'; import { showcaseTokensUpdateStateFromWeb } from '@/redux/showcaseTokens'; @@ -15,7 +15,7 @@ export default function useLoadAccountLateData() { const dispatch = useDispatch(); const loadAccountLateData = useCallback(async () => { - logger.debug('[useLoadAccountLateData]: Load wallet account late data'); + logger.sentry('Load wallet account late data'); const promises = []; diff --git a/src/hooks/useLoadGlobalEarlyData.ts b/src/hooks/useLoadGlobalEarlyData.ts index fab0b4b3d0c..8427c958ef0 100644 --- a/src/hooks/useLoadGlobalEarlyData.ts +++ b/src/hooks/useLoadGlobalEarlyData.ts @@ -3,13 +3,13 @@ import { useDispatch } from 'react-redux'; import { settingsLoadLanguage, settingsLoadState } from '@/redux/settings'; import { promiseUtils } from '@/utils'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; export default function useLoadGlobalEarlyData() { const dispatch = useDispatch(); const loadGlobalData = useCallback(async () => { - logger.debug('[useLoadGlobalEarlyData]: Load wallet global early data'); + logger.sentry('Load wallet global early data'); const promises = []; // native currency, app icon, testnetsEnabled, flashbotsEnabled diff --git a/src/hooks/useLoadGlobalLateData.ts b/src/hooks/useLoadGlobalLateData.ts index 7ad42460955..c4d9d41fcfa 100644 --- a/src/hooks/useLoadGlobalLateData.ts +++ b/src/hooks/useLoadGlobalLateData.ts @@ -6,7 +6,7 @@ import { keyboardHeightsLoadState } from '@/redux/keyboardHeight'; import { AppState } from '@/redux/store'; import { transactionSignaturesLoadState } from '@/redux/transactionSignatures'; import { promiseUtils } from '@/utils'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -21,7 +21,7 @@ export default function useLoadGlobalLateData() { if (!walletReady) { return false; } - logger.debug('[useLoadGlobalLateData]: Load wallet global late data'); + logger.sentry('Load wallet global late data'); const promises = []; // mainnet eth balances for all wallets diff --git a/src/hooks/useManageCloudBackups.ts b/src/hooks/useManageCloudBackups.ts index 141f26b7f4e..0d39ca141d5 100644 --- a/src/hooks/useManageCloudBackups.ts +++ b/src/hooks/useManageCloudBackups.ts @@ -20,7 +20,7 @@ export default function useManageCloudBackups() { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`[useManageCloudBackups]: Error Fetching google account data for Backups Section`), { + logger.error(new RainbowError(`Error Fetching google account data for Backups Section`), { error: (error as Error).message, }); }); @@ -54,7 +54,7 @@ export default function useManageCloudBackups() { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`[useManageCloudBackups]: Logging into Google Drive failed.`), { + logger.error(new RainbowError(`Logging into Google Drive failed.`), { error: (error as Error).message, }); } diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index bda2d6f20f5..4c7323bcf24 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -1,5 +1,5 @@ import lang from 'i18n-js'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { Linking } from 'react-native'; import { ImageOrVideo } from 'react-native-image-crop-picker'; import { useDispatch } from 'react-redux'; @@ -139,7 +139,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { const isReadOnly = isReadOnlyWallet && !enableActionsOnReadOnlyWallet; const isENSProfile = profilesEnabled && profileEnabled && isOwner; - const isZeroETH = isZero(accountAsset?.balance?.amount || 0); + const isZeroETH = isZero(accountAsset?.balance?.amount); const callback = useCallback( async (buttonIndex: number) => { diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts index b077d816a9b..f6789c38b76 100644 --- a/src/hooks/usePriceImpactDetails.ts +++ b/src/hooks/usePriceImpactDetails.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import { SwappableAsset } from '@/entities'; +import { Network } from '@/helpers'; import { useTheme } from '@/theme'; import { @@ -13,9 +14,9 @@ import { } from '@/helpers/utilities'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; -import { useNativeAsset } from '@/utils/ethereumUtils'; +import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export enum SwapPriceImpactType { none = 'none', diff --git a/src/hooks/useRainbowFee.js b/src/hooks/useRainbowFee.js index 6cc0d416bef..b80d489a6be 100644 --- a/src/hooks/useRainbowFee.js +++ b/src/hooks/useRainbowFee.js @@ -47,7 +47,7 @@ export default function useRainbowFee({ tradeDetails, chainId }) { useEffect(() => { const getNativeAsset = async () => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); setNativeAsset(nativeAsset); }; !nativeAsset && getNativeAsset(); diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index 67897f81319..8b9a5f1d839 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -1,11 +1,13 @@ +import { captureException } from '@sentry/react-native'; import delay from 'delay'; import { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { walletConnectLoadState } from '../redux/walletconnect'; import { fetchWalletENSAvatars, fetchWalletNames } from '../redux/wallets'; import useAccountSettings from './useAccountSettings'; import { PROFILES, useExperimentalFlag } from '@/config'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; @@ -13,24 +15,26 @@ import { invalidateAddressNftsQueries } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { Address } from 'viem'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; +import { useNftSort } from './useNFTsSortBy'; import useWallets from './useWallets'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export default function useRefreshAccountData() { const dispatch = useDispatch(); const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); - const { connectedToHardhat } = useConnectedToHardhatStore(); + const { nftSort, nftSortDirection } = useNftSort(); const { wallets } = useWallets(); const allAddresses = useMemo( - () => Object.values(wallets || {}).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), + () => Object.values(wallets || {}).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), [wallets] ); const fetchAccountData = useCallback(async () => { + const connectedToHardhat = getIsHardhatConnected(); + invalidateAddressNftsQueries(accountAddress); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); @@ -50,10 +54,11 @@ export default function useRefreshAccountData() { wc, ]); } catch (error) { - logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`)); + logger.log('Error refreshing data', error); + captureException(error); throw error; } - }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, profilesEnabled]); + }, [accountAddress, allAddresses, dispatch, nativeCurrency, nftSort, nftSortDirection, profilesEnabled]); const refresh = useCallback(async () => { if (isRefreshing) return; @@ -62,8 +67,8 @@ export default function useRefreshAccountData() { try { await fetchAccountData(); - } catch (error) { - logger.error(new RainbowError(`[useRefreshAccountData]: Error calling fetchAccountData: ${error}`)); + } catch (e) { + logger.error(e); } finally { setIsRefreshing(false); } diff --git a/src/hooks/useSafeImageUri.ts b/src/hooks/useSafeImageUri.ts index f76b99e0264..a08ef6153b9 100644 --- a/src/hooks/useSafeImageUri.ts +++ b/src/hooks/useSafeImageUri.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { maybeSignUri } from '../handlers/imgix'; -export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching = false): string | undefined { +export default function useSafeImageUri(maybeUnsafeUri: string | undefined, skipCaching: boolean = false): string | undefined { return useMemo(() => { return maybeSignUri(maybeUnsafeUri, {}, skipCaching); }, [maybeUnsafeUri, skipCaching]); diff --git a/src/hooks/useScanner.ts b/src/hooks/useScanner.ts index 7a50df6b8f8..02406c9bc24 100644 --- a/src/hooks/useScanner.ts +++ b/src/hooks/useScanner.ts @@ -16,7 +16,7 @@ import { Navigation } from '@/navigation'; import { POAP_BASE_URL, RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { addressUtils, ethereumUtils, haptics } from '@/utils'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { checkPushNotificationPermissions } from '@/notifications/permissions'; import { pair as pairWalletConnect } from '@/walletConnect'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; @@ -28,12 +28,12 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { const enabledVar = useRef(); const enableScanning = useCallback(() => { - logger.debug('[useScanner]: 📠✅ Enabling QR Code Scanner'); + logger.log('📠✅ Enabling QR Code Scanner'); enabledVar.current = true; }, [enabledVar]); const disableScanning = useCallback(() => { - logger.debug('[useScanner]: 📠🚫 Disabling QR Code Scanner'); + logger.log('📠🚫 Disabling QR Code Scanner'); enabledVar.current = false; }, [enabledVar]); @@ -113,8 +113,8 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { } else if (version === 2) { await pairWalletConnect({ uri, connector }); } - } catch (error) { - logger.error(new RainbowError(`[useScanner]: Error handling WalletConnect QR code: ${error}`)); + } catch (e) { + logger.log('walletConnectOnSessionRequest exception', e); } }, [goBack, onSuccess, walletConnectOnSessionRequest] @@ -190,14 +190,14 @@ export default function useScanner(enabled: boolean, onSuccess: () => unknown) { if (lowerCaseData.startsWith(`${RAINBOW_PROFILES_BASE_URL}/poap`)) { const secretWordOrQrHash = lowerCaseData.split(`${RAINBOW_PROFILES_BASE_URL}/poap/`)?.[1]; - logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); + logger.log('onScan: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } if (lowerCaseData.startsWith(`rainbow://poap`)) { const secretWordOrQrHash = lowerCaseData.split(`rainbow://poap/`)?.[1]; - logger.debug('[useScanner]: handling poap scan', { secretWordOrQrHash }); + logger.log('onScan: handling poap scan', { secretWordOrQrHash }); await getPoapAndOpenSheetWithSecretWord(secretWordOrQrHash, true); return getPoapAndOpenSheetWithQRHash(secretWordOrQrHash, true); } diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 16c8bf2149d..554fe14cfb6 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -11,14 +11,13 @@ import { tokenSearch } from '@/handlers/tokenSearch'; import { addHexPrefix, getProvider } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; -import { logger } from '@/logger'; +import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; type swapCurrencyListType = | 'verifiedAssets' @@ -86,6 +85,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); const [verifiedAssets, setVerifiedAssets] = useState([]); + const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ [ChainId.mainnet]: [], @@ -200,7 +200,8 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main } as RainbowToken, ]; } catch (e) { - logger.warn('[useSearchCurrencyList]: error getting token data', { error: (e as Error).message }); + logger.log('error getting token data'); + logger.log(e); return null; } } @@ -386,7 +387,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.chainId], + color: colors.networkColors[bridgeAsset.network], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -429,7 +430,7 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.chainId], + color: colors.networkColors[bridgeAsset.network], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), diff --git a/src/hooks/useSendableUniqueTokens.ts b/src/hooks/useSendableUniqueTokens.ts index 7e9341676df..331f6b2042f 100644 --- a/src/hooks/useSendableUniqueTokens.ts +++ b/src/hooks/useSendableUniqueTokens.ts @@ -11,7 +11,7 @@ export default function useSendableUniqueTokens() { const sendableUniqueTokens = uniqueTokens?.filter((uniqueToken: any) => uniqueToken.isSendable); const grouped = groupBy(sendableUniqueTokens, token => token.familyName); const families = Object.keys(grouped).sort(); - const sendableTokens = []; + let sendableTokens = []; for (let i = 0; i < families.length; i++) { let newObject = {}; newObject = { diff --git a/src/hooks/useStepper.ts b/src/hooks/useStepper.ts index 6ac07f73523..672e8e237f5 100644 --- a/src/hooks/useStepper.ts +++ b/src/hooks/useStepper.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; -export default function useStepper(max: number, initialIndex = 0) { +export default function useStepper(max: number, initialIndex: number = 0) { const [step, setStep] = useState(initialIndex); const nextStep = useCallback(() => setStep(p => (p + 1) % max), [max]); return [step, nextStep, setStep]; diff --git a/src/hooks/useSwapCurrencyHandlers.ts b/src/hooks/useSwapCurrencyHandlers.ts index 9b835ff7a2d..3fce074a3cb 100644 --- a/src/hooks/useSwapCurrencyHandlers.ts +++ b/src/hooks/useSwapCurrencyHandlers.ts @@ -119,6 +119,8 @@ export default function useSwapCurrencyHandlers({ } : null; + // prefetchExternalToken({address: newInputCurrency.address, network: newInputCurrency.network, currency: nativeCurrency}) + dispatch(updateSwapInputCurrency(newInputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newInputCurrency); @@ -134,6 +136,7 @@ export default function useSwapCurrencyHandlers({ } : null; + // prefetchExternalToken({address: newOutputCurrency.address, network: newOutputCurrency.network, currency: nativeCurrency}) dispatch(updateSwapOutputCurrency(newOutputCurrency, crosschainSwapsEnabled)); setLastFocusedInputHandle?.(inputFieldRef); handleNavigate?.(newOutputCurrency); diff --git a/src/hooks/useSwapCurrencyList.ts b/src/hooks/useSwapCurrencyList.ts index 02bd432f880..a987758763e 100644 --- a/src/hooks/useSwapCurrencyList.ts +++ b/src/hooks/useSwapCurrencyList.ts @@ -6,20 +6,21 @@ import { rankings } from 'match-sorter'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTheme } from '../theme/ThemeContext'; import usePrevious from './usePrevious'; -import { RainbowToken, TokenSearchTokenListId } from '@/entities'; +import { RainbowToken, RainbowToken as RT, TokenSearchTokenListId } from '@/entities'; import { swapSearch } from '@/handlers/tokenSearch'; -import { addHexPrefix, getProvider } from '@/handlers/web3'; +import { addHexPrefix, getProviderForNetwork } from '@/handlers/web3'; import tokenSectionTypes from '@/helpers/tokenSectionTypes'; import { DAI_ADDRESS, erc20ABI, ETH_ADDRESS, rainbowTokenList, USDC_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; -import { ethereumUtils, filterList, isLowerCaseMatch } from '@/utils'; +import { ethereumUtils, filterList, isLowerCaseMatch, logger } from '@/utils'; import useSwapCurrencies from '@/hooks/useSwapCurrencies'; +import { Network } from '@/helpers'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { useFavorites } from '@/resources/favorites'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { logger } from '@/logger'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; +const MAINNET_CHAINID = 1; type swapCurrencyListType = | 'verifiedAssets' | 'highLiquidityAssets' @@ -28,10 +29,13 @@ type swapCurrencyListType = | 'curatedAssets' | 'importedAssets'; -type CrosschainVerifiedAssets = Record< - ChainId.mainnet | ChainId.optimism | ChainId.polygon | ChainId.bsc | ChainId.arbitrum, - RainbowToken[] ->; +type CrosschainVerifiedAssets = { + [Network.mainnet]: RT[]; + [Network.optimism]: RT[]; + [Network.polygon]: RT[]; + [Network.bsc]: RT[]; + [Network.arbitrum]: RT[]; +}; const abcSort = (list: any[], key?: string) => { return list.sort((a, b) => { @@ -42,16 +46,16 @@ const abcSort = (list: any[], key?: string) => { const searchCurrencyList = async (searchParams: { chainId: number; fromChainId?: number | ''; - searchList: RainbowToken[] | TokenSearchTokenListId; + searchList: RT[] | TokenSearchTokenListId; query: string; }) => { const { searchList, query, chainId, fromChainId } = searchParams; const isAddress = query.match(/^(0x)?[0-9a-fA-F]{40}$/); - const keys: (keyof RainbowToken)[] = isAddress ? ['address'] : ['symbol', 'name']; + const keys: (keyof RT)[] = isAddress ? ['address'] : ['symbol', 'name']; const formattedQuery = isAddress ? addHexPrefix(query).toLowerCase() : query; if (typeof searchList === 'string') { const threshold = isAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'; - if (chainId === ChainId.mainnet && !formattedQuery && searchList !== 'verifiedAssets') { + if (chainId === MAINNET_CHAINID && !formattedQuery && searchList !== 'verifiedAssets') { return []; } return swapSearch({ @@ -69,10 +73,10 @@ const searchCurrencyList = async (searchParams: { } }; -const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainnet, isDiscover = false) => { +const useSwapCurrencyList = (searchQuery: string, searchChainId = MAINNET_CHAINID, isDiscover = false) => { const previousChainId = usePrevious(searchChainId); - const searching = useMemo(() => searchQuery !== '' || ChainId.mainnet !== searchChainId, [searchChainId, searchQuery]); + const searching = useMemo(() => searchQuery !== '' || MAINNET_CHAINID !== searchChainId, [searchChainId, searchQuery]); const { favorites: favoriteAddresses, favoritesMetadata: favoriteMap } = useFavorites(); @@ -80,24 +84,24 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne const unfilteredFavorites = Object.values(favoriteMap); const [loading, setLoading] = useState(true); - const [favoriteAssets, setFavoriteAssets] = useState([]); - const [importedAssets, setImportedAssets] = useState([]); - const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); - const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); - const [verifiedAssets, setVerifiedAssets] = useState([]); + const [favoriteAssets, setFavoriteAssets] = useState([]); + const [importedAssets, setImportedAssets] = useState([]); + const [highLiquidityAssets, setHighLiquidityAssets] = useState([]); + const [lowLiquidityAssets, setLowLiquidityAssets] = useState([]); + const [verifiedAssets, setVerifiedAssets] = useState([]); const [fetchingCrosschainAssets, setFetchingCrosschainAssets] = useState(false); const [crosschainVerifiedAssets, setCrosschainVerifiedAssets] = useState({ - [ChainId.mainnet]: [], - [ChainId.optimism]: [], - [ChainId.polygon]: [], - [ChainId.bsc]: [], - [ChainId.arbitrum]: [], + [Network.mainnet]: [], + [Network.optimism]: [], + [Network.polygon]: [], + [Network.bsc]: [], + [Network.arbitrum]: [], }); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); const { inputCurrency } = useSwapCurrencies(); - const previousInputCurrencyChainId = usePrevious(inputCurrency?.chainId); - const inputChainId = inputCurrency?.chainId; + const previousInputCurrencyNetwork = usePrevious(inputCurrency?.network); + const inputChainId = useMemo(() => ethereumUtils.getChainIdFromNetwork(inputCurrency?.network), [inputCurrency?.network]); const isCrosschainSearch = useMemo(() => { if (inputChainId && inputChainId !== searchChainId && crosschainSwapsEnabled && !isDiscover) { return true; @@ -109,16 +113,17 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne [favoriteAddresses] ); const handleSearchResponse = useCallback( - (tokens: RainbowToken[], chainId?: ChainId) => { + (tokens: RT[], crosschainNetwork?: Network) => { // These transformations are necessary for L2 tokens to match our spec - const activeChainId = chainId ? chainId : searchChainId; + const activeChainId = crosschainNetwork ? ethereumUtils.getChainIdFromNetwork(crosschainNetwork) : searchChainId; return (tokens || []) .map(token => { token.address = token.networks?.[activeChainId]?.address || token.address; - token.chainId = activeChainId; - if (token.networks[ChainId.mainnet]) { - token.mainnet_address = token.networks[ChainId.mainnet].address; + const network = crosschainNetwork || ethereumUtils.getNetworkFromChainId(searchChainId); + token.network = network; + if (token.networks[MAINNET_CHAINID]) { + token.mainnet_address = token.networks[MAINNET_CHAINID].address; } token.uniqueId = getUniqueId(token.address, activeChainId); @@ -153,7 +158,6 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne return { ...token, network: Network.mainnet, - chainId: ChainId.mainnet, uniqueId: getUniqueId(token.address, ChainId.mainnet), }; }); @@ -170,14 +174,15 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne }, [searchChainId, searchQuery, searching, unfilteredFavorites]); const getImportedAsset = useCallback( - async (searchQuery: string, chainId: number): Promise => { + async (searchQuery: string, chainId: number): Promise => { if (searching) { if (isAddress(searchQuery)) { const tokenListEntry = rainbowTokenList.RAINBOW_TOKEN_LIST[searchQuery.toLowerCase()]; if (tokenListEntry) { return [tokenListEntry]; } - const provider = getProvider({ chainId }); + const network = ethereumUtils.getNetworkFromChainId(chainId); + const provider = getProviderForNetwork(network); const tokenContract = new Contract(searchQuery, erc20ABI, provider); try { const [name, symbol, decimals, address] = await Promise.all([ @@ -186,11 +191,10 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne tokenContract.decimals(), getAddress(searchQuery), ]); - const uniqueId = getUniqueId(address, chainId); + const uniqueId = `${address}_${network}`; return [ { address, - chainId, decimals, favorite: false, highLiquidity: false, @@ -204,12 +208,13 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne }, }, symbol, - network: ethereumUtils.getNetworkFromChainId(chainId), + network, uniqueId, } as RainbowToken, ]; } catch (e) { - logger.warn('[useSwapCurrencyList]: error getting token data', { error: (e as Error).message }); + logger.log('error getting token data'); + logger.log(e); return null; } } @@ -220,17 +225,18 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne ); const getCrosschainVerifiedAssetsForNetwork = useCallback( - async (chainId: ChainId) => { - const fromChainId = inputChainId !== chainId ? inputChainId : ''; + async (network: Network) => { + const crosschainId = ethereumUtils.getChainIdFromNetwork(network); + const fromChainId = inputChainId !== crosschainId ? inputChainId : ''; const results = await searchCurrencyList({ searchList: 'verifiedAssets', query: '', - chainId, + chainId: crosschainId, fromChainId, }); setCrosschainVerifiedAssets(state => ({ ...state, - [chainId]: handleSearchResponse(results, chainId), + [network]: handleSearchResponse(results, network), })); }, [handleSearchResponse, inputChainId] @@ -238,8 +244,8 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne const getCrosschainVerifiedAssets = useCallback(async () => { const crosschainAssetRequests: Promise[] = []; - Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { - crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(Number(chainIdKey))); + Object.keys(crosschainVerifiedAssets).forEach(network => { + crosschainAssetRequests.push(getCrosschainVerifiedAssetsForNetwork(network as Network)); }); await Promise.all(crosschainAssetRequests); }, [crosschainVerifiedAssets, getCrosschainVerifiedAssetsForNetwork]); @@ -300,7 +306,7 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne const search = useCallback(async () => { const categories: swapCurrencyListType[] = - searchChainId === ChainId.mainnet + searchChainId === MAINNET_CHAINID ? ['favoriteAssets', 'highLiquidityAssets', 'verifiedAssets', 'importedAssets'] : ['verifiedAssets', 'importedAssets']; setLoading(true); @@ -345,9 +351,9 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne (searching && !wasSearching) || (searching && previousSearchQuery !== searchQuery) || searchChainId !== previousChainId || - inputCurrency?.chainId !== previousInputCurrencyChainId + inputCurrency?.network !== previousInputCurrencyNetwork ) { - if (searchChainId === ChainId.mainnet) { + if (searchChainId === MAINNET_CHAINID) { search(); slowSearch(); } else { @@ -362,14 +368,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne }; doSearch(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.chainId]); + }, [searching, searchQuery, searchChainId, isCrosschainSearch, inputCurrency?.network]); const { colors } = useTheme(); const currencyList = useMemo(() => { const list = []; let bridgeAsset = isCrosschainSearch - ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.chainId !== inputCurrency?.chainId) + ? verifiedAssets.find(asset => isLowerCaseMatch(asset?.name, inputCurrency?.name) && asset?.network !== inputCurrency?.network) : null; if (searching) { const importedAsset = importedAssets?.[0]; @@ -394,14 +400,14 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne if (inputCurrency?.name && verifiedAssets.length) { if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.chainId], + color: colors.networkColors[bridgeAsset.network], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), }); } } - if (favoriteAssets?.length && searchChainId === ChainId.mainnet) { + if (favoriteAssets?.length && searchChainId === MAINNET_CHAINID) { list.push({ color: colors.yellowFavorite, data: abcSort(favoriteAssets, 'name'), @@ -432,12 +438,12 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne }); } } else { - const curatedAssets = searchChainId === ChainId.mainnet && getCurated(); + const curatedAssets = searchChainId === MAINNET_CHAINID && getCurated(); if (inputCurrency?.name && isCrosschainSearch && curatedAssets) { bridgeAsset = curatedAssets.find(asset => asset?.name === inputCurrency?.name); if (bridgeAsset) { list.push({ - color: colors.networkColors[bridgeAsset.chainId], + color: colors.networkColors[bridgeAsset.network], data: [bridgeAsset], key: 'bridgeAsset', title: lang.t(`exchange.token_sections.${tokenSectionTypes.bridgeTokenSection}`), @@ -463,39 +469,39 @@ const useSwapCurrencyList = (searchQuery: string, searchChainId = ChainId.mainne } return list; }, [ - isCrosschainSearch, - verifiedAssets, searching, - inputCurrency?.name, - inputCurrency?.chainId, importedAssets, + favoriteAssets, + verifiedAssets, highLiquidityAssets, lowLiquidityAssets, - isFavorite, - favoriteAssets, - searchChainId, - colors.networkColors, colors.yellowFavorite, - getCurated, unfilteredFavorites, + searchChainId, + getCurated, + isFavorite, + inputCurrency?.name, + colors.networkColors, + isCrosschainSearch, + inputCurrency?.network, ]); const crosschainExactMatches = useMemo(() => { if (currencyList.length) return []; if (!searchQuery) return []; - const exactMatches: RainbowToken[] = []; - Object.keys(crosschainVerifiedAssets).forEach(chainIdKey => { - const chainId = Number(chainIdKey); - if (chainId !== searchChainId) { + const exactMatches: RT[] = []; + Object.keys(crosschainVerifiedAssets).forEach(network => { + const currentNetworkChainId = ethereumUtils.getChainIdFromNetwork(network as Network); + if (currentNetworkChainId !== searchChainId) { // including goerli in our networks type is causing this type issue // @ts-ignore - const exactMatch = crosschainVerifiedAssets[chainId].find((asset: RainbowToken) => { + const exactMatch = crosschainVerifiedAssets[network as Network].find((asset: RT) => { const symbolMatch = isLowerCaseMatch(asset?.symbol, searchQuery); const nameMatch = isLowerCaseMatch(asset?.name, searchQuery); return symbolMatch || nameMatch; }); if (exactMatch) { - exactMatches.push({ ...exactMatch, chainId }); + exactMatches.push({ ...exactMatch, network }); } } }); diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index dfcd744ea3c..fa8db8c1b63 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -65,20 +65,20 @@ const getInputAmount = async ( if (!inputToken || !outputAmount || isZero(outputAmount) || !outputToken) return null; try { - const outputChainId = outputToken.chainId; + const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken?.network); - const inputChainId = inputToken.chainId; + const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken?.network); - const inputTokenAddress = isNativeAsset(inputToken.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken.address; + const inputTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; - const outputTokenAddress = isNativeAsset(outputToken.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken.address; + const outputTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; const isCrosschainSwap = inputChainId !== outputChainId; if (isCrosschainSwap) return null; const buyAmount = convertAmountToRawAmount(convertNumberToString(outputAmount), outputToken.decimals); - logger.debug('[useSwapDerivedOutputs]: ', { + logger.info(`[getInputAmount]: `, { outputToken, outputChainId, outputNetwork: outputToken?.network, @@ -105,7 +105,7 @@ const getInputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); + logger.debug('[getInputAmount]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote @@ -115,7 +115,7 @@ const getInputAmount = async ( if (!quote || (quote as QuoteError).error || !(quote as Quote).sellAmount) { if ((quote as QuoteError).error) { const quoteError = quote as unknown as QuoteError; - logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { + logger.error(new RainbowError('[getInputAmount]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -164,22 +164,24 @@ const getOutputAmount = async ( if (!inputAmount || isZero(inputAmount) || !outputToken) return null; try { - const outputChainId = outputToken.chainId; + const outputChainId = ethereumUtils.getChainIdFromNetwork(outputToken.network); const buyTokenAddress = isNativeAsset(outputToken?.address, outputChainId) ? ETH_ADDRESS_AGGREGATORS : outputToken?.address; - const inputChainId = inputToken.chainId; + const inputChainId = ethereumUtils.getChainIdFromNetwork(inputToken.network); const sellTokenAddress = isNativeAsset(inputToken?.address, inputChainId) ? ETH_ADDRESS_AGGREGATORS : inputToken?.address; const sellAmount = convertAmountToRawAmount(convertNumberToString(inputAmount), inputToken.decimals); const isCrosschainSwap = outputChainId !== inputChainId; - logger.debug(`[useSwapDerivedOutputs]: `, { - outputToken, - outputChainId, - inputToken, - inputChainId, - isCrosschainSwap, - }); + // logger.info(`[getOutputAmount]: `, { + // outputToken, + // outputChainId, + // outputNetwork, + // inputToken, + // inputChainId, + // inputNetwork, + // isCrosschainSwap, + // }); const quoteSource = getSource(source); const quoteParams: QuoteParams = { @@ -198,16 +200,16 @@ const getOutputAmount = async ( }; const rand = Math.floor(Math.random() * 100); - logger.debug('[useSwapDerivedOutputs]: Getting quote', { rand, quoteParams }); + logger.debug('[getOutputAmount]: Getting quote', { rand, quoteParams }); // Do not deleeeet the comment below 😤 // @ts-ignore About to get quote const quote: Quote | CrosschainQuote | QuoteError | null = await (isCrosschainSwap ? getCrosschainQuote : getQuote)(quoteParams); - logger.debug('[useSwapDerivedOutputs]: Got quote', { rand, quote }); + logger.debug('[getOutputAmount]: Got quote', { rand, quote }); if (!quote || (quote as QuoteError)?.error || !(quote as Quote)?.buyAmount) { const quoteError = quote as QuoteError; if (quoteError.error) { - logger.error(new RainbowError('[useSwapDerivedOutputs]: Quote error'), { + logger.error(new RainbowError('[getOutputAmount]: Quote error'), { code: quoteError.error_code, msg: quoteError.message, }); @@ -327,7 +329,7 @@ export default function useSwapDerivedOutputs(type: string) { }; } - logger.debug('[useSwapDerivedOutputs]: Getting trade details', { + logger.debug('[getTradeDetails]: Getting trade details', { independentField, independentValue, inputCurrency, @@ -448,7 +450,7 @@ export default function useSwapDerivedOutputs(type: string) { tradeDetails, }; - logger.debug('[useSwapDerivedOutputs]: Got trade details', { + logger.debug('[getTradeDetails]: Got trade details', { data, }); diff --git a/src/hooks/useSwapInputHandlers.ts b/src/hooks/useSwapInputHandlers.ts index ac7684de5c2..b6c4478bae2 100644 --- a/src/hooks/useSwapInputHandlers.ts +++ b/src/hooks/useSwapInputHandlers.ts @@ -19,12 +19,12 @@ export default function useSwapInputHandlers() { const updateMaxInputAmount = useCallback(() => { const inputCurrencyAddress = inputCurrency?.address; const inputCurrencyUniqueId = inputCurrency?.uniqueId; - const inputCurrencyChainId = inputCurrency?.chainId; + const inputCurrencyNetwork = inputCurrency?.network; const accountAsset = ethereumUtils.getAccountAsset(inputCurrencyUniqueId); const oldAmount = accountAsset?.balance?.amount ?? '0'; let newAmount = oldAmount; - if (isNativeAsset(inputCurrencyAddress, inputCurrencyChainId) && accountAsset) { + if (isNativeAsset(inputCurrencyAddress, ethereumUtils.getChainIdFromNetwork(inputCurrencyNetwork)) && accountAsset) { // this subtracts gas from the balance of the asset newAmount = toFixedDecimals(ethereumUtils.getBalanceAmount(selectedGasFee, accountAsset, l1GasFeeOptimism), 6); @@ -39,7 +39,7 @@ export default function useSwapInputHandlers() { } } dispatch(updateSwapInputAmount(newAmount, true)); - }, [dispatch, inputCurrency?.address, inputCurrency?.chainId, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); + }, [dispatch, inputCurrency?.address, inputCurrency?.network, inputCurrency?.uniqueId, l1GasFeeOptimism, selectedGasFee]); const updateInputAmount = useCallback( (value: string | null) => { diff --git a/src/hooks/useSwapRefuel.ts b/src/hooks/useSwapRefuel.ts index d2455ee51ee..488edca8bdc 100644 --- a/src/hooks/useSwapRefuel.ts +++ b/src/hooks/useSwapRefuel.ts @@ -7,8 +7,8 @@ import { useEffect, useMemo, useState } from 'react'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { useAccountSettings, useGas } from '.'; import { isNativeAsset } from '@/handlers/assets'; +import { NetworkTypes } from '@/helpers'; import { toWei } from '@/handlers/web3'; -import { ChainId } from '@/networks/types'; export enum RefuelState { 'Add' = 'Add', @@ -32,17 +32,22 @@ export default function useSwapRefuel({ const [outputNativeAsset, setOutputNativeAsset] = useState(); const [inputNativeAsset, setInputNativeAsset] = useState(); - const { chainId, toChainId, isCrosschainSwap } = useMemo(() => { - const chainId = inputCurrency.chainId; - const toChainId = outputCurrency.chainId; - const isCrosschainSwap = crosschainSwapsEnabled && chainId !== toChainId; + const { inputNetwork, outputNetwork, chainId, toChainId, isCrosschainSwap } = useMemo(() => { + const inputNetwork = inputCurrency.network; + const outputNetwork = outputCurrency.network; + const chainId = ethereumUtils.getChainIdFromNetwork(inputNetwork); + + const toChainId = ethereumUtils.getChainIdFromNetwork(outputNetwork); + const isCrosschainSwap = crosschainSwapsEnabled && inputNetwork !== outputNetwork; return { + inputNetwork, + outputNetwork, chainId, toChainId, isCrosschainSwap, }; - }, [crosschainSwapsEnabled, inputCurrency.chainId, outputCurrency.chainId]); + }, [crosschainSwapsEnabled, inputCurrency.network, outputCurrency.network]); const { data: minRefuelAmount } = useMinRefuelAmount( { @@ -54,14 +59,14 @@ export default function useSwapRefuel({ useEffect(() => { const getNativeInputOutputAssets = async () => { - if (!chainId || !toChainId || !accountAddress) return; - const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId: toChainId, address: accountAddress }); - const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }); + if (!outputNetwork || !inputNetwork || !accountAddress) return; + const outputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(toChainId, accountAddress); + const inputNativeAsset = await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress); setOutputNativeAsset(outputNativeAsset); setInputNativeAsset(inputNativeAsset); }; getNativeInputOutputAssets(); - }, [accountAddress, toChainId, chainId]); + }, [outputNetwork, inputNetwork, accountAddress, toChainId, chainId]); const { showRefuelSheet, refuelState } = useMemo(() => { const swappingToNativeAsset = isNativeAsset(outputCurrency?.address, toChainId); @@ -74,7 +79,7 @@ export default function useSwapRefuel({ return { showRefuelSheet: false, refuelState: null }; } // If we are swapping to mainnet then ignore - if (toChainId === ChainId.mainnet) return { showRefuelSheet: false, refuelState: null }; + if (outputNetwork === NetworkTypes.mainnet) return { showRefuelSheet: false, refuelState: null }; // Does the user have an existing balance on the output native asset const hasZeroOutputNativeAssetBalance = isZero(outputNativeAsset?.balance?.amount || 0); @@ -125,6 +130,7 @@ export default function useSwapRefuel({ minRefuelAmount, outputCurrency?.address, outputNativeAsset?.balance?.amount, + outputNetwork, selectedGasFee?.gasFee?.estimatedFee?.value?.amount, toChainId, tradeDetails?.sellAmount, diff --git a/src/hooks/useSwappableUserAssets.ts b/src/hooks/useSwappableUserAssets.ts index 7e4338f042d..f83282af7bd 100644 --- a/src/hooks/useSwappableUserAssets.ts +++ b/src/hooks/useSwappableUserAssets.ts @@ -1,13 +1,13 @@ import { SwappableAsset } from '@/entities'; import { walletFilter } from '@/handlers/tokenSearch'; +import { Network } from '@/helpers'; import { useCoinListEditOptions } from '@/hooks'; import { ETH_ADDRESS } from '@/references'; import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import { EthereumAddress, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS } from '@rainbow-me/swaps'; import { ethereumUtils } from '@/utils'; import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { RainbowNetworkObjects, getNetworkObject, getSwappableNetworks } from '@/networks'; -import { Network } from '@/networks/types'; +import { RainbowNetworks, getNetworkObj, getSwappableNetworks } from '@/networks'; type SwappableAddresses = Record; @@ -29,7 +29,8 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset if (hiddenCoinsObj[asset.uniqueId]) return true; // filter out networks where swaps are not enabled - if (getNetworkObject({ chainId: asset.chainId }).features.swaps) return true; + const assetNetwork = asset.network; + if (getNetworkObj(assetNetwork).features.swaps) return true; return false; }); @@ -59,7 +60,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset ); const getSwappableAddressesInWallet = useCallback(async () => { - const networks = RainbowNetworkObjects.filter(({ features }) => features.swaps).map(({ value }) => value); + const networks = RainbowNetworks.filter(({ features }) => features.swaps).map(({ value }) => value); const walletFilterRequests: Promise[] = []; networks.forEach(network => { diff --git a/src/hooks/useUserAccounts.ts b/src/hooks/useUserAccounts.ts index eafa2508ee9..3c999e3e1d5 100644 --- a/src/hooks/useUserAccounts.ts +++ b/src/hooks/useUserAccounts.ts @@ -1,37 +1,43 @@ import { values } from 'lodash'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; import walletTypes from '@/helpers/walletTypes'; +import { useSelector } from 'react-redux'; +import { AppState } from '@/redux/store'; import { useMemo } from 'react'; import { RainbowAccount } from '@/model/wallet'; +import { Network } from '@/helpers'; export default function useUserAccounts() { const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); + const network = useSelector((state: AppState) => state.settings.network); const userAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type !== walletTypes.readOnly); - const addresses: RainbowAccount[] = []; + const addresses: (RainbowAccount & { network: Network })[] = []; filteredWallets.forEach(wallet => { - wallet.addresses?.forEach(account => { + wallet.addresses.forEach(account => { addresses.push({ ...account, + network, }); }); }); return addresses; - }, [walletsWithBalancesAndNames]); + }, [network, walletsWithBalancesAndNames]); const watchedAccounts = useMemo(() => { const filteredWallets = values(walletsWithBalancesAndNames).filter(wallet => wallet.type === walletTypes.readOnly); - const addresses: RainbowAccount[] = []; + const addresses: (RainbowAccount & { network: Network })[] = []; filteredWallets.forEach(wallet => { - wallet.addresses?.forEach(account => { + wallet.addresses.forEach(account => { addresses.push({ ...account, + network, }); }); }); return addresses; - }, [walletsWithBalancesAndNames]); + }, [network, walletsWithBalancesAndNames]); return { userAccounts, diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 5e52dbf1aa2..71ff139cc6b 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -32,7 +32,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); const allAddresses = useMemo( - () => Object.values(wallets).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), + () => Object.values(wallets).flatMap(wallet => wallet.addresses.map(account => account.address as Address)), [wallets] ); @@ -79,7 +79,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { } return result; - }, [isLoading, allAddresses, summaryData?.data?.addresses, nativeCurrency]); + }, [allAddresses, summaryData, nativeCurrency]); return { balances, diff --git a/src/hooks/useWalletCloudBackup.ts b/src/hooks/useWalletCloudBackup.ts index 57b9caac681..f0efed48182 100644 --- a/src/hooks/useWalletCloudBackup.ts +++ b/src/hooks/useWalletCloudBackup.ts @@ -12,7 +12,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { CLOUD_BACKUP_ERRORS, isCloudBackupAvailable } from '@/handlers/cloudBackup'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { getSupportedBiometryType } from '@/keychain'; import { IS_ANDROID } from '@/env'; import { authenticateWithPIN } from '@/handlers/authentication'; @@ -98,19 +98,19 @@ export default function useWalletCloudBackup() { } // We have the password and we need to add it to an existing backup - logger.debug('[useWalletCloudBackup]: password fetched correctly'); + logger.log('password fetched correctly'); let updatedBackupFile = null; try { if (!latestBackup) { - logger.debug(`[useWalletCloudBackup]: backing up to ${cloudPlatform}: ${wallets![walletId]}`); + logger.log(`backing up to ${cloudPlatform}`, wallets![walletId]); updatedBackupFile = await backupWalletToCloud({ password, wallet: wallets![walletId], userPIN, }); } else { - logger.debug(`[useWalletCloudBackup]: adding wallet to ${cloudPlatform} backup: ${wallets![walletId]}`); + logger.log(`adding wallet to ${cloudPlatform} backup`, wallets![walletId]); updatedBackupFile = await addWalletToCloudBackup({ password, wallet: wallets![walletId], @@ -121,7 +121,8 @@ export default function useWalletCloudBackup() { } catch (e: any) { const userError = getUserError(e); !!onError && onError(userError); - logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to backup wallet to ${cloudPlatform}: ${e}`)); + logger.sentry(`error while trying to backup wallet to ${cloudPlatform}`); + captureException(e); analytics.track(`Error during ${cloudPlatform} Backup`, { category: 'backup', error: userError, @@ -131,13 +132,14 @@ export default function useWalletCloudBackup() { } try { - logger.debug('[useWalletCloudBackup]: backup completed!'); + logger.log('backup completed!'); await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.cloud, updatedBackupFile)); - logger.debug('[useWalletCloudBackup]: backup saved everywhere!'); + logger.log('backup saved everywhere!'); !!onSuccess && onSuccess(); return true; } catch (e) { - logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to save wallet backup state: ${e}`)); + logger.sentry('error while trying to save wallet backup state'); + captureException(e); const userError = getUserError(new Error(CLOUD_BACKUP_ERRORS.WALLET_BACKUP_STATUS_UPDATE_FAILED)); !!onError && onError(userError); analytics.track('Error updating Backup status', { diff --git a/src/hooks/useWalletManualBackup.ts b/src/hooks/useWalletManualBackup.ts index daa83ed1554..1a5ba71404b 100644 --- a/src/hooks/useWalletManualBackup.ts +++ b/src/hooks/useWalletManualBackup.ts @@ -1,8 +1,9 @@ +import { captureException } from '@sentry/react-native'; import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { setWalletBackedUp } from '../redux/wallets'; import WalletBackupTypes from '@/helpers/walletBackupTypes'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; export default function useWalletManualBackup() { const dispatch = useDispatch(); @@ -12,9 +13,8 @@ export default function useWalletManualBackup() { try { await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.manual)); } catch (e) { - logger.error( - new RainbowError(`[useWalletManualBackup]: error while trying to set walletId ${walletId} as manually backed up: ${e}`) - ); + logger.sentry(`error while trying to set walletId ${walletId} as manually backed up`); + captureException(e); } }, [dispatch] diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index 51ec2a03b12..dcf48245d98 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -11,7 +11,7 @@ export default function useWalletsWithBalancesAndNames() { const walletsWithBalancesAndNames = useMemo( () => mapValues(wallets, wallet => { - const updatedAccounts = (wallet.addresses || []).map(account => ({ + const updatedAccounts = (wallet.addresses ?? []).map(account => ({ ...account, balances: balances[account.address.toLowerCase() as Address], ens: walletNames[account.address], diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index d81780db3b2..c065a77d255 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,30 +1,26 @@ import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; -import { transactionFetchQuery } from '@/resources/transactions/transaction'; +import { MinedTransaction, RainbowTransaction } from '@/entities/transactions/transaction'; +import { getTransactionFlashbotStatus } from '@/handlers/transactions'; +import { getIsHardhatConnected, getProviderForNetwork } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; -import { getProvider } from '@/handlers/web3'; -import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; +import { Network } from '@/networks/types'; import { queryClient } from '@/react-query/queryClient'; -import { getTransactionFlashbotStatus } from '@/handlers/transactions'; -import { ChainId } from '@/networks/types'; import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { invalidateAddressNftsQueries } from '@/resources/nfts'; +import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions'; +import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { useNonceStore } from '@/state/nonces'; import { usePendingTransactionsStore } from '@/state/pendingTransactions'; import { useCallback, useMemo } from 'react'; import { Address } from 'viem'; -import { staleBalancesStore } from '@/state/staleBalances'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; import useAccountSettings from './useAccountSettings'; -import { RainbowTransaction } from '@/entities'; -import { MinedTransaction } from '@/entities/transactions/transaction'; export const useWatchPendingTransactions = ({ address }: { address: string }) => { const { storePendingTransactions, setPendingTransactions } = usePendingTransactionsStore(state => ({ storePendingTransactions: state.pendingTransactions, setPendingTransactions: state.setPendingTransactions, })); - const { connectedToHardhat } = useConnectedToHardhatStore(); const setNonce = useNonceStore(state => state.setNonce); @@ -35,6 +31,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const refreshAssets = useCallback( (_: RainbowTransaction) => { // NOTE: We have two user assets stores right now, so let's invalidate both queries and trigger a refetch + const connectedToHardhat = getIsHardhatConnected(); queryClient.invalidateQueries( userAssetsQueryKey({ address, @@ -51,7 +48,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); invalidateAddressNftsQueries(address); }, - [address, connectedToHardhat, nativeCurrency] + [address, nativeCurrency] ); const processFlashbotsTransaction = useCallback(async (tx: RainbowTransaction): Promise => { @@ -74,7 +71,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { const transaction = await transactionFetchQuery({ hash: tx.hash!, - chainId: tx.chainId, + network: tx.network, address, currency: nativeCurrency, }); @@ -91,7 +88,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => async (tx: RainbowTransaction) => { let updatedTransaction: RainbowTransaction = { ...tx }; try { - if (tx.chainId && tx.hash && address) { + if (tx.network && tx.hash && address) { updatedTransaction = await processSupportedNetworkTransaction(updatedTransaction); // if flashbots tx and no blockNumber, check if it failed if (!(tx as any).blockNumber && tx.flashbots) { @@ -100,9 +97,10 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => } else { throw new Error('Pending transaction missing chain id'); } - } catch (e) { - logger.error(new RainbowError(`[useWatchPendingTransaction]: Failed to watch transaction`), { - message: (e as Error)?.message || 'Unknown error', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + logger.error(new RainbowError(`useWatchPendingTransaction: Failed to watch transaction`), { + message: e.message, }); } @@ -117,46 +115,46 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => const processNonces = useCallback( (txs: RainbowTransaction[]) => { const userTxs = txs.filter(tx => address?.toLowerCase() === tx.from?.toLowerCase()); - const chainIds = [ + const networks = [ ...new Set( userTxs.reduce((acc, tx) => { - acc.add(tx.chainId); + acc.add(tx.network); return acc; - }, new Set()) + }, new Set()) ), ]; let flashbotsTxFailed = false; const highestNoncePerChainId = userTxs.reduce((acc, tx) => { // if tx is not on mainnet, we don't care about the nonce - if (tx.chainId !== ChainId.mainnet) { - acc.set(tx.chainId, tx.nonce); + if (tx.network !== Network.mainnet) { + acc.set(tx.network, tx.nonce); return acc; } // if tx is flashbots and failed, we want to use the lowest nonce if (tx.flashbots && (tx as any)?.flashbotsStatus === 'FAILED' && tx?.nonce) { // if we already have a failed flashbots tx, we want to use the lowest nonce - if (flashbotsTxFailed && tx.nonce < acc.get(tx.chainId)) { - acc.set(tx.chainId, tx.nonce); + if (flashbotsTxFailed && tx.nonce < acc.get(tx.network)) { + acc.set(tx.network, tx.nonce); } else { - acc.set(tx.chainId, tx.nonce); + acc.set(tx.network, tx.nonce); flashbotsTxFailed = true; } // if tx succeeded, we want to use the highest nonce - } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.chainId)) { - acc.set(tx.chainId, tx.nonce); + } else if (!flashbotsTxFailed && tx?.nonce && tx.nonce > acc.get(tx.network)) { + acc.set(tx.network, tx.nonce); } return acc; }, new Map()); - chainIds.map(async chainId => { - const provider = getProvider({ chainId }); + networks.map(async network => { + const provider = getProviderForNetwork(network); const providerTransactionCount = await provider.getTransactionCount(address, 'latest'); const currentProviderNonce = providerTransactionCount - 1; - const currentNonceForChainId = highestNoncePerChainId.get(chainId) - 1; + const currentNonceForChainId = highestNoncePerChainId.get(network) - 1; setNonce({ address, - chainId, + network: network, currentNonce: currentProviderNonce > currentNonceForChainId ? currentProviderNonce : currentNonceForChainId, latestConfirmedNonce: currentProviderNonce, }); @@ -189,23 +187,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => ); if (minedTransactions.length) { - const chainIds = RainbowNetworkObjects.filter(networkObject => networkObject.enabled && networkObject.networkType !== 'testnet').map( - networkObject => networkObject.id - ); - minedTransactions.forEach(tx => { - if (tx.changes?.length) { - tx.changes?.forEach(change => { - processStaleAsset({ asset: change?.asset, address, transactionHash: tx?.hash }); - }); - } else if (tx.asset) { - processStaleAsset({ address, asset: tx.asset, transactionHash: tx?.hash }); - } - }); - - queryClient.refetchQueries({ - queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }), - }); - + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); await queryClient.refetchQueries({ queryKey: consolidatedTransactionsQueryKey({ address, @@ -229,31 +211,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => address, pendingTransactions: newPendingTransactions, }); - }, [address, connectedToHardhat, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); + }, [address, nativeCurrency, pendingTransactions, processNonces, processPendingTransaction, setPendingTransactions]); return { watchPendingTransactions }; }; - -function processStaleAsset({ - asset, - address, - transactionHash, -}: { - asset: RainbowTransaction['asset']; - address: string; - transactionHash: string; -}) { - const { addStaleBalance } = staleBalancesStore.getState(); - const chainId = asset?.chainId; - if (asset && typeof chainId === 'number') { - const changedAssetAddress = asset?.address as Address; - addStaleBalance({ - address, - chainId, - info: { - address: changedAssetAddress, - transactionHash, - }, - }); - } -} diff --git a/src/hooks/useWatchWallet.ts b/src/hooks/useWatchWallet.ts index 19703a3b344..96aae19410f 100644 --- a/src/hooks/useWatchWallet.ts +++ b/src/hooks/useWatchWallet.ts @@ -6,8 +6,7 @@ import { useAccountProfile, useDeleteWallet, useImportingWallet, useInitializeWa import { cleanUpWalletKeys, RainbowWallet } from '@/model/wallet'; import { addressSetSelected, walletsSetSelected } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import { doesWalletsContainAddress } from '@/utils'; -import { RainbowError, logger } from '@/logger'; +import { doesWalletsContainAddress, logger } from '@/utils'; export default function useWatchWallet({ address: primaryAddress, @@ -25,9 +24,7 @@ export default function useWatchWallet({ const { wallets } = useWallets(); const watchingWallet = useMemo(() => { - return Object.values(wallets || {}).find(wallet => - (wallet.addresses || []).some(({ address }) => address === primaryAddress) - ); + return Object.values(wallets || {}).find(wallet => wallet.addresses.some(({ address }) => address === primaryAddress)); }, [primaryAddress, wallets]); const isWatching = useMemo(() => Boolean(watchingWallet), [watchingWallet]); @@ -45,9 +42,7 @@ export default function useWatchWallet({ // @ts-expect-error ts-migrate(2554) FIXME: Expected 8-9 arguments, but got 7. initializeWallet(null, null, null, false, false, null, true); } catch (e) { - logger.error(new RainbowError(`[useWatchWallet]: error while switching account`), { - error: (e as Error)?.message || 'Unknown error', - }); + logger.log('error while switching account', e); } }, [dispatch, initializeWallet, wallets] @@ -66,7 +61,7 @@ export default function useWatchWallet({ // it's deletable const isLastAvailableWallet = Object.keys(wallets!).find(key => { const someWallet = wallets![key]; - const otherAccount = someWallet.addresses?.find((account: any) => account.visible && account.address !== accountAddress); + const otherAccount = someWallet.addresses.find((account: any) => account.visible && account.address !== accountAddress); if (otherAccount) { return true; } diff --git a/src/hooks/useWebData.ts b/src/hooks/useWebData.ts index f3efe8e9585..daa5a80b5ad 100644 --- a/src/hooks/useWebData.ts +++ b/src/hooks/useWebData.ts @@ -10,7 +10,7 @@ import { containsEmoji } from '@/helpers/strings'; import WalletTypes from '@/helpers/walletTypes'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { AppState } from '@/redux/store'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { useTheme } from '@/theme'; const getAccountSymbol = (name: string) => { @@ -98,7 +98,7 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(assetIds); - logger.debug('[useWebData]: showcase initialized!'); + logger.log('showcase initialized!'); return; } @@ -112,7 +112,7 @@ export default function useWebData() { const response = await getPreference('hidden', accountAddress); if (!response || !response.hidden.ids.length) { await setPreference(PreferenceActionType.init, 'hidden', accountAddress, assetIds); - logger.debug('[useWebData]: hidden initialized!'); + logger.log('hidden initialized!'); return; } @@ -130,15 +130,15 @@ export default function useWebData() { const response = await getPreference('showcase', accountAddress); if (!response || !response.showcase.ids.length) { await initWebData(showcaseTokens); - logger.debug('[useWebData]: showcase initialized!'); + logger.log('showcase initialized!'); return; } - logger.debug('[useWebData]: showcase already initialized. skipping'); + logger.log('showcase already initialized. skipping'); } } } catch (e) { - logger.error(new RainbowError(`[useWebData]: error while trying to initialize showcase: ${e}`)); + logger.log('Error trying to initiailze showcase'); } }, [accountAddress, initWebData, showcaseTokens, webDataEnabled]); diff --git a/src/keychain/index.ts b/src/keychain/index.ts index 0ef3d78771a..9479712c1d9 100644 --- a/src/keychain/index.ts +++ b/src/keychain/index.ts @@ -67,11 +67,11 @@ export const publicAccessControlOptions: Options = { * decrypt the data. */ export async function get(key: string, options: KeychainOptions = {}): Promise> { - logger.debug(`[keychain]: get`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: get`, { key }, logger.DebugContext.keychain); async function _get(attempts = 0): Promise> { if (attempts > 0) { - logger.debug(`[keychain]: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: get attempt ${attempts}`, { key }, logger.DebugContext.keychain); } let data = cache.getString(key); @@ -93,24 +93,24 @@ export async function get(key: string, options: KeychainOptions = {}): Promise 2) { return { @@ -170,7 +170,7 @@ export async function get(key: string, options: KeychainOptions = {}): Promise { - logger.debug(`[keychain]: set`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: set`, { key }, logger.DebugContext.keychain); // only save public data to mmkv // private data has accessControl if (!options.accessControl) { cache.set(key, value); } else if (options.accessControl && IS_ANDROID && !(await getSupportedBiometryType())) { - logger.debug(`[keychain]: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); + logger.debug(`keychain: encrypting private data on android`, { key, options }, logger.DebugContext.keychain); const pin = options.androidEncryptionPin || (await authenticateWithPINAndCreateIfNeeded()); const encryptedValue = await encryptor.encrypt(pin, value); @@ -216,7 +216,7 @@ export async function set(key: string, value: string, options: KeychainOptions = if (encryptedValue) { value = encryptedValue; } else { - throw new Error(`[keychain]: failed to encrypt value`); + throw new Error(`keychain: failed to encrypt value`); } } @@ -231,7 +231,7 @@ export async function getObject = Record> { - logger.debug(`[keychain]: getObject`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: getObject`, { key }, logger.DebugContext.keychain); const { value, error } = await get(key, options); @@ -250,7 +250,7 @@ export async function getObject = Record, options: KeychainOptions = {}): Promise { - logger.debug(`[keychain]: setObject`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: setObject`, { key }, logger.DebugContext.keychain); await set(key, JSON.stringify(value), options); } @@ -259,7 +259,7 @@ export async function setObject(key: string, value: Record, options * Check if a value exists on the keychain. */ export async function has(key: string): Promise { - logger.debug(`[keychain]: has`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: has`, { key }, logger.DebugContext.keychain); return Boolean(await hasInternetCredentials(key)); } @@ -267,7 +267,7 @@ export async function has(key: string): Promise { * Remove a value from the keychain. */ export async function remove(key: string) { - logger.debug(`[keychain]: remove`, { key }, logger.DebugContext.keychain); + logger.debug(`keychain: remove`, { key }, logger.DebugContext.keychain); cache.delete(key); await resetInternetCredentials(key); @@ -281,11 +281,11 @@ export async function remove(key: string) { */ export async function getAllKeys(): Promise { try { - logger.debug(`[keychain]: getAllKeys`, {}, logger.DebugContext.keychain); + logger.debug(`keychain: getAllKeys`, {}, logger.DebugContext.keychain); const res = await getAllInternetCredentials(); return res ? res.results : []; } catch (e: any) { - logger.error(new RainbowError(`[keychain]: getAllKeys() failed`), { + logger.error(new RainbowError(`keychain: getAllKeys() failed`), { message: e.toString(), }); return undefined; @@ -299,7 +299,7 @@ export async function getAllKeys(): Promise { * `getAllKeys`. */ export async function clear() { - logger.debug(`[keychain]: clear`, {}, logger.DebugContext.keychain); + logger.debug(`keychain: clear`, {}, logger.DebugContext.keychain); cache.clearAll(); @@ -314,7 +314,7 @@ export async function clear() { * Wrapper around the underlying library's method by the same name. */ export async function getSupportedBiometryType(): Promise { - logger.debug(`[keychain]: getSupportedBiometryType`, {}, logger.DebugContext.keychain); + logger.debug(`keychain: getSupportedBiometryType`, {}, logger.DebugContext.keychain); return (await originalGetSupportedBiometryType()) || undefined; } @@ -323,7 +323,7 @@ export async function getSupportedBiometryType(): Promise> { - logger.debug(`[keychain]: getSharedWebCredentials`, {}, logger.DebugContext.keychain); + logger.debug(`keychain: getSharedWebCredentials`, {}, logger.DebugContext.keychain); let data = undefined; @@ -352,7 +352,7 @@ export async function getSharedWebCredentials(): Promise { - logger.debug(`[keychain]: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); + logger.debug(`keychain: getPrivateAccessControlOptions`, {}, logger.DebugContext.keychain); const isSimulator = IS_DEV && (await DeviceInfo.isEmulator()); diff --git a/src/languages/ar_AR.json b/src/languages/ar_AR.json index 2ca00d05971..529ecaacb6a 100644 --- a/src/languages/ar_AR.json +++ b/src/languages/ar_AR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "أوتو", - "degen_mode": "وضع Degen", - "degen_mode_description": "تخطي خطوة المراجعة للتبديل بشكل أسرع", + "degen_mode": "وضع ديجين", + "degen_mode_description": "تجاوز ورقة المراجعة للتبديل بشكل أسرع", "maximum_sold": "الحد الأقصى المباع", "minimum_received": "الحد الأدنى المستلم", "preferred_network": "الشبكة المفضلة", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "بيع", "sort": { - "ABC": "أبج", "abc": "أبج", - "MOST_RECENT": "الأخير", "most_recent": "الأحدث", - "FLOOR_PRICE": "سعر الأرضية", "floor_price": "سعر الأرضية" - }, - "empty": "القطع التذكارية", - "collect_now": "اجمع الآن", - "will_appear_here": "سوف تظهر مقتنياتك هنا" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "الأخير", - "popular": "شائع في Rainbow", "favorites": "المفضلة", "bridge": "جسر", "verified": "تم التحقق منه", @@ -2503,7 +2496,7 @@ "balance_title": "رصيد", "buy": "شراء", "change_wallet": { - "loading_balance": "جاري تحميل الرصيد...", + "no_balance": "لا يوجد رصيد", "balance_eth": "%{balanceEth} ETH", "watching": "مشاهدة", "ledger": "ليدجر" diff --git a/src/languages/en_US.json b/src/languages/en_US.json index b2c1194593f..c336b050e56 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -487,7 +487,6 @@ "clear_local_storage": "Clear local storage", "clear_mmkv_storage": "Clear MMKV storage", "connect_to_hardhat": "Connect to hardhat", - "disconnect_to_hardhat": "Disconnect from hardhat", "crash_app_render_error": "Crash app (render error)", "enable_testnets": "Enable Testnets", "installing_update": "Installing update", diff --git a/src/languages/es_419.json b/src/languages/es_419.json index 5a5759b5ab8..43216bd6eb9 100644 --- a/src/languages/es_419.json +++ b/src/languages/es_419.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Saltar el paso de revisión para intercambiar más rápido", + "degen_mode_description": "Omitir la hoja de revisión para intercambiar más rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Mínimo Recibido", "preferred_network": "Red preferida", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Vendiendo", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "Reciente", "most_recent": "Más Reciente", - "FLOOR_PRICE": "Precio Base", "floor_price": "Precio Base" - }, - "empty": "Coleccionables", - "collect_now": "Colecciona Ahora", - "will_appear_here": "Tus coleccionables aparecerán aquí" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Reciente", - "popular": "Popular en Rainbow", "favorites": "Favoritos", "bridge": "Puente", "verified": "Verificado", @@ -2503,7 +2496,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "loading_balance": "Cargando saldo...", + "no_balance": "Sin Balance", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/fr_FR.json b/src/languages/fr_FR.json index 18e626c7058..8866fc35723 100644 --- a/src/languages/fr_FR.json +++ b/src/languages/fr_FR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Auto", "degen_mode": "Mode Degen", - "degen_mode_description": "Passer l'étape de l'examen pour échanger plus rapidement", + "degen_mode_description": "Passez la feuille de révision pour un échange plus rapide", "maximum_sold": "Vendu au maximum", "minimum_received": "Montant minimum reçu", "preferred_network": "Réseau Préféré", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Vente", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "Récent", "most_recent": "Le plus récent", - "FLOOR_PRICE": "Prix Plancher", "floor_price": "Prix Plancher" - }, - "empty": "Objets de collection", - "collect_now": "Collect Now", - "will_appear_here": "Vos objets de collection apparaîtront ici" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Récent", - "popular": "Populaire sur Rainbow", "favorites": "Favoris", "bridge": "Pont", "verified": "Vérifié", @@ -2503,7 +2496,7 @@ "balance_title": "Solde", "buy": "Acheter", "change_wallet": { - "loading_balance": "Chargement du solde...", + "no_balance": "Pas de solde", "balance_eth": "%{balanceEth} ETH", "watching": "Regarder", "ledger": "Ledger" diff --git a/src/languages/hi_IN.json b/src/languages/hi_IN.json index 1a92d75d7f0..69f9d44673b 100644 --- a/src/languages/hi_IN.json +++ b/src/languages/hi_IN.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "ऑटो", - "degen_mode": "Degen मोड", - "degen_mode_description": "फ़ास्ट स्वैप के लिए समीक्षा चरण को छोड़ें", + "degen_mode": "डिजन मोड", + "degen_mode_description": "तेज स्वैप करने के लिए समीक्षा शीट को छोड़ें", "maximum_sold": "अधिकतम बेचा गया", "minimum_received": "न्यूनतम प्राप्त", "preferred_network": "पसंदीदा नेटवर्क", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "बिक्री", "sort": { - "ABC": "एबीसी", "abc": "एबीसी", - "MOST_RECENT": "हाल का", "most_recent": "सबसे हालिया", - "FLOOR_PRICE": "मंजिल मूल्य", "floor_price": "मंजिल मूल्य" - }, - "empty": "संग्रह", - "collect_now": "अभी इकट्ठा करें", - "will_appear_here": "आपके संग्रह योग्य सामग्री यहाँ दिखाई देंगे" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "हाल का", - "popular": "रेनबो में लोकप्रिय", "favorites": "पसंदीदा", "bridge": "ब्रिज", "verified": "सत्यापित", @@ -2503,7 +2496,7 @@ "balance_title": "बैलेंस", "buy": "खरीदें", "change_wallet": { - "loading_balance": "शेष लोड हो रहा है...", + "no_balance": "कोई बैलेंस नहीं", "balance_eth": "%{balanceEth} ETH", "watching": "देख रहा है", "ledger": "लेजर" diff --git a/src/languages/id_ID.json b/src/languages/id_ID.json index 55e9437efae..312d0d854ac 100644 --- a/src/languages/id_ID.json +++ b/src/languages/id_ID.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Otomatis", "degen_mode": "Mode Degen", - "degen_mode_description": "Lewati langkah ulasan untuk bertukar lebih cepat", + "degen_mode_description": "Lewati lembar tinjauan untuk swap lebih cepat", "maximum_sold": "Maksimum Dijual", "minimum_received": "Minimum yang diterima", "preferred_network": "Jaringan yang Disukai", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Menjual", "sort": { - "ABC": "Abjad", "abc": "Abjad", - "MOST_RECENT": "Baru-baru ini", "most_recent": "Terbaru", - "FLOOR_PRICE": "Harga Dasar", "floor_price": "Harga Dasar" - }, - "empty": "Koleksi", - "collect_now": "Koleksi Sekarang", - "will_appear_here": "Koleksi Anda Akan Muncul di Sini" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Baru-baru ini", - "popular": "Populer di Rainbow", "favorites": "Favorit", "bridge": "Jembatan", "verified": "Terverifikasi", @@ -2503,7 +2496,7 @@ "balance_title": "Saldo", "buy": "Beli", "change_wallet": { - "loading_balance": "Memuat Saldo...", + "no_balance": "Tidak Ada Saldo", "balance_eth": "%{balanceEth} ETH", "watching": "Menonton", "ledger": "Ledger" diff --git a/src/languages/ja_JP.json b/src/languages/ja_JP.json index ee48137fd7a..a3a1f552f82 100644 --- a/src/languages/ja_JP.json +++ b/src/languages/ja_JP.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "自動", - "degen_mode": "Degenモード", - "degen_mode_description": "レビューステップをスキップしてより早くスワップ", + "degen_mode": "デゲンモード", + "degen_mode_description": "レビューページをスキップして速くスワップする", "maximum_sold": "最大売却数量", "minimum_received": "受け取る最小額", "preferred_network": "優先ネットワーク", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "販売", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "最近の", "most_recent": "最新順", - "FLOOR_PRICE": "フロア価格", "floor_price": "フロア価格" - }, - "empty": "コレクタブル", - "collect_now": "今すぐコレクト", - "will_appear_here": "あなたのコレクティブルはこちらに表示されます" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "最近の", - "popular": "Rainbowの人気", "favorites": "お気に入り", "bridge": "ブリッジ", "verified": "確認済み", @@ -2503,7 +2496,7 @@ "balance_title": "残高", "buy": "購入", "change_wallet": { - "loading_balance": "残高を読み込み中...", + "no_balance": "残高なし", "balance_eth": "%{balanceEth} ETH", "watching": "ウォッチ中", "ledger": "Ledger" diff --git a/src/languages/ko_KR.json b/src/languages/ko_KR.json index 87f8d541567..ee720cf8789 100644 --- a/src/languages/ko_KR.json +++ b/src/languages/ko_KR.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "자동", - "degen_mode": "Degen모드", - "degen_mode_description": "리뷰 단계를 건너뛰어 더 빠르게 교환", + "degen_mode": "디겐 모드", + "degen_mode_description": "검토 시트를 건너뛰어 더 빠르게 스왑", "maximum_sold": "최대 판매", "minimum_received": "최소 수령량", "preferred_network": "선호 네트워크", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "판매", "sort": { - "ABC": "가나다", "abc": "가나다", - "MOST_RECENT": "최근", "most_recent": "최신순", - "FLOOR_PRICE": "최저 가격", "floor_price": "최저 가격" - }, - "empty": "수집품", - "collect_now": "지금 수집", - "will_appear_here": "여기에 수집품이 나타납니다" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "최근", - "popular": "레인보우에서 인기", "favorites": "즐겨찾기", "bridge": "브릿지", "verified": "인증됨", @@ -2503,7 +2496,7 @@ "balance_title": "균형", "buy": "구매", "change_wallet": { - "loading_balance": "잔액 로딩 중...", + "no_balance": "잔액 없음", "balance_eth": "%{balanceEth} 이더리움", "watching": "시청 중", "ledger": "렛저" diff --git a/src/languages/pt_BR.json b/src/languages/pt_BR.json index 503cb630c1b..37c17e65acc 100644 --- a/src/languages/pt_BR.json +++ b/src/languages/pt_BR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Automático", "degen_mode": "Modo Degen", - "degen_mode_description": "Pule a etapa de revisão para trocar mais rápido", + "degen_mode_description": "Pule a revisão para trocar mais rápido", "maximum_sold": "Máximo Vendido", "minimum_received": "Valor Mínimo Recebido", "preferred_network": "Rede Preferida", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Vendendo", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "Recente", "most_recent": "Mais Recente", - "FLOOR_PRICE": "Preço do Piso", "floor_price": "Preço do Piso" - }, - "empty": "Colecionáveis", - "collect_now": "Colecione Agora", - "will_appear_here": "Suas Colecionáveis Aparecerão Aqui" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Recente", - "popular": "Popular no Rainbow", "favorites": "Favoritos", "bridge": "Ponte", "verified": "Verificado", @@ -2503,7 +2496,7 @@ "balance_title": "Saldo", "buy": "Comprar", "change_wallet": { - "loading_balance": "Carregando saldo...", + "no_balance": "Sem Saldo", "balance_eth": "%{balanceEth} ETH", "watching": "Observando", "ledger": "Ledger" diff --git a/src/languages/ru_RU.json b/src/languages/ru_RU.json index d6868c93fc0..7bbc404f7f8 100644 --- a/src/languages/ru_RU.json +++ b/src/languages/ru_RU.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Авто", "degen_mode": "Режим Degen", - "degen_mode_description": "Пропустить этап обзора, чтобы ускорить обмен", + "degen_mode_description": "Пропустите лист проверки для ускорения обмена", "maximum_sold": "Максимум продано", "minimum_received": "Минимальная получаемая", "preferred_network": "Предпочитаемая сеть", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Продажа", "sort": { - "ABC": "АБВ", "abc": "АБВ", - "MOST_RECENT": "Последние", "most_recent": "Самые последние", - "FLOOR_PRICE": "Начальная цена", "floor_price": "Начальная цена" - }, - "empty": "Коллекционные предметы", - "collect_now": "Соберите сейчас", - "will_appear_here": "Ваши коллекции появятся здесь" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Последние", - "popular": "Популярное в Rainbow", "favorites": "Избранное", "bridge": "Мост", "verified": "Проверенные", @@ -2503,7 +2496,7 @@ "balance_title": "Баланс", "buy": "Купить", "change_wallet": { - "loading_balance": "Загрузка баланса...", + "no_balance": "Нет Баланса", "balance_eth": "%{balanceEth} ETH", "watching": "Наблюдение", "ledger": "Ledger" diff --git a/src/languages/th_TH.json b/src/languages/th_TH.json index d2fb43308fd..9c82f0bfb88 100644 --- a/src/languages/th_TH.json +++ b/src/languages/th_TH.json @@ -748,8 +748,8 @@ }, "swap_details_v2": { "automatic": "อัตโนมัติ", - "degen_mode": "Degenโหมด", - "degen_mode_description": "ข้ามขั้นตอนการตรวจสอบเพื่อการสลับที่เร็วขึ้น", + "degen_mode": "โหมดดีเจ็น", + "degen_mode_description": "ข้ามหน้าตรวจสอบเพื่อแลกเปลี่ยนเร็วขึ้น", "maximum_sold": "ขายสูงสุด", "minimum_received": "รับต่ำสุด", "preferred_network": "เครือข่ายที่คุณต้องการ", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "ขาย", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "ล่าสุด", "most_recent": "ล่าสุด", - "FLOOR_PRICE": "ราคาพื้น", "floor_price": "ราคาพื้น" - }, - "empty": "สะสม", - "collect_now": "เก็บสะสมทันที", - "will_appear_here": "ของสะสมจะปรากฏที่นี่" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "ล่าสุด", - "popular": "เป็นที่นิยมใน Rainbow", "favorites": "รายการโปรด", "bridge": "สะพาน", "verified": "ได้รับการตรวจสอบ", @@ -2503,7 +2496,7 @@ "balance_title": "ยอดคงเหลือ", "buy": "ซื้อ", "change_wallet": { - "loading_balance": "กำลังโหลดยอดคงเหลือ...", + "no_balance": "ไม่มีคงเหลือ", "balance_eth": "%{balanceEth} ETH", "watching": "กำลังรับชม", "ledger": "เลเจอร์" diff --git a/src/languages/tr_TR.json b/src/languages/tr_TR.json index 05d5df71b1e..0ccec26d073 100644 --- a/src/languages/tr_TR.json +++ b/src/languages/tr_TR.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "Oto", "degen_mode": "Degen Modu", - "degen_mode_description": "İnceleme adımını atlayarak daha hızlı takas yapın", + "degen_mode_description": "Hızlı Takas İçin İnceleme Sayfasını Atlayın", "maximum_sold": "Azami Satılan", "minimum_received": "Asgari Alınan", "preferred_network": "Tercih Edilen Ağ", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "Satış", "sort": { - "ABC": "Abc", "abc": "Abc", - "MOST_RECENT": "Son", "most_recent": "En Yeni", - "FLOOR_PRICE": "Taban Fiyatı", "floor_price": "Taban Fiyatı" - }, - "empty": "Koleksiyonlar", - "collect_now": "Şimdi Koleksiyon Yap", - "will_appear_here": "Koleksiyonlarınız Burada Görünecek" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "Son", - "popular": "Rainbow'da Popüler", "favorites": "Favoriler", "bridge": "Köprü", "verified": "Doğrulanmış", @@ -2503,7 +2496,7 @@ "balance_title": "Bakiye", "buy": "Satın Al", "change_wallet": { - "loading_balance": "Bakiye Yükleniyor...", + "no_balance": "Bakiye Yok", "balance_eth": "%{balanceEth} ETH", "watching": "İzliyor", "ledger": "Ledger" diff --git a/src/languages/zh_CN.json b/src/languages/zh_CN.json index 809425396e8..a347d97c12e 100644 --- a/src/languages/zh_CN.json +++ b/src/languages/zh_CN.json @@ -749,7 +749,7 @@ "swap_details_v2": { "automatic": "自动", "degen_mode": "Degen 模式", - "degen_mode_description": "跳过审查步骤以更快交换", + "degen_mode_description": "跳过审核表以更快地交换", "maximum_sold": "最大出售量", "minimum_received": "最低接收金额", "preferred_network": "首选网络", @@ -1321,16 +1321,10 @@ "nfts": { "selling": "出售", "sort": { - "ABC": "字母顺序", "abc": "字母顺序", - "MOST_RECENT": "最近", "most_recent": "最新的", - "FLOOR_PRICE": "底价", "floor_price": "底价" - }, - "empty": "收藏品", - "collect_now": "立即收藏", - "will_appear_here": "您的收藏品将会出现在这里" + } }, "nft_offers": { "card": { @@ -2074,7 +2068,6 @@ "token_search": { "section_header": { "recent": "最近", - "popular": "在 Rainbow 中流行", "favorites": "收藏夹", "bridge": "桥接", "verified": "已验证", @@ -2503,7 +2496,7 @@ "balance_title": "余额", "buy": "购买", "change_wallet": { - "loading_balance": "正在加载余额...", + "no_balance": "无余额", "balance_eth": "%{balanceEth} ETH", "watching": "正在关注", "ledger": "分类账" diff --git a/src/logger/sentry.ts b/src/logger/sentry.ts index fd44de21771..53d5160db83 100644 --- a/src/logger/sentry.ts +++ b/src/logger/sentry.ts @@ -21,7 +21,7 @@ export const defaultOptions: Sentry.ReactNativeOptions = { export function initSentry() { if (IS_TEST) { - logger.debug(`[sentry]: disabled for test environment`); + logger.debug(`Sentry is disabled for test environment`); return; } try { @@ -34,8 +34,8 @@ export function initSentry() { release, // MUST BE A STRING or Sentry will break in native code }); - logger.debug(`[sentry]: Successfully initialized`); + logger.debug(`Sentry initialized`); } catch (e) { - logger.error(new RainbowError(`[sentry]: initialization failed`)); + logger.error(new RainbowError(`Sentry initialization failed`)); } } diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 1f2fe83f900..8abb7f47509 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -55,7 +55,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { * should exit early */ if (debug && env.IS_PROD) { - logger.error(new RainbowError(`[migrations]: is in debug mode`), { + logger.error(new RainbowError(`Migration is in debug mode`), { migration: name, }); return; @@ -66,7 +66,7 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { if (handler) { try { logger.debug( - `[migrations]: Migrating`, + `Migrating`, { migration: name, }, @@ -75,19 +75,19 @@ export async function runMigration({ debug, name, migrate, defer }: Migration) { await handler(); if (!debug) storage.set([name], new Date().toUTCString()); logger.debug( - `[migrations]: Migrating complete`, + `Migrating complete`, { migration: name, }, MIGRATIONS_DEBUG_CONTEXT ); } catch (e) { - logger.error(new RainbowError(`[migrations]: Migration failed`), { + logger.error(new RainbowError(`Migration failed`), { migration: name, }); } } else { - logger.error(new RainbowError(`[migrations]: Migration had no handler`), { + logger.error(new RainbowError(`Migration had no handler`), { migration: name, }); } @@ -112,11 +112,11 @@ export async function runMigrations(migrations: Migration[]) { ranMigrations.push(migration.name); } else { - logger.debug(`[migrations]: Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); + logger.debug(`Already migrated`, { migration: migration.name }, MIGRATIONS_DEBUG_CONTEXT); } } - logger.debug(`[migrations]: Ran or scheduled migrations`, { + logger.info(`Ran or scheduled migrations`, { migrations: ranMigrations, }); } diff --git a/src/migrations/migrations/migrateFavorites.ts b/src/migrations/migrations/migrateFavorites.ts index 4ffa4c52259..46845e56225 100644 --- a/src/migrations/migrations/migrateFavorites.ts +++ b/src/migrations/migrations/migrateFavorites.ts @@ -3,6 +3,7 @@ import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { EthereumAddress, RainbowToken } from '@/entities'; import { createQueryKey, persistOptions, queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; +import { ethereumUtils } from '@/utils'; import { persistQueryClientRestore, persistQueryClientSave } from '@tanstack/react-query-persist-client'; import { Migration, MigrationName } from '../types'; @@ -24,7 +25,7 @@ export function migrateFavoritesV2(): Migration { for (const favorite of Object.values(v1Data)) { const uniqueId = getStandardizedUniqueIdWorklet({ address: favorite.address as AddressOrEth, - chainId: favorite.chainId, + chainId: ethereumUtils.getChainIdFromNetwork(favorite.network), }); favorite.uniqueId = uniqueId; // v2 unique uses chainId instead of Network migratedFavorites[uniqueId] = favorite; diff --git a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts index 4284569d94b..df4e1f41baa 100644 --- a/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts +++ b/src/migrations/migrations/migrateRemotePromoSheetsToZustand.ts @@ -27,7 +27,7 @@ export function migrateRemotePromoSheetsToZustand(): Migration { }); } } catch (error) { - logger.error(new RainbowError(`[migrations]: Failed to migrate remote promo sheets to zustand`), { + logger.error(new RainbowError(`Failed to migrate remote promo sheets to zustand`), { data: error, }); } diff --git a/src/model/backup.ts b/src/model/backup.ts index 2eb50a7c297..b5d02bdedac 100644 --- a/src/model/backup.ts +++ b/src/model/backup.ts @@ -11,6 +11,7 @@ import * as keychain from '@/model/keychain'; import * as kc from '@/keychain'; import { AllRainbowWallets, allWalletsVersion, createWallet, RainbowWallet } from './wallet'; import { analytics } from '@/analytics'; +import oldLogger from '@/utils/logger'; import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_DEV } from '@/env'; import AesEncryptor from '../handlers/aesEncryption'; @@ -151,7 +152,7 @@ export async function backupAllWalletsToCloud({ ); const now = Date.now(); - logger.debug(`[backup]: Creating backup with all wallets to ${cloudPlatform}`, { + logger.debug(`Creating backup with all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -203,7 +204,7 @@ export async function backupAllWalletsToCloud({ const walletIdsToUpdate = Object.keys(wallets); await dispatch(setAllWalletsWithIdsAsBackedUp(walletIdsToUpdate, WalletBackupTypes.cloud, updatedBackupFile)); - logger.debug(`[backup]: Successfully backed up all wallets to ${cloudPlatform}`, { + logger.debug(`Successfully backed up all wallets to ${cloudPlatform}`, { category: 'backup', time: now, label: cloudPlatform, @@ -429,7 +430,7 @@ export async function restoreCloudBackup({ if (message === CLOUD_BACKUP_ERRORS.ERROR_DECRYPTING_DATA) { return RestoreCloudBackupResultStates.incorrectPassword; } - logger.error(new RainbowError(`[backup]: Error while restoring back up`), { + logger.error(new RainbowError('Error while restoring back up'), { message, }); return RestoreCloudBackupResultStates.failedWhenRestoring; @@ -520,7 +521,8 @@ async function restoreSpecificBackupIntoKeychain(backedUpData: BackedUpData, use } return true; } catch (e) { - logger.error(new RainbowError(`[backup]: Error restoring specific backup into keychain: ${e}`)); + oldLogger.sentry('error in restoreSpecificBackupIntoKeychain'); + captureException(e); return false; } } @@ -588,7 +590,8 @@ async function restoreCurrentBackupIntoKeychain(backedUpData: BackedUpData, newP return true; } catch (e) { - logger.error(new RainbowError(`[backup]: Error restoring current backup into keychain: ${e}`)); + oldLogger.sentry('error in restoreBackupIntoKeychain'); + captureException(e); return false; } } @@ -617,7 +620,7 @@ async function decryptSecretFromBackupPin({ secret, backupPIN }: { secret?: stri } processedSecret = decryptedSecretToUse; } else { - logger.error(new RainbowError(`[backup]: Failed to decrypt backed up seed phrase using backup PIN.`)); + logger.error(new RainbowError('Failed to decrypt backed up seed phrase using backup PIN.')); return processedSecret; } } @@ -670,7 +673,8 @@ export async function fetchBackupPassword(): Promise { } return null; } catch (e) { - logger.error(new RainbowError(`[backup]: Error while fetching backup password: ${e}`)); + oldLogger.sentry('Error while fetching backup password', e); + captureException(e); return null; } } @@ -683,7 +687,7 @@ export async function getDeviceUUID(): Promise { return new Promise(resolve => { DeviceUUID.getUUID((error: unknown, uuid: string[]) => { if (error) { - logger.error(new RainbowError(`[backup]: Received error when trying to get uuid from Native side`), { + logger.error(new RainbowError('Received error when trying to get uuid from Native side'), { error, }); resolve(null); @@ -735,7 +739,7 @@ export async function checkIdentifierOnLaunch() { if (currentIdentifier.error) { switch (currentIdentifier.error) { case kc.ErrorType.Unavailable: { - logger.debug(`[backup]: Value for current identifier not found, setting it to new UUID...`, { + logger.debug('Value for current identifier not found, setting it to new UUID...', { uuid, error: currentIdentifier.error, }); @@ -744,7 +748,7 @@ export async function checkIdentifierOnLaunch() { } default: - logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { + logger.error(new RainbowError('Error while checking identifier on launch'), { error: currentIdentifier.error, }); break; @@ -794,8 +798,10 @@ export async function checkIdentifierOnLaunch() { }); }); } catch (error) { - logger.error(new RainbowError(`[backup]: Error while checking identifier on launch`), { - error, + logger.error(new RainbowError('Error while checking identifier on launch'), { + extra: { + error, + }, }); } diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 561500aa58a..5026391f774 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -32,7 +32,7 @@ import { returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { updateWebDataEnabled } from '@/redux/showcaseTokens'; import { ethereumUtils, profileUtils } from '@/utils'; import { review } from '@/storage'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { queryClient } from '@/react-query'; import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; @@ -52,11 +52,13 @@ export default async function runMigrations() { * using the updated Keychain settings (THIS_DEVICE_ONLY) */ const v0 = async () => { + logger.sentry('Start migration v0'); const walletAddress = await loadAddress(); if (walletAddress) { - logger.debug('[runMigrations]: v0 migration - Save loaded address'); + logger.sentry('v0 migration - Save loaded address'); await saveAddress(walletAddress); } + logger.sentry('Complete migration v0'); }; migrations.push(v0); @@ -68,13 +70,14 @@ export default async function runMigrations() { * that were created / imported before we launched this feature */ const v1 = async () => { + logger.sentry('Start migration v1'); const { selected } = store.getState().wallets; if (!selected) { // Read from the old wallet data const address = await loadAddress(); if (address) { - logger.debug('[runMigrations]: v1 migration - address found'); + logger.sentry('v1 migration - address found'); const id = `wallet_${Date.now()}`; const currentWallet = { addresses: [ @@ -97,11 +100,12 @@ export default async function runMigrations() { const wallets = { [id]: currentWallet }; - logger.debug('[runMigrations]: v1 migration - update wallets and selected wallet'); + logger.sentry('v1 migration - update wallets and selected wallet'); await store.dispatch(walletsUpdate(wallets)); await store.dispatch(walletsSetSelected(currentWallet)); } } + logger.sentry('Complete migration v1'); }; migrations.push(v1); @@ -112,10 +116,11 @@ export default async function runMigrations() { * which are the only wallets allowed to create new accounts under it */ const v2 = async () => { + logger.sentry('Start migration v2'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.debug('[runMigrations]: Complete migration v2 early'); + logger.sentry('Complete migration v2 early'); return; } @@ -126,7 +131,7 @@ export default async function runMigrations() { // if there's a wallet with seed phrase that wasn't imported // and set it as primary if (!primaryWallet) { - logger.debug('[runMigrations]: v2 migration - primary wallet not found'); + logger.sentry('v2 migration - primary wallet not found'); let primaryWalletKey = null; Object.keys(wallets).some(key => { const wallet = wallets[key]; @@ -156,7 +161,7 @@ export default async function runMigrations() { ...updatedWallets[primaryWalletKey], primary: true, }; - logger.debug('[runMigrations]: v2 migration - update wallets'); + logger.sentry('v2 migration - update wallets'); await store.dispatch(walletsUpdate(updatedWallets)); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too @@ -166,6 +171,7 @@ export default async function runMigrations() { } } } + logger.sentry('Complete migration v2'); }; migrations.push(v2); @@ -176,7 +182,7 @@ export default async function runMigrations() { */ const v3 = async () => { - logger.debug('[runMigrations]: Ignoring migration v3'); + logger.sentry('Ignoring migration v3'); return true; }; @@ -188,7 +194,7 @@ export default async function runMigrations() { */ const v4 = async () => { - logger.debug('[runMigrations]: Ignoring migration v4'); + logger.sentry('Ignoring migration v4'); return true; }; @@ -200,41 +206,43 @@ export default async function runMigrations() { * incorrectly by the keychain integrity checks */ const v5 = async () => { + logger.sentry('Start migration v5'); const { wallets, selected } = store.getState().wallets; if (!wallets) { - logger.debug('[runMigrations]: Complete migration v5 early'); + logger.sentry('Complete migration v5 early'); return; } const hasMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (!hasMigratedFlag) { - logger.debug('[runMigrations]: Migration flag not set'); + logger.sentry('Migration flag not set'); const hasOldSeedphraseKey = await hasKey(seedPhraseKey); if (hasOldSeedphraseKey) { - logger.debug('[runMigrations]: Old seedphrase is still there'); + logger.sentry('Old seedphrase is still there'); let incorrectDamagedWalletId = null; const updatedWallets = { ...wallets }; keys(updatedWallets).forEach(walletId => { if (updatedWallets[walletId].damaged && !updatedWallets[walletId].imported) { - logger.debug(`[runMigrations]: found incorrect damaged wallet ${walletId}`); + logger.sentry('found incorrect damaged wallet', walletId); delete updatedWallets[walletId].damaged; incorrectDamagedWalletId = walletId; } }); - logger.debug('[runMigrations]: updating all wallets'); + logger.sentry('updating all wallets'); await store.dispatch(walletsUpdate(updatedWallets)); - logger.debug('[runMigrations]: done updating all wallets'); + logger.sentry('done updating all wallets'); // Additionally, we need to check if it's the selected wallet // and if that's the case, update it too if (selected!.id === incorrectDamagedWalletId) { - logger.debug('[runMigrations]: need to update the selected wallet'); + logger.sentry('need to update the selected wallet'); const updatedSelectedWallet = updatedWallets[incorrectDamagedWalletId]; await store.dispatch(walletsSetSelected(updatedSelectedWallet)); - logger.debug('[runMigrations]: selected wallet updated'); + logger.sentry('selected wallet updated'); } } } + logger.sentry('Complete migration v5'); }; migrations.push(v5); @@ -244,7 +252,6 @@ export default async function runMigrations() { */ /* Fix dollars => stablecoins */ const v6 = async () => { - logger.debug('[runMigrations]: Ignoring migration v6'); // try { // const userLists = await getUserLists(); // const newLists = userLists.map((list: { id: string }) => { @@ -273,9 +280,9 @@ export default async function runMigrations() { const wallet = wallets[walletKeys[i]]; if (wallet.type !== WalletTypes.readOnly) { // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < (wallet.addresses || []).length; x++) { - const { address } = (wallet.addresses || [])[x]; - logger.debug(`[runMigrations]: setting web profiles for address ${address}`); + for (let x = 0; x < wallet.addresses.length; x++) { + const { address } = wallet.addresses[x]; + logger.log('setting web profiles for address', address); await store.dispatch(updateWebDataEnabled(true, address)); } } @@ -285,7 +292,7 @@ export default async function runMigrations() { migrations.push(v7); const v8 = async () => { - logger.debug('[runMigrations]: wiping old metadata'); + logger.log('wiping old metadata'); await deprecatedRemoveLocal(IMAGE_METADATA); }; @@ -298,6 +305,7 @@ export default async function runMigrations() { * same for contacts */ const v9 = async () => { + logger.log('Start migration v9'); // map from old color index to closest new color's index const newColorIndexes = [0, 4, 12, 21, 1, 20, 4, 9, 10]; try { @@ -308,7 +316,7 @@ export default async function runMigrations() { // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; - const newAddresses = (wallet.addresses || []).map((account: RainbowAccount) => { + const newAddresses = wallet.addresses.map((account: RainbowAccount) => { const accountEmoji = returnStringFirstEmoji(account?.label); return { ...account, @@ -321,12 +329,12 @@ export default async function runMigrations() { const newWallet = { ...wallet, addresses: newAddresses }; updatedWallets[walletKeys[i]] = newWallet; } - logger.debug('[runMigrations]: update wallets in store to index new colors'); + logger.log('update wallets in store to index new colors'); await store.dispatch(walletsUpdate(updatedWallets)); const selectedWalletId = selected?.id; if (selectedWalletId) { - logger.debug('[runMigrations]: update selected wallet to index new color'); + logger.log('update selected wallet to index new color'); await store.dispatch(walletsSetSelected(updatedWallets[selectedWalletId])); } @@ -347,10 +355,12 @@ export default async function runMigrations() { : getRandomColor(), }; } - logger.debug('[runMigrations]: update contacts to index new colors'); + logger.log('update contacts to index new colors'); await saveContacts(updatedContacts); } catch (error) { - logger.error(new RainbowError(`[runMigrations]: Migration v9 failed: ${error}`)); + logger.sentry('Migration v9 failed: ', error); + const migrationError = new Error('Migration 9 failed'); + captureException(migrationError); } }; @@ -361,6 +371,7 @@ export default async function runMigrations() { * This step makes sure all contacts have an emoji set based on the address */ const v10 = async () => { + logger.log('Start migration v10'); try { // migrate contacts to corresponding emoji const contacts = await getContacts(); @@ -392,10 +403,12 @@ export default async function runMigrations() { } } } - logger.debug('[runMigrations]: update contacts to add emojis / colors'); + logger.log('update contacts to add emojis / colors'); await saveContacts(updatedContacts); } catch (error) { - logger.error(new RainbowError(`[runMigrations]: Migration v10 failed: ${error}`)); + logger.sentry('Migration v10 failed: ', error); + const migrationError = new Error('Migration 10 failed'); + captureException(migrationError); } }; @@ -406,9 +419,9 @@ export default async function runMigrations() { * This step resets review timers if we havnt asked in the last 2 weeks prior to running this */ const v11 = async () => { + logger.log('Start migration v11'); const hasReviewed = review.get(['hasReviewed']); if (hasReviewed) { - logger.debug('[runMigrations]: Migration v11: exiting early - already reviewed'); return; } @@ -417,12 +430,10 @@ export default async function runMigrations() { const TWO_MONTHS = 2 * 30 * 24 * 60 * 60 * 1000; if (Number(reviewAsked) > Date.now() - TWO_WEEKS) { - logger.debug('[runMigrations]: Migration v11: exiting early - not reviewed in the last 2 weeks'); return; } review.set(['timeOfLastPrompt'], Date.now() - TWO_MONTHS); - logger.debug('[runMigrations]: Migration v11: updated review timeOfLastPrompt'); }; migrations.push(v11); @@ -440,15 +451,15 @@ export default async function runMigrations() { for (let i = 0; i < walletKeys.length; i++) { const wallet = wallets[walletKeys[i]]; // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let x = 0; x < (wallet.addresses || []).length; x++) { - const { address } = (wallet.addresses || [])[x]; + for (let x = 0; x < wallet.addresses.length; x++) { + const { address } = wallet.addresses[x]; const assets = await getAssets(address, network); const hiddenCoins = await getHiddenCoins(address, network); const pinnedCoins = await getPinnedCoins(address, network); - logger.debug(`[runMigrations]: pinnedCoins: ${JSON.stringify({ pinnedCoins }, null, 2)}`); - logger.debug(`[runMigrations]: hiddenCoins: ${JSON.stringify({ hiddenCoins }, null, 2)}`); + logger.log(JSON.stringify({ pinnedCoins }, null, 2)); + logger.log(JSON.stringify({ hiddenCoins }, null, 2)); const pinnedCoinsMigrated = pinnedCoins.map((address: string) => { const asset = assets?.find((asset: any) => asset.address === address.toLowerCase()); @@ -460,8 +471,8 @@ export default async function runMigrations() { return getUniqueIdNetwork(asset?.address, network); }); - logger.debug(`[runMigrations]: pinnedCoinsMigrated: ${JSON.stringify({ pinnedCoinsMigrated }, null, 2)}`); - logger.debug(`[runMigrations]: hiddenCoinsMigrated: ${JSON.stringify({ hiddenCoinsMigrated }, null, 2)}`); + logger.log(JSON.stringify({ pinnedCoinsMigrated }, null, 2)); + logger.log(JSON.stringify({ hiddenCoinsMigrated }, null, 2)); await savePinnedCoins(uniq(pinnedCoinsMigrated), address, network); await saveHiddenCoins(uniq(hiddenCoinsMigrated), address, network); @@ -503,14 +514,17 @@ export default async function runMigrations() { const value = await loadString(key); if (typeof value === 'string') { await saveString(key, value, publicAccessControlOptions); - logger.debug(`[runMigrations]: key migrated: ${key}`); + logger.debug('key migrated', key); } } catch (error) { - logger.error(new RainbowError(`[runMigrations]: Error migration 13 :: key ${key}: ${error}`)); + logger.sentry('Error migration 13 :: key ', key); + logger.sentry('reason', error); } } } catch (error) { - logger.error(new RainbowError(`[runMigrations]: Migration v13 failed: ${error}`)); + logger.sentry('Migration v13 failed: ', error); + const migrationError = new Error('Migration 13 failed'); + captureException(migrationError); } }; @@ -543,7 +557,6 @@ export default async function runMigrations() { Ignored */ const v15 = async () => { - logger.debug('[runMigrations]: Ignoring migration v15'); return true; }; @@ -564,7 +577,9 @@ export default async function runMigrations() { // we don't care if it fails }); } catch (error: any) { - logger.error(new RainbowError(`[runMigrations]: Migration v16 failed: ${error}`)); + logger.sentry('Migration v16 failed: ', error); + const migrationError = new Error('Migration 16 failed'); + captureException(migrationError); } }; @@ -626,19 +641,9 @@ export default async function runMigrations() { /** *************** Migration v19 ****************** - * Deleted migration - */ - const v19 = async () => { - return; - }; - - migrations.push(v19); - - /** - *************** Migration v20 ****************** * Migrates dapp browser favorites store from createStore to createRainbowStore */ - const v20 = async () => { + const v19 = async () => { const initializeLegacyStore = () => { return new Promise(resolve => { // Give the async legacy store a moment to initialize @@ -661,20 +666,20 @@ export default async function runMigrations() { } }; - migrations.push(v20); + migrations.push(v19); - logger.debug(`[runMigrations]: ready to run migrations starting on number ${currentVersion}`); + logger.sentry(`Migrations: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); if (migrations.length === currentVersion) { - logger.debug(`[runMigrations]: Nothing to run`); + logger.sentry(`Migrations: Nothing to run`); return; } for (let i = currentVersion; i < migrations.length; i++) { - logger.debug(`[runMigrations]: Running migration v${i}`); + logger.sentry(`Migrations: Running migration v${i}`); // @ts-expect-error await migrations[i].apply(null); - logger.debug(`[runMigrations]: Migration ${i} completed succesfully`); + logger.sentry(`Migrations: Migration ${i} completed succesfully`); await setMigrationVersion(i + 1); } } diff --git a/src/model/preferences.ts b/src/model/preferences.ts index b74867ed820..226424912e2 100644 --- a/src/model/preferences.ts +++ b/src/model/preferences.ts @@ -88,13 +88,13 @@ export async function setPreference( }; const message = JSON.stringify(objToSign); const signature2 = await signWithSigningWallet(message); - logger.debug(`[preferences]: ☁️ SENDING `, { message }); + logger.debug('☁️ SENDING ', { message }); const { data } = await preferencesAPI.post>(`${PREFS_ENDPOINT}/${key}`, { message, signature, signature2, }); - logger.debug(`[preferences]: ☁️ RESPONSE`, { + logger.debug('☁️ RESPONSE', { reason: data?.reason, success: data?.success, }); @@ -105,7 +105,7 @@ export async function setPreference( return data?.success; } catch (e) { - logger.warn(`[preferences]: Preferences API failed to set preference`, { + logger.warn(`Preferences API failed to set preference`, { preferenceKey: key, }); return false; @@ -120,7 +120,7 @@ export async function getPreference( const { data } = await preferencesAPI.get>(`${PREFS_ENDPOINT}/${key}`, { params: { address }, }); - logger.debug(`[preferences]: ☁️ RESPONSE`, { + logger.debug('☁️ RESPONSE', { reason: data?.reason, success: data?.success, }); @@ -131,9 +131,8 @@ export async function getPreference( return data.data; } catch (e) { - logger.warn(`[preferences]: Preferences API failed to get preference`, { + logger.warn(`Preferences API failed to get preference`, { preferenceKey: key, - error: e, }); return null; } diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index bf79c65490d..2f16922be6b 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -1,4 +1,4 @@ -import { getChainId, saveChainId } from '@/handlers/localstorage/globalSettings'; +import { getNetwork, saveNetwork } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowError, logger } from '@/logger'; import { createQueryKey, queryClient } from '@/react-query'; @@ -91,7 +91,6 @@ export interface RainbowConfig extends Record rewards_enabled: boolean; degen_mode: boolean; - featured_results: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -174,14 +173,13 @@ export const DEFAULT_CONFIG: RainbowConfig = { rewards_enabled: true, degen_mode: false, - featured_results: false, }; export async function fetchRemoteConfig(): Promise { const config: RainbowConfig = { ...DEFAULT_CONFIG }; try { await remoteConfig().fetchAndActivate(); - logger.debug(`[remoteConfig]: Remote config fetched successfully`); + logger.debug('Remote config fetched successfully'); const parameters = remoteConfig().getAll(); Object.entries(parameters).forEach($ => { const [key, entry] = $; @@ -229,8 +227,7 @@ export async function fetchRemoteConfig(): Promise { key === 'swaps_v2' || key === 'idfa_check_enabled' || key === 'rewards_enabled' || - key === 'degen_mode' || - key === 'featured_results' + key === 'degen_mode' ) { config[key] = entry.asBoolean(); } else { @@ -239,15 +236,15 @@ export async function fetchRemoteConfig(): Promise { }); return config; } catch (e) { - logger.error(new RainbowError(`[remoteConfig]: Failed to fetch remote config`), { + logger.error(new RainbowError('Failed to fetch remote config'), { error: e, }); throw e; } finally { - logger.debug(`[remoteConfig]: Current remote config:\n${JSON.stringify(config, null, 2)}`); - const currentChainId = await getChainId(); - web3SetHttpProvider(currentChainId); - saveChainId(currentChainId); + logger.debug(`Current remote config:\n${JSON.stringify(config, null, 2)}`); + const currentNetwork = await getNetwork(); + web3SetHttpProvider(currentNetwork); + saveNetwork(currentNetwork); } } diff --git a/src/model/wallet.ts b/src/model/wallet.ts index d35c72f8b48..a79e52b0d23 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -56,8 +56,8 @@ import { IS_ANDROID } from '@/env'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { Signer } from '@ethersproject/abstract-signer'; import { sanitizeTypedData } from '@/utils/signingUtils'; +import { Network } from '@/helpers'; import { ExecuteFnParamsWithoutFn, performanceTracking, Screen } from '@/state/performance/performance'; -import { Network } from '@/networks/types'; export type EthereumPrivateKey = string; type EthereumMnemonic = string; @@ -317,7 +317,7 @@ export const sendTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.debug('[wallet]: sending transaction', { transaction }, DebugContext.wallet); + logger.info('wallet: sending transaction', { transaction }); const wallet = existingWallet || (await loadWallet({ @@ -330,10 +330,10 @@ export const sendTransaction = async ({ if (!wallet) return null; try { const result = await wallet.sendTransaction(transaction); - logger.debug(`[wallet]: send - tx result`, { result }, DebugContext.wallet); + logger.debug('send - tx result', { result }, DebugContext.wallet); return { result }; } catch (error) { - logger.error(new RainbowError(`[wallet]: Failed to send transaction`), { error }); + logger.error(new RainbowError('Failed to send transaction'), { error }); if (isHardwareWallet) { setHardwareTXError(true); } else { @@ -348,7 +348,7 @@ export const sendTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError(`[wallet]: Failed to send transaction due to auth`), { + logger.error(new RainbowError('Failed to send transaction due to auth'), { error, }); return null; @@ -365,7 +365,7 @@ export const signTransaction = async ({ }> => { let isHardwareWallet = false; try { - logger.debug('[wallet]: signing transaction', {}, DebugContext.wallet); + logger.info('wallet: signing transaction'); const wallet = existingWallet || (await loadWallet({ @@ -385,7 +385,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.failed_transaction')); } - logger.error(new RainbowError(`[wallet]: Failed to sign transaction`), { error }); + logger.error(new RainbowError('Failed to sign transaction'), { error }); return { error }; } } catch (error) { @@ -394,7 +394,7 @@ export const signTransaction = async ({ } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError(`[wallet]: Failed to sign transaction due to auth`), { + logger.error(new RainbowError('Failed to sign transaction due to auth'), { error, }); return null; @@ -411,7 +411,7 @@ export const signPersonalMessage = async ( }> => { let isHardwareWallet = false; try { - logger.debug('[wallet]: signing personal message', { message }, DebugContext.wallet); + logger.info('wallet: signing personal message', { message }); const wallet = existingWallet || (await loadWallet({ @@ -433,7 +433,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError(`[wallet]: Failed to sign personal message`), { + logger.error(new RainbowError('Failed to sign personal message'), { error, }); return { error }; @@ -444,7 +444,7 @@ export const signPersonalMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError(`[wallet]: Failed to sign personal message due to auth`), { error }); + logger.error(new RainbowError('Failed to sign personal message due to auth'), { error }); return null; } }; @@ -459,7 +459,7 @@ export const signTypedDataMessage = async ( }> => { let isHardwareWallet = false; try { - logger.debug('[wallet]: signing typed data message', { message }, DebugContext.wallet); + logger.info('wallet: signing typed data message', { message }); const wallet = existingWallet || (await loadWallet({ @@ -511,7 +511,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.failed_sign_message')); } - logger.error(new RainbowError(`[wallet]: Failed to sign typed data message`), { + logger.error(new RainbowError('Failed to sign typed data message'), { error, }); return { error }; @@ -522,7 +522,7 @@ export const signTypedDataMessage = async ( } else { Alert.alert(lang.t('wallet.transaction.alert.authentication')); } - logger.error(new RainbowError(`[wallet]: Failed to sign typed data message due to auth`), { error }); + logger.error(new RainbowError('Failed to sign typed data message due to auth'), { error }); return null; } }; @@ -558,7 +558,7 @@ export const loadPrivateKey = async (address: EthereumAddress, hardware: boolean return privateKey; } catch (error) { - logger.error(new RainbowError(`[wallet]: Error loading private key`), { error }); + logger.error(new RainbowError('Error loading private key'), { error }); return null; } }; @@ -617,10 +617,15 @@ export const createWallet = async ({ callbackAfterSeeds = null; } const isImported = !!seed; - logger.debug(`[wallet]: ${isImported ? 'Importing new wallet' : 'Creating new wallet'}`, {}, DebugContext.wallet); + logger.info('Importing new wallet'); + if (!seed) { + logger.info('Creating new wallet'); + } const walletSeed = seed || generateMnemonic(); const addresses: RainbowAccount[] = []; try { + const { dispatch } = store; + const { isHDWallet, type, @@ -640,19 +645,19 @@ export const createWallet = async ({ // hardware pkey format is ${bluetooth device id}/${index} pkey = `${seed}/0`; } - logger.debug('[wallet]: getWallet from seed', {}, DebugContext.wallet); + logger.debug('[createWallet] - getWallet from seed', {}, DebugContext.wallet); // Get all wallets const allWalletsResult = await getAllWallets(); - logger.debug('[wallet]: getAllWallets', {}, DebugContext.wallet); + logger.debug('[createWallet] - getAllWallets', {}, DebugContext.wallet); const allWallets: AllRainbowWallets = allWalletsResult?.wallets ?? {}; let existingWalletId = null; if (isImported) { // Checking if the generated account already exists and is visible - logger.debug('[wallet]: checking if account already exists', {}, DebugContext.wallet); + logger.debug('[createWallet] - checking if account already exists', {}, DebugContext.wallet); const alreadyExistingWallet = Object.values(allWallets).find((someWallet: RainbowWallet) => { - return !!someWallet.addresses?.find( + return !!someWallet.addresses.find( account => toChecksumAddress(account.address) === toChecksumAddress(walletAddress) && account.visible ); }); @@ -668,13 +673,13 @@ export const createWallet = async ({ if (!isRestoring) { setTimeout(() => Alert.alert(lang.t('wallet.new.alert.oops'), lang.t('wallet.new.alert.looks_like_already_imported')), 1); } - logger.debug('[wallet]: already imported this wallet', {}, DebugContext.wallet); + logger.debug('[createWallet] - already imported this wallet', {}, DebugContext.wallet); return null; } } const id = existingWalletId || `wallet_${Date.now()}`; - logger.debug('[wallet]: wallet ID', { id }, DebugContext.wallet); + logger.debug('[createWallet] - wallet ID', { id }, DebugContext.wallet); // load this up front and pass to other keychain setters to avoid multiple // auth requests @@ -683,17 +688,17 @@ export const createWallet = async ({ await saveSeedPhrase(walletSeed, id, { androidEncryptionPin }); - logger.debug('[wallet]: saved seed phrase', {}, DebugContext.wallet); + logger.debug('[createWallet] - saved seed phrase', {}, DebugContext.wallet); // Save address await saveAddress(walletAddress); - logger.debug('[wallet]: saved address', {}, DebugContext.wallet); + logger.debug('[createWallet] - saved address', {}, DebugContext.wallet); // Save private key await saveKeyForWallet(walletAddress, pkey, isHardwareWallet, { androidEncryptionPin, }); - logger.debug('[wallet]: saved private key', {}, DebugContext.wallet); + logger.debug('[createWallet] - saved private key', {}, DebugContext.wallet); const colorIndexForWallet = color !== null ? color : addressHashedColorIndex(walletAddress) || 0; @@ -710,10 +715,10 @@ export const createWallet = async ({ }); if (type !== EthereumWalletType.readOnly && type !== EthereumWalletType.bluetooth) { // Creating signature for this wallet - logger.debug(`[wallet]: generating signature`, {}, DebugContext.wallet); + logger.debug(`[createWallet] - generating signature`, {}, DebugContext.wallet); await createSignature(walletAddress, pkey); // Enable web profile - logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); store.dispatch(updateWebDataEnabled(true, walletAddress)); // Save the color setPreference(PreferenceActionType.init, 'profile', address, { @@ -725,7 +730,7 @@ export const createWallet = async ({ // Initiate auto account discovery for imported wallets via seedphrase // or for hardware wallets if ((isHDWallet && root && isImported) || (isHardwareWallet && seed)) { - logger.debug('[wallet]: initializing account auto discovery', {}, DebugContext.wallet); + logger.debug('[createWallet] - initializing account auto discovery', {}, DebugContext.wallet); let index = 1; let lookup = 0; // Starting on index 1, we check the tx history @@ -752,14 +757,14 @@ export const createWallet = async ({ try { hasTxHistory = await ethereumUtils.hasPreviousTransactions(nextWallet.address); } catch (error) { - logger.error(new RainbowError('[wallet]: Error getting txn history for address'), { error }); + logger.error(new RainbowError('[createWallet] - Error getting txn history for address'), { error }); } let discoveredAccount: RainbowAccount | undefined; let discoveredWalletId: RainbowWallet['id'] | undefined; Object.values(allWallets).forEach(someWallet => { - const existingAccount = someWallet.addresses?.find( + const existingAccount = someWallet.addresses.find( account => toChecksumAddress(account.address) === toChecksumAddress(nextWallet.address) ); if (existingAccount) { @@ -787,7 +792,7 @@ export const createWallet = async ({ if (hasTxHistory) { // Save private key await saveKeyForWallet(nextWallet.address, nextWallet.privateKey, isHardwareWallet, { androidEncryptionPin }); - logger.debug(`[wallet]: saved private key for wallet index: ${index}`, {}, DebugContext.wallet); + logger.debug(`[createWallet] - saved private key for wallet index: ${index}`, {}, DebugContext.wallet); addresses.push({ address: nextWallet.address, @@ -801,7 +806,7 @@ export const createWallet = async ({ if (!isHardwareWallet) { // Creating signature for this wallet - logger.debug(`[wallet]: enabling web profile`, {}, DebugContext.wallet); + logger.debug(`[createWallet] - enabling web profile`, {}, DebugContext.wallet); await createSignature(nextWallet.address, nextWallet.privateKey); // Enable web profile store.dispatch(updateWebDataEnabled(true, nextWallet.address)); @@ -865,11 +870,11 @@ export const createWallet = async ({ } if (!silent) { - logger.debug('[wallet]: setting selected wallet', {}, DebugContext.wallet); + logger.debug('[createWallet] - setting selected wallet', {}, DebugContext.wallet); await setSelectedWallet(allWallets[id]); } - logger.debug('[wallet]: saving all wallets', {}, DebugContext.wallet); + logger.debug('[createWallet] - saving all wallets', {}, DebugContext.wallet); await saveAllWallets(allWallets); if (walletResult && walletAddress) { @@ -880,7 +885,7 @@ export const createWallet = async ({ } return null; } catch (error) { - logger.error(new RainbowError('[wallet]: Error in createWallet'), { error }); + logger.error(new RainbowError('Error in createWallet'), { error }); return null; } }; @@ -991,7 +996,7 @@ export const getPrivateKey = async (address: EthereumAddress): Promise => } return null; } catch (error) { - logger.error(new RainbowError('[wallet]: Error in getAllWallets'), { error }); + logger.error(new RainbowError('Error in getAllWallets'), { error }); return null; } }; @@ -1154,26 +1159,26 @@ export const generateAccount = async (id: RainbowWallet['id'], index: number): P return newAccount; } catch (error) { - logger.error(new RainbowError('[wallet]: Error generating account for keychain'), { error }); + logger.error(new RainbowError('[generateAccount] - Error generating account for keychain'), { error }); return null; } }; const migrateSecrets = async (): Promise => { try { - logger.debug('[wallet]: Migrating wallet secrets', {}, DebugContext.wallet); + logger.info('Migrating wallet secrets'); const seedphrase = await oldLoadSeedPhrase(); if (!seedphrase) { - logger.debug('[wallet]: old seed doesnt exist!', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - old seed doesnt exist!', {}, DebugContext.wallet); // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[wallet]: marking secrets as migrated', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - marking secrets as migrated', {}, DebugContext.wallet); return null; } const type = identifyWalletType(seedphrase); - logger.debug(`[wallet]: wallet type: ${type}`, {}, DebugContext.wallet); + logger.debug(`[migrateSecrets] - wallet type: ${type}`, {}, DebugContext.wallet); let hdnode: undefined | HDNode, node: undefined | HDNode, existingAccount: undefined | Wallet; switch (type) { case EthereumWalletType.privateKey: @@ -1195,10 +1200,10 @@ const migrateSecrets = async (): Promise => { } if (!existingAccount && hdnode) { - logger.debug('[wallet]: No existing account, so we have to derive it', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - No existing account, so we have to derive it', {}, DebugContext.wallet); node = hdnode.derivePath(getHdPath({ type: WalletLibraryType.ethers, index: 0 })); existingAccount = new Wallet(node.privateKey); - logger.debug('[wallet]: Got existing account', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - Got existing account', {}, DebugContext.wallet); } if (!existingAccount) { @@ -1208,10 +1213,10 @@ const migrateSecrets = async (): Promise => { // Check that wasn't migrated already! const pkeyExists = await keychain.hasKey(`${existingAccount.address}_${privateKeyKey}`); if (!pkeyExists) { - logger.debug('[wallet]: new pkey didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - new pkey didnt exist so we should save it', {}, DebugContext.wallet); // Save the private key in the new format await saveKeyForWallet(existingAccount.address, existingAccount.privateKey, false); - logger.debug('[wallet]: new pkey saved', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - new pkey saved', {}, DebugContext.wallet); } const selectedWalletData = await getSelectedWallet(); @@ -1223,13 +1228,13 @@ const migrateSecrets = async (): Promise => { // Save the seedphrase in the new format const seedExists = await keychain.hasKey(`${wallet.id}_${seedPhraseKey}`); if (!seedExists) { - logger.debug('[wallet]: new seed didnt exist so we should save it', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - new seed didnt exist so we should save it', {}, DebugContext.wallet); await saveSeedPhrase(seedphrase, wallet.id); - logger.debug('[wallet]: new seed saved', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - new seed saved', {}, DebugContext.wallet); } // Save the migration flag to prevent this flow in the future await keychain.saveString(oldSeedPhraseMigratedKey, 'true', keychain.publicAccessControlOptions); - logger.debug('[wallet]: saved migrated key', {}, DebugContext.wallet); + logger.debug('[migrateSecrets] - saved migrated key', {}, DebugContext.wallet); return { hdnode, privateKey: existingAccount.privateKey, @@ -1237,7 +1242,7 @@ const migrateSecrets = async (): Promise => { type, }; } catch (error) { - logger.error(new RainbowError('[wallet]: Error while migrating secrets'), { error }); + logger.error(new RainbowError('[migrateSecrets] - Error while migrating secrets'), { error }); return null; } }; @@ -1252,7 +1257,7 @@ export const cleanUpWalletKeys = async (): Promise => { keychain.remove(key); } catch (error) { // key might not exists - logger.warn('[wallet]: failure to delete key', { + logger.warn('[cleanUpWalletKeys] - failure to delete key', { key, error, }); @@ -1272,10 +1277,10 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): // First we need to check if that key already exists const keyFound = await keychain.hasKey(`${id}_${seedPhraseKey}`); if (!keyFound) { - logger.debug('[wallet]: key not found, should need migration', {}, DebugContext.wallet); + logger.debug('[loadAndMigrate] - key not found, should need migration', {}, DebugContext.wallet); // if it doesn't we might have a migration pending const isSeedPhraseMigrated = await keychain.loadString(oldSeedPhraseMigratedKey); - logger.debug(`[wallet]: Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); + logger.debug(`[loadAndMigrate] - Migration pending? ${!isSeedPhraseMigrated}`, {}, DebugContext.wallet); // We need to migrate the seedphrase & private key first // In that case we regenerate the existing private key to store it with the new format @@ -1283,24 +1288,24 @@ export const loadSeedPhraseAndMigrateIfNeeded = async (id: RainbowWallet['id']): const migratedSecrets = await migrateSecrets(); seedPhrase = migratedSecrets?.seedphrase ?? null; } else { - logger.error(new RainbowError('[wallet]: Migrated flag was set but there is no key!'), { id }); + logger.error(new RainbowError('[loadAndMigrate] - Migrated flag was set but there is no key!'), { id }); } } else { - logger.debug('[wallet]: Getting seed directly', {}, DebugContext.wallet); + logger.debug('[loadAndMigrate] - Getting seed directly', {}, DebugContext.wallet); const androidEncryptionPin = IS_ANDROID && !(await kc.getSupportedBiometryType()) ? await authenticateWithPIN() : undefined; const seedData = await getSeedPhrase(id, { androidEncryptionPin }); seedPhrase = seedData?.seedphrase ?? null; if (seedPhrase) { - logger.debug('[wallet]: got seed succesfully', {}, DebugContext.wallet); + logger.debug('[loadAndMigrate] - got seed succesfully', {}, DebugContext.wallet); } else { - logger.error(new RainbowError('[wallet]: Missing seed for wallet - (Key exists but value isnt valid)!')); + logger.error(new RainbowError('[loadAndMigrate] - Missing seed for wallet - (Key exists but value isnt valid)!')); } } return seedPhrase; } catch (error) { - logger.error(new RainbowError('[wallet]: Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); + logger.error(new RainbowError('[loadAndMigrate] - Error in loadSeedPhraseAndMigrateIfNeeded'), { error }); throw error; } }; diff --git a/src/navigation/HardwareWalletTxNavigator.tsx b/src/navigation/HardwareWalletTxNavigator.tsx index cff47c33435..f91b59e5a83 100644 --- a/src/navigation/HardwareWalletTxNavigator.tsx +++ b/src/navigation/HardwareWalletTxNavigator.tsx @@ -23,7 +23,7 @@ export const ledgerStorage = new MMKV({ export const HARDWARE_TX_ERROR_KEY = 'hardwareTXError'; export const setHardwareTXError = (value: boolean) => { - logger.warn(`[HardwareWalletTxNavigator]: setHardwareTXError`, { value }); + logger.info(`setHardwareTXError`, { value }); ledgerStorage.set(HARDWARE_TX_ERROR_KEY, value); }; @@ -83,14 +83,14 @@ export const HardwareWalletTxNavigator = () => { ); const successCallback = useCallback(() => { - logger.debug('[HardwareWalletTxNavigator]: submitting tx', {}, DebugContext.ledger); + logger.debug('[LedgerTx] - submitting tx', {}, DebugContext.ledger); if (!isReady) { setReadyForPolling(false); setIsReady(true); setHardwareTXError(false); submit(); } else { - logger.debug('[HardwareWalletTxNavigator]: already submitted', {}, DebugContext.ledger); + logger.debug('[LedgerTx] - already submitted', {}, DebugContext.ledger); } }, [isReady, setIsReady, setReadyForPolling, submit]); diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index 361fbf5d462..b3f89f1ec1e 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -7,7 +7,8 @@ import { TestnetToast } from '@/components/toasts'; import { DAPP_BROWSER, POINTS, useExperimentalFlag } from '@/config'; import { Box, Columns, globalColors, Stack, useForegroundColor, Text, Cover, useColorMode } from '@/design-system'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import { isUsingButtonNavigation } from '@/utils/deviceUtils'; +import { web3Provider } from '@/handlers/web3'; +import { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; import { useAccountAccentColor, useAccountSettings, useCoinListEdited, useDimensions, usePendingTransactions } from '@/hooks'; import { useRemoteConfig } from '@/model/remoteConfig'; import RecyclerListViewScrollToTopProvider, { @@ -51,7 +52,7 @@ function getTabBarHeight() { return 82; } if (!isUsingButtonNavigation()) { - return 82; + return 72; } return 48; } @@ -91,13 +92,7 @@ const ActivityTabIcon = React.memo( }, [pendingCount]); return pendingCount > 0 ? ( - + @@ -445,7 +440,7 @@ function SwipeNavigatorScreens() { } export function SwipeNavigator() { - const { chainId } = useAccountSettings(); + const { network } = useAccountSettings(); const { colors } = useTheme(); return ( @@ -461,7 +456,7 @@ export function SwipeNavigator() { - + ); } diff --git a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx index 9c215ed804d..5de36d9bbfb 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetBackground.tsx @@ -3,15 +3,15 @@ import React, { useCallback } from 'react'; import { StyleSheet, TouchableWithoutFeedback, View } from 'react-native'; const BottomSheetBackground = () => { - // #region hooks + //#region hooks const { close } = useBottomSheet(); - // #endregion + //#endregion - // #region callbacks + //#region callbacks const handleOnPress = useCallback(() => { close(); }, [close]); - // #endregion + //#endregion return ( diff --git a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx index 8e17cd77670..b01d9db999c 100644 --- a/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx +++ b/src/navigation/bottom-sheet/views/BottomSheetNavigatorView.tsx @@ -12,11 +12,11 @@ type Props = BottomSheetNavigationConfig & { }; const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => { - // #region hooks + //#region hooks const forceUpdate = useForceUpdate(); - // #endregion + //#endregion - // #region variables + //#region variables const descriptorsCache = useRef({}); const [firstKey, ...restKeys] = useMemo( // @ts-ignore navigation type mismatch @@ -40,9 +40,9 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => .forEach(key => { descriptorsCache.current[key].removing = true; }); - // #endregion + //#endregion - // #region callbacks + //#region callbacks const handleOnDismiss = useCallback((key: string, removed: boolean) => { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete descriptorsCache.current[key]; @@ -64,7 +64,7 @@ const BottomSheetNavigatorView = ({ descriptors, state, navigation }: Props) => // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // #endregion + //#endregion return ( {descriptors[firstKey].render()} diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 182550aa6c8..3e9de2383ff 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -1,12 +1,14 @@ import React from 'react'; -import { Keyboard } from 'react-native'; +import { Keyboard, StatusBar } from 'react-native'; import { useTheme } from '@/theme/ThemeContext'; import colors from '@/theme/currentColors'; import styled from '@/styled-thing'; import { fonts } from '@/styles'; +import networkTypes from '@/helpers/networkTypes'; import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; +import { getNetworkObj } from '@/networks'; import { getPositionSheetHeight } from '@/screens/positions/PositionSheet'; import { Icon } from '@/components/icons'; @@ -27,7 +29,6 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -489,7 +490,7 @@ export const ensAdditionalRecordsSheetConfig: PartialNavigatorConfigOptions = { }; export const explainSheetConfig: PartialNavigatorConfigOptions = { - options: ({ route: { params = { network: chainIdToNameMapping[ChainId.mainnet] } } }) => { + options: ({ route: { params = { network: getNetworkObj(networkTypes.mainnet).name } } }) => { // @ts-ignore const explainerConfig = explainers(params.network)[params?.type]; return buildCoolModalConfig({ diff --git a/src/navigation/types.ts b/src/navigation/types.ts index d84c2139148..8a35cda2633 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -72,7 +72,4 @@ export type RootStackParamList = { onSuccess: () => Promise; onFailure: () => Promise; }; - [Routes.SWAP]: { - action?: 'open_swap_settings'; - }; }; diff --git a/src/networks/README.md b/src/networks/README.md index 678269fb209..47dd785beaa 100644 --- a/src/networks/README.md +++ b/src/networks/README.md @@ -3,9 +3,9 @@ Handling for networks throughout the codebase. ```typescript -import { getNetworkObject } from '@/networks'; +import { getNetworkObj, Networks } from '@/networks'; -const networkObj = getNetworkObject({ chainId: ChainId.mainnet }); +const networkObj = getNetworkObj(Networks.mainnet); // Get static properties based on network const networkName = networkObj.name; @@ -19,10 +19,10 @@ const gasPrices = networkObj.getGasPrices(); // Getting a subset of network objects -const layer2s = RainbowNetworkObjects.filter(network => network.networkType === 'layer2'); +const layer2s = RainbowNetworks.filter(network => network.networkType === 'layer2'); // Or networks that match specific properties -const walletconnectNetworks = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => network.value); +const walletconnectNetworks = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.value); ``` ## Network Objects diff --git a/src/networks/arbitrum.ts b/src/networks/arbitrum.ts index 35c33af556b..72c95c4cad5 100644 --- a/src/networks/arbitrum.ts +++ b/src/networks/arbitrum.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { arbitrum } from '@wagmi/chains'; import { ARBITRUM_ETH_ADDRESS } from '@/references'; import { getArbitrumGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getArbitrumNetworkObject = (): NetworkProperties => { const { arbitrum_enabled, arbitrum_tx_enabled } = getRemoteConfig(); @@ -26,7 +25,7 @@ export const getArbitrumNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(arbitrum.id), - getProvider: () => getProvider({ chainId: ChainId.arbitrum }), + getProvider: () => getProviderForNetwork(Network.arbitrum), balanceCheckerAddress: '0x54A4E5800345c01455a7798E0D96438364e22723', // features diff --git a/src/networks/avalanche.ts b/src/networks/avalanche.ts index cc411357c81..ecb8b628d99 100644 --- a/src/networks/avalanche.ts +++ b/src/networks/avalanche.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { avalanche } from '@wagmi/chains'; import { AVAX_AVALANCHE_ADDRESS } from '@/references'; import { getAvalancheGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getAvalancheNetworkObject = (): NetworkProperties => { const { avalanche_enabled, avalanche_tx_enabled } = getRemoteConfig(); @@ -27,7 +26,7 @@ export const getAvalancheNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(avalanche.id), - getProvider: () => getProvider({ chainId: ChainId.avalanche }), + getProvider: () => getProviderForNetwork(Network.avalanche), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/base.ts b/src/networks/base.ts index 7a978ddca6d..ea2b6163d5a 100644 --- a/src/networks/base.ts +++ b/src/networks/base.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { base } from '@wagmi/chains'; import { BASE_ETH_ADDRESS } from '@/references'; import { getBaseGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getBaseNetworkObject = (): NetworkProperties => { const { base_enabled, base_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -27,7 +26,7 @@ export const getBaseNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(base.id), - getProvider: () => getProvider({ chainId: ChainId.base }), + getProvider: () => getProviderForNetwork(Network.base), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/blast.ts b/src/networks/blast.ts index 6d4d482fc87..f7b168d3e72 100644 --- a/src/networks/blast.ts +++ b/src/networks/blast.ts @@ -1,4 +1,4 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { blast } from 'viem/chains'; @@ -6,7 +6,6 @@ import { getBlastGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { BLAST_MAINNET_RPC } from 'react-native-dotenv'; import { BLAST_ETH_ADDRESS } from '@/references'; -import { ChainId } from '@/networks/types'; export const getBlastNetworkObject = (): NetworkProperties => { const { blast_enabled, blast_tx_enabled } = getRemoteConfig(); @@ -30,7 +29,7 @@ export const getBlastNetworkObject = (): NetworkProperties => { balanceCheckerAddress: '', rpc: () => proxyRpcEndpoint(blast.id), - getProvider: () => getProvider({ chainId: ChainId.blast }), + getProvider: () => getProviderForNetwork(Network.blast), // features features: { diff --git a/src/networks/bsc.ts b/src/networks/bsc.ts index 89aa63c17d8..3e38d897969 100644 --- a/src/networks/bsc.ts +++ b/src/networks/bsc.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { bsc } from '@wagmi/chains'; import { BNB_BSC_ADDRESS, BNB_MAINNET_ADDRESS } from '@/references'; import { getBscGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getBSCNetworkObject = (): NetworkProperties => { const { bsc_enabled, bsc_tx_enabled } = getRemoteConfig(); @@ -29,7 +28,7 @@ export const getBSCNetworkObject = (): NetworkProperties => { // this should be refactored to have less deps rpc: () => proxyRpcEndpoint(bsc.id), - getProvider: () => getProvider({ chainId: ChainId.bsc }), + getProvider: () => getProviderForNetwork(Network.bsc), balanceCheckerAddress: '0x400A9f1Bb1Db80643C33710C2232A0D74EF5CFf1', // features diff --git a/src/networks/degen.ts b/src/networks/degen.ts index 044e3e6baba..bc7c43aad2d 100644 --- a/src/networks/degen.ts +++ b/src/networks/degen.ts @@ -1,4 +1,4 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { degen } from 'viem/chains'; @@ -6,7 +6,6 @@ import { DEGEN_CHAIN_DEGEN_ADDRESS } from '@/references'; import { getDegenGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; import { DEGEN_MAINNET_RPC } from 'react-native-dotenv'; -import { ChainId } from '@/networks/types'; export const getDegenNetworkObject = (): NetworkProperties => { const { degen_enabled, degen_tx_enabled } = getRemoteConfig(); @@ -29,7 +28,7 @@ export const getDegenNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(degen.id), - getProvider: () => getProvider({ chainId: ChainId.degen }), + getProvider: () => getProviderForNetwork(Network.degen), // need to find balance checker address balanceCheckerAddress: '', diff --git a/src/networks/gnosis.ts b/src/networks/gnosis.ts index cac594c0e13..f6e18c742b7 100644 --- a/src/networks/gnosis.ts +++ b/src/networks/gnosis.ts @@ -1,10 +1,9 @@ -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { gnosis } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; -import { ChainId } from '@/networks/types'; export const getGnosisNetworkObject = (): NetworkProperties => { return { @@ -25,7 +24,7 @@ export const getGnosisNetworkObject = (): NetworkProperties => { }, rpc: () => '', - getProvider: () => getProvider({ chainId: ChainId.gnosis }), + getProvider: () => getProviderForNetwork(Network.optimism), balanceCheckerAddress: '', // features diff --git a/src/networks/goerli.ts b/src/networks/goerli.ts index 71d3e19aeca..2d9423a7ac2 100644 --- a/src/networks/goerli.ts +++ b/src/networks/goerli.ts @@ -1,10 +1,9 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { goerli } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getGoerliNetworkObject = (): NetworkProperties => { const { goerli_enabled, goerli_tx_enabled } = getRemoteConfig(); @@ -26,7 +25,7 @@ export const getGoerliNetworkObject = (): NetworkProperties => { }, // this should be refactored to have less deps - getProvider: () => getProvider({ chainId: ChainId.goerli }), + getProvider: () => getProviderForNetwork(Network.goerli), rpc: () => proxyRpcEndpoint(goerli.id), balanceCheckerAddress: '0xf3352813b612a2d198e437691557069316b84ebe', diff --git a/src/networks/index.ts b/src/networks/index.ts index b4c5dd2f1f6..79c9089f23a 100644 --- a/src/networks/index.ts +++ b/src/networks/index.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import store from '@/redux/store'; import * as ls from '@/storage'; import { getArbitrumNetworkObject } from './arbitrum'; @@ -12,14 +12,14 @@ import { getGoerliNetworkObject } from './goerli'; import { getMainnetNetworkObject } from './mainnet'; import { getOptimismNetworkObject } from './optimism'; import { getPolygonNetworkObject } from './polygon'; -import { NetworkProperties } from './types'; +import { Network, NetworkProperties } from './types'; import { getZoraNetworkObject } from './zora'; /** * Array of all Rainbow Networks * the ordering is the default sorting */ -export const RainbowNetworkObjects = [ +export const RainbowNetworks = [ getMainnetNetworkObject(), getArbitrumNetworkObject(), getBaseNetworkObject(), @@ -34,24 +34,46 @@ export const RainbowNetworkObjects = [ getDegenNetworkObject(), ]; -export const RainbowSupportedChainIds = [ - ChainId.mainnet, - ChainId.arbitrum, - ChainId.base, - ChainId.optimism, - ChainId.polygon, - ChainId.zora, - ChainId.gnosis, - ChainId.goerli, - ChainId.bsc, - ChainId.avalanche, - ChainId.blast, - ChainId.degen, -]; - /** * Helper function to get specific Rainbow Network's Object */ +export function getNetworkObj(network: Network): NetworkProperties { + switch (network) { + // Mainnet + case Network.mainnet: + return getMainnetNetworkObject(); + + // L2s + case Network.arbitrum: + return getArbitrumNetworkObject(); + case Network.base: + return getBaseNetworkObject(); + case Network.bsc: + return getBSCNetworkObject(); + case Network.optimism: + return getOptimismNetworkObject(); + case Network.polygon: + return getPolygonNetworkObject(); + case Network.zora: + return getZoraNetworkObject(); + case Network.gnosis: + return getGnosisNetworkObject(); + case Network.avalanche: + return getAvalancheNetworkObject(); + case Network.blast: + return getBlastNetworkObject(); + case Network.degen: + return getDegenNetworkObject(); + // Testnets + case Network.goerli: + return getGoerliNetworkObject(); + + // Fallback + default: + return getMainnetNetworkObject(); + } +} + export function getNetworkObject({ chainId }: { chainId: ChainId }): NetworkProperties { switch (chainId) { // Mainnet @@ -103,14 +125,14 @@ export function sortNetworks(): NetworkProperties[] { return count1 > count2 ? -1 : 1; }; - return RainbowNetworkObjects.sort(tokenSort); + return RainbowNetworks.sort(tokenSort); } export function getSwappableNetworks(): NetworkProperties[] { - return RainbowNetworkObjects.filter(network => network.features.swaps); + return RainbowNetworks.filter(network => network.features.swaps); } -export const RainbowNetworkByChainId = RainbowNetworkObjects.reduce( +export const RainbowNetworkByChainId = RainbowNetworks.reduce( (acc, network) => { acc[network.id] = network; return acc; diff --git a/src/networks/mainnet.ts b/src/networks/mainnet.ts index e185b5c1b25..2c3644511ec 100644 --- a/src/networks/mainnet.ts +++ b/src/networks/mainnet.ts @@ -1,10 +1,9 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; -import { Network, NetworkProperties, ChainId } from './types'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; +import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { mainnet } from '@wagmi/chains'; import { ETH_ADDRESS } from '@/references'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export const getMainnetNetworkObject = (): NetworkProperties => { const { mainnet_enabled, mainnet_tx_enabled } = getRemoteConfig(); @@ -25,8 +24,9 @@ export const getMainnetNetworkObject = (): NetworkProperties => { address: ETH_ADDRESS, }, - getProvider: () => getProvider({ chainId: ChainId.mainnet }), - rpc: () => (useConnectedToHardhatStore.getState().connectedToHardhat ? 'http://127.0.0.1:8545' : proxyRpcEndpoint(mainnet.id)), + // this should be refactored to have less deps + getProvider: () => getProviderForNetwork(Network.mainnet), + rpc: () => proxyRpcEndpoint(mainnet.id), balanceCheckerAddress: '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b', // features diff --git a/src/networks/optimism.ts b/src/networks/optimism.ts index 2a9bf3c4884..b2d6ce8c8a6 100644 --- a/src/networks/optimism.ts +++ b/src/networks/optimism.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { optimism } from '@wagmi/chains'; import { OPTIMISM_ETH_ADDRESS } from '@/references'; import { getOptimismGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getOptimismNetworkObject = (): NetworkProperties => { const { optimism_enabled, optimism_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -27,7 +26,7 @@ export const getOptimismNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(optimism.id), - getProvider: () => getProvider({ chainId: ChainId.optimism }), + getProvider: () => getProviderForNetwork(Network.optimism), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/networks/polygon.ts b/src/networks/polygon.ts index 49f4feba581..7866a77a4c4 100644 --- a/src/networks/polygon.ts +++ b/src/networks/polygon.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { polygon } from '@wagmi/chains'; import { MATIC_MAINNET_ADDRESS, MATIC_POLYGON_ADDRESS } from '@/references'; import { getPolygonGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getPolygonNetworkObject = (): NetworkProperties => { const { polygon_tx_enabled } = getRemoteConfig(); @@ -28,7 +27,7 @@ export const getPolygonNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(polygon.id), - getProvider: () => getProvider({ chainId: ChainId.polygon }), + getProvider: () => getProviderForNetwork(Network.polygon), balanceCheckerAddress: '0x54A4E5800345c01455a77798E0D96438364e22723', // features diff --git a/src/networks/types.ts b/src/networks/types.ts index 6e94851fc28..052080ce5c7 100644 --- a/src/networks/types.ts +++ b/src/networks/types.ts @@ -2,11 +2,7 @@ import { EthereumAddress } from '@/entities'; import { GasPricesAPIData } from '@/entities/gas'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { Chain } from '@wagmi/chains'; -import * as chain from 'viem/chains'; - -const HARDHAT_CHAIN_ID = 1337; -const HARDHAT_OP_CHAIN_ID = 1338; - +// network.ts export enum Network { arbitrum = 'arbitrum', goerli = 'goerli', @@ -22,199 +18,6 @@ export enum Network { degen = 'degen', } -export enum ChainId { - arbitrum = chain.arbitrum.id, - arbitrumNova = chain.arbitrumNova.id, - arbitrumSepolia = chain.arbitrumSepolia.id, - avalanche = chain.avalanche.id, - avalancheFuji = chain.avalancheFuji.id, - base = chain.base.id, - baseSepolia = chain.baseSepolia.id, - blast = chain.blast.id, - blastSepolia = chain.blastSepolia.id, - bsc = chain.bsc.id, - bscTestnet = chain.bscTestnet.id, - celo = chain.celo.id, - degen = chain.degen.id, - gnosis = chain.gnosis.id, - goerli = chain.goerli.id, - hardhat = HARDHAT_CHAIN_ID, - hardhatOptimism = HARDHAT_OP_CHAIN_ID, - holesky = chain.holesky.id, - linea = chain.linea.id, - mainnet = chain.mainnet.id, - manta = chain.manta.id, - optimism = chain.optimism.id, - optimismSepolia = chain.optimismSepolia.id, - polygon = chain.polygon.id, - polygonAmoy = chain.polygonAmoy.id, - polygonMumbai = chain.polygonMumbai.id, - polygonZkEvm = chain.polygonZkEvm.id, - rari = 1380012617, - scroll = chain.scroll.id, - sepolia = chain.sepolia.id, - zora = chain.zora.id, - zoraSepolia = chain.zoraSepolia.id, -} - -export enum ChainName { - arbitrum = 'arbitrum', - arbitrumNova = 'arbitrum-nova', - arbitrumSepolia = 'arbitrum-sepolia', - avalanche = 'avalanche', - avalancheFuji = 'avalanche-fuji', - base = 'base', - blast = 'blast', - blastSepolia = 'blast-sepolia', - bsc = 'bsc', - celo = 'celo', - degen = 'degen', - gnosis = 'gnosis', - goerli = 'goerli', - linea = 'linea', - manta = 'manta', - optimism = 'optimism', - polygon = 'polygon', - polygonZkEvm = 'polygon-zkevm', - rari = 'rari', - scroll = 'scroll', - zora = 'zora', - mainnet = 'mainnet', - holesky = 'holesky', - hardhat = 'hardhat', - hardhatOptimism = 'hardhat-optimism', - sepolia = 'sepolia', - optimismSepolia = 'optimism-sepolia', - bscTestnet = 'bsc-testnet', - polygonMumbai = 'polygon-mumbai', - baseSepolia = 'base-sepolia', - zoraSepolia = 'zora-sepolia', - polygonAmoy = 'polygon-amoy', -} - -export const networkToIdMapping: { [key in Network]: ChainId } = { - [Network.arbitrum]: ChainId.arbitrum, - [Network.goerli]: ChainId.goerli, - [Network.mainnet]: ChainId.mainnet, - [Network.optimism]: ChainId.optimism, - [Network.polygon]: ChainId.polygon, - [Network.base]: ChainId.base, - [Network.bsc]: ChainId.bsc, - [Network.zora]: ChainId.zora, - [Network.gnosis]: ChainId.gnosis, - [Network.avalanche]: ChainId.avalanche, - [Network.blast]: ChainId.blast, - [Network.degen]: ChainId.degen, -}; - -export const chainNameToIdMapping: { - [key in ChainName | 'ethereum' | 'ethereum-sepolia']: ChainId; -} = { - ['ethereum']: ChainId.mainnet, - [ChainName.arbitrum]: ChainId.arbitrum, - [ChainName.arbitrumNova]: ChainId.arbitrumNova, - [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, - [ChainName.avalanche]: ChainId.avalanche, - [ChainName.avalancheFuji]: ChainId.avalancheFuji, - [ChainName.base]: ChainId.base, - [ChainName.bsc]: ChainId.bsc, - [ChainName.celo]: ChainId.celo, - [ChainName.degen]: ChainId.degen, - [ChainName.gnosis]: ChainId.gnosis, - [ChainName.linea]: ChainId.linea, - [ChainName.manta]: ChainId.manta, - [ChainName.optimism]: ChainId.optimism, - [ChainName.goerli]: ChainId.goerli, - [ChainName.polygon]: ChainId.polygon, - [ChainName.polygonZkEvm]: ChainId.polygonZkEvm, - [ChainName.rari]: ChainId.rari, - [ChainName.scroll]: ChainId.scroll, - [ChainName.zora]: ChainId.zora, - [ChainName.mainnet]: ChainId.mainnet, - [ChainName.holesky]: ChainId.holesky, - [ChainName.hardhat]: ChainId.hardhat, - [ChainName.hardhatOptimism]: ChainId.hardhatOptimism, - ['ethereum-sepolia']: ChainId.sepolia, - [ChainName.sepolia]: ChainId.sepolia, - [ChainName.optimismSepolia]: ChainId.optimismSepolia, - [ChainName.bscTestnet]: ChainId.bscTestnet, - [ChainName.polygonMumbai]: ChainId.polygonMumbai, - [ChainName.baseSepolia]: ChainId.baseSepolia, - [ChainName.zoraSepolia]: ChainId.zoraSepolia, - [ChainName.blast]: ChainId.blast, - [ChainName.blastSepolia]: ChainId.blastSepolia, - [ChainName.polygonAmoy]: ChainId.polygonAmoy, -}; - -export const chainIdToNameMapping: { - [key in ChainId]: ChainName; -} = { - [ChainId.arbitrum]: ChainName.arbitrum, - [ChainId.arbitrumNova]: ChainName.arbitrumNova, - [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, - [ChainId.avalanche]: ChainName.avalanche, - [ChainId.avalancheFuji]: ChainName.avalancheFuji, - [ChainId.base]: ChainName.base, - [ChainId.blast]: ChainName.blast, - [ChainId.blastSepolia]: ChainName.blastSepolia, - [ChainId.bsc]: ChainName.bsc, - [ChainId.celo]: ChainName.celo, - [ChainId.degen]: ChainName.degen, - [ChainId.gnosis]: ChainName.gnosis, - [ChainId.linea]: ChainName.linea, - [ChainId.manta]: ChainName.manta, - [ChainId.optimism]: ChainName.optimism, - [ChainId.polygon]: ChainName.polygon, - [ChainId.polygonZkEvm]: ChainName.polygonZkEvm, - [ChainId.rari]: ChainName.rari, - [ChainId.scroll]: ChainName.scroll, - [ChainId.zora]: ChainName.zora, - [ChainId.mainnet]: ChainName.mainnet, - [ChainId.holesky]: ChainName.holesky, - [ChainId.hardhat]: ChainName.hardhat, - [ChainId.hardhatOptimism]: ChainName.hardhatOptimism, - [ChainId.sepolia]: ChainName.sepolia, - [ChainId.optimismSepolia]: ChainName.optimismSepolia, - [ChainId.bscTestnet]: ChainName.bscTestnet, - [ChainId.polygonMumbai]: ChainName.polygonMumbai, - [ChainId.baseSepolia]: ChainName.baseSepolia, - [ChainId.zoraSepolia]: ChainName.zoraSepolia, - [ChainId.polygonAmoy]: ChainName.polygonAmoy, -}; - -export const ChainNameDisplay = { - [ChainId.arbitrum]: 'Arbitrum', - [ChainId.arbitrumNova]: chain.arbitrumNova.name, - [ChainId.avalanche]: 'Avalanche', - [ChainId.avalancheFuji]: 'Avalanche Fuji', - [ChainId.base]: 'Base', - [ChainId.blast]: 'Blast', - [ChainId.blastSepolia]: 'Blast Sepolia', - [ChainId.bsc]: 'BSC', - [ChainId.celo]: chain.celo.name, - [ChainId.degen]: 'Degen Chain', - [ChainId.linea]: 'Linea', - [ChainId.manta]: 'Manta', - [ChainId.optimism]: 'Optimism', - [ChainId.polygon]: 'Polygon', - [ChainId.polygonZkEvm]: chain.polygonZkEvm.name, - [ChainId.rari]: 'RARI Chain', - [ChainId.scroll]: chain.scroll.name, - [ChainId.zora]: 'Zora', - [ChainId.mainnet]: 'Ethereum', - [ChainId.hardhat]: 'Hardhat', - [ChainId.hardhatOptimism]: 'Hardhat OP', - [ChainId.sepolia]: chain.sepolia.name, - [ChainId.holesky]: chain.holesky.name, - [ChainId.optimismSepolia]: chain.optimismSepolia.name, - [ChainId.bscTestnet]: 'BSC Testnet', - [ChainId.polygonMumbai]: chain.polygonMumbai.name, - [ChainId.arbitrumSepolia]: chain.arbitrumSepolia.name, - [ChainId.baseSepolia]: chain.baseSepolia.name, - [ChainId.zoraSepolia]: 'Zora Sepolia', - [ChainId.polygonAmoy]: 'Polygon Amoy', -} as const; - export type NetworkTypes = 'layer1' | 'layer2' | 'testnet'; export interface NetworkProperties extends Chain { diff --git a/src/networks/zora.ts b/src/networks/zora.ts index 58d95526fdc..e8c5da58ffd 100644 --- a/src/networks/zora.ts +++ b/src/networks/zora.ts @@ -1,11 +1,10 @@ -import { getProvider, proxyRpcEndpoint } from '@/handlers/web3'; +import { getProviderForNetwork, proxyRpcEndpoint } from '@/handlers/web3'; import { Network, NetworkProperties } from './types'; import { gasUtils } from '@/utils'; import { zora } from '@wagmi/chains'; import { ZORA_ETH_ADDRESS } from '@/references'; import { getZoraGasPrices } from '@/redux/gas'; import { getRemoteConfig } from '@/model/remoteConfig'; -import { ChainId } from '@/networks/types'; export const getZoraNetworkObject = (): NetworkProperties => { const { zora_enabled, zora_tx_enabled, op_chains_enabled, op_chains_tx_enabled } = getRemoteConfig(); @@ -27,7 +26,7 @@ export const getZoraNetworkObject = (): NetworkProperties => { }, rpc: () => proxyRpcEndpoint(zora.id), - getProvider: () => getProvider({ chainId: ChainId.arbitrum }), + getProvider: () => getProviderForNetwork(Network.zora), balanceCheckerAddress: '0x1C8cFdE3Ba6eFc4FF8Dd5C93044B9A690b6CFf36', // features diff --git a/src/notifications/NotificationsHandler.tsx b/src/notifications/NotificationsHandler.tsx index bb5240980db..d809e788d47 100644 --- a/src/notifications/NotificationsHandler.tsx +++ b/src/notifications/NotificationsHandler.tsx @@ -22,7 +22,7 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { AppState as ApplicationState, AppStateStatus, NativeEventSubscription } from 'react-native'; import notifee, { Event as NotifeeEvent, EventType } from '@notifee/react-native'; -import { isLowerCaseMatch } from '@/utils'; +import { ethereumUtils, isLowerCaseMatch } from '@/utils'; import walletTypes from '@/helpers/walletTypes'; import { NotificationSubscriptionChangesListener, @@ -165,11 +165,10 @@ export const NotificationsHandler = ({ walletReady }: Props) => { } Navigation.handleAction(Routes.PROFILE_SCREEN, {}); - const chainId = parseInt(data.chain, 10); - + const network = ethereumUtils.getNetworkFromChainId(parseInt(data.chain, 10)); const transaction = await transactionFetchQuery({ hash: data.hash, - chainId, + network: network, address: walletAddress, currency: nativeCurrency, }); @@ -182,9 +181,9 @@ export const NotificationsHandler = ({ walletReady }: Props) => { transaction, }); } else if (type === NotificationTypes.walletConnect) { - logger.debug(`[NotificationsHandler]: handling wallet connect notification`, { notification }); + logger.info(`NotificationsHandler: handling wallet connect notification`, { notification }); } else if (type === NotificationTypes.marketing) { - logger.debug(`[NotificationsHandler]: handling marketing notification`, { + logger.info(`NotificationsHandler: handling marketing notification`, { notification, }); const data = notification.data as unknown as MarketingNotificationData; @@ -195,7 +194,7 @@ export const NotificationsHandler = ({ walletReady }: Props) => { }); } } else { - logger.warn(`[NotificationsHandler]: received unknown notification`, { + logger.warn(`NotificationsHandler: received unknown notification`, { notification, }); } diff --git a/src/notifications/foregroundHandler.ts b/src/notifications/foregroundHandler.ts index 75282a3f7a3..d8df820f7e1 100644 --- a/src/notifications/foregroundHandler.ts +++ b/src/notifications/foregroundHandler.ts @@ -30,8 +30,6 @@ export function handleShowingForegroundNotification(remoteMessage: FixedRemoteMe } notifee.displayNotification(notification).catch(error => { - logger.error(new RainbowError('[notifications]: Error while displaying notification with notifee library'), { - error, - }); + logger.error(new RainbowError('Error while displaying notification with notifee library'), { error }); }); } diff --git a/src/notifications/permissions.ts b/src/notifications/permissions.ts index b2b1869fd32..11a794f542b 100644 --- a/src/notifications/permissions.ts +++ b/src/notifications/permissions.ts @@ -25,9 +25,7 @@ export const checkPushNotificationPermissions = async () => { try { permissionStatus = await getPermissionStatus(); } catch (error) { - logger.error(new RainbowError('[notifications]: Error checking if a user has push notifications permission'), { - error, - }); + logger.error(new RainbowError('Error checking if a user has push notifications permission'), { error }); } if (permissionStatus !== messaging.AuthorizationStatus.AUTHORIZED && permissionStatus !== messaging.AuthorizationStatus.PROVISIONAL) { @@ -40,7 +38,7 @@ export const checkPushNotificationPermissions = async () => { trackPushNotificationPermissionStatus(status ? 'enabled' : 'disabled'); await saveFCMToken(); } catch (error) { - logger.error(new RainbowError('[notifications]: Error while getting permissions'), { error }); + logger.error(new RainbowError('Error while getting permissions'), { error }); } finally { resolve(true); } diff --git a/src/notifications/settings/firebase.ts b/src/notifications/settings/firebase.ts index 09fe6271985..4448d4eb641 100644 --- a/src/notifications/settings/firebase.ts +++ b/src/notifications/settings/firebase.ts @@ -35,7 +35,7 @@ export const subscribeWalletToNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ): Promise => { - logger.debug(`[notifications]: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`Notifications: subscribing ${type}:${address} to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => trackChangedNotificationSettings(topic, 'subscribe', chainId, type)); @@ -47,7 +47,7 @@ export const unsubscribeWalletFromNotificationTopic = async ( address: string, topic: WalletNotificationTopicType ) => { - logger.debug(`[notifications]: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`Notifications: unsubscribing ${type}:${address} from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(`${type}_${chainId}_${address.toLowerCase()}_${topic}`) .then(() => { @@ -56,14 +56,14 @@ export const unsubscribeWalletFromNotificationTopic = async ( }; export const subscribeToGlobalNotificationTopic = async (topic: GlobalNotificationTopicType): Promise => { - logger.debug(`[notifications]: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`Notifications: subscribing to [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .subscribeToTopic(topic) .then(() => trackChangedNotificationSettings(topic, 'subscribe')); }; export const unsubscribeFromGlobalNotificationTopic = async (topic: GlobalNotificationTopicType) => { - logger.debug(`[notifications]: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); + logger.debug(`Notifications: unsubscribing from [ ${topic.toUpperCase()} ]`, {}, logger.DebugContext.notifications); return messaging() .unsubscribeFromTopic(topic) .then(() => { diff --git a/src/notifications/settings/initialization.ts b/src/notifications/settings/initialization.ts index 58d67e30215..4cd40737e78 100644 --- a/src/notifications/settings/initialization.ts +++ b/src/notifications/settings/initialization.ts @@ -189,7 +189,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting await unsubscribeWalletFromAllNotificationTopics(newSettings.oldType, NOTIFICATIONS_DEFAULT_CHAIN_ID, newSettings.address); newSettings.oldType = undefined; } catch (e) { - logger.error(new RainbowError('[notifications]: Failed to unsubscribe old watcher mode notification topics')); + logger.error(new RainbowError('Failed to unsubscribe old watcher mode notification topics')); } } if (newSettings.type === WalletNotificationRelationship.OWNER && !newSettings.successfullyFinishedInitialSubscription) { @@ -198,7 +198,7 @@ const processSubscriptionQueueItem = async (queueItem: WalletNotificationSetting newSettings.successfullyFinishedInitialSubscription = true; newSettings.enabled = true; } catch (e) { - logger.error(new RainbowError('[notifications]: Failed to subscribe to default notification topics for newly added wallet')); + logger.error(new RainbowError('Failed to subscribe to default notification topics for newly added wallet')); } } diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index 8c88b9ee1bf..bbf194a46c7 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -19,7 +19,7 @@ export const saveFCMToken = async () => { } } } catch (error) { - logger.warn('[notifications]: Error while getting and saving FCM token', { + logger.warn('Error while getting and saving FCM token', { error, }); } @@ -30,7 +30,7 @@ export async function getFCMToken(): Promise { const token = fcmTokenLocal?.data || undefined; if (!token) { - logger.debug('[notifications]: getFCMToken No FCM token found'); + logger.debug('getFCMToken: No FCM token found'); } return token; diff --git a/src/parsers/accounts.js b/src/parsers/accounts.js new file mode 100644 index 00000000000..aa31cce971b --- /dev/null +++ b/src/parsers/accounts.js @@ -0,0 +1,72 @@ +import isNil from 'lodash/isNil'; +import toUpper from 'lodash/toUpper'; +import { isNativeAsset } from '@/handlers/assets'; +import * as i18n from '@/languages'; +import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; +import { getTokenMetadata, isLowerCaseMatch } from '@/utils'; +import { memoFn } from '@/utils/memoFn'; +import { getUniqueId } from '@/utils/ethereumUtils'; +import { ChainId } from '@/__swaps__/types/chains'; + +// eslint-disable-next-line no-useless-escape +const sanitize = memoFn(s => s.replace(/[^a-z0-9áéíóúñü \.,_@:-]/gim, '')); + +export const parseAssetName = (metadata, name) => { + if (metadata?.name) return metadata?.name; + return name ? sanitize(name) : i18n.t(i18n.l.assets.unkown_token); +}; + +export const parseAssetSymbol = (metadata, symbol) => { + if (metadata?.symbol) return metadata?.symbol; + return symbol ? toUpper(sanitize(symbol)) : '———'; +}; + +/** + * @desc parse asset + * @param {Object} assetData + * @return The parsed asset. + */ +export const parseAsset = ({ asset_code: address, ...asset } = {}) => { + const metadata = getTokenMetadata(asset.mainnet_address || address); + const name = parseAssetName(metadata, asset.name); + const symbol = parseAssetSymbol(metadata, asset.symbol); + + const parsedAsset = { + ...asset, + ...metadata, + address, + isNativeAsset: isNativeAsset(address, asset.chain_id || ChainId.mainnet), + name, + symbol, + uniqueId: getUniqueId(address, asset.chain_id), + }; + + return parsedAsset; +}; + +export const parseAssetsNative = (assets, nativeCurrency) => assets.map(asset => parseAssetNative(asset, nativeCurrency)); + +export const parseAssetNative = (asset, nativeCurrency) => { + const assetNativePrice = asset?.price; + if (isNil(assetNativePrice)) { + return asset; + } + + const priceUnit = assetNativePrice?.value ?? 0; + const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); + return { + ...asset, + native: { + balance: nativeDisplay, + change: isLowerCaseMatch(asset.symbol, nativeCurrency) + ? null + : assetNativePrice.relative_change_24h + ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) + : '', + price: { + amount: priceUnit, + display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), + }, + }, + }; +}; diff --git a/src/parsers/accounts.ts b/src/parsers/accounts.ts deleted file mode 100644 index 279e7eaa89a..00000000000 --- a/src/parsers/accounts.ts +++ /dev/null @@ -1,32 +0,0 @@ -import isNil from 'lodash/isNil'; -import { convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; -import { isLowerCaseMatch } from '@/utils'; -import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; - -export const parseAssetsNative = (assets: ParsedAddressAsset[], nativeCurrency: NativeCurrencyKey) => - assets.map(asset => parseAssetNative(asset, nativeCurrency)); - -export const parseAssetNative = (asset: ParsedAddressAsset, nativeCurrency: NativeCurrencyKey) => { - const assetNativePrice = asset?.price; - if (isNil(assetNativePrice)) { - return asset; - } - - const priceUnit = assetNativePrice?.value ?? 0; - const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); - return { - ...asset, - native: { - balance: nativeDisplay, - change: isLowerCaseMatch(asset.symbol, nativeCurrency) - ? undefined - : assetNativePrice.relative_change_24h - ? convertAmountToPercentageDisplay(assetNativePrice.relative_change_24h) - : '', - price: { - amount: priceUnit?.toString(), - display: convertAmountToNativeDisplay(priceUnit, nativeCurrency), - }, - }, - }; -}; diff --git a/src/parsers/gas.ts b/src/parsers/gas.ts index ce48764e84b..01058c09b71 100644 --- a/src/parsers/gas.ts +++ b/src/parsers/gas.ts @@ -33,6 +33,7 @@ import { multiply, toFixedDecimals, } from '@/helpers/utilities'; +import { Network } from '@/networks/types'; type BigNumberish = number | string | BigNumber; @@ -97,7 +98,8 @@ const parseGasDataConfirmationTime = ( }; export const parseRainbowMeteorologyData = ( - rainbowMeterologyData: RainbowMeteorologyData + rainbowMeterologyData: RainbowMeteorologyData, + network: Network ): { gasFeeParamsBySpeed: GasFeeParamsBySpeed; baseFeePerGas: GasFeeParam; diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 705b2cde5c0..1f520d23bde 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,4 +1,4 @@ -export { parseAssetNative, parseAssetsNative } from './accounts'; +export { parseAssetName, parseAssetSymbol, parseAsset, parseAssetNative, parseAssetsNative } from './accounts'; export { parseL2GasPrices, parseGasFeesBySpeed, diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 3bf47986390..80c13845313 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -43,11 +43,7 @@ export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { message = toUtf8String(message); } } catch (error) { - logger.warn( - '[parsers/requests]: WC v2: getting display details, unable to decode hex message to UTF8 string', - { error }, - logger.DebugContext.walletconnect - ); + logger.debug('WC v2: getting display details, unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); } return getMessageDisplayDetails(message, timestampInMs); } @@ -77,7 +73,7 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork({ chainId }); + const nativeAsset = ethereumUtils.getNativeAssetForNetwork(chainId); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/parsers/transactions.ts b/src/parsers/transactions.ts index 712b8de45b7..495ff7ba0c4 100644 --- a/src/parsers/transactions.ts +++ b/src/parsers/transactions.ts @@ -20,7 +20,7 @@ import { TransactionType, TransactionWithChangesType, } from '@/resources/transactions/types'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const LAST_TXN_HASH_BUFFER = 20; @@ -113,16 +113,10 @@ export const parseTransaction = async ( iconUrl: meta.contract_icon_url, }; - // NOTE: For send transactions, the to address should be pulled from the outgoing change directly, not the txn.address_to - let to = txn.address_to; - if (meta.type === 'send') { - to = txn.changes.find(change => change?.direction === 'out')?.address_to ?? txn.address_to; - } - return { chainId, from: txn.address_from, - to, + to: txn.address_to, title: `${type}.${status}`, description, hash, diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index 5143fd2e922..208f337024f 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -1,5 +1,6 @@ -import { NewTransaction, ParsedAddressAsset, TransactionGasParamAmounts } from '@/entities'; -import { getProvider } from '@/handlers/web3'; +import { NewTransaction, TransactionGasParamAmounts } from '@/entities'; +import { getProviderForNetwork } from '@/handlers/web3'; +import { Network } from '@/helpers'; import { add, addBuffer, greaterThan, lessThan, multiply, subtract } from '@/helpers/utilities'; import { RainbowError } from '@/logger'; import store from '@/redux/store'; @@ -12,7 +13,6 @@ import { CrosschainQuote, QuoteError, SwapType, getClaimBridgeQuote } from '@rai import { Address } from 'viem'; import { ActionProps } from '../references'; import { executeCrosschainSwap } from './crosschainSwap'; -import { ChainId } from '@/networks/types'; // This action is used to bridge the claimed funds to another chain export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps<'claimBridge'>) { @@ -51,7 +51,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps // 2 - We use the default gas limit (already inflated) from the quote to calculate the aproximate gas fee const initalGasLimit = bridgeQuote.defaultGasLimit as string; - const provider = getProvider({ chainId: ChainId.optimism }); + const provider = getProviderForNetwork(Network.optimism); const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism( // @ts-ignore @@ -153,21 +153,17 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps throw new Error('[CLAIM-BRIDGE]: executeCrosschainSwap returned undefined'); } - const typedAssetToBuy: ParsedAddressAsset = { + const typedAssetToBuy = { ...parameters.assetToBuy, network: getNetworkFromChainId(parameters.assetToBuy.chainId), - chainId: parameters.assetToBuy.chainId, colors: undefined, networks: undefined, - native: undefined, }; const typedAssetToSell = { ...parameters.assetToSell, network: getNetworkFromChainId(parameters.assetToSell.chainId), - chainId: parameters.assetToSell.chainId, colors: undefined, networks: undefined, - native: undefined, }; // 5 - if the swap was successful we add the transaction to the store @@ -201,7 +197,7 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps addNewTransaction({ address: bridgeQuote.from as Address, - chainId: parameters.chainId, + network: getNetworkFromChainId(parameters.chainId), transaction, }); diff --git a/src/raps/actions/crosschainSwap.ts b/src/raps/actions/crosschainSwap.ts index ddbd4789be3..be6a0df7c9a 100644 --- a/src/raps/actions/crosschainSwap.ts +++ b/src/raps/actions/crosschainSwap.ts @@ -1,10 +1,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { CrosschainQuote, fillCrosschainQuote } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; +import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; import { REFERRER, gasUnits, ReferrerType } from '@/references'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { addNewTransaction } from '@/state/pendingTransactions'; @@ -39,7 +39,7 @@ export const estimateCrosschainSwapGasLimit = async ({ quote: CrosschainQuote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProvider({ chainId }); + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -130,7 +130,7 @@ export const crosschainSwap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('[raps/crosschainSwap]: error estimateCrosschainSwapGasLimit'), { + logger.error(new RainbowError('crosschainSwap: error estimateCrosschainSwapGasLimit'), { message: (e as Error)?.message, }); throw e; @@ -158,13 +158,11 @@ export const crosschainSwap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'), { - message: (e as Error)?.message, - }); + logger.error(new RainbowError('crosschainSwap: error executeCrosschainSwap'), { message: (e as Error)?.message }); throw e; } - if (!swap) throw new RainbowError('[raps/crosschainSwap]: error executeCrosschainSwap'); + if (!swap) throw new RainbowError('crosschainSwap: error executeCrosschainSwap'); // TODO: MARK - Replace this once we migrate network => chainId const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); @@ -201,10 +199,8 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToSell, network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), - chainId: parameters.assetToSell.chainId, colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, - native: undefined, }, value: quote.sellAmount.toString(), }, @@ -215,10 +211,8 @@ export const crosschainSwap = async ({ asset: { ...parameters.assetToBuy, network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), - chainId: parameters.assetToBuy.chainId, colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, - native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -227,6 +221,7 @@ export const crosschainSwap = async ({ hash: swap.hash as TxHash, // TODO: MARK - Replace this once we migrate network => chainId network, + // chainId: parameters.chainId, nonce: swap.nonce, status: 'pending', type: 'swap', @@ -236,7 +231,8 @@ export const crosschainSwap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - chainId, + // chainId: parameters.chainId as ChainId, + network, transaction, }); diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts index 6fc53450458..6c804fad086 100644 --- a/src/raps/actions/ens.ts +++ b/src/raps/actions/ens.ts @@ -6,14 +6,15 @@ import { analytics } from '@/analytics'; import { ENSRegistrationRecords, NewTransaction, TransactionGasParamAmounts } from '@/entities'; import { estimateENSTransactionGasLimit, formatRecordsForTransaction } from '@/handlers/ens'; import { toHex } from '@/handlers/web3'; +import { NetworkTypes } from '@/helpers'; import { ENSRegistrationTransactionType, getENSExecutionDetails, REGISTRATION_MODES } from '@/helpers/ens'; import * as i18n from '@/languages'; import { saveCommitRegistrationParameters, updateTransactionRegistrationParameters } from '@/redux/ensRegistration'; import store from '@/redux/store'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { parseGasParamAmounts } from '@/parsers'; import { addNewTransaction } from '@/state/pendingTransactions'; -import { ChainId, Network } from '@/networks/types'; +import { Network } from '@/networks/types'; import { createRegisterENSRap, createRenewENSRap, @@ -24,6 +25,7 @@ import { } from '../registerENS'; import { Logger } from '@ethersproject/logger'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; +import { ChainId } from '@/__swaps__/types/chains'; export interface ENSRapActionResponse { baseNonce?: number | null; @@ -306,23 +308,24 @@ const ensAction = async ( type: ENSRegistrationTransactionType, baseNonce?: number ): Promise => { - logger.debug(`[raps/ens]: [${actionName}] base nonce ${baseNonce} index: ${index}`); + logger.log(`[${actionName}] base nonce`, baseNonce, 'index:', index); const { dispatch } = store; const { accountAddress: ownerAddress } = store.getState().settings; const { name, duration, rentPrice, records, salt, toAddress, mode } = parameters; - logger.debug(`[raps/ens]: [${actionName}] rap for ${name}`); + logger.log(`[${actionName}] rap for`, name); let gasLimit; const ensRegistrationRecords = formatRecordsForTransaction(records); try { - logger.debug(`[raps/ens]: [${actionName}] estimate gas`, { - data: { + logger.sentry( + `[${actionName}] estimate gas`, + { ...parameters, - type, }, - }); + type + ); // when registering the ENS if we try to estimate gas for setting records // (MULTICALL || SET_TEXT) it's going to fail if we put the account address @@ -343,7 +346,8 @@ const ensAction = async ( type, }); } catch (e) { - logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error estimating gas: ${e}`)); + logger.sentry(`[${actionName}] Error estimating gas`); + captureException(e); throw e; } let tx; @@ -354,11 +358,8 @@ const ensAction = async ( maxFeePerGas = gasParams.maxFeePerGas; maxPriorityFeePerGas = gasParams.maxPriorityFeePerGas; - logger.debug(`[raps/ens]: [${actionName}] about to ${type}`, { - data: { - ...parameters, - type, - }, + logger.sentry(`[${actionName}] about to ${type}`, { + ...parameters, }); const nonce = baseNonce ? baseNonce + index : null; @@ -456,11 +457,12 @@ const ensAction = async ( }); } } catch (e) { - logger.error(new RainbowError(`[raps/ens]: [${actionName}] Error executing: ${e}`)); + logger.sentry(`[${actionName}] Error executing`); + captureException(e); throw e; } - logger.debug(`[raps/ens]: [${actionName}] response`, { data: tx }); + logger.log(`[${actionName}] response`, tx); const newTransaction: NewTransaction = { chainId: ChainId.mainnet, @@ -479,16 +481,15 @@ const ensAction = async ( }, to: tx?.to, value: toHex(tx.value), - network: Network.mainnet, + network: NetworkTypes.mainnet, status: 'pending', }; - - logger.debug(`[raps/ens]: [${actionName}] adding new txn`, { data: newTransaction }); + logger.log(`[${actionName}] adding new txn`, newTransaction); addNewTransaction({ address: ownerAddress, transaction: newTransaction, - chainId: ChainId.mainnet, + network: Network.mainnet, }); return tx?.nonce; }; @@ -669,11 +670,11 @@ const executeAction = async ( rapName: string, baseNonce?: number ): Promise => { - logger.debug(`[raps/ens]: [${rapName}] 1 INNER index: ${index}`); + logger.log('[1 INNER] index', index); const { type, parameters } = action; let nonce; try { - logger.debug(`[raps/ens]: [${rapName}] 2 INNER executing type: ${type}`); + logger.log('[2 INNER] executing type', type); const actionPromise = findENSActionByType(type); nonce = await performanceTracking.getState().executeFn({ fn: actionPromise, @@ -682,7 +683,9 @@ const executeAction = async ( })(wallet, rap, index, parameters as RapENSActionParameters, baseNonce); return { baseNonce: nonce, errorMessage: null }; } catch (error: any) { - logger.error(new RainbowError(`[raps/ens]: [${rapName}] Error executing action: ${action} ${error}`)); + logger.debug('Rap blew up', error); + logger.sentry('[3 INNER] error running action, code:', error?.code); + captureException(error); analytics.track('Rap failed', { category: 'raps', failed_action: type, @@ -691,7 +694,7 @@ const executeAction = async ( // If the first action failed, return an error message if (index === 0) { const errorMessage = parseError(error); - logger.debug(`[raps/ens]: [${rapName}] 4 INNER displaying error message ${errorMessage}`); + logger.log('[4 INNER] displaying error message', errorMessage); return { baseNonce: null, errorMessage }; } return { baseNonce: null, errorMessage: null }; @@ -719,7 +722,7 @@ export const executeENSRap = async ( let nonce = parameters?.nonce; - logger.debug(`[raps/ens]: [${rapName}] actions`, { actions }); + logger.log('[common - executing rap]: actions', actions); if (actions.length) { const firstAction = actions[0]; const { baseNonce, errorMessage } = await executeAction(firstAction, wallet, rap, 0, rapName, nonce); @@ -741,8 +744,7 @@ export const executeENSRap = async ( category: 'raps', label: rapName, }); - - logger.debug(`[raps/ens]: [${rapName}] finished execute rap function`); + logger.log('[common - executing rap] finished execute rap function'); return { nonce }; }; diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index 2b1eddb690c..5dd61e809f2 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -15,11 +15,11 @@ import { unwrapNativeAsset, wrapNativeAsset, } from '@rainbow-me/swaps'; -import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; +import { getProviderForNetwork, estimateGasWithPadding } from '@/handlers/web3'; import { Address } from 'viem'; import { metadataPOSTClient } from '@/graphql'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { add } from '@/helpers/utilities'; @@ -62,7 +62,7 @@ export const estimateSwapGasLimit = async ({ quote: Quote; }): Promise => { // TODO: MARK - Replace this once we migrate network => chainId - const provider = getProvider({ chainId }); + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); if (!provider || !quote) { return gasUnits.basic_swap[chainId]; } @@ -145,7 +145,8 @@ export const estimateUnlockAndSwapFromMetadata = async ({ chainId, }); - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const swapTransaction = await populateSwap({ provider, quote, @@ -266,7 +267,7 @@ export const swap = async ({ quote, }); } catch (e) { - logger.error(new RainbowError('[raps/swap]: error estimateSwapGasLimit'), { + logger.error(new RainbowError('swap: error estimateSwapGasLimit'), { message: (e as Error)?.message, }); @@ -294,7 +295,7 @@ export const swap = async ({ }, })(swapParams); } catch (e) { - logger.error(new RainbowError('[raps/swap]: error executeSwap'), { + logger.error(new RainbowError('swap: error executeSwap'), { message: (e as Error)?.message, }); throw e; @@ -302,6 +303,9 @@ export const swap = async ({ if (!swap || !swap?.hash) throw new RainbowError('swap: error executeSwap'); + // TODO: MARK - Replace this once we migrate network => chainId + const network = ethereumUtils.getNetworkFromChainId(parameters.chainId); + const nativePriceForAssetToBuy = (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice ? { value: (parameters.assetToBuy as ExtendedAnimatedAssetWithColors)?.nativePrice, @@ -338,7 +342,6 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToSell.chainId), colors: parameters.assetToSell.colors as TokenColors, price: nativePriceForAssetToSell, - native: undefined, }, value: quote.sellAmount.toString(), }, @@ -351,7 +354,6 @@ export const swap = async ({ network: ethereumUtils.getNetworkFromChainId(parameters.assetToBuy.chainId), colors: parameters.assetToBuy.colors as TokenColors, price: nativePriceForAssetToBuy, - native: undefined, }, value: quote.buyAmountMinusFees.toString(), }, @@ -384,7 +386,8 @@ export const swap = async ({ addNewTransaction({ address: parameters.quote.from as Address, - chainId: parameters.chainId, + // chainId: parameters.chainId as ChainId, + network, transaction, }); diff --git a/src/raps/actions/unlock.ts b/src/raps/actions/unlock.ts index b4377935b99..d2c95ea1c00 100644 --- a/src/raps/actions/unlock.ts +++ b/src/raps/actions/unlock.ts @@ -2,10 +2,10 @@ import { Signer } from '@ethersproject/abstract-signer'; import { MaxUint256 } from '@ethersproject/constants'; import { Contract, PopulatedTransaction } from '@ethersproject/contracts'; import { parseUnits } from '@ethersproject/units'; -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork } from '@/handlers/web3'; import { Address, erc20Abi, erc721Abi } from 'viem'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { TransactionGasParams, TransactionLegacyGasParams } from '@/__swaps__/types/gas'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; @@ -35,12 +35,13 @@ export const getAssetRawAllowance = async ({ chainId: ChainId; }) => { try { - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const tokenContract = new Contract(assetAddress, erc20Abi, provider); const allowance = await tokenContract.allowance(owner, spender); return allowance.toString(); } catch (error) { - logger.error(new RainbowError('[raps/unlock]: error'), { + logger.error(new RainbowError('getRawAllowance: error'), { message: (error as Error)?.message, }); return null; @@ -86,14 +87,15 @@ export const estimateApprove = async ({ chainId: ChainId; }): Promise => { try { - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const gasLimit = await tokenContract.estimateGas.approve(spender, MaxUint256, { from: owner, }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { + logger.error(new RainbowError('unlock: error estimateApprove'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -112,14 +114,15 @@ export const populateApprove = async ({ chainId: ChainId; }): Promise => { try { - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const tokenContract = new Contract(tokenAddress, erc20Abi, provider); const approveTransaction = await tokenContract.populateTransaction.approve(spender, MaxUint256, { from: owner, }); return approveTransaction; } catch (error) { - logger.error(new RainbowError('[raps/unlock]: error populateApprove'), { + logger.error(new RainbowError(' error populateApprove'), { message: (error as Error)?.message, }); return null; @@ -138,14 +141,15 @@ export const estimateERC721Approval = async ({ chainId: ChainId; }): Promise => { try { - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); const gasLimit = await tokenContract.estimateGas.setApprovalForAll(spender, false, { from: owner, }); return gasLimit ? gasLimit.toString() : `${gasUnits.basic_approval}`; } catch (error) { - logger.error(new RainbowError('[raps/unlock]: error estimateApproval'), { + logger.error(new RainbowError('estimateERC721Approval: error estimateApproval'), { message: (error as Error)?.message, }); return `${gasUnits.basic_approval}`; @@ -164,7 +168,8 @@ export const populateRevokeApproval = async ({ type: 'erc20' | 'nft'; }): Promise => { if (!tokenAddress || !spenderAddress || !chainId) return {}; - const provider = getProvider({ chainId }); + // TODO: MARK - Replace this once we migrate network => chainId + const provider = getProviderForNetwork(ethereumUtils.getNetworkFromChainId(chainId)); const tokenContract = new Contract(tokenAddress, erc721Abi, provider); if (type === 'erc20') { const amountToApprove = parseUnits('0', 'ether'); @@ -229,7 +234,7 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('[raps/unlock]: error estimateApprove'), { + logger.error(new RainbowError('unlock: error estimateApprove'), { message: (e as Error)?.message, }); throw e; @@ -255,13 +260,13 @@ export const unlock = async ({ chainId, }); } catch (e) { - logger.error(new RainbowError('[raps/unlock]: error executeApprove'), { + logger.error(new RainbowError('unlock: error executeApprove'), { message: (e as Error)?.message, }); throw e; } - if (!approval) throw new RainbowError('[raps/unlock]: error executeApprove'); + if (!approval) throw new RainbowError('unlock: error executeApprove'); const transaction = { asset: { @@ -285,9 +290,12 @@ export const unlock = async ({ ...gasParams, } satisfies NewTransaction; + // TODO: MARK - Replace this once we migrate network => chainId + const network = ethereumUtils.getNetworkFromChainId(approval.chainId); + addNewTransaction({ address: parameters.fromAddress as Address, - chainId: approval.chainId, + network, transaction, }); diff --git a/src/raps/common.ts b/src/raps/common.ts index 477a0f68b46..82282d68678 100644 --- a/src/raps/common.ts +++ b/src/raps/common.ts @@ -98,7 +98,7 @@ export const createNewENSAction = (type: ENSRapActionType, parameters: ENSAction type, }; - logger.debug('[raps/common]: Creating a new action', { + logger.log('[common] Creating a new action', { extra: { ...newAction, }, diff --git a/src/raps/execute.ts b/src/raps/execute.ts index 3ee2c9ec9b4..5481e159b72 100644 --- a/src/raps/execute.ts +++ b/src/raps/execute.ts @@ -94,7 +94,7 @@ export async function executeAction({ const { nonce, hash } = (await typeAction(type, actionProps)()) as RapActionResult; return { baseNonce: nonce, errorMessage: null, hash }; } catch (error) { - logger.error(new RainbowError(`[raps/execute]: ${rapName} - error execute action`), { + logger.error(new RainbowError(`rap: ${rapName} - error execute action`), { message: (error as Error)?.message, }); if (index === 0) { diff --git a/src/raps/references.ts b/src/raps/references.ts index 6c6bf22dba7..d8f68cf8bb9 100644 --- a/src/raps/references.ts +++ b/src/raps/references.ts @@ -4,7 +4,7 @@ import { Address } from 'viem'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export enum SwapModalField { input = 'inputAmount', diff --git a/src/raps/unlockAndSwap.ts b/src/raps/unlockAndSwap.ts index cdf9349adcb..614e66b992c 100644 --- a/src/raps/unlockAndSwap.ts +++ b/src/raps/unlockAndSwap.ts @@ -7,7 +7,7 @@ import { } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; import { isLowerCaseMatch } from '@/utils'; diff --git a/src/raps/utils.ts b/src/raps/utils.ts index cacc8795bb2..b91b60cb99b 100644 --- a/src/raps/utils.ts +++ b/src/raps/utils.ts @@ -5,10 +5,11 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { ALLOWS_PERMIT, CrosschainQuote, Quote, getQuoteExecutionDetails, getRainbowRouterContractAddress } from '@rainbow-me/swaps'; import { mainnet } from 'viem/chains'; import { Chain, erc20Abi } from 'viem'; +import { Network } from '@/helpers'; import { GasFeeParamsBySpeed, LegacyGasFeeParamsBySpeed, LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { ethereumUtils, gasUtils } from '@/utils'; import { add, greaterThan, multiply } from '@/helpers/utilities'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { gasUnits } from '@/references'; import { toHexNoLeadingZeros } from '@/handlers/web3'; diff --git a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js index 02a09ec6041..a557b350e51 100644 --- a/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js +++ b/src/react-native-animated-charts/src/interpolations/bSplineInterpolation.js @@ -28,8 +28,8 @@ class BSpline { } seqAt(dim) { - const points = this.points; - const margin = this.degree + 1; + let points = this.points; + let margin = this.degree + 1; return function (n) { if (n < margin) { return points[0][dim]; @@ -84,9 +84,9 @@ class BSpline { } getInterpol(seq, t) { - const f = this.baseFunc; - const rangeInt = this.baseFuncRangeInt; - const tInt = Math.floor(t); + let f = this.baseFunc; + let rangeInt = this.baseFuncRangeInt; + let tInt = Math.floor(t); let result = 0; for (let i = tInt - rangeInt; i <= tInt + rangeInt; i++) { result += seq(i) * f(t - i); @@ -113,13 +113,13 @@ class BSpline { } calcAt(t) { - t = t * ((this.degree + 1) * 2 + this.points.length); // t must be in [0,1] + t = t * ((this.degree + 1) * 2 + this.points.length); //t must be in [0,1] if (this.dimension === 2) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t)]; } else if (this.dimension === 3) { return [this.getInterpol(this.seqAt(0), t), this.getInterpol(this.seqAt(1), t), this.getInterpol(this.seqAt(2), t)]; } else { - const res = []; + let res = []; for (let i = 0; i < this.dimension; i++) { res.push(this.getInterpol(this.seqAt(i), t)); } diff --git a/src/react-native-cool-modals/createNativeStackNavigator.js b/src/react-native-cool-modals/createNativeStackNavigator.js index f4bba2bb834..391384ec141 100644 --- a/src/react-native-cool-modals/createNativeStackNavigator.js +++ b/src/react-native-cool-modals/createNativeStackNavigator.js @@ -2,7 +2,7 @@ import { createNavigatorFactory, StackRouter as OldStackRouter, StackActions, us import * as React from 'react'; import NativeStackView from './NativeStackView'; -import { logger } from '@/logger'; +import logger from '@/utils/logger'; function NativeStackNavigator(props) { const { children, initialRouteName, screenOptions, ...rest } = props; @@ -13,7 +13,7 @@ function NativeStackNavigator(props) { getStateForAction(state, action, options) { if (action.type === 'PUSH') { if (state.routes[state.routes.length - 1].name === action.payload.name) { - logger.debug(`[NativeStackNavigator]: pushing twice the same name is not allowed`); + logger.log('pushing twice the same name is not allowed'); return state; } } diff --git a/src/redux/contacts.ts b/src/redux/contacts.ts index 7de2fe6610f..bfa15e0e912 100644 --- a/src/redux/contacts.ts +++ b/src/redux/contacts.ts @@ -1,5 +1,6 @@ import { Dispatch } from 'redux'; import { getContacts, saveContacts } from '@/handlers/localstorage/contacts'; +import { Network } from '@/helpers/networkTypes'; import { omitFlatten } from '@/helpers/utilities'; import { AppGetState } from '@/redux/store'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -32,6 +33,11 @@ export interface Contact { */ ens: string; + /** + * The network. + */ + network: Network; + /** * The contact's nickname. */ @@ -79,7 +85,8 @@ export const contactsLoadState = () => async (dispatch: Dispatch (dispatch: Dispatch, getState: AppGetState) => { + (address: string, nickname: string, color: number, network: Network, ens: string) => + (dispatch: Dispatch, getState: AppGetState) => { const loweredAddress = address.toLowerCase(); const { contacts } = getState().contacts; const updatedContacts = { @@ -88,6 +95,7 @@ export const contactsAddOrUpdate = address: loweredAddress, color, ens, + network, nickname, }, }; diff --git a/src/redux/ensRegistration.ts b/src/redux/ensRegistration.ts index 44c6d1041d4..69d95299692 100644 --- a/src/redux/ensRegistration.ts +++ b/src/redux/ensRegistration.ts @@ -3,9 +3,9 @@ import { Dispatch } from 'react'; import { AppDispatch, AppGetState } from './store'; import { ENSRegistrations, ENSRegistrationState, Records, RegistrationParameters, TransactionRegistrationParameters } from '@/entities'; import { getLocalENSRegistrations, saveLocalENSRegistrations } from '@/handlers/localstorage/accountLocal'; +import { NetworkTypes } from '@/helpers'; import { ENS_RECORDS, REGISTRATION_MODES } from '@/helpers/ens'; import { omitFlatten } from '@/helpers/utilities'; -import { Network } from '@/networks/types'; const ENS_REGISTRATION_SET_CHANGED_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_CHANGED_RECORDS'; const ENS_REGISTRATION_SET_INITIAL_RECORDS = 'ensRegistration/ENS_REGISTRATION_SET_INITIAL_RECORDS'; @@ -345,7 +345,7 @@ export const saveCommitRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -382,7 +382,7 @@ export const updateTransactionRegistrationParameters = }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); dispatch({ payload: updatedEnsRegistrationManager, @@ -408,7 +408,7 @@ export const removeRegistrationByName = (name: string) => async (dispatch: AppDi }, }; - saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, Network.mainnet); + saveLocalENSRegistrations(updatedEnsRegistrationManager.registrations, accountAddress, NetworkTypes.mainnet); dispatch({ payload: updatedEnsRegistrationManager, diff --git a/src/redux/explorer.ts b/src/redux/explorer.ts index 0892e4b5e8c..20c9f151f31 100644 --- a/src/redux/explorer.ts +++ b/src/redux/explorer.ts @@ -4,8 +4,8 @@ import { ThunkDispatch } from 'redux-thunk'; import { io, Socket } from 'socket.io-client'; import { getRemoteConfig } from '@/model/remoteConfig'; import { AppGetState, AppState } from './store'; -import { ChainId } from '@/networks/types'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; +import { getProviderForNetwork, isHardHat } from '@/handlers/web3'; +import { Network } from '@/helpers/networkTypes'; // -- Constants --------------------------------------- // const EXPLORER_UPDATE_SOCKETS = 'explorer/EXPLORER_UPDATE_SOCKETS'; @@ -110,7 +110,7 @@ export const explorerClearState = () => (dispatch: ThunkDispatch (dispatch: ThunkDispatch, getState: AppGetState) => { - const { accountAddress, chainId } = getState().settings; + const { network, accountAddress } = getState().settings; const { addressSocket } = getState().explorer; // if there is another socket unsubscribe first @@ -118,7 +118,9 @@ export const explorerInit = () => (dispatch: ThunkDispatch void) => await mutex.runExclusive(callback); -const getGasPricePollingInterval = (chainId: ChainId): number => { - return getNetworkObject({ chainId }).gas.pollingIntervalInMs; +const getGasPricePollingInterval = (network: Network): number => { + return getNetworkObj(network).gas.pollingIntervalInMs; }; -const getDefaultGasLimit = (chainId: ChainId, defaultGasLimit: number): number => { - switch (chainId) { - case ChainId.arbitrum: +const getDefaultGasLimit = (network: Network, defaultGasLimit: number): number => { + switch (network) { + case Network.arbitrum: return ethUnits.arbitrum_basic_tx; - case ChainId.polygon: - case ChainId.bsc: - case ChainId.optimism: - case ChainId.mainnet: - case ChainId.zora: + case Network.polygon: + case Network.bsc: + case Network.optimism: + case Network.mainnet: + case Network.zora: default: return defaultGasLimit; } @@ -74,7 +73,7 @@ export interface GasState { gasFeeParamsBySpeed: GasFeeParamsBySpeed | LegacyGasFeeParamsBySpeed; selectedGasFee: SelectedGasFee | LegacySelectedGasFee; gasFeesBySpeed: GasFeesBySpeed | LegacyGasFeesBySpeed; - chainId: ChainId; + txNetwork: Network | null; currentBlockParams: CurrentBlockParams; blocksToConfirmation: BlocksToConfirmation; customGasFeeModifiedByUser: boolean; @@ -122,22 +121,22 @@ const getUpdatedGasFeeParams = ( gasLimit: string, nativeCurrency: NativeCurrencyKey, selectedGasFeeOption: string, - chainId: ChainId, + txNetwork: Network, l1GasFeeOptimism: BigNumber | null = null ) => { let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (chainId) { - case ChainId.polygon: + switch (txNetwork) { + case Network.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case ChainId.bsc: + case Network.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; - case ChainId.avalanche: + case Network.avalanche: nativeTokenPriceUnit = ethereumUtils.getAvaxPriceUnit(); break; - case ChainId.degen: + case Network.degen: nativeTokenPriceUnit = ethereumUtils.getDegenPriceUnit(); break; default: @@ -145,7 +144,7 @@ const getUpdatedGasFeeParams = ( break; } - const isLegacyGasNetwork = getNetworkObject({ chainId }).gas.gasType === 'legacy'; + const isLegacyGasNetwork = getNetworkObj(txNetwork).gas.gasType === 'legacy'; const gasFeesBySpeed = isLegacyGasNetwork ? parseLegacyGasFeesBySpeed( @@ -193,7 +192,7 @@ export const updateGasFeeForSpeed = export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispatch: AppDispatch, getState: AppGetState) => { const { - chainId, + txNetwork, defaultGasLimit, gasFeesBySpeed, gasFeeParamsBySpeed, @@ -205,15 +204,15 @@ export const gasUpdateToCustomGasFee = (gasParams: GasFeeParams) => async (dispa } = getState().gas; const { nativeCurrency } = getState().settings; - const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); let nativeTokenPriceUnit = ethereumUtils.getEthPriceUnit(); - switch (chainId) { - case ChainId.polygon: + switch (txNetwork) { + case Network.polygon: nativeTokenPriceUnit = ethereumUtils.getMaticPriceUnit(); break; - case ChainId.bsc: + case Network.bsc: nativeTokenPriceUnit = ethereumUtils.getBnbPriceUnit(); break; default: @@ -258,7 +257,7 @@ export const getPolygonGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(ChainId.polygon)) as { + } = (await rainbowMeteorologyGetData(Network.polygon)) as { data: RainbowMeteorologyLegacyData; }; const polygonGasPriceBumpFactor = 1.05; @@ -277,7 +276,7 @@ export const getPolygonGasPrices = async () => { }; return polygonGasStationPrices; } catch (e) { - logger.error(new RainbowError(`[redux/gas]: failed to fetch polygon gas prices ${e}`)); + logger.error(new RainbowError(`failed to fetch polygon gas prices ${e}`)); return null; } }; @@ -288,7 +287,7 @@ export const getBscGasPrices = async () => { data: { data: { legacy: result }, }, - } = (await rainbowMeteorologyGetData(ChainId.bsc)) as { + } = (await rainbowMeteorologyGetData(Network.bsc)) as { data: RainbowMeteorologyLegacyData; }; @@ -308,12 +307,12 @@ export const getBscGasPrices = async () => { }; return bscGasStationPrices; } catch (e) { - logger.error(new RainbowError(`[redux/gas]: failed to fetch BSC gas prices ${e}`)); + logger.error(new RainbowError(`failed to fetch BSC gas prices ${e}`)); return null; } }; export const getArbitrumGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.arbitrum }); + const provider = getProviderForNetwork(Network.arbitrum); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -331,7 +330,7 @@ export const getArbitrumGasPrices = async () => { }; export const getOptimismGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.optimism }); + const provider = getProviderForNetwork(Network.optimism); const baseGasPrice = await provider.getGasPrice(); const normalGasPrice = weiToGwei(baseGasPrice.toString()); @@ -348,7 +347,7 @@ export const getOptimismGasPrices = async () => { }; export const getBaseGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.base }); + const provider = getProviderForNetwork(Network.base); const baseGasPrice = await provider.getGasPrice(); const BasePriceBumpFactor = 1.05; @@ -367,7 +366,7 @@ export const getBaseGasPrices = async () => { }; export const getAvalancheGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.avalanche }); + const provider = getProviderForNetwork(Network.avalanche); const baseGasPrice = await provider.getGasPrice(); const AvalanchePriceBumpFactor = 1.05; @@ -386,7 +385,7 @@ export const getAvalancheGasPrices = async () => { }; export const getDegenGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.degen }); + const provider = getProviderForNetwork(Network.degen); const baseGasPrice = await provider.getGasPrice(); const DegenPriceBumpFactor = 1.05; @@ -405,7 +404,7 @@ export const getDegenGasPrices = async () => { }; export const getBlastGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.blast }); + const provider = getProviderForNetwork(Network.blast); const baseGasPrice = await provider.getGasPrice(); const BlastPriceBumpFactor = 1.05; @@ -424,7 +423,7 @@ export const getBlastGasPrices = async () => { }; export const getZoraGasPrices = async () => { - const provider = getProvider({ chainId: ChainId.zora }); + const provider = getProviderForNetwork(Network.zora); const baseGasPrice = await provider.getGasPrice(); const ZoraPriceBumpFactor = 1.05; @@ -442,12 +441,12 @@ export const getZoraGasPrices = async () => { return priceData; }; -export const getEIP1559GasParams = async (chainId: ChainId) => { - const { data } = (await rainbowMeteorologyGetData(chainId)) as { +export const getEIP1559GasParams = async (network: Network) => { + const { data } = (await rainbowMeteorologyGetData(network)) as { data: RainbowMeteorologyData; }; const { gasFeeParamsBySpeed, baseFeePerGas, baseFeeTrend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - parseRainbowMeteorologyData(data); + parseRainbowMeteorologyData(data, network); return { baseFeePerGas, blocksToConfirmation, @@ -459,45 +458,44 @@ export const getEIP1559GasParams = async (chainId: ChainId) => { }; export const gasPricesStartPolling = - (chainId = ChainId.mainnet, flashbots = false) => + (network = Network.mainnet, flashbots = false) => async (dispatch: AppDispatch, getState: AppGetState) => { dispatch(gasPricesStopPolling()); // this should be chain agnostic - const getGasPrices = () => + const getGasPrices = (network: Network) => withRunExclusive( () => new Promise(async (fetchResolve, fetchReject) => { try { + dispatch({ + payload: network, + type: GAS_UPDATE_TRANSACTION_NETWORK, + }); const { gasFeeParamsBySpeed: existingGasFees, customGasFeeModifiedByUser, defaultGasLimit, gasLimit, selectedGasFee, - chainId, + txNetwork, selectedGasFee: lastSelectedGasFee, gasFeesBySpeed: lastGasFeesBySpeed, currentBlockParams, l1GasFeeOptimism, } = getState().gas; - dispatch({ - payload: chainId, - type: GAS_UPDATE_TRANSACTION_NETWORK, - }); - const { nativeCurrency } = getState().settings; - const networkObject = getNetworkObject({ chainId }); + const networkObj = getNetworkObj(network); let dataIsReady = true; - if (networkObject.gas.gasType === 'legacy') { + if (networkObj.gas.gasType === 'legacy') { // OP chains have an additional fee we need to load - if (networkObject.gas?.OptimismTxFee) { + if (getNetworkObj(network).gas?.OptimismTxFee) { dataIsReady = l1GasFeeOptimism !== null; } - const adjustedGasFees = await networkObject.gas.getGasPrices(); + const adjustedGasFees = await networkObj.gas.getGasPrices(); if (!adjustedGasFees) return; @@ -505,7 +503,7 @@ export const gasPricesStartPolling = if (!gasFeeParamsBySpeed) return; const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed: updatedGasFeesBySpeed } = dataIsReady ? getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -513,7 +511,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - chainId, + txNetwork, l1GasFeeOptimism ) : { @@ -531,7 +529,7 @@ export const gasPricesStartPolling = } else { try { const { gasFeeParamsBySpeed, baseFeePerGas, trend, currentBaseFee, blocksToConfirmation, secondsPerNewBlock } = - await getEIP1559GasParams(chainId); + await getEIP1559GasParams(network); if (flashbots) { [SLOW, NORMAL, FAST, URGENT].forEach(speed => { @@ -555,8 +553,16 @@ export const gasPricesStartPolling = // Set a really gas estimate to guarantee that we're gonna be over // the basefee at the time we fork mainnet during our hardhat tests let baseFee = baseFeePerGas; - if (chainId === ChainId.mainnet && IS_TESTING === 'true' && useConnectedToHardhatStore.getState().connectedToHardhat) { - baseFee = parseGasFeeParam(gweiToWei(1000)); + if (network === Network.mainnet && IS_TESTING === 'true') { + const providerUrl = ( + web3Provider || + ({} as { + connection: { url: string }; + }) + )?.connection?.url; + if (isHardHat(providerUrl)) { + baseFee = parseGasFeeParam(gweiToWei(1000)); + } } if (customGasFeeModifiedByUser) { @@ -570,7 +576,7 @@ export const gasPricesStartPolling = gasFeeParamsBySpeed[CUSTOM] = gasFeeParamsBySpeed[URGENT]; } const _selectedGasFeeOption = selectedGasFee.option || NORMAL; - const _gasLimit = gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); + const _gasLimit = gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBaseFee, @@ -578,7 +584,7 @@ export const gasPricesStartPolling = _gasLimit, nativeCurrency, _selectedGasFeeOption, - chainId, + txNetwork, l1GasFeeOptimism ); @@ -597,32 +603,33 @@ export const gasPricesStartPolling = type: GAS_FEES_SUCCESS, }); } catch (e) { - logger.error(new RainbowError(`[redux/gas]: Etherscan gas estimates error: ${e}`)); + logger.error(new RainbowError(`Etherscan gas estimates error: ${e}`)); + logger.debug('falling back to eth gas station'); } } fetchResolve(true); } catch (e) { - logger.error(new RainbowError(`[redux/gas]: Gas Estimates Failed for ${chainId}: ${e}`)); + logger.error(new RainbowError(`Gas Estimates Failed for ${network}: ${e}`)); fetchReject(e); } }) ); - const watchGasPrices = async (chainId: ChainId, pollingInterval: number) => { + const watchGasPrices = async (network: Network, pollingInterval: number) => { try { - await getGasPrices(); + await getGasPrices(network); // eslint-disable-next-line no-empty } catch (e) { } finally { gasPricesHandle && clearTimeout(gasPricesHandle); gasPricesHandle = setTimeout(() => { - watchGasPrices(chainId, pollingInterval); + watchGasPrices(network, pollingInterval); }, pollingInterval); } }; - const pollingInterval = getGasPricePollingInterval(chainId); - watchGasPrices(chainId, pollingInterval); + const pollingInterval = getGasPricePollingInterval(network); + watchGasPrices(network, pollingInterval); }; export const gasUpdateGasFeeOption = (newGasPriceOption: string) => (dispatch: AppDispatch, getState: AppGetState) => @@ -654,10 +661,10 @@ export const gasUpdateTxFee = (updatedGasLimit?: number, overrideGasOption?: string, l1GasFeeOptimism: BigNumber | null = null) => (dispatch: AppDispatch, getState: AppGetState) => withRunExclusive(async () => { - const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, chainId, currentBlockParams } = getState().gas; + const { defaultGasLimit, gasLimit, gasFeeParamsBySpeed, selectedGasFee, txNetwork, currentBlockParams } = getState().gas; const { nativeCurrency } = getState().settings; - if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObject({ chainId }).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { + if (isEmpty(gasFeeParamsBySpeed) || (getNetworkObj(txNetwork).gas?.OptimismTxFee && l1GasFeeOptimism === null)) { // if fee prices not ready, we need to store the gas limit for future calculations // the rest is as the initial state value if (updatedGasLimit) { @@ -668,7 +675,7 @@ export const gasUpdateTxFee = } } else { const _selectedGasFeeOption = overrideGasOption || selectedGasFee.option || NORMAL; - const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(chainId, defaultGasLimit); + const _gasLimit = updatedGasLimit || gasLimit || getDefaultGasLimit(txNetwork, defaultGasLimit); const { selectedGasFee: updatedSelectedGasFee, gasFeesBySpeed } = getUpdatedGasFeeParams( currentBlockParams?.baseFeePerGas, @@ -676,7 +683,7 @@ export const gasUpdateTxFee = _gasLimit, nativeCurrency, _selectedGasFeeOption, - chainId, + txNetwork, l1GasFeeOptimism ); dispatch({ @@ -709,7 +716,7 @@ const INITIAL_STATE: GasState = { gasLimit: null, l1GasFeeOptimism: null, selectedGasFee: {} as SelectedGasFee, - chainId: ChainId.mainnet, + txNetwork: null, secondsPerNewBlock: 15, }; @@ -764,7 +771,7 @@ export default (state = INITIAL_STATE, action: { type: string; payload: any }) = case GAS_UPDATE_TRANSACTION_NETWORK: return { ...state, - chainId: action.payload, + txNetwork: action.payload, }; case GAS_PRICES_RESET: return INITIAL_STATE; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index a15206fbace..d583e40cae1 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -5,8 +5,10 @@ import { maybeSignUri } from '@/handlers/imgix'; import { getLocalRequests, removeLocalRequest, saveLocalRequests } from '@/handlers/localstorage/walletconnectRequests'; import { omitFlatten } from '@/helpers/utilities'; import { getRequestDisplayDetails } from '@/parsers'; -import { logger } from '@/logger'; -import { ChainId } from '@/networks/types'; +import { ethereumUtils } from '@/utils'; +import logger from '@/utils/logger'; +import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; // -- Constants --------------------------------------- // @@ -170,7 +172,7 @@ export const addRequestToApprove = // by an invalid access might be caught or expected elsewhere, so for now // `ts-expect-error` is used. if (displayDetails.timestampInMs < oneHourAgoTs) { - logger.debug(`[redux/requests]: [${requestId}] request expired!`); + logger.log('request expired!'); return; } const unsafeImageUrl = peerMeta?.icons?.[0]; diff --git a/src/redux/settings.ts b/src/redux/settings.ts index 844d28b1348..ab3e0a2d4aa 100644 --- a/src/redux/settings.ts +++ b/src/redux/settings.ts @@ -10,23 +10,24 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { getAppIcon, - getChainId, getFlashbotsEnabled, getLanguage, getNativeCurrency, + getNetwork, getTestnetsEnabled, saveAppIcon, - saveChainId, saveFlashbotsEnabled, saveLanguage, saveNativeCurrency, + saveNetwork, saveTestnetsEnabled, } from '@/handlers/localstorage/globalSettings'; import { web3SetHttpProvider } from '@/handlers/web3'; +import { Network } from '@/helpers/networkTypes'; import { explorerClearState, explorerInit } from '@/redux/explorer'; import { AppState } from '@/redux/store'; -import { logger, RainbowError } from '@/logger'; -import { Network, ChainId } from '@/networks/types'; +import { ethereumUtils } from '@/utils'; +import logger from '@/utils/logger'; // -- Constants ------------------------------------------------------------- // const SETTINGS_UPDATE_SETTINGS_ADDRESS = 'settings/SETTINGS_UPDATE_SETTINGS_ADDRESS'; @@ -104,6 +105,7 @@ interface SettingsStateUpdateNetworkSuccessAction { type: typeof SETTINGS_UPDATE_NETWORK_SUCCESS; payload: { chainId: SettingsState['chainId']; + network: SettingsState['network']; }; } @@ -143,20 +145,21 @@ export const settingsLoadState = type: SETTINGS_UPDATE_ACCOUNT_SETTINGS_SUCCESS, }); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error loading native currency and testnets pref: ${error}`)); + logger.log('Error loading native currency and testnets pref', error); } }; export const settingsLoadNetwork = () => async (dispatch: Dispatch) => { try { - const chainId = await getChainId(); - await web3SetHttpProvider(chainId); + const network = await getNetwork(); + const chainId = ethereumUtils.getChainIdFromNetwork(network); + await web3SetHttpProvider(network); dispatch({ - payload: { chainId }, + payload: { chainId, network }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error loading network settings: ${error}`)); + logger.log('Error loading network settings', error); } }; @@ -172,7 +175,7 @@ export const settingsLoadLanguage = () => async (dispatch: Dispatch (dispatch: Dispatch) => { const callback = async () => { - logger.debug(`[redux/settings]: changing app icon to ${appIcon}`); + logger.log('changing app icon to', appIcon); try { await changeIcon(appIcon); - logger.debug(`[redux/settings]: icon changed to ${appIcon}`); + logger.log('icon changed to ', appIcon); saveAppIcon(appIcon); dispatch({ payload: appIcon, type: SETTINGS_UPDATE_APP_ICON_SUCCESS, }); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error changing app icon: ${error}`)); + logger.log('Error changing app icon', error); } }; @@ -234,16 +237,17 @@ export const settingsUpdateAccountAddress = }); }; -export const settingsUpdateNetwork = (chainId: ChainId) => async (dispatch: Dispatch) => { - await web3SetHttpProvider(chainId); +export const settingsUpdateNetwork = (network: Network) => async (dispatch: Dispatch) => { + const chainId = ethereumUtils.getChainIdFromNetwork(network); + await web3SetHttpProvider(network); try { dispatch({ - payload: { chainId }, + payload: { chainId, network }, type: SETTINGS_UPDATE_NETWORK_SUCCESS, }); - saveChainId(chainId); + saveNetwork(network); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error updating network settings: ${error}`)); + logger.log('Error updating network settings', error); } }; @@ -257,7 +261,7 @@ export const settingsChangeLanguage = (language: Language) => async (dispatch: D saveLanguage(language); analytics.identify({ language }); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error changing language: ${error}`)); + logger.log('Error changing language', error); } }; @@ -274,7 +278,7 @@ export const settingsChangeNativeCurrency = saveNativeCurrency(nativeCurrency); analytics.identify({ currency: nativeCurrency }); } catch (error) { - logger.error(new RainbowError(`[redux/settings]: Error changing native currency: ${error}`)); + logger.log('Error changing native currency', error); } }; @@ -311,6 +315,7 @@ export default (state = INITIAL_STATE, action: SettingsStateUpdateAction) => { return { ...state, chainId: action.payload.chainId, + network: action.payload.network, }; case SETTINGS_UPDATE_LANGUAGE_SUCCESS: return { diff --git a/src/redux/showcaseTokens.ts b/src/redux/showcaseTokens.ts index 23252088f9f..d0d1d934ab5 100644 --- a/src/redux/showcaseTokens.ts +++ b/src/redux/showcaseTokens.ts @@ -4,8 +4,8 @@ import { Dispatch } from 'redux'; import { getPreference } from '../model/preferences'; import { AppGetState } from './store'; import { getShowcaseTokens, getWebDataEnabled, saveShowcaseTokens, saveWebDataEnabled } from '@/handlers/localstorage/accountLocal'; +import networkTypes from '@/helpers/networkTypes'; import WalletTypes from '@/helpers/walletTypes'; -import { Network } from '@/networks/types'; // -- Constants --------------------------------------- // @@ -204,7 +204,7 @@ export const removeShowcaseToken = (tokenId: string) => (dispatch: Dispatch + (enabled: boolean, address: string, network = networkTypes.mainnet) => async (dispatch: Dispatch) => { dispatch({ payload: enabled, diff --git a/src/redux/swap.ts b/src/redux/swap.ts index 0ca1da8addd..10bf5a81080 100644 --- a/src/redux/swap.ts +++ b/src/redux/swap.ts @@ -109,7 +109,7 @@ export const updateSwapInputCurrency = dispatch({ payload: newInputCurrency, type: SWAP_UPDATE_INPUT_CURRENCY }); if ( type === ExchangeModalTypes.swap && - newInputCurrency?.chainId !== outputCurrency?.chainId && + newInputCurrency?.network !== outputCurrency?.network && newInputCurrency && !ignoreTypeCheck ) { @@ -131,7 +131,7 @@ export const updateSwapOutputCurrency = } else { if ( type === ExchangeModalTypes.swap && - newOutputCurrency?.chainId !== inputCurrency?.chainId && + newOutputCurrency?.network !== inputCurrency?.network && newOutputCurrency && !ignoreTypeCheck ) { diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index f75a286e044..564d2893e5e 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -25,15 +25,13 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { convertHexToString, delay, omitBy, pickBy } from '@/helpers/utilities'; import WalletConnectApprovalSheetType from '@/helpers/walletConnectApprovalSheetTypes'; import Routes from '@/navigation/routesNames'; -import { watchingAlert } from '@/utils'; +import { ethereumUtils, watchingAlert } from '@/utils'; import { getFCMToken } from '@/notifications/tokens'; import { logger, RainbowError } from '@/logger'; import { IS_DEV, IS_IOS, IS_TEST } from '@/env'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { Verify } from '@walletconnect/types'; import { RequestSource, handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; -import { ChainId } from '@/networks/types'; -import { Address } from 'viem'; // -- Variables --------------------------------------- // let showRedirectSheetThreshold = 300; @@ -134,8 +132,8 @@ export type WalletconnectResultType = 'timedOut' | 'sign' | 'transaction' | 'sig export interface WalletconnectApprovalSheetRouteParams { callback: ( approved: boolean, - chainId: ChainId, - accountAddress: Address, + chainId: number, + accountAddress: string, peerId: WalletconnectRequestData['peerId'], dappScheme: WalletconnectRequestData['dappScheme'], dappName: WalletconnectRequestData['dappName'], @@ -191,7 +189,7 @@ const getNativeOptions = async () => { const token = await getFCMToken(); if (!token && !IS_DEV) { - logger.error(new RainbowError(`[redux/walletconnect]: FCM token not found, push notifications will not be received`)); + logger.error(new RainbowError(`WC: FCM token not found, push notifications will not be received`)); } const nativeOptions = { @@ -355,7 +353,7 @@ export const walletConnectOnSessionRequest = error, payload, }); - logger.error(new RainbowError('[redux/walletconnect]: Error on wc session_request'), { + logger.error(new RainbowError('WC: Error on wc session_request'), { error, payload, }); @@ -426,7 +424,7 @@ export const walletConnectOnSessionRequest = }, 2000); } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('[redux/walletconnect]: Exception during wc session_request'), { error }); + logger.error(new RainbowError('WC: Exception during wc session_request'), { error }); analytics.track('Exception on wc session_request', { error, }); @@ -434,7 +432,7 @@ export const walletConnectOnSessionRequest = } } catch (error: any) { clearTimeout(timeout!); - logger.error(new RainbowError('[redux/walletconnect]: FCM exception during wc session_request'), { error }); + logger.error(new RainbowError('WC: FCM exception during wc session_request'), { error }); analytics.track('FCM exception on wc session_request', { error, }); @@ -451,7 +449,7 @@ export const walletConnectOnSessionRequest = const listenOnNewMessages = (walletConnector: WalletConnect) => (dispatch: ThunkDispatch, getState: AppGetState) => { walletConnector.on('call_request', async (error, payload) => { - logger.debug('[redux/walletconnect]: Request!', { error, payload }, logger.DebugContext.walletconnect); + logger.debug('WC: Request!', { error, payload }, logger.DebugContext.walletconnect); if (error) { analytics.track('Error on wc call_request', { @@ -459,7 +457,7 @@ const listenOnNewMessages = error, payload, }); - logger.error(new RainbowError('[redux/walletconnect]: Error on wc call_request'), { + logger.error(new RainbowError('WC: Error on wc call_request'), { message: error, }); return; @@ -472,11 +470,11 @@ const listenOnNewMessages = const requestId = payload.id; if (payload.method === 'wallet_addEthereumChain' || payload.method === `wallet_switchEthereumChain`) { const { chainId } = payload.params[0]; - // @ts-expect-error "_chainId" is private. - const currentChainId = Number(walletConnector._chainId); - const supportedChains = RainbowNetworkObjects.filter(network => network.features.walletconnect).map(network => - network.id.toString() + const currentNetwork = ethereumUtils.getNetworkFromChainId( + // @ts-expect-error "_chainId" is private. + Number(walletConnector._chainId) ); + const supportedChains = RainbowNetworks.filter(network => network.features.walletconnect).map(network => network.id.toString()); const numericChainId = convertHexToString(chainId); if (supportedChains.includes(numericChainId)) { dispatch(walletConnectSetPendingRedirect()); @@ -488,7 +486,7 @@ const listenOnNewMessages = result: null, }); const { accountAddress } = getState().settings; - logger.debug('[redux/walletconnect]: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); + logger.debug('WC: Updating session for chainID', { numericChainId }, logger.DebugContext.walletconnect); await walletConnector.updateSession({ accounts: [accountAddress], // @ts-expect-error "numericChainId" is a string, not a number. @@ -513,7 +511,7 @@ const listenOnNewMessages = }); } }, - currentChainId, + currentNetwork, meta: { chainIds: [Number(numericChainId)], dappName, @@ -523,7 +521,7 @@ const listenOnNewMessages = type: WalletConnectApprovalSheetType.switch_chain, }); } else { - logger.warn(`[redux/walletconnect]: Unsupported chain ${numericChainId}`); + logger.info('WC: NOT SUPPORTED CHAIN'); walletConnector.rejectRequest({ error: { message: 'Chain currently not supported' }, id: requestId, @@ -575,7 +573,7 @@ const listenOnNewMessages = }); walletConnector.on('disconnect', error => { if (error) { - logger.error(new RainbowError('[redux/walletconnect]: Error on wc disconnect'), { + logger.error(new RainbowError('WC: Error on wc disconnect'), { message: error, }); @@ -622,7 +620,7 @@ export const walletConnectLoadState = // @ts-ignore error, }); - logger.error(new RainbowError('[redux/walletconnect]: Error on wc walletConnectLoadState'), { + logger.error(new RainbowError('WC: Error on wc walletConnectLoadState'), { error, }); newWalletConnectors = {}; diff --git a/src/redux/wallets.ts b/src/redux/wallets.ts index deb49a5ea9b..519223b54c3 100644 --- a/src/redux/wallets.ts +++ b/src/redux/wallets.ts @@ -157,12 +157,12 @@ export const walletsLoadState = } else { keys(wallets).some(key => { const someWallet = wallets[key]; - const found = (someWallet.addresses || []).some(account => { + const found = someWallet.addresses.some(account => { return toChecksumAddress(account.address) === toChecksumAddress(address!); }); if (found) { selectedWallet = someWallet; - logger.debug('[redux/wallets]: Found selected wallet based on loadAddress result'); + logger.info('Found selected wallet based on loadAddress result'); } return found; }); @@ -172,7 +172,7 @@ export const walletsLoadState = // Recover from broken state (account address not in selected wallet) if (!addressFromKeychain) { addressFromKeychain = await loadAddress(); - logger.debug("[redux/wallets]: addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); + logger.info("addressFromKeychain wasn't set on settings so it is being loaded from loadAddress"); } const selectedAddress = selectedWallet?.addresses.find(a => { @@ -184,7 +184,7 @@ export const walletsLoadState = const allWallets = Object.values(allWalletsResult?.wallets || {}); let account = null; for (const wallet of allWallets) { - for (const rainbowAccount of wallet.addresses || []) { + for (const rainbowAccount of wallet.addresses) { if (rainbowAccount.visible) { account = rainbowAccount; break; @@ -194,7 +194,7 @@ export const walletsLoadState = if (!account) return; await dispatch(settingsUpdateAccountAddress(account.address)); await saveAddress(account.address); - logger.debug('[redux/wallets]: Selected the first visible address because there was not selected one'); + logger.info('Selected the first visible address because there was not selected one'); } const walletNames = await getWalletNames(); @@ -209,7 +209,7 @@ export const walletsLoadState = return wallets; } catch (error) { - logger.error(new RainbowError('[redux/wallets]: Exception during walletsLoadState'), { + logger.error(new RainbowError('Exception during walletsLoadState'), { message: (error as Error)?.message, }); } @@ -281,7 +281,7 @@ export const setAllWalletsWithIdsAsBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('[redux/wallets]: Saving multiple wallets UserData to cloud failed.'), { + logger.error(new RainbowError('Saving multiple wallets UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -325,7 +325,7 @@ export const setWalletBackedUp = try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('[redux/wallets]: Saving wallet UserData to cloud failed.'), { + logger.error(new RainbowError('Saving wallet UserData to cloud failed.'), { message: (e as Error)?.message, }); throw e; @@ -346,7 +346,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = try { currentUserData = await fetchUserDataFromCloud(); } catch (error) { - logger.error(new RainbowError('[redux/wallets]: There was an error when trying to update wallet backup statuses'), { + logger.error(new RainbowError('There was an error when trying to update wallet backup statuses'), { error: (error as Error).message, }); return; @@ -358,7 +358,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = // build hashmap of address to wallet based on backup metadata const addressToWalletLookup = new Map(); Object.values(currentUserData.wallets).forEach(wallet => { - wallet.addresses?.forEach(account => { + wallet.addresses.forEach(account => { addressToWalletLookup.set(account.address, wallet); }); }); @@ -376,7 +376,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = const localWalletId = wallet.id; let relatedCloudWalletId: string | null = null; - for (const account of wallet.addresses || []) { + for (const account of wallet.addresses) { const walletDataForCurrentAddress = addressToWalletLookup.get(account.address); if (!walletDataForCurrentAddress) { return; @@ -385,7 +385,7 @@ export const updateWalletBackupStatusesBasedOnCloudUserData = relatedCloudWalletId = walletDataForCurrentAddress.id; } else if (relatedCloudWalletId !== walletDataForCurrentAddress.id) { logger.warn( - '[redux/wallets]: Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' + 'Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.' ); return; } @@ -584,7 +584,7 @@ export const fetchWalletNames = () => async (dispatch: Dispatch { - const visibleAccounts = (wallet.addresses || []).filter(address => address.visible); + const visibleAccounts = wallet.addresses?.filter(address => address.visible); return visibleAccounts.map(async account => { try { const ens = await fetchReverseRecordWithRetry(account.address); @@ -612,37 +612,37 @@ export const fetchWalletNames = () => async (dispatch: Dispatch async (dispatch: ThunkDispatch, getState: AppGetState) => { try { let healthyKeychain = true; - logger.debug('[redux/wallets]: Starting keychain integrity checks'); + logger.info('[KeychainIntegrityCheck]: starting checks'); const hasAddress = await hasKey(addressKey); if (hasAddress) { - logger.debug('[redux/wallets]: address is ok'); + logger.info('[KeychainIntegrityCheck]: address is ok'); } else { healthyKeychain = false; - logger.debug(`[redux/wallets]: address is missing: ${hasAddress}`); + logger.info(`[KeychainIntegrityCheck]: address is missing: ${hasAddress}`); } const hasOldSeedPhraseMigratedFlag = await hasKey(oldSeedPhraseMigratedKey); if (hasOldSeedPhraseMigratedFlag) { - logger.debug('[redux/wallets]: migrated flag is OK'); + logger.info('[KeychainIntegrityCheck]: migrated flag is OK'); } else { - logger.debug(`[redux/wallets]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); + logger.info(`[KeychainIntegrityCheck]: migrated flag is present: ${hasOldSeedPhraseMigratedFlag}`); } const hasOldSeedphrase = await hasKey(seedPhraseKey); if (hasOldSeedphrase) { - logger.debug('[redux/wallets]: old seed is still present!'); + logger.info('[KeychainIntegrityCheck]: old seed is still present!'); } else { - logger.debug(`[redux/wallets]: old seed is present: ${hasOldSeedphrase}`); + logger.info(`[KeychainIntegrityCheck]: old seed is present: ${hasOldSeedphrase}`); } const { wallets, selected } = getState().wallets; if (!wallets) { - logger.warn('[redux/wallets]: wallets are missing from redux'); + logger.warn('[KeychainIntegrityCheck]: wallets are missing from redux'); } if (!selected) { - logger.warn('[redux/wallets]: selectedWallet is missing from redux'); + logger.warn('[KeychainIntegrityCheck]: selectedWallet is missing from redux'); } const nonReadOnlyWalletKeys = keys(wallets).filter(key => wallets![key].type !== WalletTypes.readOnly); @@ -654,18 +654,18 @@ export const checkKeychainIntegrity = () => async (dispatch: ThunkDispatch async (dispatch: ThunkDispatch { - const mainnetChains: Chain[] = [mainnet, base, optimism, arbitrum, polygon, zora, blast, degen, avalanche, bsc]; +export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolean }): Chain[] => + [ + // In default order of appearance + mainnet, + base, + optimism, + arbitrum, + polygon, + zora, + blast, + degen, + avalanche, + bsc, - const testnetChains: Chain[] = [ + // Testnets goerli, holesky, sepolia, @@ -243,12 +255,12 @@ export const SUPPORTED_CHAINS = ({ testnetMode = false }: { testnetMode?: boolea zoraSepolia, avalancheFuji, bscTestnet, - ]; - - const allChains = mainnetChains.concat(testnetMode ? testnetChains : []); - - return allChains.map(chain => ({ ...chain, name: ChainNameDisplay[chain.id] ?? chain.name })); -}; + ].reduce((chainList, chain) => { + if (testnetMode || !chain.testnet) { + chainList.push({ ...chain, name: ChainNameDisplay[chain.id] }); + } + return chainList; + }, [] as Chain[]); export const SUPPORTED_CHAIN_IDS = ({ testnetMode = false }: { testnetMode?: boolean }): ChainId[] => SUPPORTED_CHAINS({ testnetMode }).map(chain => chain.id); diff --git a/src/references/rainbow-token-list/index.ts b/src/references/rainbow-token-list/index.ts index 688e07eef13..845443a6cc3 100644 --- a/src/references/rainbow-token-list/index.ts +++ b/src/references/rainbow-token-list/index.ts @@ -1,12 +1,14 @@ import { EventEmitter } from 'events'; +import { captureException } from '@sentry/react-native'; import { keyBy } from 'lodash'; import { MMKV } from 'react-native-mmkv'; import { ETH_ADDRESS } from '../index'; import RAINBOW_TOKEN_LIST_DATA from './rainbow-token-list.json'; import { RainbowToken } from '@/entities'; import { STORAGE_IDS } from '@/model/mmkv'; -import { logger, RainbowError } from '@/logger'; -import { Network, ChainId } from '@/networks/types'; +import logger from '@/utils/logger'; +import { Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const rainbowListStorage = new MMKV({ id: STORAGE_IDS.RAINBOW_TOKEN_LIST, @@ -77,7 +79,10 @@ function readJson(key: string): T | null { return JSON.parse(data); } catch (error) { - logger.error(new RainbowError(`[rainbow-token-list]: Error parsing token-list-cache data: ${error}`)); + logger.sentry('Error parsing token-list-cache data'); + logger.error(error); + captureException(error); + return null; } } @@ -86,7 +91,9 @@ function writeJson(key: string, data: T) { try { rainbowListStorage.set(key, JSON.stringify(data)); } catch (error) { - logger.error(new RainbowError(`[rainbow-token-list]: Error saving ${key}: ${error}`)); + logger.sentry(`Token List: Error saving ${key}`); + logger.error(error); + captureException(error); } } @@ -108,7 +115,7 @@ class RainbowTokenList extends EventEmitter { } } - logger.debug('[rainbow-token-list]: Token list initialized'); + logger.debug('Token list initialized'); } // Wrapping #tokenListDataStorage so we can add events around updates. @@ -120,7 +127,7 @@ class RainbowTokenList extends EventEmitter { this.#tokenListDataStorage = val; this.#derivedData = generateDerivedData(RAINBOW_TOKEN_LIST_DATA); this.emit('update'); - logger.debug('[rainbow-token-list]: Token list data replaced'); + logger.debug('Token list data replaced'); } get CURATED_TOKENS() { diff --git a/src/references/shitcoins.ts b/src/references/shitcoins.ts index dec188eb98c..a3ba26b1621 100644 --- a/src/references/shitcoins.ts +++ b/src/references/shitcoins.ts @@ -1,7 +1,7 @@ export default [ '0xe233a118042ca180570bb450cceecc8f46c23710', // PolkaCover (fake) '0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e', // Kick token - '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', // 启动公链 + '0xc30951ff31c04a47b26ce496b0510a3b2d709e92', //启动公链 '0xf222ba8af81d799c565241b0d3eedf9bdc4fc462', // betbeb.com空投1万个 '0xdbadabe39b91f2069e27291add14a1d95e3ff54f', // betbeb.com 挖矿每天 '0xc92e74b131d7b1d46e60e07f3fae5d8877dd03f0', // Minereum diff --git a/src/references/testnet-assets-by-chain.ts b/src/references/testnet-assets-by-chain.ts deleted file mode 100644 index 8bfb3c1d61f..00000000000 --- a/src/references/testnet-assets-by-chain.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; -import { ChainId, ChainName } from '@/networks/types'; - -type ChainAssets = { - [uniqueId: UniqueId]: { - asset: ZerionAsset; - quantity: string; - }; -}; - -// NOTE: Don't import `ETH_ADDRESS` as it's resolving to undefined... -export const chainAssets: Partial> = { - [ChainId.goerli]: { - eth_5: { - asset: { - asset_code: 'eth', - mainnet_address: 'eth', - colors: { - fallback: '#E8EAF5', - primary: '#808088', - }, - implementations: {}, - bridging: { - bridgeable: true, - networks: {}, // TODO: Add bridgeable networks - }, - decimals: 18, - icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', - name: 'Goerli', - network: ChainName.goerli, - price: { - relative_change_24h: -4.586615622469276, - value: 2590.2, - }, - symbol: 'ETH', - }, - quantity: '0', - }, - }, - [ChainId.mainnet]: { - eth_1: { - asset: { - asset_code: 'eth', - mainnet_address: 'eth', - colors: { - fallback: '#E8EAF5', - primary: '#808088', - }, - decimals: 18, - icon_url: 'https://s3.amazonaws.com/icons.assets/ETH.png', - name: 'Ethereum', - network: ChainName.mainnet, - implementations: {}, - bridging: { - bridgeable: true, - networks: {}, // TODO: Add bridgeable networks - }, - price: { - relative_change_24h: -4.586615622469276, - value: 2590.2, - }, - symbol: 'ETH', - }, - quantity: '0', - }, - }, -}; - -export default chainAssets; diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts index 47db136d5e2..5f2ea008d0e 100644 --- a/src/resources/assets/UserAssetsQuery.ts +++ b/src/resources/assets/UserAssetsQuery.ts @@ -2,16 +2,15 @@ import isEmpty from 'lodash/isEmpty'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { NativeCurrencyKey } from '@/entities'; import { saveAccountEmptyState } from '@/handlers/localstorage/accountLocal'; +import { Network } from '@/helpers/networkTypes'; import { greaterThan } from '@/helpers/utilities'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { createQueryKey, queryClient, QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { useQuery } from '@tanstack/react-query'; import { filterPositionsData, parseAddressAsset } from './assets'; import { fetchHardhatBalances } from './hardhatAssets'; import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types'; -import { Network } from '@/networks/types'; -import { staleBalancesStore } from '@/state/staleBalances'; // /////////////////////////////////////////////// // Query Types @@ -33,26 +32,15 @@ type UserAssetsQueryKey = ReturnType; // /////////////////////////////////////////////// // Query Function -const fetchUserAssetsForChainIds = async ({ - address, - currency, - chainIds, - staleBalanceParam, -}: { - address: string; - currency: NativeCurrencyKey; - chainIds: number[]; - staleBalanceParam?: string; -}) => { +const fetchUserAssetsForChainIds = async (address: string, currency: NativeCurrencyKey, chainIds: number[]) => { const chainIdsString = chainIds.join(','); - let url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets?currency=${currency.toLowerCase()}`; - - if (staleBalanceParam) { - url += url + staleBalanceParam; - } + const url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets`; const response = await rainbowFetch(url, { method: 'get', + params: { + currency: currency.toLowerCase(), + }, headers: { Authorization: `Bearer ${ADDYS_API_KEY}`, }, @@ -74,14 +62,9 @@ async function userAssetsQueryFunction({ } try { - const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( - network => network.id - ); + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); - staleBalancesStore.getState().clearExpiredData(address); - const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address); - - const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); + const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds(address, currency, chainIds); let parsedSuccessResults = results; // grab cached data for chain IDs with errors @@ -117,7 +100,7 @@ const retryErroredChainIds = async ( connectedToHardhat: boolean, erroredChainIds: number[] ) => { - const { meta, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds: erroredChainIds }); + const { meta, results } = await fetchAndParseUserAssetsForChainIds(address, currency, erroredChainIds); let parsedSuccessResults = results; const successChainIds = meta?.chain_ids; @@ -157,18 +140,12 @@ interface AssetsAndMetadata { results: RainbowAddressAssets; } -const fetchAndParseUserAssetsForChainIds = async ({ - address, - currency, - chainIds, - staleBalanceParam, -}: { - address: string; - currency: NativeCurrencyKey; - chainIds: number[]; - staleBalanceParam?: string; -}): Promise => { - const data = await fetchUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam }); +const fetchAndParseUserAssetsForChainIds = async ( + address: string, + currency: NativeCurrencyKey, + chainIds: number[] +): Promise => { + const data = await fetchUserAssetsForChainIds(address, currency, chainIds); let parsedSuccessResults = parseUserAssetsByChain(data); // filter out positions data diff --git a/src/resources/assets/assetSelectors.ts b/src/resources/assets/assetSelectors.ts index 41d6d2ba9e5..c5fb9bf467b 100644 --- a/src/resources/assets/assetSelectors.ts +++ b/src/resources/assets/assetSelectors.ts @@ -1,4 +1,4 @@ -import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; +import { ParsedAddressAsset } from '@/entities'; import { parseAssetsNative } from '@/parsers'; import isEmpty from 'lodash/isEmpty'; import isNil from 'lodash/isNil'; @@ -12,13 +12,13 @@ export function selectUserAssetWithUniqueId(uniqueId: string) { }; } -export function selectSortedUserAssets(nativeCurrency: NativeCurrencyKey) { +export function selectSortedUserAssets(nativeCurrency: string) { return (accountAssets: RainbowAddressAssets) => { return sortAssetsByNativeAmount(accountAssets, nativeCurrency); }; } -const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: NativeCurrencyKey): ParsedAddressAsset[] => { +const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: string): ParsedAddressAsset[] => { let assetsNativePrices = Object.values(accountAssets); if (!isEmpty(assetsNativePrices)) { diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index 37cee5c850b..48cf161d614 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -3,6 +3,7 @@ import isEmpty from 'lodash/isEmpty'; import { MMKV } from 'react-native-mmkv'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; +import { Network } from '@/helpers/networkTypes'; import { convertRawAmountToBalance } from '@/helpers/utilities'; import { BooleanMap } from '@/hooks/useCoinListEditOptions'; import { queryClient } from '@/react-query'; @@ -13,10 +14,11 @@ import { RainbowPositions } from '@/resources/defi/types'; import { ethereumUtils } from '@/utils'; import { AddysAddressAsset, AddysAsset, ParsedAsset, RainbowAddressAssets } from './types'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { ChainId } from '@/networks/types'; const storage = new MMKV(); +const MAINNET_CHAIN_ID = ethereumUtils.getChainIdFromNetwork(Network.mainnet); + export const filterPositionsData = ( address: string, currency: NativeCurrencyKey, @@ -41,7 +43,7 @@ export const filterPositionsData = ( export function parseAsset({ address, asset }: { address: string; asset: AddysAsset }): ParsedAsset { const network = asset?.network; const chainId = ethereumUtils.getChainIdFromNetwork(network); - const mainnetAddress = asset?.networks?.[ChainId.mainnet]?.address; + const mainnetAddress = asset?.networks?.[MAINNET_CHAIN_ID]?.address; const uniqueId = getUniqueId(address, chainId); const parsedAsset = { @@ -72,7 +74,6 @@ export function parseAddressAsset({ assetData }: { assetData: AddysAddressAsset const asset = assetData?.asset; const quantity = assetData?.quantity; const address = assetData?.asset?.asset_code; - const parsedAsset = parseAsset({ address, asset, diff --git a/src/resources/assets/externalAssetsQuery.ts b/src/resources/assets/externalAssetsQuery.ts index de29cf9d322..0092abdeebc 100644 --- a/src/resources/assets/externalAssetsQuery.ts +++ b/src/resources/assets/externalAssetsQuery.ts @@ -4,9 +4,8 @@ import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunct import { convertAmountAndPriceToNativeDisplay, convertAmountToPercentageDisplay } from '@/helpers/utilities'; import { NativeCurrencyKey } from '@/entities'; import { Token } from '@/graphql/__generated__/metadata'; -import { ChainId } from '@/networks/types'; -import { isNativeAsset } from '@/__swaps__/utils/chains'; -import { AddressOrEth } from '@/__swaps__/types/assets'; +import { ethereumUtils } from '@/utils'; +import { ChainId } from '@/__swaps__/types/chains'; export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute @@ -20,9 +19,7 @@ export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute // Types type ExternalToken = Pick; export type FormattedExternalAsset = ExternalToken & { - address: string; icon_url?: string; - isNativeAsset: boolean; native: { change: string; price: { @@ -46,16 +43,9 @@ export const externalTokenQueryKey = ({ address, chainId, currency }: ExternalTo type externalTokenQueryKey = ReturnType; // Helpers -const formatExternalAsset = ( - address: string, - chainId: ChainId, - asset: ExternalToken, - nativeCurrency: NativeCurrencyKey -): FormattedExternalAsset => { +const formatExternalAsset = (asset: ExternalToken, nativeCurrency: NativeCurrencyKey): FormattedExternalAsset => { return { ...asset, - address, - isNativeAsset: isNativeAsset(address as AddressOrEth, chainId), native: { change: asset?.price?.relativeChange24h ? convertAmountToPercentageDisplay(`${asset?.price?.relativeChange24h}`) : '', price: convertAmountAndPriceToNativeDisplay(1, asset?.price?.value || 0, nativeCurrency), @@ -72,7 +62,7 @@ export async function fetchExternalToken({ address, chainId, currency }: Externa currency, }); if (response.token) { - return formatExternalAsset(address, chainId, response.token, currency); + return formatExternalAsset(response.token, currency); } else { return null; } diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/hardhatAssets.ts index 606dad3c8b3..697900446c8 100644 --- a/src/resources/assets/hardhatAssets.ts +++ b/src/resources/assets/hardhatAssets.ts @@ -1,24 +1,22 @@ import { Contract } from '@ethersproject/contracts'; +import { captureException } from '@sentry/react-native'; import { keyBy, mapValues } from 'lodash'; -import { getProvider } from '@/handlers/web3'; -import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS, SUPPORTED_CHAIN_IDS } from '@/references'; +import { Network } from '@/helpers/networkTypes'; +import { web3Provider } from '@/handlers/web3'; // TODO JIN +import { getNetworkObj } from '@/networks'; +import { balanceCheckerContractAbi, chainAssets, ETH_ADDRESS } from '@/references'; import { parseAddressAsset } from './assets'; import { RainbowAddressAssets } from './types'; -import { logger, RainbowError } from '@/logger'; -import { AddressOrEth, UniqueId, ZerionAsset } from '@/__swaps__/types/assets'; -import { AddressZero } from '@ethersproject/constants'; -import chainAssetsByChainId from '@/references/testnet-assets-by-chain'; -import { getNetworkObject } from '@/networks'; -import { ChainId, ChainName, Network } from '@/networks/types'; +import logger from '@/utils/logger'; + +const ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT = '0x0000000000000000000000000000000000000000'; const fetchHardhatBalancesWithBalanceChecker = async ( tokens: string[], address: string, - chainId: ChainId = ChainId.mainnet + network: Network = Network.mainnet ): Promise<{ [tokenAddress: string]: string } | null> => { - const networkObject = getNetworkObject({ chainId }); - const provider = getProvider({ chainId }); - const balanceCheckerContract = new Contract(networkObject.balanceCheckerAddress, balanceCheckerContractAbi, provider); + const balanceCheckerContract = new Contract(getNetworkObj(network).balanceCheckerAddress, balanceCheckerContractAbi, web3Provider); try { const values = await balanceCheckerContract.balances([address], tokens); const balances: { @@ -26,33 +24,24 @@ const fetchHardhatBalancesWithBalanceChecker = async ( } = {}; tokens.forEach((tokenAddr, tokenIdx) => { const balance = values[tokenIdx]; - const assetCode = tokenAddr === AddressZero ? ETH_ADDRESS : tokenAddr; + const assetCode = tokenAddr === ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT ? ETH_ADDRESS : tokenAddr; balances[assetCode] = balance.toString(); }); return balances; } catch (e) { - logger.error(new RainbowError(`[hardhatAssets]: Error fetching balances from balanceCheckerContract: ${e}`)); + logger.sentry('Error fetching balances from balanceCheckerContract', network, e); + captureException(new Error('fallbackExplorer::balanceChecker failure')); return null; } }; -/** - * @deprecated - to be removed once rest of the app is converted to new userAssetsStore - * Fetches the balances of the hardhat assets for the given account address and network. - * @param accountAddress - The address of the account to fetch the balances for. - * @param network - The network to fetch the balances for. - * @returns The balances of the hardhat assets for the given account address and network. - */ -export const fetchHardhatBalances = async (accountAddress: string, chainId: ChainId = ChainId.mainnet): Promise => { - const chainAssetsMap = keyBy( - chainAssets[`${chainId}` as keyof typeof chainAssets], - ({ asset }) => `${asset.asset_code}_${asset.chainId}` - ); +export const fetchHardhatBalances = async (accountAddress: string, network: Network = Network.mainnet): Promise => { + const chainAssetsMap = keyBy(chainAssets[network as keyof typeof chainAssets], ({ asset }) => `${asset.asset_code}_${asset.network}`); const tokenAddresses = Object.values(chainAssetsMap).map(({ asset: { asset_code } }) => - asset_code === ETH_ADDRESS ? AddressZero : asset_code.toLowerCase() + asset_code === ETH_ADDRESS ? ETHEREUM_ADDRESS_FOR_BALANCE_CONTRACT : asset_code.toLowerCase() ); - const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); + const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, network); if (!balances) return {}; const updatedAssets = mapValues(chainAssetsMap, chainAsset => { @@ -68,62 +57,3 @@ export const fetchHardhatBalances = async (accountAddress: string, chainId: Chai }); return updatedAssets; }; - -export const fetchHardhatBalancesByChainId = async ( - accountAddress: string, - chainId: ChainId = ChainId.mainnet -): Promise<{ - assets: { - [uniqueId: UniqueId]: { - asset: ZerionAsset; - quantity: string; - }; - }; - chainIdsInResponse: ChainId[]; -}> => { - const chainAssetsMap = chainAssetsByChainId[`${chainId}` as keyof typeof chainAssets] || {}; - - const tokenAddresses = Object.values(chainAssetsMap).map(({ asset }) => - asset.asset_code === ETH_ADDRESS ? AddressZero : asset.asset_code.toLowerCase() - ); - - const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress, chainId); - if (!balances) - return { - assets: {}, - chainIdsInResponse: [], - }; - - const updatedAssets = Object.entries(chainAssetsMap).reduce( - (acc, [uniqueId, chainAsset]) => { - const assetCode = chainAsset.asset.asset_code || ETH_ADDRESS; - const quantity = balances[assetCode.toLowerCase()] || '0'; - - const asset: ZerionAsset = { - ...chainAsset.asset, - asset_code: assetCode as AddressOrEth, - mainnet_address: (chainAsset.asset.mainnet_address as AddressOrEth) || (assetCode as AddressOrEth), - network: (chainAsset.asset.network as ChainName) || ChainName.mainnet, - bridging: chainAsset.asset.bridging || { - bridgeable: false, - networks: {}, - }, - implementations: chainAsset.asset.implementations || {}, - name: chainAsset.asset.name || 'Unknown Token', - symbol: chainAsset.asset.symbol || 'UNKNOWN', - decimals: chainAsset.asset.decimals || 18, - icon_url: chainAsset.asset.icon_url || '', - price: chainAsset.asset.price || { value: 0, relative_change_24h: 0 }, - }; - - acc[uniqueId] = { asset, quantity }; - return acc; - }, - {} as { [uniqueId: UniqueId]: { asset: ZerionAsset; quantity: string } } - ); - - return { - assets: updatedAssets, - chainIdsInResponse: SUPPORTED_CHAIN_IDS({ testnetMode: true }), - }; -}; diff --git a/src/resources/assets/types.ts b/src/resources/assets/types.ts index a056e695b91..39a5f41ea6d 100644 --- a/src/resources/assets/types.ts +++ b/src/resources/assets/types.ts @@ -1,6 +1,6 @@ +import { Network } from '@/helpers/networkTypes'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { TokenColors } from '@/graphql/__generated__/metadata'; -import { Network } from '@/networks/types'; export type AddysAccountAssetsResponse = { meta: AddysAccountAssetsMeta; @@ -54,7 +54,7 @@ export interface ParsedAsset { address: string; color?: string; colors?: TokenColors; - chainId: number; + chainId?: number; chainName?: string; decimals: number; icon_url?: string; diff --git a/src/resources/assets/useSortedUserAssets.ts b/src/resources/assets/useSortedUserAssets.ts index b7e80d92c71..96cd2e91490 100644 --- a/src/resources/assets/useSortedUserAssets.ts +++ b/src/resources/assets/useSortedUserAssets.ts @@ -1,11 +1,11 @@ +import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { selectSortedUserAssets } from '@/resources/assets/assetSelectors'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export function useSortedUserAssets() { const { accountAddress, nativeCurrency } = useAccountSettings(); - const { connectedToHardhat } = useConnectedToHardhatStore(); + const connectedToHardhat = getIsHardhatConnected(); return useUserAssets( { diff --git a/src/resources/assets/useUserAsset.ts b/src/resources/assets/useUserAsset.ts index bad1a672d05..b94d22c15ec 100644 --- a/src/resources/assets/useUserAsset.ts +++ b/src/resources/assets/useUserAsset.ts @@ -1,14 +1,14 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; +import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { getNetworkObject } from '@/networks'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; import { selectUserAssetWithUniqueId } from '@/resources/assets/assetSelectors'; import { getUniqueId } from '@/utils/ethereumUtils'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; export function useUserAsset(uniqueId: string) { const { accountAddress, nativeCurrency } = useAccountSettings(); - const { connectedToHardhat } = useConnectedToHardhatStore(); + const connectedToHardhat = getIsHardhatConnected(); return useUserAssets( { diff --git a/src/resources/assets/useUserAssetCount.ts b/src/resources/assets/useUserAssetCount.ts index 7cf00ced409..bd414a8c651 100644 --- a/src/resources/assets/useUserAssetCount.ts +++ b/src/resources/assets/useUserAssetCount.ts @@ -1,13 +1,13 @@ +import { getIsHardhatConnected } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; import { useUserAssets } from '@/resources/assets/UserAssetsQuery'; import { RainbowAddressAssets } from './types'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const countSelector = (accountAssets: RainbowAddressAssets) => accountAssets?.length; export function useUserAssetCount() { const { accountAddress, nativeCurrency } = useAccountSettings(); - const { connectedToHardhat } = useConnectedToHardhatStore(); + const connectedToHardhat = getIsHardhatConnected(); return useUserAssets( { diff --git a/src/resources/defi/PositionsQuery.ts b/src/resources/defi/PositionsQuery.ts index 124948a1c93..8579523d939 100644 --- a/src/resources/defi/PositionsQuery.ts +++ b/src/resources/defi/PositionsQuery.ts @@ -3,14 +3,14 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { NativeCurrencyKey } from '@/entities'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { AddysPositionsResponse, PositionsArgs } from './types'; import { parsePositions } from './utils'; export const buildPositionsUrl = (address: string) => { - const networkString = RainbowNetworkObjects.filter(network => network.enabled) + const networkString = RainbowNetworks.filter(network => network.enabled) .map(network => network.id) .join(','); return `https://addys.p.rainbow.me/v3/${networkString}/${address}/positions`; diff --git a/src/resources/ens/ensAddressQuery.ts b/src/resources/ens/ensAddressQuery.ts index a4110e5c5d1..f84bb000bb8 100644 --- a/src/resources/ens/ensAddressQuery.ts +++ b/src/resources/ens/ensAddressQuery.ts @@ -1,8 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { createQueryKey, queryClient, QueryFunctionArgs } from '@/react-query'; -import { getProvider } from '@/handlers/web3'; -import { ChainId } from '@/networks/types'; +import { getProviderForNetwork } from '@/handlers/web3'; // Set a default stale time of 10 seconds so we don't over-fetch // (query will serve cached data & invalidate after 10s). @@ -24,7 +23,7 @@ const ensAddressQueryKey = ({ name }: ENSAddressArgs) => createQueryKey('ensAddr // Query Function async function ensAddressQueryFunction({ queryKey: [{ name }] }: QueryFunctionArgs) { - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(); const address = await provider.resolveName(name); return address; } diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index 6a3b918f42a..3bb6096f8aa 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -1,7 +1,8 @@ import { AddressOrEth, UniqueId } from '@/__swaps__/types/assets'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { getStandardizedUniqueIdWorklet } from '@/__swaps__/utils/swaps'; import { NativeCurrencyKeys, RainbowToken } from '@/entities'; +import { Network } from '@/networks/types'; import { createQueryKey, queryClient } from '@/react-query'; import { DAI_ADDRESS, ETH_ADDRESS, SOCKS_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS } from '@/references'; import { promiseUtils } from '@/utils'; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultById.ts b/src/resources/featuredResults/_selectors/getFeaturedResultById.ts deleted file mode 100644 index 7e4a79456ec..00000000000 --- a/src/resources/featuredResults/_selectors/getFeaturedResultById.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FeaturedResult } from '@/graphql/__generated__/arc'; -import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; - -export const getFeaturedResultById = (data: FeaturedResults, id: FeaturedResult['id']): FeaturedResult | undefined => { - return data.featuredResults.items?.find(item => item.id === id); -}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts deleted file mode 100644 index 429bc501e42..00000000000 --- a/src/resources/featuredResults/_selectors/getFeaturedResultIds.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; - -export const getFeaturedResultsById = (data: FeaturedResults) => { - return data.featuredResults.items?.map(item => item.id) ?? []; -}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts deleted file mode 100644 index a4e6a04ee67..00000000000 --- a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacement.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FeaturedResult } from '@/graphql/__generated__/arc'; -import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; - -export const getFeaturedResultsForPlacement = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { - return data.featuredResults.items?.filter(item => item.placementSlug === placement) ?? []; -}; diff --git a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts b/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts deleted file mode 100644 index 9ee209fab76..00000000000 --- a/src/resources/featuredResults/_selectors/getFeaturedResultsForPlacementWithIds.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FeaturedResult } from '@/graphql/__generated__/arc'; -import { FeaturedResults } from '@/resources/featuredResults/getFeaturedResults'; - -export const getFeaturedResultsForPlacementWithIds = (data: FeaturedResults, placement: FeaturedResult['placementSlug']) => { - return ( - data.featuredResults.items?.reduce((acc, item) => { - if (item.placementSlug === placement) { - acc.push(item.id); - } - return acc; - }, [] as string[]) ?? [] - ); -}; diff --git a/src/resources/featuredResults/getFeaturedResults.ts b/src/resources/featuredResults/getFeaturedResults.ts deleted file mode 100644 index 99e80c3ef33..00000000000 --- a/src/resources/featuredResults/getFeaturedResults.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; -import { useQuery } from '@tanstack/react-query'; -import { arcClient } from '@/graphql'; - -const defaultStaleTime = 60_000; // 1 minute -const defaultCacheTime = 1000 * 60 * 60 * 24; // 1 day - -export type FeaturedResultsVariables = Parameters['0']; -export type FeaturedResults = Awaited>; - -// /////////////////////////////////////////////// -// Query Key -export const featuredResultsQueryKey = (props: FeaturedResultsVariables) => - createQueryKey('featured-results', props, { persisterVersion: 1 }); - -export type FeaturedResultsQueryKey = ReturnType; - -// /////////////////////////////////////////////// -// Query Hook - -export function useFeaturedResults( - props: FeaturedResultsVariables, - config: QueryConfigWithSelect = {} -) { - return useQuery(featuredResultsQueryKey(props), () => arcClient.getFeaturedResults(props), { - ...config, - staleTime: defaultStaleTime, - cacheTime: defaultCacheTime, - }); -} diff --git a/src/resources/featuredResults/trackFeaturedResult.ts b/src/resources/featuredResults/trackFeaturedResult.ts deleted file mode 100644 index 094882b369b..00000000000 --- a/src/resources/featuredResults/trackFeaturedResult.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; -import { useMutation } from '@tanstack/react-query'; -import { arcPOSTClient } from '@/graphql'; - -export type TrackFeaturedResultVariables = Parameters['0']; -export type TrackFeaturedResultResult = Awaited>; - -// /////////////////////////////////////////////// -// Mutation Key -export const trackFeaturedResultMutationKey = (props: Partial) => - createQueryKey('track-featured-result', props, { persisterVersion: 1 }); - -export type TrackFeaturedResultMutationKey = ReturnType; - -// /////////////////////////////////////////////// -// Query Hook - -export function useTrackFeaturedResult( - props: Partial = {}, - config: QueryConfigWithSelect = {} -) { - return useMutation(trackFeaturedResultMutationKey(props), arcPOSTClient.trackFeaturedResult, { - ...config, - }); -} diff --git a/src/resources/metadata/dapps.tsx b/src/resources/metadata/dapps.tsx index feeee95dc99..0b7832d2fde 100644 --- a/src/resources/metadata/dapps.tsx +++ b/src/resources/metadata/dapps.tsx @@ -64,7 +64,7 @@ export function useDapps(config?: UseQueryOptions): { dapps: Dapp[] } { }, })); } catch (e: any) { - logger.error(new RainbowError('[dapps]: Failed to fetch dApps'), { message: e.message }); + logger.error(new RainbowError('Failed to fetch dApps'), { message: e.message }); return []; } }, diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index c7c92601830..c2f78124ffc 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,14 +1,14 @@ -import { QueryFunction, useQuery } from '@tanstack/react-query'; +import { UniqueAsset } from '@/entities'; +import { arcClient } from '@/graphql'; +import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; +import { Network } from '@/helpers'; import { QueryConfigWithSelect, createQueryKey, queryClient } from '@/react-query'; +import { AppState } from '@/redux/store'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; +import { QueryFunction, useQuery } from '@tanstack/react-query'; import { useSelector } from 'react-redux'; -import { AppState } from '@/redux/store'; -import { UniqueAsset } from '@/entities'; -import { arcClient } from '@/graphql'; -import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; import { createSelector } from 'reselect'; -import { ChainId } from '@/networks/types'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour @@ -31,12 +31,12 @@ export const invalidateAddressNftsQueries = (address: string) => { export const nftListingQueryKey = ({ contractAddress, tokenId, - chainId, + network, }: { contractAddress: string; tokenId: string; - chainId: Omit; -}) => createQueryKey('nftListing', { contractAddress, tokenId, chainId }); + network: Omit; +}) => createQueryKey('nftListing', { contractAddress, tokenId, network }); const walletsSelector = (state: AppState) => state.wallets?.wallets; @@ -48,7 +48,7 @@ const isImportedWalletSelector = createSelector( return false; } for (const wallet of Object.values(wallets)) { - if ((wallet.addresses || []).some(account => account.address === address)) { + if (wallet.addresses.some(account => account.address === address)) { return true; } } @@ -117,17 +117,17 @@ export function useLegacyNFTs({ export function useNFTListing({ contractAddress, tokenId, - chainId, + network, }: { contractAddress: string; tokenId: string; - chainId: Omit; + network: Omit; }) { return useQuery( - nftListingQueryKey({ contractAddress, tokenId, chainId }), - async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, chainId)) ?? null, + nftListingQueryKey({ contractAddress, tokenId, network }), + async () => (await fetchSimpleHashNFTListing(contractAddress, tokenId, network)) ?? null, { - enabled: !!chainId && !!contractAddress && !!tokenId, + enabled: !!network && !!contractAddress && !!tokenId, staleTime: 0, cacheTime: 0, } diff --git a/src/resources/nfts/simplehash/index.ts b/src/resources/nfts/simplehash/index.ts index 5d716846c47..4e39000052b 100644 --- a/src/resources/nfts/simplehash/index.ts +++ b/src/resources/nfts/simplehash/index.ts @@ -1,11 +1,11 @@ import { NFT_API_KEY, NFT_API_URL } from 'react-native-dotenv'; import { RainbowFetchClient } from '@/rainbow-fetch'; +import { Network } from '@/helpers'; import { SimpleHashListing, SimpleHashNFT, SimpleHashMarketplaceId } from '@/resources/nfts/simplehash/types'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj } from '@/networks'; import { UniqueAsset } from '@/entities'; import { RainbowError, logger } from '@/logger'; import { getGnosisNetworkObject } from '@/networks/gnosis'; -import { ChainId } from '@/networks/types'; export const START_CURSOR = 'start'; @@ -18,16 +18,16 @@ const createCursorSuffix = (cursor: string) => (cursor === START_CURSOR ? '' : ` export async function fetchSimpleHashNFT( contractAddress: string, tokenId: string, - chainId: Omit = ChainId.mainnet + network: Omit = Network.mainnet ): Promise { - const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; + const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; - if (!simplehashNetwork) { - logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); + if (!chain) { + logger.error(new RainbowError(`fetchSimpleHashNFT: no SimpleHash chain for network: ${network}`)); return; } - const response = await nftApi.get(`/nfts/${simplehashNetwork}/${contractAddress}/${tokenId}`, { + const response = await nftApi.get(`/nfts/${chain}/${contractAddress}/${tokenId}`, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', @@ -40,15 +40,15 @@ export async function fetchSimpleHashNFT( export async function fetchSimpleHashNFTListing( contractAddress: string, tokenId: string, - chainId: Omit = ChainId.mainnet + network: Omit = Network.mainnet ): Promise { // array of all eth listings on OpenSea for this token let listings: SimpleHashListing[] = []; let cursor = START_CURSOR; - const simplehashNetwork = getNetworkObject({ chainId: chainId as ChainId })?.nfts?.simplehashNetwork; + const chain = getNetworkObj(network as Network).nfts.simplehashNetwork; - if (!simplehashNetwork) { - logger.warn(`[simplehash]: no SimpleHash for chainId: ${chainId}`); + if (!chain) { + logger.error(new RainbowError(`fetchSimpleHashNFTListing: no SimpleHash chain for network: ${network}`)); return; } @@ -57,7 +57,7 @@ export async function fetchSimpleHashNFTListing( // eslint-disable-next-line no-await-in-loop const response = await nftApi.get( // OpenSea ETH offers only for now - `/nfts/listings/${simplehashNetwork}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, + `/nfts/listings/${chain}/${contractAddress}/${tokenId}?marketplaces=${SimpleHashMarketplaceId.OpenSea}${cursorSuffix}`, { headers: { 'Accept': 'application/json', @@ -84,16 +84,16 @@ export async function fetchSimpleHashNFTListing( * @param nft */ export async function refreshNFTContractMetadata(nft: UniqueAsset) { - const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; + const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; - if (!simplehashNetwork) { - logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); + if (!chain) { + logger.error(new RainbowError(`refreshNFTContractMetadata: no SimpleHash chain for network: ${nft.network}`)); return; } try { await nftApi.post( - `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}`, + `/nfts/refresh/${chain}/${nft.asset_contract.address}`, {}, { headers: { @@ -105,13 +105,13 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { ); } catch { logger.warn( - `[simplehash]: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` + `refreshNFTContractMetadata: failed to refresh metadata for NFT contract ${nft.asset_contract.address}, falling back to refreshing NFT #${nft.id}` ); try { // If the collection has > 20k NFTs, the above request will fail. // In that case, we need to refresh the given NFT individually. await nftApi.post( - `/nfts/refresh/${simplehashNetwork}/${nft.asset_contract.address}/${nft.id}`, + `/nfts/refresh/${chain}/${nft.asset_contract.address}/${nft.id}`, {}, { headers: { @@ -124,7 +124,7 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { } catch { logger.error( new RainbowError( - `[simplehash]: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` + `refreshNFTContractMetadata: failed to refresh metadata for NFT #${nft.id} after failing to refresh metadata for NFT contract ${nft.asset_contract.address}` ) ); } @@ -136,10 +136,10 @@ export async function refreshNFTContractMetadata(nft: UniqueAsset) { * @param nft */ export async function reportNFT(nft: UniqueAsset) { - const simplehashNetwork = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObject({ chainId: nft.chainId }))?.nfts?.simplehashNetwork; + const chain = (nft.isPoap ? getGnosisNetworkObject() : getNetworkObj(nft.network)).nfts.simplehashNetwork; - if (!simplehashNetwork) { - logger.warn(`[simplehash]: no SimpleHash for chainId: ${nft.chainId}`); + if (!chain) { + logger.error(new RainbowError(`reportNFT: no SimpleHash chain for network: ${nft.network}`)); return; } @@ -148,7 +148,7 @@ export async function reportNFT(nft: UniqueAsset) { '/nfts/report/spam', { contract_address: nft.asset_contract.address, - chain_id: simplehashNetwork, + chain_id: chain, token_id: nft.id, event_type: 'mark_as_spam', }, @@ -161,6 +161,6 @@ export async function reportNFT(nft: UniqueAsset) { } ); } catch { - logger.error(new RainbowError(`[simplehash]: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); + logger.error(new RainbowError(`reportNFT: failed to report NFT ${nft.asset_contract.address} #${nft.id} as spam to SimpleHash`)); } } diff --git a/src/resources/nfts/simplehash/types.ts b/src/resources/nfts/simplehash/types.ts index 7cc2c802cbe..a26cddcf86b 100644 --- a/src/resources/nfts/simplehash/types.ts +++ b/src/resources/nfts/simplehash/types.ts @@ -1,4 +1,4 @@ -import { Network } from '@/networks/types'; +import { Network } from '@/helpers'; /** * @see https://docs.simplehash.com/reference/sale-model diff --git a/src/resources/nfts/simplehash/utils.ts b/src/resources/nfts/simplehash/utils.ts index 8cf2c3e8ac6..68929492ca2 100644 --- a/src/resources/nfts/simplehash/utils.ts +++ b/src/resources/nfts/simplehash/utils.ts @@ -20,7 +20,7 @@ import { deviceUtils } from '@/utils'; import { TokenStandard } from '@/handlers/web3'; import { handleNFTImages } from '@/utils/handleNFTImages'; import { SimpleHashNft } from '@/graphql/__generated__/arc'; -import { Network, chainNameToIdMapping } from '@/networks/types'; +import { Network } from '@/helpers'; const ENS_COLLECTION_NAME = 'ENS'; const SVG_MIME_TYPE = 'image/svg+xml'; @@ -78,7 +78,6 @@ export function simpleHashNFTToUniqueAsset(nft: SimpleHashNft, address: string): slug: marketplace?.marketplace_collection_id ?? '', twitter_username: collection.twitter_username, }, - chainId: chainNameToIdMapping[nft.chain as keyof typeof chainNameToIdMapping], description: nft.description, external_link: nft.external_url, familyImage: collection.image_url, diff --git a/src/resources/nfts/types.ts b/src/resources/nfts/types.ts index 5f92ca09560..aafbe3f300a 100644 --- a/src/resources/nfts/types.ts +++ b/src/resources/nfts/types.ts @@ -1,5 +1,5 @@ +import { Network } from '@/helpers/networkTypes'; import { Asset, AssetContract, AssetType } from '@/entities'; -import { Network } from '@/networks/types'; import { UniqueTokenType } from '@/utils/uniqueTokens'; export enum NFTMarketplaceId { diff --git a/src/resources/nfts/utils.ts b/src/resources/nfts/utils.ts index 587bd19d742..8934f01eddc 100644 --- a/src/resources/nfts/utils.ts +++ b/src/resources/nfts/utils.ts @@ -3,9 +3,9 @@ import { gretch } from 'gretchen'; import { paths } from '@reservoir0x/reservoir-sdk'; import { RainbowError, logger } from '@/logger'; import { handleSignificantDecimals } from '@/helpers/utilities'; +import { Network } from '@/helpers'; import { IS_PROD } from '@/env'; import { RESERVOIR_API_KEY_DEV, RESERVOIR_API_KEY_PROD } from 'react-native-dotenv'; -import { Network } from '@/networks/types'; const SUPPORTED_NETWORKS = [Network.mainnet, Network.polygon, Network.bsc, Network.arbitrum, Network.optimism, Network.base, Network.zora]; @@ -42,7 +42,7 @@ export async function fetchReservoirNFTFloorPrice(nft: UniqueAsset): Promise { Alert.alert( @@ -16,14 +17,14 @@ const showAlert = () => { { cancelable: false } ); }; - -export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, chainId: ChainId) => { - logger.debug('[mints]: Navigating to Mint Collection', { +export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint: BigNumberish, network: Network) => { + logger.debug('Mints: Navigating to Mint Collection', { contractAddress, - chainId, + network, }); try { + const chainId = getNetworkObj(network).id; const res = await arcClient.getReservoirCollection({ contractAddress, chainId, @@ -34,13 +35,13 @@ export const navigateToMintCollection = async (contractAddress: EthereumAddress, pricePerMint, }); } else { - logger.warn('[mints]: No collection found', { contractAddress, chainId }); + logger.warn('Mints: No collection found', { contractAddress, network }); showAlert(); } } catch (e) { - logger.warn(`[mints]: navigateToMintCollection error`, { + logger.warn('Mints: navigateToMintCollection error', { contractAddress, - chainId, + network, error: e, }); showAlert(); diff --git a/src/resources/reservoir/utils.ts b/src/resources/reservoir/utils.ts index 4e027b18c67..ec4dcf2140c 100644 --- a/src/resources/reservoir/utils.ts +++ b/src/resources/reservoir/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const RAINBOW_FEE_ADDRESS_MAINNET = '0x69d6d375de8c7ade7e44446df97f49e661fdad7d'; const RAINBOW_FEE_ADDRESS_POLYGON = '0xfb9af3db5e19c4165f413f53fe3bbe6226834548'; diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts index 2d01ef35f24..b1324ca1e4b 100644 --- a/src/resources/transactions/consolidatedTransactions.ts +++ b/src/resources/transactions/consolidatedTransactions.ts @@ -5,7 +5,7 @@ import { TransactionApiResponse, TransactionsReceivedMessage } from './types'; import { RainbowError, logger } from '@/logger'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { parseTransaction } from '@/parsers/transactions'; import { ethereumUtils } from '@/utils'; @@ -83,7 +83,7 @@ export async function consolidatedTransactionsQueryFunction({ transactions: consolidatedTransactions, }; } catch (e) { - logger.error(new RainbowError('[consolidatedTransactions]: '), { + logger.error(new RainbowError('consolidatedTransactionsQueryFunction: '), { message: e, }); return { transactions: [] }; @@ -125,7 +125,7 @@ export function useConsolidatedTransactions( { address, currency }: Pick, config: InfiniteQueryConfig = {} ) { - const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); return useInfiniteQuery( consolidatedTransactionsQueryKey({ diff --git a/src/resources/transactions/firstTransactionTimestampQuery.ts b/src/resources/transactions/firstTransactionTimestampQuery.ts index c1bbbc42888..033c295dad8 100644 --- a/src/resources/transactions/firstTransactionTimestampQuery.ts +++ b/src/resources/transactions/firstTransactionTimestampQuery.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; +import PQueue from 'p-queue/dist'; import { createQueryKey, queryClient, QueryConfig, QueryFunctionArgs, QueryFunctionResult } from '@/react-query'; import { getFirstTransactionTimestamp } from '@/utils/ethereumUtils'; @@ -22,6 +23,8 @@ export type FirstTransactionTimestampQueryKey = ReturnType) { @@ -32,10 +35,7 @@ export async function firstTransactionTimestampQueryFunction({ address = (await fetchENSAddress({ name: addressOrName })) ?? ''; } - let timestamp; - if (address) { - timestamp = await getFirstTransactionTimestamp(address); - } + const timestamp = address ? await queue.add(async () => getFirstTransactionTimestamp(address)) : null; return timestamp ?? null; } diff --git a/src/resources/transactions/transaction.ts b/src/resources/transactions/transaction.ts index ded3b12f7a9..b4210f302bc 100644 --- a/src/resources/transactions/transaction.ts +++ b/src/resources/transactions/transaction.ts @@ -3,13 +3,13 @@ import { createQueryKey, queryClient, QueryFunctionArgs, QueryFunctionResult } f import { useQuery } from '@tanstack/react-query'; import { consolidatedTransactionsQueryFunction, consolidatedTransactionsQueryKey } from './consolidatedTransactions'; import { useAccountSettings } from '@/hooks'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks, getNetworkObj } from '@/networks'; import { rainbowFetch } from '@/rainbow-fetch'; import { ADDYS_API_KEY } from 'react-native-dotenv'; import { parseTransaction } from '@/parsers/transactions'; +import { Network } from '@/networks/types'; import { RainbowError, logger } from '@/logger'; import { TransactionApiResponse } from './types'; -import { ChainId } from '@/networks/types'; export type ConsolidatedTransactionsResult = QueryFunctionResult; export type PaginatedTransactions = { pages: ConsolidatedTransactionsResult[] }; @@ -18,24 +18,25 @@ export type TransactionArgs = { hash: string; address: string; currency: NativeCurrencyKey; - chainId: ChainId; + network: Network; }; type TransactionQueryKey = ReturnType; export type BackendTransactionArgs = { hash: string; - chainId: ChainId; + network: Network; enabled: boolean; }; -export const transactionQueryKey = ({ hash, address, currency, chainId }: TransactionArgs) => - createQueryKey('transactions', { address, currency, chainId, hash }, { persisterVersion: 1 }); +export const transactionQueryKey = ({ hash, address, currency, network }: TransactionArgs) => + createQueryKey('transactions', { address, currency, network, hash }, { persisterVersion: 1 }); export const fetchTransaction = async ({ - queryKey: [{ address, currency, chainId, hash }], + queryKey: [{ address, currency, network, hash }], }: QueryFunctionArgs): Promise => { try { + const chainId = getNetworkObj(network).id; const url = `https://addys.p.rainbow.me/v3/${chainId}/${address}/transactions/${hash}`; const response = await rainbowFetch<{ payload: { transaction: TransactionApiResponse } }>(url, { method: 'get', @@ -56,7 +57,7 @@ export const fetchTransaction = async ({ if (!parsedTx) throw new Error('Failed to parse transaction'); return parsedTx; } catch (e) { - logger.error(new RainbowError('[transaction]: Failed to fetch transaction'), { + logger.error(new RainbowError('fetchTransaction: '), { message: (e as Error)?.message, }); return null; @@ -69,19 +70,19 @@ export const fetchTransaction = async ({ export const transactionFetchQuery = async ({ address, currency, - chainId, + network, hash, }: { address: string; currency: NativeCurrencyKey; - chainId: ChainId; + network: Network; hash: string; -}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, chainId, hash }), fetchTransaction); +}) => queryClient.fetchQuery(transactionQueryKey({ address, currency, network, hash }), fetchTransaction); -export function useBackendTransaction({ hash, chainId }: BackendTransactionArgs) { +export function useBackendTransaction({ hash, network }: BackendTransactionArgs) { const { accountAddress, nativeCurrency } = useAccountSettings(); - const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const paginatedTransactionsKey = consolidatedTransactionsQueryKey({ address: accountAddress, @@ -93,11 +94,11 @@ export function useBackendTransaction({ hash, chainId }: BackendTransactionArgs) hash: hash, address: accountAddress, currency: nativeCurrency, - chainId: chainId, + network: network, }; return useQuery(transactionQueryKey(params), fetchTransaction, { - enabled: !!hash && !!accountAddress && !!chainId, + enabled: !!hash && !!accountAddress && !!network, initialData: () => { const queryData = queryClient.getQueryData(paginatedTransactionsKey); const pages = queryData?.pages || []; @@ -113,15 +114,15 @@ export function useBackendTransaction({ hash, chainId }: BackendTransactionArgs) }); } -export const useTransaction = ({ chainId, hash }: { chainId: ChainId; hash: string }) => { +export const useTransaction = ({ network, hash }: { network: Network; hash: string }) => { const { data: backendTransaction, isLoading: backendTransactionIsLoading, isFetched: backendTransactionIsFetched, } = useBackendTransaction({ hash, - chainId, - enabled: !!hash && !!chainId, + network, + enabled: !!hash && !!network, }); return { diff --git a/src/screens/AddCash/components/ProviderCard.tsx b/src/screens/AddCash/components/ProviderCard.tsx index 9dae223ca5f..cbe8ffcb9a1 100644 --- a/src/screens/AddCash/components/ProviderCard.tsx +++ b/src/screens/AddCash/components/ProviderCard.tsx @@ -5,18 +5,20 @@ import chroma from 'chroma-js'; import { IS_IOS } from '@/env'; import { Box, Text, Inline, Bleed, useBackgroundColor } from '@/design-system'; +import { Network } from '@/helpers/networkTypes'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { Ramp as RampLogo } from '@/components/icons/svg/Ramp'; +import { Ratio as RatioLogo } from '@/components/icons/svg/Ratio'; import { Coinbase as CoinbaseLogo } from '@/components/icons/svg/Coinbase'; import { Moonpay as MoonpayLogo } from '@/components/icons/svg/Moonpay'; import { FiatProviderName } from '@/entities/f2c'; -import { convertAPINetworkToInternalChainIds } from '@/screens/AddCash/utils'; +import { convertAPINetworkToInternalNetwork } from '@/screens/AddCash/utils'; import { ProviderConfig, CalloutType, PaymentMethod } from '@/screens/AddCash/types'; import * as i18n from '@/languages'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ChainId } from '@/networks/types'; +import { ethereumUtils } from '@/utils'; type PaymentMethodConfig = { name: string; @@ -79,22 +81,26 @@ function getPaymentMethodConfigs(paymentMethods: { type: PaymentMethod }[]) { return methods; } -function NetworkIcons({ chainIds }: { chainIds?: ChainId[] }) { +function NetworkIcons({ networks }: { networks: Network[] }) { return ( - {chainIds?.map((chainId, index) => { + {networks.map((network, index) => { return ( 0 ? -6 : 0 }} style={{ position: 'relative', - zIndex: chainIds.length - index, + zIndex: networks.length - index, borderRadius: 30, }} > - {chainId !== ChainId.mainnet ? : } + {network !== Network.mainnet ? ( + + ) : ( + + )} ); })} @@ -227,7 +233,7 @@ export function ProviderCard({ config }: { config: ProviderConfig }) { ); diff --git a/src/screens/AddCash/index.tsx b/src/screens/AddCash/index.tsx index 5d8da30e7b0..bba373b1960 100644 --- a/src/screens/AddCash/index.tsx +++ b/src/screens/AddCash/index.tsx @@ -53,7 +53,7 @@ export function AddCashSheet() { const [{ data, error }] = await wait(1000, [await getProviders()]); if (!data || error) { - const e = new RainbowError('[AddCash]: failed to fetch providers'); + const e = new RainbowError('F2C: failed to fetch providers'); logger.error(e); diff --git a/src/screens/AddCash/providers/Coinbase/index.tsx b/src/screens/AddCash/providers/Coinbase/index.tsx index ef82205ad2c..0bf8b26c88d 100644 --- a/src/screens/AddCash/providers/Coinbase/index.tsx +++ b/src/screens/AddCash/providers/Coinbase/index.tsx @@ -34,13 +34,13 @@ export function Coinbase({ accountAddress, config }: { accountAddress: string; c sessionId, }); - logger.debug('[AddCash]: opening provider', { + logger.info('F2C: opening provider', { provider: FiatProviderName.Coinbase, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('[AddCash]: failed to open provider'), { + logger.error(new RainbowError('F2C: failed to open provider'), { provider: FiatProviderName.Coinbase, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Moonpay/index.tsx b/src/screens/AddCash/providers/Moonpay/index.tsx index 64444471b4e..cc45c5306c9 100644 --- a/src/screens/AddCash/providers/Moonpay/index.tsx +++ b/src/screens/AddCash/providers/Moonpay/index.tsx @@ -35,13 +35,13 @@ export function Moonpay({ accountAddress, config }: { accountAddress: string; co sessionId, }); - logger.debug('[AddCash]: opening provider', { + logger.info('F2C: opening provider', { provider: FiatProviderName.Moonpay, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('[AddCash]: failed to open provider'), { + logger.error(new RainbowError('F2C: failed to open provider'), { provider: FiatProviderName.Moonpay, message: (e as Error).message, }); diff --git a/src/screens/AddCash/providers/Ramp/index.tsx b/src/screens/AddCash/providers/Ramp/index.tsx index 8b956a11dd4..679cfd3e02b 100644 --- a/src/screens/AddCash/providers/Ramp/index.tsx +++ b/src/screens/AddCash/providers/Ramp/index.tsx @@ -35,13 +35,13 @@ export function Ramp({ accountAddress, config }: { accountAddress: string; confi sessionId, }); - logger.debug('[AddCash]: opening provider', { + logger.info('F2C: opening provider', { provider: FiatProviderName.Ramp, }); Linking.openURL(url); } catch (e) { - logger.error(new RainbowError('[AddCash]: failed to open provider'), { + logger.error(new RainbowError('F2C: failed to open provider'), { provider: FiatProviderName.Ramp, message: (e as Error).message, }); diff --git a/src/screens/AddCash/utils.ts b/src/screens/AddCash/utils.ts index aa4508030c3..cedf027759e 100644 --- a/src/screens/AddCash/utils.ts +++ b/src/screens/AddCash/utils.ts @@ -1,14 +1,14 @@ -import { ChainId } from '@/networks/types'; +import { Network } from '@/helpers/networkTypes'; import { Network as APINetwork } from '@/screens/AddCash/types'; -export function convertAPINetworkToInternalChainIds(network: APINetwork): ChainId | undefined { +export function convertAPINetworkToInternalNetwork(network: APINetwork): Network | undefined { const networkMap = { - [APINetwork.Ethereum]: ChainId.mainnet, - [APINetwork.Arbitrum]: ChainId.arbitrum, - [APINetwork.Optimism]: ChainId.optimism, - [APINetwork.Polygon]: ChainId.polygon, - [APINetwork.Base]: ChainId.base, - [APINetwork.BSC]: ChainId.bsc, + [APINetwork.Ethereum]: Network.mainnet, + [APINetwork.Arbitrum]: Network.arbitrum, + [APINetwork.Optimism]: Network.optimism, + [APINetwork.Polygon]: Network.polygon, + [APINetwork.Base]: Network.base, + [APINetwork.BSC]: Network.bsc, }; // @ts-ignore diff --git a/src/screens/AddWalletSheet.tsx b/src/screens/AddWalletSheet.tsx index 67c13b27d42..dbad7feed37 100644 --- a/src/screens/AddWalletSheet.tsx +++ b/src/screens/AddWalletSheet.tsx @@ -123,9 +123,10 @@ export const AddWalletSheet = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError('[AddWalletSheet]: Updating wallet userdata failed after new account creation'), { - error: e, + logger.error(e as RainbowError, { + description: 'Updating wallet userdata failed after new account creation', }); + captureException(e); throw e; } } @@ -142,9 +143,10 @@ export const AddWalletSheet = () => { await initializeWallet(); } } catch (e) { - logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { - error: e, + logger.error(e as RainbowError, { + description: 'Error while trying to add account', }); + captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -163,8 +165,8 @@ export const AddWalletSheet = () => { }, 50); }); } catch (e) { - logger.error(new RainbowError('[AddWalletSheet]: Error while trying to add account'), { - error: e, + logger.error(e as RainbowError, { + description: 'Error while trying to add account', }); } }; @@ -211,9 +213,7 @@ export const AddWalletSheet = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(new RainbowError('[AddWalletSheet]: Error while trying to restore from cloud'), { - error: e, - }); + logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); diff --git a/src/screens/ChangeWalletSheet.tsx b/src/screens/ChangeWalletSheet.tsx index 82918ffdcd7..f345079b3e3 100644 --- a/src/screens/ChangeWalletSheet.tsx +++ b/src/screens/ChangeWalletSheet.tsx @@ -20,7 +20,7 @@ import { useAccountSettings, useInitializeWallet, useWallets, useWalletsWithBala import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { doesWalletsContainAddress, showActionSheetWithOptions } from '@/utils'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { useTheme } from '@/theme'; import { EthereumAddress } from '@/entities'; import { getNotificationSettingsForWalletWithAddress } from '@/notifications/settings/storage'; @@ -149,9 +149,7 @@ export default function ChangeWalletSheet() { setTimeout(runChecks, 10_000); } } catch (e) { - logger.error(new RainbowError('[ChangeWalletSheet]: Error while switching account'), { - error: e, - }); + logger.log('error while switching account', e); } }, [currentAddress, dispatch, editMode, goBack, initializeWallet, onChangeWallet, runChecks, wallets, watchOnly] @@ -167,14 +165,14 @@ export default function ChangeWalletSheet() { ...wallets, [walletId]: { ...currentWallet, - addresses: (currentWallet.addresses || []).map(account => + addresses: (currentWallet.addresses ?? []).map(account => account.address.toLowerCase() === address.toLowerCase() ? { ...account, visible: false } : account ), }, }; // If there are no visible wallets // then delete the wallet - const visibleAddresses = ((newWallets as any)[walletId]?.addresses || []).filter((account: any) => account.visible); + const visibleAddresses = (newWallets as any)[walletId].addresses.filter((account: any) => account.visible); if (visibleAddresses.length === 0) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete newWallets[walletId]; @@ -191,7 +189,7 @@ export default function ChangeWalletSheet() { (walletId: string, address: string) => { const wallet = wallets?.[walletId]; if (!wallet) return; - const account = wallet.addresses?.find(account => account.address === address); + const account = wallet.addresses.find(account => account.address === address); InteractionManager.runAfterInteractions(() => { goBack(); diff --git a/src/screens/CheckIdentifierScreen.tsx b/src/screens/CheckIdentifierScreen.tsx index 060d4831031..f4b19d9aa62 100644 --- a/src/screens/CheckIdentifierScreen.tsx +++ b/src/screens/CheckIdentifierScreen.tsx @@ -60,14 +60,14 @@ export default function CheckIdentifierScreen() { const allKeys = await kc.getAllKeys(); if (!allKeys?.length) { - logger.error(new RainbowError('[CheckIdentifierScreen]: Unable to retrieve keychain values')); + logger.error(new RainbowError('Unable to retrieve keychain values')); ErrorAlert(); return; } const allAccountKeys = allKeys.filter(item => item.username.includes('_rainbowPrivateKey')); if (!allAccountKeys?.length) { - logger.error(new RainbowError('[CheckIdentifierScreen]: No private keys found in keychain')); + logger.error(new RainbowError('No private keys found in keychain')); return onFailure(); } @@ -81,7 +81,7 @@ export default function CheckIdentifierScreen() { }); if (hasAccountWithoutPrivateKey) { - logger.error(new RainbowError('[CheckIdentifierScreen]: Detected account without matching private key')); + logger.error(new RainbowError('Detected account without matching private key')); return onFailure(); } diff --git a/src/screens/CurrencySelectModal.tsx b/src/screens/CurrencySelectModal.tsx index 9d141f2b049..25abf58d032 100644 --- a/src/screens/CurrencySelectModal.tsx +++ b/src/screens/CurrencySelectModal.tsx @@ -15,7 +15,7 @@ import { Modal } from '../components/modal'; import { STORAGE_IDS } from '../model/mmkv'; import { analytics } from '@/analytics'; import { addHexPrefix, isL2Chain } from '@/handlers/web3'; -import { CurrencySelectionTypes, TokenSectionTypes } from '@/helpers'; +import { CurrencySelectionTypes, Network, TokenSectionTypes } from '@/helpers'; import { useAccountSettings, useInteraction, @@ -40,7 +40,7 @@ import DiscoverSearchInput from '@/components/discover/DiscoverSearchInput'; import { externalTokenQueryKey, fetchExternalToken } from '@/resources/assets/externalAssetsQuery'; import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { queryClient } from '@/react-query/queryClient'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export interface EnrichedExchangeAsset extends SwappableAsset { ens: boolean; @@ -151,11 +151,15 @@ export default function CurrencySelectModal() { (newAsset: any, selectAsset: any, type: any) => { const otherAsset = type === 'input' ? outputCurrency : inputCurrency; const hasShownWarning = getHasShownWarning(); - if (otherAsset && newAsset?.chainId !== otherAsset?.chainId && !hasShownWarning) { + if ( + otherAsset && + ethereumUtils.getChainIdFromNetwork(newAsset?.network) !== ethereumUtils.getChainIdFromNetwork(otherAsset?.network) && + !hasShownWarning + ) { Keyboard.dismiss(); InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { - chainId: newAsset?.chainId, + network: newAsset?.network, onClose: () => { setHasShownWarning(); selectAsset(); @@ -209,7 +213,6 @@ export default function CurrencySelectModal() { name: 'Unswappable', symbol: 'UNSWAP', network: Network.mainnet, - chainId: ChainId.mainnet, id: 'foobar', uniqueId: '0x123', }); @@ -290,14 +293,14 @@ export default function CurrencySelectModal() { screen: Routes.MAIN_EXCHANGE_SCREEN, }); setSearchQuery(''); - setCurrentChainId(item.chainId); + setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); }, android ? 500 : 0 ); } else { navigate(Routes.MAIN_EXCHANGE_SCREEN); setSearchQuery(''); - setCurrentChainId(item.chainId); + setCurrentChainId(ethereumUtils.getChainIdFromNetwork(item.network)); } if (searchQueryForSearch) { analytics.track('Selected a search result in Swap', { @@ -323,7 +326,8 @@ export default function CurrencySelectModal() { InteractionManager.runAfterInteractions(() => { navigate(Routes.EXPLAIN_SHEET, { assetName: item?.symbol, - chainId: currentChainId, + network: ethereumUtils.getNetworkFromChainId(currentChainId), + networkName: currentL2Name, onClose: linkToHop, type: 'obtainL2Assets', }); @@ -426,10 +430,11 @@ export default function CurrencySelectModal() { const handleBackButton = useCallback(() => { setSearchQuery(''); InteractionManager.runAfterInteractions(() => { - setCurrentChainId(inputCurrency?.chainId); + const inputChainId = ethereumUtils.getChainIdFromNetwork(inputCurrency?.network); + setCurrentChainId(inputChainId); }); setIsTransitioning(true); // continue to display list while transitiong back - }, [inputCurrency?.chainId]); + }, [inputCurrency?.network]); useEffect(() => { // check if list has items before attempting to scroll diff --git a/src/screens/Diagnostics/DiagnosticsItemRow.tsx b/src/screens/Diagnostics/DiagnosticsItemRow.tsx index 4253c1d8eb4..e109d7a7163 100644 --- a/src/screens/Diagnostics/DiagnosticsItemRow.tsx +++ b/src/screens/Diagnostics/DiagnosticsItemRow.tsx @@ -28,7 +28,7 @@ export const DiagnosticsItemRow = ({ data }: any) => { // @ts-expect-error poorly typed function await handlePressImportButton(null, data.secret); } catch (error) { - logger.error(new RainbowError('[DiagnosticsItemRow]: Error restoring from wallet diagnostics'), { + logger.error(new RainbowError('Error restoring from wallet diagnostics'), { message: (error as Error).message, context: 'restore', }); diff --git a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts index fec38fd194b..60abfb63ea0 100644 --- a/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts +++ b/src/screens/Diagnostics/helpers/createAndShareStateDumpFile.ts @@ -72,6 +72,6 @@ export async function createAndShareStateDumpFile() { // clean up the file since we don't need it anymore await RNFS.unlink(documentsFilePath); } catch (error) { - logger.error(new RainbowError('[createAndShareStateDumpFile]: Saving app state dump data failed')); + logger.error(new RainbowError('Saving app state dump data failed')); } } diff --git a/src/screens/Diagnostics/index.tsx b/src/screens/Diagnostics/index.tsx index b3fe96aa2f3..cdbbf4bd044 100644 --- a/src/screens/Diagnostics/index.tsx +++ b/src/screens/Diagnostics/index.tsx @@ -102,7 +102,7 @@ export const WalletDiagnosticsSheet = () => { setKeys(processedKeys); } } catch (error) { - logger.error(new RainbowError('[WalletDiagnosticsSheet]: Error processing keys for wallet diagnostics'), { + logger.error(new RainbowError('Error processing keys for wallet diagnostics'), { message: (error as Error).message, context: 'init', }); diff --git a/src/screens/ENSConfirmRegisterSheet.tsx b/src/screens/ENSConfirmRegisterSheet.tsx index 82daf89ba8e..beb2f56167c 100644 --- a/src/screens/ENSConfirmRegisterSheet.tsx +++ b/src/screens/ENSConfirmRegisterSheet.tsx @@ -40,7 +40,7 @@ import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDomina import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { ReviewPromptAction } from '@/storage/schema'; import { ActionTypes } from '@/hooks/useENSRegistrationActionHandler'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const ENSConfirmRegisterSheetHeight = 600; export const ENSConfirmRenewSheetHeight = 560; diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 86441c24004..7398cd95bb7 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -27,7 +27,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { Box, Row, Rows } from '@/design-system'; import { GasFee, LegacyGasFee, LegacyGasFeeParams, SwappableAsset } from '@/entities'; -import { ExchangeModalTypes, isKeyboardOpen } from '@/helpers'; +import { ExchangeModalTypes, isKeyboardOpen, Network } from '@/helpers'; import { KeyboardType } from '@/helpers/keyboardTypes'; import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; import { delay, greaterThan } from '@/helpers/utilities'; @@ -54,7 +54,7 @@ import { ethUnits } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, gasUtils } from '@/utils'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; import store from '@/redux/store'; @@ -63,6 +63,7 @@ import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; import { Wallet } from '@ethersproject/wallet'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; import { useTheme } from '@/theme'; +import { logger as loggr } from '@/logger'; import { getNetworkObject } from '@/networks'; import Animated from 'react-native-reanimated'; import { handleReviewPromptAction } from '@/utils/reviewAlert'; @@ -70,7 +71,7 @@ import { ReviewPromptAction } from '@/storage/schema'; import { SwapPriceImpactType } from '@/hooks/usePriceImpactDetails'; import { getNextNonce } from '@/state/nonces'; import { getChainName } from '@/__swaps__/utils/chains'; -import { ChainId, ChainName } from '@/networks/types'; +import { ChainId, ChainName } from '@/__swaps__/types/chains'; import { AddressOrEth, ParsedAsset } from '@/__swaps__/types/assets'; import { TokenColors } from '@/graphql/__generated__/metadata'; import { estimateSwapGasLimit } from '@/raps/actions'; @@ -82,7 +83,7 @@ export const DEFAULT_SLIPPAGE_BIPS = { [ChainId.polygon]: 200, [ChainId.base]: 200, [ChainId.bsc]: 200, - [ChainId.optimism]: 200, + [Network.optimism]: 200, [ChainId.arbitrum]: 200, [ChainId.goerli]: 100, [ChainId.gnosis]: 200, @@ -145,14 +146,15 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty updateDefaultGasLimit, updateGasFeeOption, updateTxFee, - chainId, + txNetwork, + isGasReady, } = useGas(); const { accountAddress, flashbotsEnabled, nativeCurrency } = useAccountSettings(); const [isAuthorizing, setIsAuthorizing] = useState(false); const prevGasFeesParamsBySpeed = usePrevious(gasFeeParamsBySpeed); - const prevChainId = usePrevious(chainId); + const prevChainId = usePrevious(ethereumUtils.getChainIdFromNetwork(txNetwork)); const keyboardListenerSubscription = useRef(); @@ -221,7 +223,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty if (currentChainId !== prevChainId) { speedUrgentSelected.current = false; } - }, [currentChainId, prevChainId]); + }, [currentChainId, prevChainId, txNetwork]); const defaultGasLimit = useMemo(() => { return ethereumUtils.getBasicSwapGasLimit(Number(currentChainId)); @@ -346,14 +348,14 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty ) { updateGasLimit(); } - }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, updateGasLimit]); + }, [currentChainId, gasFeeParamsBySpeed, isGasReady, prevChainId, prevGasFeesParamsBySpeed, txNetwork, updateGasLimit]); // Listen to gas prices, Uniswap reserves updates useEffect(() => { updateDefaultGasLimit(defaultGasLimit); InteractionManager.runAfterInteractions(() => { // Start polling in the current network - startPollingGasFees(currentChainId, flashbots); + startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId), flashbots); }); return () => { stopPollingGasFees(); @@ -405,7 +407,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty }); if (!wallet) { setIsAuthorizing(false); - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing wallet`)); + logger.sentry(`aborting ${type} due to missing wallet`); Alert.alert('Unable to determine wallet address'); return false; } @@ -413,26 +415,25 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Switch to the flashbots provider if enabled // TODO(skylarbarrera): need to check if ledger and handle differently here if (flashbots && getNetworkObject({ chainId: currentChainId }).features?.flashbots && wallet instanceof Wallet) { - logger.debug('[ExchangeModal]: flashbots provider being set on mainnet'); + logger.debug('flashbots provider being set on mainnet'); const flashbotsProvider = await getFlashbotsProvider(); wallet = new Wallet(wallet.privateKey, flashbotsProvider); } if (!inputAmount || !outputAmount) { - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing inputAmount or outputAmount`)); + logger.log('[exchange - handle submit] inputAmount or outputAmount is missing'); Alert.alert('Input amount or output amount is missing'); return false; } if (!tradeDetails) { - logger.error(new RainbowError(`[ExchangeModal]: aborting ${type} due to missing tradeDetails`)); + logger.log('[exchange - handle submit] tradeDetails is missing'); Alert.alert('Missing trade details for swap'); return false; } - logger.debug(`[ExchangeModal]: getting nonce for account ${accountAddress}`); - const currentNonce = await getNextNonce({ address: accountAddress, chainId: currentChainId }); - logger.debug(`[ExchangeModal]: nonce for account ${accountAddress} is ${currentNonce}`); + logger.log('[exchange - handle submit] rap'); + const currentNonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); const { independentField, independentValue, slippageInBips, source } = store.getState().swap; const transformedAssetToSell = { @@ -495,7 +496,9 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setIsAuthorizing(false); // if the transaction was not successful, we need to bubble that up to the caller if (errorMessage) { - logger.error(new RainbowError(`[ExchangeModal]: transaction was not successful: ${errorMessage}`)); + loggr.debug('[ExchangeModal] transaction was not successful', { + errorMessage, + }); if (wallet instanceof Wallet) { Alert.alert(errorMessage); } else { @@ -504,7 +507,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return false; } - logger.debug('[ExchangeModal]: executed rap!'); + logger.log('[exchange - handle submit] executed rap!'); const slippage = slippageInBips / 100; analytics.track(`Completed ${type}`, { aggregator: tradeDetails?.source || '', @@ -544,7 +547,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty return true; } catch (error) { setIsAuthorizing(false); - logger.error(new RainbowError(`[ExchangeModal]: error submitting swap: ${error}`)); + logger.log('[exchange - handle submit] error submitting swap', error); setParams({ focused: false }); // close the hardware wallet modal before navigating if (isHardwareWallet) { @@ -585,7 +588,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty // Tell iOS we're running a rap (for tracking purposes) NotificationManager?.postNotification('rapInProgress'); } catch (e) { - logger.error(new RainbowError(`[ExchangeModal]: error posting notification for rapInProgress: ${e}`)); + logger.log('error getting the swap amount in USD price', e); } finally { const slippage = slippageInBips / 100; analytics.track(`Submitted ${type}`, { @@ -648,7 +651,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty const confirmButtonProps = useMemoOne( () => ({ - chainId: currentChainId, + currentNetwork: ethereumUtils.getNetworkFromChainId(currentChainId), disabled: !Number(inputAmount) || (!loading && !tradeDetails), inputAmount, isAuthorizing, @@ -690,7 +693,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty setParams({ focused: false }); navigate(Routes.SWAP_SETTINGS_SHEET, { asset: outputCurrency, - chainId: currentChainId, + network: ethereumUtils.getNetworkFromChainId(currentChainId), restoreFocusOnSwapModal: () => { android && (lastFocusedInputHandle.current = lastFocusedInputHandleTemporary); setParams({ focused: true }); @@ -780,8 +783,8 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty lastFocusedInput?.blur(); navigate(Routes.EXPLAIN_SHEET, { inputToken: inputCurrency?.symbol, - fromChainId: inputChainId, - toChainId: outputChainId, + fromNetwork: ethereumUtils.getNetworkFromChainId(inputChainId), + toNetwork: ethereumUtils.getNetworkFromChainId(outputChainId), isCrosschainSwap, isBridgeSwap, onClose: () => { diff --git a/src/screens/ExplainSheet.js b/src/screens/ExplainSheet.js index f92f02ea5a9..d5ce85ef357 100644 --- a/src/screens/ExplainSheet.js +++ b/src/screens/ExplainSheet.js @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; @@ -11,19 +10,21 @@ import { Emoji, GradientText, Text } from '../components/text'; import { useNavigation } from '../navigation/Navigation'; import { DoubleChevron } from '@/components/icons'; import { Box } from '@/design-system'; +import networkTypes from '@/helpers/networkTypes'; import { useDimensions } from '@/hooks'; import styled from '@/styled-thing'; import { fonts, fontWithWidth, padding, position } from '@/styles'; -import { ethereumUtils, gasUtils } from '@/utils'; +import { ethereumUtils, gasUtils, getTokenMetadata } from '@/utils'; import { buildRainbowLearnUrl } from '@/utils/buildRainbowUrl'; import { cloudPlatformAccountName } from '@/utils/platform'; import { useTheme } from '@/theme'; import { isL2Chain } from '@/handlers/web3'; import { IS_ANDROID } from '@/env'; import * as i18n from '@/languages'; +import { getNetworkObj } from '@/networks'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const { GAS_TRENDS } = gasUtils; export const ExplainSheetHeight = android ? 454 : 434; @@ -77,8 +78,8 @@ const FLOOR_PRICE_EXPLAINER = lang.t('explain.floor_price.text'); const gasExplainer = network => lang.t('explain.gas.text', { networkName: network }); -const availableNetworksExplainer = (tokenSymbol, chainIds) => { - const readableNetworks = chainIds?.map(chainId => chainIdToNameMapping[chainId])?.join(', '); +const availableNetworksExplainer = (tokenSymbol, networks) => { + const readableNetworks = networks?.map(network => getNetworkObj(network).name)?.join(', '); return lang.t('explain.available_networks.text', { tokenSymbol: tokenSymbol, @@ -157,11 +158,8 @@ const ENS_CONFIGURATION_EXPLAINER = export const explainers = (params, theme) => { const colors = theme?.colors; - const chainId = params?.chainId; - const network = ethereumUtils.getNetworkFromChainId(chainId); - const networkName = chainIdToNameMapping[chainId]; - const fromChainId = params?.fromChainId; - const toChainId = params?.toChainId; + const fromNetworkObject = getNetworkObj(params?.fromNetwork); + const toNetworkObject = getNetworkObj(params?.toNetwork); return { op_rewards_airdrop_timing: { emoji: '📦', @@ -212,7 +210,7 @@ export const explainers = (params, theme) => { title: params?.inputToken ? lang.t(`explain.output_disabled.${params?.isCrosschainSwap ? 'title_crosschain' : 'title'}`, { inputToken: params?.inputToken, - fromNetwork: chainIdToNameMapping[fromChainId], + fromNetwork: fromNetworkObject.name, }) : lang.t('explain.output_disabled.title_empty'), @@ -220,18 +218,18 @@ export const explainers = (params, theme) => { ? lang.t(`explain.output_disabled.${params?.isBridgeSwap ? 'text_bridge' : 'text_crosschain'}`, { inputToken: params?.inputToken, outputToken: params?.outputToken, - fromNetwork: chainIdToNameMapping[fromChainId], - toNetwork: chainIdToNameMapping[toChainId], + fromNetwork: fromNetworkObject.name, + toNetwork: toNetworkObject.name, }) : lang.t('explain.output_disabled.text', { - fromNetwork: chainIdToNameMapping[fromChainId]?.name, + fromNetwork: fromNetworkObject?.name, inputToken: params?.inputToken, outputToken: params?.outputToken, }), - logo: !isL2Chain({ chainId: fromChainId }) ? ( + logo: !isL2Chain({ chainId: fromNetworkObject.id }) ? ( ) : ( - + ), }, floor_price: { @@ -246,15 +244,15 @@ export const explainers = (params, theme) => { size={40} icon={params?.nativeAsset?.icon_url} symbol={params?.nativeAsset?.symbol} - chainId={chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.network)} colors={params?.nativeAsset?.colors} theme={theme} /> ), extraHeight: 2, - text: gasExplainer(chainIdToNameMapping[chainId]), + text: gasExplainer(getNetworkObj(params?.network).name), title: lang.t('explain.gas.title', { - networkName: chainIdToNameMapping[chainId], + networkName: getNetworkObj(params?.network).name, }), }, ens_primary_name: { @@ -535,17 +533,22 @@ export const explainers = (params, theme) => { }, swapResetInputs: { button: { - label: `Continue with ${networkName}`, - bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.06), - textColor: colors?.networkColors[chainId] && colors?.networkColors?.[network], + label: `Continue with ${getNetworkObj(params?.network)?.name}`, + bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.06), + textColor: colors?.networkColors[params?.network] && colors?.networkColors?.[params?.network], }, emoji: '🔐', extraHeight: -90, text: SWAP_RESET_EXPLAINER, - title: `Switching to ${networkName}`, + title: `Switching to ${getNetworkObj(params?.network)?.name}`, logo: - chainId !== ChainId.mainnet ? ( - + params?.network !== 'mainnet' ? ( + ) : ( ), @@ -593,7 +596,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={params?.inputCurrency?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -649,7 +652,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={params?.inputCurrency?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -658,7 +661,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={params?.outputCurrency?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -675,7 +678,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.inputCurrency?.icon_url} symbol={params?.inputCurrency?.symbol} - chainId={params?.inputCurrency?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.inputCurrency?.network)} colors={params?.inputCurrency?.colors} theme={theme} /> @@ -684,7 +687,7 @@ export const explainers = (params, theme) => { size={40} icon={params?.outputCurrency?.icon_url} symbol={params?.outputCurrency?.symbol} - chainId={params?.outputCurrency?.chainId} + chainId={ethereumUtils.getChainIdFromNetwork(params?.outputCurrency?.network)} colors={params?.outputCurrency?.colors} theme={theme} /> @@ -694,24 +697,24 @@ export const explainers = (params, theme) => { availableNetworks: { buttonText: `Go to Hop`, extraHeight: -90, - text: availableNetworksExplainer(params?.tokenSymbol, params?.chainIds), + text: availableNetworksExplainer(params?.tokenSymbol, params?.networks), title: - params?.chainIds?.length > 1 + params?.networks?.length > 1 ? lang.t('explain.available_networks.title_plural', { - length: params?.chainIds?.length, + length: params?.networks?.length, }) : lang.t('explain.available_networks.title_singular', { - network: params?.chainIds?.[0], + network: params?.networks?.[0], }), logo: ( - {params?.chainIds?.map((chainId, index) => { + {params?.networks?.map((network, index) => { return ( 0 ? -12 : params?.chainIds?.length % 2 === 0 ? -2 : -30, + custom: index > 0 ? -12 : params?.networks?.length % 2 === 0 ? -2 : -30, }} style={{ borderColor: colors.transparent, @@ -720,10 +723,10 @@ export const explainers = (params, theme) => { zIndex: index, }} width={{ custom: 40 }} - zIndex={params?.chainIds?.length - index} + zIndex={params?.networks?.length - index} > - {chainId !== ChainId.mainnet ? ( - + {network !== 'mainnet' ? ( + ) : ( @@ -776,7 +779,7 @@ export const explainers = (params, theme) => { {lang.t('explain.obtain_l2_asset.fragment3')} ), - logo: , + logo: , title: lang.t('explain.obtain_l2_asset.title', { networkName: params?.networkName, }), @@ -864,12 +867,19 @@ export const explainers = (params, theme) => { }, swap_refuel_add: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[chainId], - bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), + textColor: colors?.networkColors[params?.network], + bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_deduct: { logo: ( - + { networkName: params?.networkName, gasToken: params?.gasToken, }), - textColor: colors?.networkColors[chainId], - bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05), + textColor: colors?.networkColors[params?.network], + bgColor: colors?.networkColors[params?.network] && colors?.alpha(colors?.networkColors[params?.network], 0.05), onPress: params?.onRefuel, }, }, swap_refuel_notice: { extraHeight: 50, logo: ( - + diff --git a/src/screens/MintsSheet/card/Card.tsx b/src/screens/MintsSheet/card/Card.tsx index d9e30975c0a..1870ce8454e 100644 --- a/src/screens/MintsSheet/card/Card.tsx +++ b/src/screens/MintsSheet/card/Card.tsx @@ -3,7 +3,8 @@ import React, { useEffect, useState } from 'react'; import { getTimeElapsedFromDate } from '../utils'; import { Bleed, Box, Cover, Inline, Inset, Stack, Text, useForegroundColor } from '@/design-system'; import { abbreviateNumber, convertRawAmountToRoundedDecimal } from '@/helpers/utilities'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj } from '@/networks'; +import { getNetworkFromChainId } from '@/utils/ethereumUtils'; import { ButtonPressAnimation } from '@/components/animations'; import { Placeholder, RecentMintCell } from './RecentMintCell'; import { View } from 'react-native'; @@ -13,7 +14,7 @@ import * as i18n from '@/languages'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const NUM_NFTS = 3; @@ -28,9 +29,11 @@ export function Card({ collection }: { collection: MintableCollection }) { const separatorTertiary = useForegroundColor('separatorTertiary'); const price = convertRawAmountToRoundedDecimal(collection.mintStatus.price, 18, 6); - const currencySymbol = getNetworkObject({ chainId: collection.chainId }).nativeCurrency.symbol; + const currencySymbol = getNetworkObj(getNetworkFromChainId(collection.chainId)).nativeCurrency.symbol; const isFree = !price; + const network = getNetworkFromChainId(collection.chainId); + // update elapsed time every minute if it's less than an hour useEffect(() => { if (timeElapsed && timeElapsed[timeElapsed.length - 1] === 'm') { @@ -103,7 +106,7 @@ export function Card({ collection }: { collection: MintableCollection }) { chainId: collection.chainId, priceInEth: price, }); - navigateToMintCollection(collection.contract, collection.mintStatus.price, collection.chainId); + navigateToMintCollection(collection.contract, collection.mintStatus.price, network); }} style={{ borderRadius: 99, diff --git a/src/screens/NFTOffersSheet/OfferRow.tsx b/src/screens/NFTOffersSheet/OfferRow.tsx index 93b64b83c18..2fb228536a2 100644 --- a/src/screens/NFTOffersSheet/OfferRow.tsx +++ b/src/screens/NFTOffersSheet/OfferRow.tsx @@ -18,7 +18,6 @@ import { Network } from '@/networks/types'; import { useAccountSettings } from '@/hooks'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { ethereumUtils } from '@/utils'; -import { AddressOrEth } from '@/__swaps__/types/assets'; const NFT_SIZE = 50; const MARKETPLACE_ORB_SIZE = 18; @@ -101,7 +100,7 @@ export const OfferRow = ({ offer }: { offer: NftOffer }) => { const bgColor = useBackgroundColor('surfaceSecondaryElevated'); const chainId = ethereumUtils.getChainIdFromNetwork(offer.network as Network); const { data: externalAsset } = useExternalToken({ - address: offer.paymentToken.address as AddressOrEth, + address: offer.paymentToken.address, chainId, currency: nativeCurrency, }); diff --git a/src/screens/NFTSingleOfferSheet/index.tsx b/src/screens/NFTSingleOfferSheet/index.tsx index 16869b59a77..2902e041e9b 100644 --- a/src/screens/NFTSingleOfferSheet/index.tsx +++ b/src/screens/NFTSingleOfferSheet/index.tsx @@ -39,7 +39,7 @@ import { createWalletClient, http } from 'viem'; import { RainbowError, logger } from '@/logger'; import { useTheme } from '@/theme'; -import { Network, ChainId } from '@/networks/types'; +import { Network } from '@/helpers'; import { getNetworkObject } from '@/networks'; import { CardSize } from '@/components/unique-token/CardSize'; import { queryClient } from '@/react-query'; @@ -205,7 +205,7 @@ export function NFTSingleOfferSheet() { let reservoirEstimate = 0; const txs: Transaction[] = []; const fallbackEstimate = - offerChainId === ChainId.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; + offer.network === Network.mainnet ? ethUnits.mainnet_nft_offer_gas_fee_fallback : ethUnits.l2_nft_offer_gas_fee_fallback; steps.forEach(step => step.items?.forEach(item => { if (item?.data?.to && item?.data?.from && item?.data?.data) { @@ -240,23 +240,23 @@ export function NFTSingleOfferSheet() { }, }); } catch { - logger.error(new RainbowError('[NFTSingleOfferSheet]: Failed to estimate gas')); + logger.error(new RainbowError('NFT Offer: Failed to estimate gas')); } }, [accountAddress, feeParam, offerChainId, offer, updateTxFee]); // estimate gas useEffect(() => { if (!isReadOnlyWallet && !isExpired) { - startPollingGasFees(offerChainId); + startPollingGasFees(offer?.network as Network); estimateGas(); } return () => { stopPollingGasFees(); }; - }, [estimateGas, isExpired, isReadOnlyWallet, offer.network, offerChainId, startPollingGasFees, stopPollingGasFees, updateTxFee]); + }, [estimateGas, isExpired, isReadOnlyWallet, offer?.network, startPollingGasFees, stopPollingGasFees, updateTxFee]); const acceptOffer = useCallback(async () => { - logger.debug(`[NFTSingleOfferSheet]: Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.info(`Initiating sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); const analyticsEventObject = { nft: { contractAddress: offer.nft.contractAddress, @@ -287,7 +287,7 @@ export function NFTSingleOfferSheet() { chain: networkObj, transport: http(networkObj.rpc()), }); - const nonce = await getNextNonce({ address: accountAddress, chainId: networkObj.id }); + const nonce = await getNextNonce({ address: accountAddress, network: networkObj.value }); try { let errorMessage = ''; let didComplete = false; @@ -333,7 +333,6 @@ export function NFTSingleOfferSheet() { nonce: item?.txHashes?.length > 1 ? nonce + 1 : nonce, asset: { ...offer.paymentToken, - chainId: offerChainId, network: offer.network as Network, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, @@ -348,7 +347,6 @@ export function NFTSingleOfferSheet() { asset: { ...offer.paymentToken, network: offer.network as Network, - chainId: offerChainId, uniqueId: getUniqueId(offer.paymentToken.address, offerChainId), }, value: offer.grossAmount.raw, @@ -373,7 +371,7 @@ export function NFTSingleOfferSheet() { addNewTransaction({ transaction: tx, address: accountAddress, - chainId: offerChainId, + network: offer.network as Network, }); txsRef.current.push(tx.hash); } @@ -396,7 +394,7 @@ export function NFTSingleOfferSheet() { } ); - logger.debug(`[NFTSingleOfferSheet]: Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); + logger.info(`Completed sale of NFT ${offer.nft.contractAddress}:${offer.nft.tokenId}`); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { status: 'completed', ...analyticsEventObject, @@ -406,7 +404,7 @@ export function NFTSingleOfferSheet() { } catch (e) { logger.error( new RainbowError( - `[NFTSingleOfferSheet]: Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` + `Error selling NFT ${offer.nft.contractAddress} #${offer.nft.tokenId} on marketplace ${offer.marketplace.name}: ${e}` ) ); analyticsV2.track(analyticsV2.event.nftOffersAcceptedOffer, { diff --git a/src/screens/NotificationsPromoSheet/index.tsx b/src/screens/NotificationsPromoSheet/index.tsx index cdafe241eb9..3f9350a5827 100644 --- a/src/screens/NotificationsPromoSheet/index.tsx +++ b/src/screens/NotificationsPromoSheet/index.tsx @@ -61,7 +61,7 @@ export function NotificationsPromoSheetInner({ const primaryButtonOnPress = React.useCallback(async () => { if (notificationsDenied) { - logger.debug(`[NotificationsPromoSheet]: notifications permissions denied (could be default state)`); + logger.debug(`NotificationsPromoSheet: notifications permissions denied (could be default state)`); const result = await requestNotificationPermissions(); if (result.status === perms.RESULTS.BLOCKED) { analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsBlocked); @@ -71,11 +71,11 @@ export function NotificationsPromoSheetInner({ analyticsV2.track(analyticsV2.event.notificationsPromoPermissionsGranted); } } else if (!hasSettingsEnabled || notificationsBlocked) { - logger.debug(`[NotificationsPromoSheet]: notifications permissions either blocked or all settings are disabled`); + logger.debug(`NotificationsPromoSheet: notifications permissions either blocked or all settings are disabled`); analyticsV2.track(analyticsV2.event.notificationsPromoSystemSettingsOpened); await perms.openSettings(); } else if (notificationsEnabled) { - logger.debug(`[NotificationsPromoSheet]: notifications permissions enabled`); + logger.debug(`NotificationsPromoSheet: notifications permissions enabled`); analyticsV2.track(analyticsV2.event.notificationsPromoNotificationSettingsOpened); navigateToNotifications(); } else { diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 9d1613e22b5..dcb196b20f1 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -56,12 +56,12 @@ import styled from '@/styled-thing'; import { position } from '@/styles'; import { useTheme } from '@/theme'; import { ethereumUtils, getUniqueTokenType, promiseUtils } from '@/utils'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; +import { getNetworkObj } from '@/networks'; import { IS_ANDROID } from '@/env'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { performanceTracking, TimeToSignOperation, Screens } from '@/state/performance/performance'; -import { ChainId, chainIdToNameMapping } from '@/networks/types'; const Container = styled(Centered).attrs({ direction: 'column', @@ -97,12 +97,12 @@ const checkboxOffset = 44; export function getDefaultCheckboxes({ isENS, ensProfile, - chainId, + network, toAddress, }: { isENS: boolean; ensProfile: ENSProfile; - chainId: ChainId; + network: string; toAddress: string; }): Checkbox[] { if (isENS) { @@ -132,7 +132,7 @@ export function getDefaultCheckboxes({ checked: false, id: 'has-wallet-that-supports', label: lang.t('wallet.transaction.checkboxes.has_a_wallet_that_supports', { - networkName: capitalize(chainIdToNameMapping[chainId]), + networkName: capitalize(network), }), }, ]; @@ -195,7 +195,7 @@ export const SendConfirmationSheet = () => { }, []); const { - params: { amountDetails, asset, callback, ensProfile, isL2, isNft, chainId, to, toAddress }, + params: { amountDetails, asset, callback, ensProfile, isL2, isNft, network, to, toAddress }, // eslint-disable-next-line @typescript-eslint/no-explicit-any } = useRoute(); @@ -229,7 +229,7 @@ export const SendConfirmationSheet = () => { transactions.forEach(tx => { if (tx.to?.toLowerCase() === toAddress?.toLowerCase() && tx.from?.toLowerCase() === accountAddress?.toLowerCase()) { sends += 1; - if (tx.chainId === chainId) { + if (tx.network === network) { sendsCurrentNetwork += 1; } } @@ -241,7 +241,7 @@ export const SendConfirmationSheet = () => { } } } - }, [accountAddress, isSendingToUserAccount, chainId, toAddress, transactions]); + }, [accountAddress, isSendingToUserAccount, network, toAddress, transactions]); const contact = useMemo(() => { return contacts?.[toAddress?.toLowerCase()]; @@ -250,7 +250,7 @@ export const SendConfirmationSheet = () => { const uniqueTokenType = getUniqueTokenType(asset); const isENS = uniqueTokenType === 'ENS' && profilesEnabled; - const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, chainId, toAddress })); + const [checkboxes, setCheckboxes] = useState(getDefaultCheckboxes({ ensProfile, isENS, network, toAddress })); useEffect(() => { if (isENS) { @@ -318,7 +318,7 @@ export const SendConfirmationSheet = () => { updateTxFee(gasLimit, null); }) .catch(e => { - logger.error(new RainbowError(`[SendConfirmationSheet]: error calculating gas limit: ${e}`)); + logger.sentry('Error calculating gas limit', e); updateTxFee(null, null); }); } @@ -395,7 +395,7 @@ export const SendConfirmationSheet = () => { await callback(); } } catch (e) { - logger.error(new RainbowError(`[SendConfirmationSheet]: error submitting transaction: ${e}`)); + logger.sentry('TX submit failed', e); setIsAuthorizing(false); } }, @@ -500,7 +500,7 @@ export const SendConfirmationSheet = () => { badgeYPosition={0} borderRadius={10} imageUrl={imageUrl} - chainId={asset?.chainId} + network={asset.network} showLargeShadow size={50} /> @@ -508,7 +508,7 @@ export const SendConfirmationSheet = () => { { address: toAddress, name: avatarName || address(to, 4, 8), }} - chainId={chainId} + network={network} scaleTo={0.75} > { {/* @ts-expect-error JavaScript component */} { onPress={handleL2DisclaimerPress} prominent customText={i18n.t(i18n.l.expanded_state.asset.l2_disclaimer_send, { - network: chainIdToNameMapping[asset.chainId], + network: getNetworkObj(asset.network).name, })} symbol={asset.symbol} /> diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 3cff2615a5b..17a7cfe86dd 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -1,4 +1,5 @@ import { useRoute } from '@react-navigation/native'; +import { captureEvent, captureException } from '@sentry/react-native'; import lang from 'i18n-js'; import { isEmpty, isEqual, isString } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -22,7 +23,9 @@ import { getProvider, isL2Chain, resolveNameOrAddress, + web3Provider, } from '@/handlers/web3'; +import Network from '@/helpers/networkTypes'; import { checkIsValidAddressOrDomain, checkIsValidAddressOrDomainFormat, isENSAddressFormat } from '@/helpers/validators'; import { prefetchENSAvatar, @@ -51,7 +54,7 @@ import styled from '@/styled-thing'; import { borders } from '@/styles'; import { convertAmountAndPriceToNativeDisplay, convertAmountFromNativeValue, formatInputDecimals, lessThan } from '@/helpers/utilities'; import { deviceUtils, ethereumUtils, getUniqueTokenType, safeAreaInsetValues } from '@/utils'; -import { logger, RainbowError } from '@/logger'; +import logger from '@/utils/logger'; import { IS_ANDROID, IS_IOS } from '@/env'; import { NoResults } from '@/components/list'; import { NoResultsType } from '@/components/list/NoResults'; @@ -63,7 +66,7 @@ import { getNextNonce } from '@/state/nonces'; import { usePersistentDominantColorFromImage } from '@/hooks/usePersistentDominantColorFromImage'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { REGISTRATION_STEPS } from '@/helpers/ens'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; @@ -117,7 +120,7 @@ export default function SendSheet(props) { const { contacts, onRemoveContact, filteredContacts } = useContacts(); const { userAccounts, watchedAccounts } = useUserAccounts(); const { sendableUniqueTokens } = useSendableUniqueTokens(); - const { accountAddress, nativeCurrency, chainId } = useAccountSettings(); + const { accountAddress, nativeCurrency, network } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const { action: transferENS } = useENSRegistrationActionHandler({ @@ -264,10 +267,10 @@ export default function SendSheet(props) { // belongs to if (prevChainId !== currentChainId) { InteractionManager.runAfterInteractions(() => { - startPollingGasFees(currentChainId); + startPollingGasFees(ethereumUtils.getNetworkFromChainId(currentChainId)); }); } - }, [startPollingGasFees, selected.chainId, prevChainId, currentChainId]); + }, [startPollingGasFees, selected.network, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -279,19 +282,21 @@ export default function SendSheet(props) { }, [stopPollingGasFees]); useEffect(() => { - const assetChainId = selected.chainId; + const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); + const networkChainId = ethereumUtils.getChainIdFromNetwork(network); if (assetChainId && (assetChainId !== currentChainId || !currentChainId || prevChainId !== currentChainId)) { - if (chainId === ChainId.goerli) { + let provider = web3Provider; + if (networkChainId === ChainId.goerli) { setCurrentChainId(ChainId.goerli); - const provider = getProvider({ chainId: ChainId.goerli }); + provider = getProvider({ chainId: ChainId.goerli }); setCurrentProvider(provider); } else { setCurrentChainId(assetChainId); - const provider = getProvider({ chainId: currentChainId }); + provider = getProvider({ chainId: currentChainId }); setCurrentProvider(provider); } } - }, [currentChainId, isNft, chainId, prevChainId, selected?.chainId, sendUpdateSelected]); + }, [currentChainId, isNft, network, prevChainId, selected?.network, sendUpdateSelected]); const onChangeNativeAmount = useCallback( newNativeAmount => { @@ -395,11 +400,12 @@ export default function SendSheet(props) { const validTransaction = isValidAddress && amountDetails.isSufficientBalance && isSufficientGas && isValidGas; if (!selectedGasFee?.gasFee?.estimatedFee || !validTransaction) { - logger.error(new RainbowError(`[SendSheet]: preventing tx submit because selectedGasFee is missing or validTransaction is false`), { - selectedGasFee, - validTransaction, - isValidGas, - }); + logger.sentry('preventing tx submit for one of the following reasons:'); + logger.sentry('selectedGasFee ? ', selectedGasFee); + logger.sentry('selectedGasFee.maxFee ? ', selectedGasFee?.maxFee); + logger.sentry('validTransaction ? ', validTransaction); + logger.sentry('isValidGas ? ', isValidGas); + captureEvent('Preventing tx submit'); return false; } @@ -419,7 +425,7 @@ export default function SendSheet(props) { }, true, currentProvider, - currentChainId + currentChainIdNetwork ); if (!lessThan(updatedGasLimit, gasLimit)) { @@ -461,7 +467,7 @@ export default function SendSheet(props) { from: accountAddress, gasLimit: gasLimitToUse, network: currentChainIdNetwork, - nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, chainId: currentChainId })), + nonce: nextNonce ?? (await getNextNonce({ address: accountAddress, network: currentChainIdNetwork })), to: toAddress, ...gasParams, }; @@ -473,10 +479,11 @@ export default function SendSheet(props) { screen: isENS ? Screens.SEND_ENS : Screens.SEND, })(txDetails); if (!signableTransaction.to) { - logger.error(new RainbowError(`[SendSheet]: txDetails is missing the "to" field`), { - txDetails, - signableTransaction, - }); + logger.sentry('txDetails', txDetails); + logger.sentry('signableTransaction', signableTransaction); + logger.sentry('"to" field is missing!'); + const e = new Error('Transaction missing TO field'); + captureException(e); Alert.alert(lang.t('wallet.transaction.alert.invalid_transaction')); submitSuccess = false; } else { @@ -510,17 +517,17 @@ export default function SendSheet(props) { txDetails.status = 'pending'; addNewTransaction({ address: accountAddress, - chainId: currentChainId, + network: currentChainIdNetwork, transaction: txDetails, }); } } } catch (error) { submitSuccess = false; - logger.error(new RainbowError(`[SendSheet]: onSubmit error`), { - txDetails, - error, - }); + logger.sentry('TX Details', txDetails); + logger.sentry('SendSheet onSubmit error'); + logger.sentry(error); + captureException(error); // if hardware wallet, we need to tell hardware flow there was error // have to check inverse or we trigger unwanted BT permissions requests @@ -557,9 +564,8 @@ export default function SendSheet(props) { const submitTransaction = useCallback( async (...args) => { if (Number(amountDetails.assetAmount) <= 0) { - logger.error(new RainbowError(`[SendSheet]: preventing tx submit because amountDetails.assetAmount is <= 0`), { - amountDetails, - }); + logger.sentry('amountDetails.assetAmount ? ', amountDetails?.assetAmount); + captureEvent('Preventing tx submit due to amount <= 0'); return false; } const submitSuccessful = await onSubmit(...args); @@ -669,7 +675,7 @@ export default function SendSheet(props) { const checkboxes = getDefaultCheckboxes({ ensProfile, isENS: true, - chainId, + network, toAddress: recipient, }); navigate(Routes.SEND_CONFIRMATION_SHEET, { @@ -681,7 +687,7 @@ export default function SendSheet(props) { isENS, isL2, isNft, - chainId: currentChainId, + network: ethereumUtils.getNetworkFromChainId(currentChainId), profilesEnabled, to: recipient, toAddress, @@ -696,7 +702,7 @@ export default function SendSheet(props) { isNft, nativeCurrencyInputRef, navigate, - chainId, + network, profilesEnabled, recipient, selected, @@ -746,11 +752,11 @@ export default function SendSheet(props) { const [ensSuggestions, setEnsSuggestions] = useState([]); const [loadingEnsSuggestions, setLoadingEnsSuggestions] = useState(false); useEffect(() => { - if (chainId === ChainId.mainnet && !recipientOverride && recipient?.length) { + if (network === Network.mainnet && !recipientOverride && recipient?.length) { setLoadingEnsSuggestions(true); debouncedFetchSuggestions(recipient, setEnsSuggestions, setLoadingEnsSuggestions, profilesEnabled); } - }, [chainId, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); + }, [network, recipient, recipientOverride, setEnsSuggestions, watchedAccounts, profilesEnabled]); useEffect(() => { checkAddress(debouncedInput); @@ -759,7 +765,7 @@ export default function SendSheet(props) { useEffect(() => { if (!currentProvider?._network?.chainId) return; - const assetChainId = selected.chainId; + const assetChainId = ethereumUtils.getChainIdFromNetwork(selected?.network); const currentProviderChainId = currentProvider._network.chainId; if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { @@ -772,7 +778,7 @@ export default function SendSheet(props) { }, false, currentProvider, - currentChainId + ethereumUtils.getNetworkFromChainId(currentChainId) ) .then(async gasLimit => { if (getNetworkObject({ chainId: currentChainId }).gas?.OptimismTxFee) { @@ -782,7 +788,7 @@ export default function SendSheet(props) { } }) .catch(e => { - logger.error(new RainbowError(`[SendSheet]: error calculating gas limit: ${e}`)); + logger.sentry('Error calculating gas limit', e); updateTxFee(null, null); }); } @@ -796,7 +802,7 @@ export default function SendSheet(props) { toAddress, updateTxFee, updateTxFeeForOptimism, - chainId, + network, isNft, currentChainId, ]); @@ -849,6 +855,7 @@ export default function SendSheet(props) { { const onSelectIcon = useCallback( (icon: string) => { - logger.debug(`[AppIconSection]: onSelectIcon: ${icon}`); + Logger.log('onSelectIcon', icon); analytics.track('Set App Icon', { appIcon: icon }); settingsChangeAppIcon(icon); }, @@ -32,9 +32,9 @@ const AppIconSection = () => { (unlockedAppIcons, appIconKey) => { const appIcon = unlockableAppIcons[appIconKey]; const unlocked = unlockableAppIconStorage.getBoolean(appIconKey); - logger.debug(`[AppIconSection]: checking if unlocked ${appIcon.displayName} ${unlocked} ${appIconKey}`); + Logger.log('checking if unlocked', appIcon.displayName, unlocked, appIconKey); if (unlocked) { - logger.debug(`[AppIconSection]: unlocked ${appIcon.displayName}`); + Logger.log('unlocked', appIcon.displayName); unlockedAppIcons[appIconKey] = appIcon; } return unlockedAppIcons; diff --git a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx index 6ea5ddbc87a..b776bc6dd9a 100644 --- a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx @@ -201,7 +201,7 @@ const ViewWalletBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(new RainbowError(`[ViewWalletBackup]: Logging into Google Drive failed`), { error: e }); + logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -281,9 +281,10 @@ const ViewWalletBackup = () => { try { await backupUserDataIntoCloud({ wallets: newWallets }); } catch (e) { - logger.error(new RainbowError(`[ViewWalletBackup]: Updating wallet userdata failed after new account creation`), { - error: e, + logger.error(e as RainbowError, { + description: 'Updating wallet userdata failed after new account creation', }); + captureException(e); throw e; } } @@ -300,9 +301,10 @@ const ViewWalletBackup = () => { await initializeWallet(); } } catch (e) { - logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { - error: e, + logger.error(e as RainbowError, { + description: 'Error while trying to add account', }); + captureException(e); if (isDamaged) { setTimeout(() => { showWalletErrorAlert(); @@ -321,8 +323,8 @@ const ViewWalletBackup = () => { }, 50); }); } catch (e) { - logger.error(new RainbowError(`[ViewWalletBackup]: Error while trying to add account`), { - error: e, + logger.error(e as RainbowError, { + description: 'Error while trying to add account', }); } }, [creatingWallet, dispatch, isDamaged, navigate, initializeWallet, profilesEnabled, wallet]); diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx index 9823fd2555f..17f0290ca6c 100644 --- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx @@ -179,9 +179,7 @@ export const WalletsAndBackup = () => { }); } catch (e) { Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found)); - logger.error(new RainbowError(`[WalletsAndBackup]: Logging into Google Drive failed`), { - error: e, - }); + logger.error(e as RainbowError); } } else { const isAvailable = await isCloudBackupAvailable(); @@ -234,8 +232,10 @@ export const WalletsAndBackup = () => { // @ts-expect-error - no params await initializeWallet(); } catch (err) { - logger.error(new RainbowError(`[WalletsAndBackup]: Failed to create new secret phrase`), { - error: err, + logger.error(new RainbowError('Failed to create new secret phrase'), { + extra: { + error: err, + }, }); } }, @@ -253,7 +253,7 @@ export const WalletsAndBackup = () => { (walletId: string, name: string) => { const wallet = wallets?.[walletId]; - const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? (wallet.addresses || [])[0].label : name; + const title = wallet?.imported && wallet.type === WalletTypes.privateKey ? wallet.addresses[0].label : name; navigate(SETTINGS_BACKUP_ROUTES.VIEW_WALLET_BACKUP, { imported: wallet?.imported, title, diff --git a/src/screens/SettingsSheet/components/CurrencySection.tsx b/src/screens/SettingsSheet/components/CurrencySection.tsx index ef2ce846884..b276d398354 100644 --- a/src/screens/SettingsSheet/components/CurrencySection.tsx +++ b/src/screens/SettingsSheet/components/CurrencySection.tsx @@ -10,7 +10,7 @@ import { ETH_ADDRESS, WBTC_ADDRESS, emojis, supportedNativeCurrencies } from '@/ import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { useTheme } from '@/theme'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const emojiData = Object.entries(emojis).map(([emoji, { name }]) => [name, emoji]); diff --git a/src/screens/SettingsSheet/components/DevSection.tsx b/src/screens/SettingsSheet/components/DevSection.tsx index 8821b07b2b8..26538473b3b 100644 --- a/src/screens/SettingsSheet/components/DevSection.tsx +++ b/src/screens/SettingsSheet/components/DevSection.tsx @@ -1,6 +1,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import lang from 'i18n-js'; import React, { useCallback, useContext, useState } from 'react'; +import { + // @ts-ignore + HARDHAT_URL_ANDROID, + // @ts-ignore + HARDHAT_URL_IOS, +} from 'react-native-dotenv'; // @ts-ignore import Restart from 'react-native-restart'; import { useDispatch } from 'react-redux'; @@ -10,8 +16,10 @@ import MenuContainer from './MenuContainer'; import MenuItem from './MenuItem'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { deleteAllBackups } from '@/handlers/cloudBackup'; +import { web3SetHttpProvider } from '@/handlers/web3'; import { RainbowContext } from '@/helpers/RainbowContext'; import isTestFlight from '@/helpers/isTestFlight'; +import networkTypes from '@/helpers/networkTypes'; import { useWallets } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { wipeKeychain } from '@/model/keychain'; @@ -23,7 +31,7 @@ import { clearImageMetadataCache } from '@/redux/imageMetadata'; import store from '@/redux/store'; import { walletsUpdate } from '@/redux/wallets'; import Routes from '@/navigation/routesNames'; -import { logger, RainbowError } from '@/logger'; +import logger from 'logger'; import { removeNotificationSettingsForWallet, useAllNotificationSettingsFromStorage, @@ -40,13 +48,11 @@ import { getFCMToken } from '@/notifications/tokens'; import { removeGlobalNotificationSettings } from '@/notifications/settings/settings'; import { nonceStore } from '@/state/nonces'; import { pendingTransactionsStore } from '@/state/pendingTransactions'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; const DevSection = () => { const { navigate } = useNavigation(); const { config, setConfig } = useContext(RainbowContext) as any; const { wallets } = useWallets(); - const setConnectedToHardhat = useConnectedToHardhatStore.getState().setConnectedToHardhat; const { walletNotificationSettings } = useAllNotificationSettingsFromStorage(); const dispatch = useDispatch(); @@ -69,16 +75,15 @@ const DevSection = () => { const connectToHardhat = useCallback(async () => { try { - const connectToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; - setConnectedToHardhat(!connectToHardhat); - logger.debug(`[DevSection] connected to hardhat`); + const ready = await web3SetHttpProvider((ios && HARDHAT_URL_IOS) || (android && HARDHAT_URL_ANDROID) || 'http://127.0.0.1:8545'); + logger.log('connected to hardhat', ready); } catch (e) { - setConnectedToHardhat(false); - logger.error(new RainbowError(`[DevSection] error connecting to hardhat: ${e}`)); + await web3SetHttpProvider(networkTypes.mainnet); + logger.log('error connecting to hardhat', e); } navigate(Routes.PROFILE_SCREEN); dispatch(explorerInit()); - }, [dispatch, navigate, setConnectedToHardhat]); + }, [dispatch, navigate]); const checkAlert = useCallback(async () => { try { @@ -312,15 +317,7 @@ const DevSection = () => { onPress={connectToHardhat} size={52} testID="hardhat-section" - titleComponent={ - - } + titleComponent={} /> } diff --git a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx index b415e1d4d30..9f8d64e0a9f 100644 --- a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx +++ b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx @@ -20,7 +20,7 @@ export const GoogleAccountSection: React.FC = () => { setAccountDetails(accountDetails ?? undefined); }) .catch(error => { - logger.error(new RainbowError(`[GoogleAccountSection]: Fetching google account data to display in Backups Section failed`), { + logger.error(new RainbowError(`Fetching google account data to display in Backups Section failed`), { error: (error as Error).message, }); }) @@ -66,7 +66,7 @@ export const GoogleAccountSection: React.FC = () => { const accountDetails = await getGoogleAccountUserData(); setAccountDetails(accountDetails ?? undefined); } catch (error) { - logger.error(new RainbowError(`[GoogleAccountSection]: Logging into Google Drive failed`), { + logger.error(new RainbowError(`Logging into Google Drive failed.`), { error: (error as Error).message, }); } finally { diff --git a/src/screens/SettingsSheet/components/NetworkSection.tsx b/src/screens/SettingsSheet/components/NetworkSection.tsx index b8edc2256cc..7e8e32ffa21 100644 --- a/src/screens/SettingsSheet/components/NetworkSection.tsx +++ b/src/screens/SettingsSheet/components/NetworkSection.tsx @@ -9,44 +9,44 @@ import { analytics } from '@/analytics'; import { Separator, Stack } from '@/design-system'; import { useAccountSettings, useInitializeAccountData, useLoadAccountData, useResetAccountState } from '@/hooks'; import { settingsUpdateNetwork } from '@/redux/settings'; -import { RainbowNetworkObjects } from '@/networks'; -import { ChainId } from '@/networks/types'; +import { Network } from '@/helpers'; +import { RainbowNetworks } from '@/networks'; -const networkObjects = values(RainbowNetworkObjects).filter(({ networkType }) => networkType !== 'layer2'); +const networks = values(RainbowNetworks).filter(({ networkType }) => networkType !== 'layer2'); interface NetworkSectionProps { inDevSection?: boolean; } const NetworkSection = ({ inDevSection }: NetworkSectionProps) => { - const { chainId, testnetsEnabled } = useAccountSettings(); + const { network, testnetsEnabled } = useAccountSettings(); const resetAccountState = useResetAccountState(); const loadAccountData = useLoadAccountData(); const initializeAccountData = useInitializeAccountData(); const dispatch = useDispatch(); const onNetworkChange = useCallback( - async (chainId: ChainId) => { + async (network: Network) => { await resetAccountState(); - await dispatch(settingsUpdateNetwork(chainId)); + await dispatch(settingsUpdateNetwork(network)); InteractionManager.runAfterInteractions(async () => { await loadAccountData(); initializeAccountData(); - analytics.track('Changed network', { chainId }); + analytics.track('Changed network', { network }); }); }, [dispatch, initializeAccountData, loadAccountData, resetAccountState] ); const renderNetworkList = useCallback(() => { - return networkObjects.map(({ name, id, networkType }) => ( + return networks.map(({ name, value, networkType }) => ( onNetworkChange(id)} - rightComponent={id === chainId && } + key={value} + onPress={() => onNetworkChange(value)} + rightComponent={value === network && } size={52} - testID={`${id}-network`} + testID={`${value}-network`} titleComponent={ { } /> )); - }, [inDevSection, chainId, onNetworkChange, testnetsEnabled]); + }, [inDevSection, network, onNetworkChange, testnetsEnabled]); return inDevSection ? ( }>{renderNetworkList()} diff --git a/src/screens/SettingsSheet/components/NotificationsSection.tsx b/src/screens/SettingsSheet/components/NotificationsSection.tsx index 1fb1099dd9a..6cbc3dee350 100644 --- a/src/screens/SettingsSheet/components/NotificationsSection.tsx +++ b/src/screens/SettingsSheet/components/NotificationsSection.tsx @@ -17,7 +17,7 @@ import { abbreviations, deviceUtils } from '@/utils'; import { Box } from '@/design-system'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '@/helpers/emojiHandler'; import { RainbowAccount } from '@/model/wallet'; -import { isTestnetChain } from '@/handlers/web3'; +import { isTestnetNetwork } from '@/handlers/web3'; import { useFocusEffect } from '@react-navigation/native'; import { SettingsLoadingIndicator } from '@/screens/SettingsSheet/components/SettingsLoadingIndicator'; import { showNotificationSubscriptionErrorAlert, showOfflineAlert } from '@/screens/SettingsSheet/components/notificationAlerts'; @@ -157,8 +157,8 @@ const WalletRow = ({ ens, groupOff, isTestnet, loading, notificationSettings, wa const NotificationsSection = () => { const { justBecameActive } = useAppState(); const { navigate } = useNavigation(); - const { chainId } = useAccountSettings(); - const isTestnet = isTestnetChain({ chainId }); + const { network } = useAccountSettings(); + const isTestnet = isTestnetNetwork(network); const { wallets, walletNames } = useWallets(); const { isConnected } = useNetInfo(); const { points_enabled, points_notifications_toggle } = useRemoteConfig(); diff --git a/src/screens/SettingsSheet/components/PrivacySection.tsx b/src/screens/SettingsSheet/components/PrivacySection.tsx index ef0da3ea34c..2be68989d53 100644 --- a/src/screens/SettingsSheet/components/PrivacySection.tsx +++ b/src/screens/SettingsSheet/components/PrivacySection.tsx @@ -25,7 +25,7 @@ const PrivacySection = () => { analyticsEnabled => { if (analyticsEnabled) { device.set(['doNotTrack'], true); - logger.debug(`[PrivacySection]: Analytics tracking disabled`); + logger.debug(`Analytics tracking disabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingDisabled); logger.disable(); analyticsV2.disable(); @@ -34,7 +34,7 @@ const PrivacySection = () => { device.set(['doNotTrack'], false); logger.enable(); analyticsV2.enable(); - logger.debug(`[PrivacySection]: Analytics tracking enabled`); + logger.debug(`Analytics tracking enabled`); analyticsV2.track(analyticsV2.event.analyticsTrackingEnabled); return true; } diff --git a/src/screens/SettingsSheet/components/SettingsSection.tsx b/src/screens/SettingsSheet/components/SettingsSection.tsx index 9fae44a89eb..27c29e18593 100644 --- a/src/screens/SettingsSheet/components/SettingsSection.tsx +++ b/src/screens/SettingsSheet/components/SettingsSection.tsx @@ -203,6 +203,27 @@ const SettingsSection = ({ testID="currency-section" titleComponent={} /> + {/* {(testnetsEnabled || IS_DEV) && ( + + } + onPress={onPressNetwork} + rightComponent={ + + {getNetworkObj(network).name} + + } + size={60} + testID="network-section" + titleComponent={ + + } + /> + )} */} wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth) .map(key => { const wallet = wallets[key]; - const visibleAccounts = (wallet.addresses || []).filter(a => a.visible); + const visibleAccounts = wallet.addresses.filter(a => a.visible); const totalAccounts = visibleAccounts.length; if ( diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 7f48aa81aaa..c3e68c1c306 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -43,7 +43,7 @@ import { TransactionSimulationResult, TransactionScanResultType, } from '@/graphql/__generated__/metadataPOST'; -import { Network, ChainId, chainIdToNameMapping } from '@/networks/types'; +import { Network } from '@/networks/types'; import { convertAmountToNativeDisplay, convertHexToString, @@ -64,7 +64,7 @@ import { IS_IOS } from '@/env'; import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { GasSpeedButton } from '@/components/gas'; -import { getNetworkObject } from '@/networks'; +import { getNetworkObj, getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; import { PERSONAL_SIGN, @@ -97,7 +97,7 @@ import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; import { getOnchainAssetBalance } from '@/handlers/assets'; import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { AddressOrEth } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; const COLLAPSED_CARD_HEIGHT = 56; const MAX_CARD_HEIGHT = 176; @@ -241,18 +241,18 @@ export const SignTransactionSheet = () => { const provider = getProvider({ chainId: currentChainId }); try { // attempt to re-run estimation - logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); // safety precaution: we want to ensure these properties are not used for gas estimation const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); if (rawGasLimit) { gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); + logger.error(new RainbowError('WC: error estimating gas'), { error }); } finally { - logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); const networkObject = getNetworkObject({ chainId: currentChainId }); if (networkObject && networkObject.gas.OptimismTxFee) { const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); @@ -283,7 +283,8 @@ export const SignTransactionSheet = () => { InteractionManager.runAfterInteractions(() => { if (currentChainId) { if (!isMessageRequest) { - startPollingGasFees(currentChainId); + const network = ethereumUtils.getNetworkFromChainId(currentChainId); + startPollingGasFees(network); fetchMethodName(transactionDetails?.payload?.params[0].data); } else { setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); @@ -367,9 +368,14 @@ export const SignTransactionSheet = () => { useEffect(() => { (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId: currentChainId, address: accountInfo.address }); + const asset = await ethereumUtils.getNativeAssetForNetwork(currentChainId, accountInfo.address); if (asset && provider) { - const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentChainId, provider); + const balance = await getOnchainAssetBalance( + asset, + accountInfo.address, + ethereumUtils.getNetworkFromChainId(currentChainId), + provider + ); if (balance) { const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; setNativeAsset(assetWithOnchainBalance); @@ -384,7 +390,7 @@ export const SignTransactionSheet = () => { (async () => { if (!isMessageRequest && !nonceForDisplay) { try { - const nonce = await getNextNonce({ address: currentAddress, chainId: currentChainId }); + const nonce = await getNextNonce({ address: currentAddress, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); if (nonce || nonce === 0) { const nonceAsString = nonce.toString(); setNonceForDisplay(nonceAsString); @@ -453,7 +459,7 @@ export const SignTransactionSheet = () => { } } } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); + logger.error(new RainbowError('Error while simulating'), { error }); } finally { setIsLoading(false); } @@ -513,7 +519,7 @@ export const SignTransactionSheet = () => { closeScreen(true); }, 300); } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error while handling cancel request'), { error }); + logger.error(new RainbowError('WC: error while handling cancel request'), { error }); closeScreen(true); } }, @@ -602,7 +608,7 @@ export const SignTransactionSheet = () => { if (!currentChainId) return; try { logger.debug( - '[SignTransactionSheet]: gas suggested by dapp', + 'WC: gas suggested by dapp', { gas: convertHexToString(gas), gasLimitFromPayload: convertHexToString(gasLimitFromPayload), @@ -623,22 +629,18 @@ export const SignTransactionSheet = () => { (!isNil(gas) && greaterThan(rawGasLimit, convertHexToString(gas))) || (!isNil(gasLimitFromPayload) && greaterThan(rawGasLimit, convertHexToString(gasLimitFromPayload))) ) { - logger.debug( - '[SignTransactionSheet]: using padded estimation!', - { gas: rawGasLimit.toString() }, - logger.DebugContext.walletconnect - ); + logger.debug('WC: using padded estimation!', { gas: rawGasLimit.toString() }, logger.DebugContext.walletconnect); gas = toHex(rawGasLimit); } } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); + logger.error(new RainbowError('WC: error estimating gas'), { error }); } // clean gas prices / fees sent from the dapp const cleanTxPayload = omitFlatten(txPayload, ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, chainId: currentChainId }); + const nonce = await getNextNonce({ address: accountInfo.address, network: ethereumUtils.getNetworkFromChainId(currentChainId) }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -647,7 +649,7 @@ export const SignTransactionSheet = () => { }; txPayloadUpdated = omitFlatten(txPayloadUpdated, ['from', 'gas', 'chainId']); - logger.debug(`[SignTransactionSheet]: ${transactionDetails.payload.method} payload`, { + logger.debug(`WC: ${transactionDetails.payload.method} payload`, { txPayload, txPayloadUpdated, }); @@ -701,7 +703,7 @@ export const SignTransactionSheet = () => { }); } } catch (e) { - logger.error(new RainbowError(`[SignTransactionSheet]: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); + logger.error(new RainbowError(`WC: Error while ${sendInsteadOfSign ? 'sending' : 'signing'} transaction`)); } if (response?.result) { @@ -734,7 +736,7 @@ export const SignTransactionSheet = () => { if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - chainId: currentChainId, + network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, address: accountAddress, }); txSavedInCurrentWallet = true; @@ -766,13 +768,13 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - chainId: currentChainId, + network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, address: txDetails?.from as string, }); }); } } else { - logger.error(new RainbowError(`[SignTransactionSheet]: Tx failure - ${formattedDappUrl}`), { + logger.error(new RainbowError(`WC: Tx failure - ${formattedDappUrl}`), { dappName: transactionDetails?.dappName, dappUrl: transactionDetails?.dappUrl, formattedDappUrl, @@ -922,7 +924,7 @@ export const SignTransactionSheet = () => { { /> ) : ( { }; interface SimulationCardProps { - chainId: ChainId; + currentNetwork: Network; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1097,7 +1099,7 @@ interface SimulationCardProps { } const SimulationCard = ({ - chainId, + currentNetwork, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1310,7 +1312,7 @@ const SimulationCard = ({ {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { symbol: walletBalance?.symbol, - network: chainIdToNameMapping[chainId], + network: getNetworkObj(currentNetwork).name, })} ) : ( @@ -1348,7 +1350,7 @@ const SimulationCard = ({ }; interface DetailsCardProps { - chainId: ChainId; + currentNetwork: Network; expandedCardBottomInset: number; isBalanceEnough: boolean | undefined; isLoading: boolean; @@ -1360,7 +1362,7 @@ interface DetailsCardProps { } const DetailsCard = ({ - chainId, + currentNetwork, expandedCardBottomInset, isBalanceEnough, isLoading, @@ -1418,15 +1420,15 @@ const DetailsCard = ({ - {} + {} {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - ethereumUtils.openAddressInBlockExplorer({ - address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', - chainId, - }) + ethereumUtils.openAddressInBlockExplorer( + meta?.to?.address || toAddress || meta?.transferTo?.address || '', + ethereumUtils.getChainIdFromNetwork(currentNetwork) + ) } value={ meta?.to?.name || @@ -1469,7 +1471,7 @@ const MessageCard = ({ displayMessage = sanitizedMessage; // eslint-disable-next-line no-empty } catch (e) { - logger.warn('[SignTransactionSheet]: Error while parsing message'); + logger.warn(''); } displayMessage = JSON.stringify(displayMessage, null, 4); @@ -1554,7 +1556,7 @@ const SimulatedEventRow = ({ const theme = useTheme(); const { nativeCurrency } = useAccountSettings(); const { data: externalAsset } = useExternalToken({ - address: (asset?.assetCode || '') as AddressOrEth, + address: asset?.assetCode || '', chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), currency: nativeCurrency, }); @@ -1630,12 +1632,12 @@ const SimulatedEventRow = ({ }; const DetailRow = ({ - chainId, + currentNetwork, detailType, onPress, value, }: { - chainId?: ChainId; + currentNetwork?: Network; detailType: DetailType; onPress?: () => void; value: string; @@ -1656,7 +1658,9 @@ const DetailRow = ({ {detailType === 'sourceCodeVerification' && ( )} - {detailType === 'chain' && chainId && } + {detailType === 'chain' && currentNetwork && ( + + )} {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( {value} diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index 0226d4e9323..dc3f25f771b 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -1,4 +1,5 @@ import { useRoute } from '@react-navigation/native'; +import { captureException } from '@sentry/react-native'; import { BigNumber } from 'bignumber.js'; import lang from 'i18n-js'; import { isEmpty } from 'lodash'; @@ -16,7 +17,7 @@ import { Emoji, Text } from '../components/text'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { removeRegistrationByName, saveCommitRegistrationParameters } from '@/redux/ensRegistration'; import { GasFeeTypes } from '@/entities'; -import { getFlashbotsProvider, getProvider, isL2Chain, toHex } from '@/handlers/web3'; +import { getFlashbotsProvider, getProviderForNetwork, isL2Chain, toHex } from '@/handlers/web3'; import { greaterThan } from '@/helpers/utilities'; import { useAccountSettings, useDimensions, useGas, useWallets } from '@/hooks'; import { sendTransaction } from '@/model/wallet'; @@ -26,9 +27,9 @@ import { updateGasFeeForSpeed } from '@/redux/gas'; import { ethUnits } from '@/references'; import styled from '@/styled-thing'; import { position } from '@/styles'; -import { gasUtils, safeAreaInsetValues } from '@/utils'; -import { logger, RainbowError } from '@/logger'; -import { getNetworkObject } from '@/networks'; +import { ethereumUtils, gasUtils, safeAreaInsetValues } from '@/utils'; +import logger from '@/utils/logger'; +import { getNetworkObj } from '@/networks'; import * as i18n from '@/languages'; import { updateTransaction } from '@/state/pendingTransactions'; @@ -100,7 +101,7 @@ const calcGasParamRetryValue = prevWeiValue => { export default function SpeedUpAndCancelSheet() { const { navigate, goBack } = useNavigation(); - const { accountAddress, chainId } = useAccountSettings(); + const { accountAddress, network } = useAccountSettings(); const { isHardwareWallet } = useWallets(); const dispatch = useDispatch(); const { height: deviceHeight } = useDimensions(); @@ -117,7 +118,7 @@ export default function SpeedUpAndCancelSheet() { const [minMaxPriorityFeePerGas, setMinMaxPriorityFeePerGas] = useState(calcGasParamRetryValue(tx.maxPriorityFeePerGas)); const [minMaxFeePerGas, setMinMaxFeePerGas] = useState(calcGasParamRetryValue(tx.maxFeePerGas)); const fetchedTx = useRef(false); - const [currentChainId, setCurrentChainId] = useState(null); + const [currentNetwork, setCurrentNetwork] = useState(null); const [currentProvider, setCurrentProvider] = useState(null); const [data, setData] = useState(null); const [gasLimit, setGasLimit] = useState(null); @@ -171,13 +172,9 @@ export default function SpeedUpAndCancelSheet() { updatedTx.hash = res.result?.hash; updatedTx.status = 'pending'; updatedTx.type = 'cancel'; - updateTransaction({ - address: accountAddress, - transaction: updatedTx, - chainId: currentChainId, - }); + updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); } catch (e) { - logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting cancel tx: ${e}`)); + logger.log('Error submitting cancel tx', e); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -188,7 +185,7 @@ export default function SpeedUpAndCancelSheet() { }, [ accountAddress, cancelCommitTransactionHash, - currentChainId, + currentNetwork, currentProvider, getNewTransactionGasParams, goBack, @@ -245,13 +242,9 @@ export default function SpeedUpAndCancelSheet() { updatedTx.status = 'pending'; updatedTx.type = 'speed_up'; - updateTransaction({ - address: accountAddress, - transaction: updatedTx, - chainId: currentChainId, - }); + updateTransaction({ address: accountAddress, transaction: updatedTx, network: currentNetwork }); } catch (e) { - logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error submitting speed up tx: ${e}`)); + logger.log('Error submitting speed up tx', e); } finally { // if its a hardware wallet we need to close the hardware tx sheet if (isHardwareWallet) { @@ -261,7 +254,7 @@ export default function SpeedUpAndCancelSheet() { } }, [ accountAddress, - currentChainId, + currentNetwork, currentProvider, data, gasLimit, @@ -285,21 +278,21 @@ export default function SpeedUpAndCancelSheet() { // Set the network useEffect(() => { - setCurrentChainId(tx?.chainId || chainId); - }, [chainId, tx.chainId]); + setCurrentNetwork(tx?.network || network); + }, [network, tx.network]); // Set the provider useEffect(() => { - if (currentChainId) { - startPollingGasFees(currentChainId, tx.flashbots); + if (currentNetwork) { + startPollingGasFees(currentNetwork, tx.flashbots); const updateProvider = async () => { let provider; - if (getNetworkObject({ chainId: tx?.chainId })?.features?.flashbots && tx.flashbots) { - logger.debug(`[SpeedUpAndCancelSheet]: using flashbots provider for chainId ${tx?.chainId}`); + if (getNetworkObj(tx?.network).features.flashbots && tx.flashbots) { + logger.debug('using flashbots provider'); provider = await getFlashbotsProvider(); } else { - logger.debug(`[SpeedUpAndCancelSheet]: using provider for network ${tx?.chainId}`); - provider = getProvider({ chainId: currentChainId }); + logger.debug('using normal provider'); + provider = getProviderForNetwork(currentNetwork); } setCurrentProvider(provider); }; @@ -310,7 +303,7 @@ export default function SpeedUpAndCancelSheet() { stopPollingGasFees(); }; } - }, [currentChainId, startPollingGasFees, stopPollingGasFees, tx?.chainId, tx.flashbots]); + }, [currentNetwork, startPollingGasFees, stopPollingGasFees, tx.flashbots, tx?.network]); // Update gas limit useEffect(() => { @@ -320,11 +313,11 @@ export default function SpeedUpAndCancelSheet() { updateGasFeeOption(gasUtils.URGENT); speedUrgentSelected.current = true; } - }, [gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); + }, [currentNetwork, gasLimit, gasFeeParamsBySpeed, updateGasFeeOption, updateTxFee]); useEffect(() => { const init = async () => { - if (currentChainId && currentProvider && !fetchedTx.current) { + if (currentNetwork && currentProvider && !fetchedTx.current) { try { fetchedTx.current = true; const hexGasLimit = toHex(tx?.gasLimit?.toString() || '0x'); @@ -349,13 +342,11 @@ export default function SpeedUpAndCancelSheet() { setMinGasPrice(calcGasParamRetryValue(hexGasPrice)); } } catch (e) { - logger.error(new RainbowError(`[SpeedUpAndCancelSheet]: error fetching tx info: ${e}`), { - data: { - tx, - }, - }); - - // NOTE: We don't care about this for cancellations + logger.log('something went wrong while fetching tx info ', e); + logger.sentry('Error speeding up or canceling transaction: [error]', e); + logger.sentry('Error speeding up or canceling transaction: [transaction]', tx); + const speedUpOrCancelError = new Error('Error speeding up or canceling transaction'); + captureException(speedUpOrCancelError); if (type === SPEED_UP) { Alert.alert(lang.t('wallet.speed_up.unable_to_speed_up'), lang.t('wallet.speed_up.problem_while_fetching_transaction_data'), [ { @@ -363,12 +354,13 @@ export default function SpeedUpAndCancelSheet() { }, ]); } + // We don't care about this for cancellations } } }; init(); - }, [currentChainId, currentProvider, goBack, isL2, tx, tx?.gasLimit, tx.hash, type, updateGasFeeOption]); + }, [currentNetwork, currentProvider, goBack, isL2, network, tx, tx.gasLimit, tx.hash, type, updateGasFeeOption]); useEffect(() => { if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current) { @@ -482,7 +474,8 @@ export default function SpeedUpAndCancelSheet() { ({ color: colors.alpha(colors.blueGreyDark, 0.3), @@ -67,8 +69,8 @@ const NetworkPill = ({ chainIds }) => { const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); const networkMenuItems = useMemo(() => { - RainbowNetworkObjects.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ - actionKey: network.id, + RainbowNetworks.filter(({ features, id }) => features.walletconnect && chainIds.includes(id)).map(network => ({ + actionKey: network.value, actionTitle: network.name, icon: { iconType: 'ASSET', @@ -126,7 +128,7 @@ const NetworkPill = ({ chainIds }) => { ) : ( - {availableNetworkChainIds[0] !== ChainId.mainnet ? ( + {availableNetworkChainIds[0] !== Network.mainnet ? ( ) : ( @@ -134,7 +136,7 @@ const NetworkPill = ({ chainIds }) => { - {chainIdToNameMapping[availableNetworkChainIds[0]]} + {getNetworkObj(availableNetworkChainIds[0]).name} @@ -149,7 +151,7 @@ export default function WalletConnectApprovalSheet() { const { colors, isDarkMode } = useTheme(); const { goBack } = useNavigation(); const { params } = useRoute(); - const { chainId: settingsChainId, accountAddress } = useAccountSettings(); + const { network, accountAddress } = useAccountSettings(); const { navigate } = useNavigation(); const { selectedWallet, walletNames, wallets } = useWallets(); const handled = useRef(false); @@ -180,8 +182,8 @@ export default function WalletConnectApprovalSheet() { const failureExplainSheetVariant = params?.failureExplainSheetVariant; const chainIds = meta?.chainIds; // WC v2 supports multi-chain const chainId = meta?.proposedChainId || chainIds?.[0] || 1; // WC v1 only supports 1 - const currentChainId = params?.currentChainId; - const [approvalChainId, setApprovalChainId] = useState(currentChainId || settingsChainId); + const currentNetwork = params?.currentNetwork; + const [approvalNetwork, setApprovalNetwork] = useState(currentNetwork || network); const isWalletConnectV2 = meta.isWalletConnectV2; const { dappName, dappUrl, dappScheme, imageUrl, peerId } = meta; @@ -223,18 +225,18 @@ export default function WalletConnectApprovalSheet() { * v2. */ const approvalNetworkInfo = useMemo(() => { - const networkObj = getNetworkObject({ chainId: approvalChainId }); + const networkObj = getNetworkObj(approvalNetwork); return { chainId: networkObj.id, color: isDarkMode ? networkObj.colors.dark : networkObj.colors.light, name: networkObj.name, value: networkObj.value, }; - }, [approvalChainId, isDarkMode]); + }, [approvalNetwork, isDarkMode]); const handleOnPressNetworksMenuItem = useCallback( - ({ nativeEvent }) => setApprovalChainId(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), - [setApprovalChainId] + ({ nativeEvent }) => setApprovalNetwork(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), + [setApprovalNetwork] ); const handleSuccess = useCallback( @@ -251,7 +253,8 @@ export default function WalletConnectApprovalSheet() { useEffect(() => { if (chainId && type === WalletConnectApprovalSheetType.connect) { - setApprovalChainId(chainId); + const network = ethereumUtils.getNetworkFromChainId(Number(chainId)); + setApprovalNetwork(network); } }, [chainId, type]); @@ -281,7 +284,7 @@ export default function WalletConnectApprovalSheet() { }, [handleSuccess, goBack]); const onPressAndroid = useCallback(() => { - androidShowNetworksActionSheet(({ chainId }) => setApprovalChainId(chainId)); + androidShowNetworksActionSheet(({ network }) => setApprovalNetwork(network)); }, []); const handlePressChangeWallet = useCallback(() => { @@ -355,11 +358,19 @@ export default function WalletConnectApprovalSheet() { }} > - + {`${ - type === WalletConnectApprovalSheetType.connect ? approvalNetworkInfo.name : chainIdToNameMapping[chainId] + type === WalletConnectApprovalSheetType.connect + ? approvalNetworkInfo.name + : ethereumUtils.getNetworkNameFromChainId(Number(chainId)) } ${type === WalletConnectApprovalSheetType.connect && menuItems.length > 1 ? '􀁰' : ''}`} @@ -368,8 +379,8 @@ export default function WalletConnectApprovalSheet() { } }, [ NetworkSwitcherParent, - approvalNetworkInfo.chainId, approvalNetworkInfo.name, + approvalNetworkInfo.value, chainId, chainIds, handleOnPressNetworksMenuItem, @@ -400,7 +411,7 @@ export default function WalletConnectApprovalSheet() { {type === WalletConnectApprovalSheetType.connect ? lang.t(lang.l.walletconnect.wants_to_connect) : lang.t(lang.l.walletconnect.wants_to_connect_to_network, { - network: chainIdToNameMapping[chainId], + network: ethereumUtils.getNetworkNameFromChainId(Number(chainId)), })} diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index d78336c324e..02ea352ec5c 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -1,17 +1,22 @@ -import { View } from 'react-native'; -import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { InteractionManager, View } from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { AssetList } from '../../components/asset-list'; import { Page } from '../../components/layout'; +import { Network } from '@/helpers'; import { useRemoveFirst } from '@/navigation/useRemoveFirst'; +import { settingsUpdateNetwork } from '@/redux/settings'; import { navbarHeight } from '@/components/navbar/Navbar'; import { Box } from '@/design-system'; import { useAccountAccentColor, useAccountSettings, + useInitializeAccountData, useInitializeWallet, + useLoadAccountData, useLoadAccountLateData, useLoadGlobalLateData, + useResetAccountState, useWalletSectionsData, } from '@/hooks'; import Routes from '@rainbow-me/routes'; @@ -46,8 +51,28 @@ const WalletScreen: React.FC = ({ navigation, route }) => { const loadAccountLateData = useLoadAccountLateData(); const loadGlobalLateData = useLoadGlobalLateData(); + const dispatch = useDispatch(); + const resetAccountState = useResetAccountState(); + const loadAccountData = useLoadAccountData(); + const initializeAccountData = useInitializeAccountData(); const insets = useSafeAreaInsets(); + const revertToMainnet = useCallback(async () => { + await resetAccountState(); + await dispatch(settingsUpdateNetwork(Network.mainnet)); + InteractionManager.runAfterInteractions(async () => { + await loadAccountData(); + initializeAccountData(); + }); + }, [dispatch, initializeAccountData, loadAccountData, resetAccountState]); + + useEffect(() => { + const supportedNetworks = [Network.mainnet]; + if (!supportedNetworks.includes(currentNetwork)) { + revertToMainnet(); + } + }, [currentNetwork, revertToMainnet]); + const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady); const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData(); diff --git a/src/screens/WelcomeScreen/index.tsx b/src/screens/WelcomeScreen/index.tsx index c175c259d0a..462c892cfed 100644 --- a/src/screens/WelcomeScreen/index.tsx +++ b/src/screens/WelcomeScreen/index.tsx @@ -27,7 +27,7 @@ import Routes from '@rainbow-me/routes'; import styled from '@/styled-thing'; import { position } from '@/styles'; import { ThemeContextProps, useTheme } from '@/theme'; -import { logger } from '@/logger'; +import logger from 'logger'; import { IS_ANDROID, IS_TEST } from '@/env'; import { WelcomeScreenRainbowButton } from '@/screens/WelcomeScreen/WelcomeScreenRainbowButton'; @@ -85,7 +85,7 @@ export default function WelcomeScreen() { useEffect(() => { const initialize = async () => { if (IS_TEST) { - logger.debug('[WelcomeScreen] Skipping animations because IS_TEST is true'); + logger.log('Skipping animations because IS_TEST is true'); contentAnimation.value = 1; createWalletButtonAnimation.value = 1; colorAnimation.value = 0; diff --git a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx b/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx deleted file mode 100644 index 78a7249adf2..00000000000 --- a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { GenericCard } from '@/components/cards/GenericCard'; -import { ImgixImage } from '@/components/images'; -import { HORIZONTAL_PADDING } from './DiscoverHome'; -import { deviceUtils } from '@/utils'; -import { FeaturedResult } from '@/graphql/__generated__/arc'; -import { StyleSheet } from 'react-native'; - -const { width: SCREEN_WIDTH } = deviceUtils.dimensions; -const CARD_WIDTH = SCREEN_WIDTH - HORIZONTAL_PADDING * 2; -const CARD_HEIGHT = 238; - -type DiscoverFeaturedResultsCardProps = { - handlePress: () => void; - featuredResult: FeaturedResult; -}; - -export const DiscoverFeaturedResultsCard = ({ handlePress, featuredResult }: DiscoverFeaturedResultsCardProps) => { - return ( - - - - ); -}; - -const styles = StyleSheet.create({ - image: { - width: CARD_WIDTH, - height: CARD_HEIGHT, - borderRadius: 12, - }, -}); diff --git a/src/screens/discover/components/DiscoverHome.tsx b/src/screens/discover/components/DiscoverHome.js similarity index 69% rename from src/screens/discover/components/DiscoverHome.tsx rename to src/screens/discover/components/DiscoverHome.js index 866c7688096..1cf59a2c636 100644 --- a/src/screens/discover/components/DiscoverHome.tsx +++ b/src/screens/discover/components/DiscoverHome.js @@ -1,13 +1,6 @@ -import React, { useCallback } from 'react'; -import useExperimentalFlag, { - OP_REWARDS, - PROFILES, - HARDWARE_WALLETS, - MINTS, - NFT_OFFERS, - FEATURED_RESULTS, -} from '@rainbow-me/config/experimentalHooks'; -import { isTestnetChain } from '@/handlers/web3'; +import React, { useMemo } from 'react'; +import useExperimentalFlag, { OP_REWARDS, PROFILES, HARDWARE_WALLETS, MINTS, NFT_OFFERS } from '@rainbow-me/config/experimentalHooks'; +import { isTestnetNetwork } from '@/handlers/web3'; import { Inline, Inset, Stack, Box } from '@/design-system'; import { useAccountSettings, useWallets } from '@/hooks'; import { ENSCreateProfileCard } from '@/components/cards/ENSCreateProfileCard'; @@ -24,43 +17,26 @@ import { MintsCard } from '@/components/cards/MintsCard/MintsCard'; import { FeaturedMintCard } from '@/components/cards/FeaturedMintCard'; import { IS_TEST } from '@/env'; import { RemoteCardCarousel } from '@/components/cards/remote-cards'; -import { FeaturedResultStack } from '@/components/FeaturedResult/FeaturedResultStack'; -import Routes from '@/navigation/routesNames'; -import { useNavigation } from '@/navigation'; -import { DiscoverFeaturedResultsCard } from './DiscoverFeaturedResultsCard'; - -export const HORIZONTAL_PADDING = 20; export default function DiscoverHome() { - const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results } = useRemoteConfig(); - const { chainId } = useAccountSettings(); + const { profiles_enabled, mints_enabled, op_rewards_enabled } = useRemoteConfig(); + const { network } = useAccountSettings(); const profilesEnabledLocalFlag = useExperimentalFlag(PROFILES); const profilesEnabledRemoteFlag = profiles_enabled; const hardwareWalletsEnabled = useExperimentalFlag(HARDWARE_WALLETS); const nftOffersEnabled = useExperimentalFlag(NFT_OFFERS); - const featuredResultsEnabled = (useExperimentalFlag(FEATURED_RESULTS) || featured_results) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; const opRewardsLocalFlag = useExperimentalFlag(OP_REWARDS); const opRewardsRemoteFlag = op_rewards_enabled; - const testNetwork = isTestnetChain({ chainId }); - const { navigate } = useNavigation(); + const testNetwork = isTestnetNetwork(network); const isProfilesEnabled = profilesEnabledLocalFlag && profilesEnabledRemoteFlag; const { wallets } = useWallets(); - const hasHardwareWallets = Object.keys(wallets || {}).filter(key => (wallets || {})[key].type === walletTypes.bluetooth).length > 0; - - const onNavigate = useCallback( - (url: string) => { - navigate(Routes.DAPP_BROWSER_SCREEN, { - url, - }); - }, - [navigate] - ); + const hasHardwareWallets = Object.keys(wallets || {}).filter(key => wallets[key].type === walletTypes.bluetooth).length > 0; return ( - + {!testNetwork ? ( @@ -79,9 +55,6 @@ export default function DiscoverHome() { {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */} {!IS_TEST && nftOffersEnabled && } {/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/} - {featuredResultsEnabled && ( - - )} {(opRewardsRemoteFlag || opRewardsLocalFlag) && } {hardwareWalletsEnabled && !hasHardwareWallets && } {isProfilesEnabled && } diff --git a/src/screens/discover/components/DiscoverSearch.js b/src/screens/discover/components/DiscoverSearch.js index 52741d9fc4e..74233112e6b 100644 --- a/src/screens/discover/components/DiscoverSearch.js +++ b/src/screens/discover/components/DiscoverSearch.js @@ -19,10 +19,11 @@ import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { ethereumUtils } from '@/utils'; +import { Network } from '@/helpers'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { navigateToMintCollection } from '@/resources/reservoir/mints'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; -import { ChainId, Network } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; export const SearchContainer = styled(Row)({ height: '100%', @@ -133,7 +134,7 @@ export default function DiscoverSearch() { network === Network.optimism; } const contractAddress = query.split('/')[1]; - navigateToMintCollection(contractAddress, ethereumUtils.getChainIdFromNetwork(network)); + navigateToMintCollection(contractAddress, network); } }; checkAndHandleMint(searchQuery); diff --git a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx index fca391ca357..4842cdbc8cb 100644 --- a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx +++ b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx @@ -109,10 +109,10 @@ export function PairHardwareWalletSigningSheet() { const importHardwareWallet = useCallback( async (deviceId: string) => { if (busy) { - logger.debug('[PairHardwareWalletSigningSheet]: busy, already trying to import', { deviceId }, DebugContext.ledger); + logger.debug('[importHardwareWallet] - busy, already trying to import', { deviceId }, DebugContext.ledger); return; } - logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId }, DebugContext.ledger); + logger.debug('[importHardwareWallet] - importing Hardware Wallet', { deviceId }, DebugContext.ledger); handleSetSeedPhrase(deviceId); handlePressImportButton(null, deviceId, null, null); }, @@ -137,7 +137,8 @@ export function PairHardwareWalletSigningSheet() { }, }); } else { - logger.error(new RainbowError('[PairHardwareWalletSigningSheet]: Disconnected or Unkown Error'), { errorType }); + logger.error(new RainbowError('[importHardwareWallet] - Disconnected or Unkown Error'), { errorType }); + logger.info('[importHardwareWallet] - issue connecting, trying again '); const transport = await TransportBLE.open(deviceId); await checkLedgerConnection({ transport, diff --git a/src/screens/mints/MintSheet.tsx b/src/screens/mints/MintSheet.tsx index 399b8ca3e56..2a58d15fcf7 100644 --- a/src/screens/mints/MintSheet.tsx +++ b/src/screens/mints/MintSheet.tsx @@ -56,7 +56,7 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { getNextNonce } from '@/state/nonces'; import { metadataPOSTClient } from '@/graphql'; import { Transaction } from '@/graphql/__generated__/metadataPOST'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const NFT_IMAGE_HEIGHT = 250; // inset * 2 -> 28 *2 @@ -200,7 +200,7 @@ const MintSheet = () => { // check address balance useEffect(() => { const checkInsufficientEth = async () => { - const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork({ chainId, address: accountAddress }))?.balance?.amount ?? 0; + const nativeBalance = (await ethereumUtils.getNativeAssetForNetwork(chainId, accountAddress))?.balance?.amount ?? 0; const totalMintPrice = multiply(price.amount, quantity); if (greaterThanOrEqualTo(totalMintPrice, nativeBalance)) { @@ -237,7 +237,8 @@ const MintSheet = () => { // start poll gas price useEffect(() => { - startPollingGasFees(chainId); + const network = ethereumUtils.getNetworkFromChainId(chainId); + startPollingGasFees(network); return () => { stopPollingGasFees(); @@ -263,7 +264,7 @@ const MintSheet = () => { const txs: Transaction[] = []; steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${step.error}`)); + logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${step.error}`)); return; } step.items?.forEach(item => { @@ -295,7 +296,7 @@ const MintSheet = () => { }); } catch (e) { setGasError(true); - logger.error(new RainbowError(`[MintSheet]: Gas Step Error: ${(e as Error).message}`)); + logger.error(new RainbowError(`NFT Mints: Gas Step Error: ${(e as Error).message}`)); } }; estimateMintGas(); @@ -343,7 +344,7 @@ const MintSheet = () => { return; } - logger.debug('[MintSheet]: Minting NFT', { name: mintCollection.name }); + logger.info('Minting NFT', { name: mintCollection.name }); analyticsV2.track(event.mintsMintingNFT, { collectionName: mintCollection.name || '', contract: mintCollection.id || '', @@ -364,8 +365,9 @@ const MintSheet = () => { }); const feeAddress = getRainbowFeeAddress(chainId); - const nonce = await getNextNonce({ address: accountAddress, chainId }); + const nonce = await getNextNonce({ address: accountAddress, network: ethereumUtils.getNetworkFromChainId(chainId) }); try { + const currentNetwork = ethereumUtils.getNetworkFromChainId(chainId); await getClient()?.actions.mintToken({ items: [ { @@ -379,18 +381,17 @@ const MintSheet = () => { onProgress: (steps: Execute['steps']) => { steps.forEach(step => { if (step.error) { - logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${step.error}`)); + logger.error(new RainbowError(`Error minting NFT: ${step.error}`)); setMintStatus('error'); return; } step.items?.forEach(item => { if (item.txHashes?.[0]?.txHash && txRef.current !== item.txHashes[0].txHash && item.status === 'incomplete') { const asset = { - chainId, type: 'nft', icon_url: imageUrl, address: mintCollection.id || '', - network: ethereumUtils.getNetworkFromChainId(chainId), + network: currentNetwork, name: mintCollection.name || '', decimals: 18, symbol: 'NFT', @@ -400,7 +401,7 @@ const MintSheet = () => { const paymentAsset = { type: 'nft', address: ETH_ADDRESS, - network: ethereumUtils.getNetworkFromChainId(chainId), + network: currentNetwork, name: mintCollection.publicMintInfo?.price?.currency?.name || 'Ethereum', decimals: mintCollection.publicMintInfo?.price?.currency?.decimals || 18, symbol: ETH_SYMBOL, @@ -413,7 +414,7 @@ const MintSheet = () => { to: item.data?.to, from: item.data?.from, hash: item.txHashes[0].txHash, - network: ethereumUtils.getNetworkFromChainId(chainId), + network: currentNetwork, nonce, changes: [ { @@ -436,7 +437,7 @@ const MintSheet = () => { addNewTransaction({ transaction: tx, address: accountAddress, - chainId, + network: currentNetwork, }); analyticsV2.track(event.mintsMintedNFT, { collectionName: mintCollection.name || '', @@ -461,7 +462,7 @@ const MintSheet = () => { quantity, priceInEth: mintPriceAmount, }); - logger.error(new RainbowError(`[MintSheet]: Error minting NFT: ${(e as Error).message}`)); + logger.error(new RainbowError(`Error minting NFT: ${(e as Error).message}`)); } }, [ accountAddress, @@ -682,9 +683,7 @@ const MintSheet = () => { symbol="􀉆" label={i18n.t(i18n.l.minting.contract)} value={ - ethereumUtils.openAddressInBlockExplorer({ address: mintCollection.id!, chainId })} - > + ethereumUtils.openAddressInBlockExplorer(mintCollection.id!, chainId)}> {contractAddressDisplay} diff --git a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx index 7fa9780b228..5bee2b44794 100644 --- a/src/screens/points/claim-flow/ClaimRewardsPanel.tsx +++ b/src/screens/points/claim-flow/ClaimRewardsPanel.tsx @@ -5,7 +5,7 @@ import { Bleed, Box, Text, TextShadow, globalColors, useBackgroundColor, useColo import * as i18n from '@/languages'; import { ListHeader, ListPanel, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; import { ChainImage } from '@/components/coin-icon/ChainImage'; -import { ChainId, ChainNameDisplay } from '@/networks/types'; +import { ChainId, ChainNameDisplay } from '@/__swaps__/types/chains'; import ethereumUtils, { useNativeAsset } from '@/utils/ethereumUtils'; import { useAccountAccentColor, useAccountProfile, useAccountSettings } from '@/hooks'; import { safeAreaInsetValues } from '@/utils'; @@ -17,6 +17,7 @@ import { PointsErrorType } from '@/graphql/__generated__/metadata'; import { useMutation } from '@tanstack/react-query'; import { invalidatePointsQuery, usePoints } from '@/resources/points'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; +import { Network } from '@/helpers'; import { ButtonPressAnimation } from '@/components/animations'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; @@ -28,7 +29,7 @@ import { walletExecuteRap } from '@/raps/execute'; import { ParsedAsset } from '@/__swaps__/types/assets'; import { chainNameFromChainId } from '@/__swaps__/utils/chains'; import { loadWallet } from '@/model/wallet'; -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork } from '@/handlers/web3'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { getGasSettingsBySpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { useMeteorologySuggestions } from '@/__swaps__/utils/meteorology'; @@ -210,7 +211,7 @@ const ClaimingRewards = ({ }>({ mutationFn: async () => { // Fetch the native asset from the origin chain - const opEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.optimism }); + const opEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.optimism); const opEth = { ...opEth_, chainName: chainNameFromChainId(ChainId.optimism), @@ -219,9 +220,9 @@ const ClaimingRewards = ({ // Fetch the native asset from the destination chain let destinationEth_; if (chainId === ChainId.base) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.base }); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.base); } else if (chainId === ChainId.zora) { - destinationEth_ = await ethereumUtils.getNativeAssetForNetwork({ chainId: ChainId.zora }); + destinationEth_ = await ethereumUtils.getNativeAssetForNetwork(ChainId.zora); } else { destinationEth_ = opEth; } @@ -260,7 +261,7 @@ const ClaimingRewards = ({ gasParams, } satisfies RapSwapActionParameters<'claimBridge'>; - const provider = getProvider({ chainId: ChainId.optimism }); + const provider = getProviderForNetwork(Network.optimism); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, @@ -289,7 +290,7 @@ const ClaimingRewards = ({ setClaimStatus('bridge-error'); } - logger.error(new RainbowError('[ClaimRewardsPanel]: Failed to claim ETH rewards'), { message: errorMessage }); + logger.error(new RainbowError('ETH REWARDS CLAIM ERROR'), { message: errorMessage }); return { nonce: null }; } diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 915e6c22a8f..60951803f17 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -9,6 +9,7 @@ import { RAINBOW_PROFILES_BASE_URL } from '@/references'; import Routes from '@/navigation/routesNames'; import { ethereumUtils, isENSNFTRecord } from '@/utils'; import { address as formatAddress } from '@/utils/abbreviations'; +import { Network } from '@/networks/types'; import { ContactAvatar, showDeleteContactActionSheet } from '@/components/contacts'; import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; import MaskedView from '@react-native-masked-view/masked-view'; @@ -19,7 +20,7 @@ import { useTheme } from '@/theme'; import LinearGradient from 'react-native-linear-gradient'; import { ButtonPressAnimation } from '@/components/animations'; import { noop } from 'lodash'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const ACTIONS = { ADD_CONTACT: 'add-contact', @@ -51,8 +52,9 @@ export const LeaderboardRow = memo(function LeaderboardRow({ const { setClipboard } = useClipboard(); const { contacts, onRemoveContact } = useContacts(); const isSelectedWallet = useMemo(() => { - const visibleWallet = selectedWallet.addresses?.find((wallet: { visible: boolean }) => wallet.visible); - return visibleWallet?.address.toLowerCase() === address?.toLowerCase(); + const visibleWallet = selectedWallet.addresses.find((wallet: { visible: boolean }) => wallet.visible); + ``; + return visibleWallet.address.toLowerCase() === address?.toLowerCase(); }, [selectedWallet.addresses, address]); const contact = address ? contacts[address.toLowerCase()] : undefined; @@ -127,7 +129,7 @@ export const LeaderboardRow = memo(function LeaderboardRow({ setClipboard(address); } if (address && actionKey === ACTIONS.ETHERSCAN) { - ethereumUtils.openAddressInBlockExplorer({ address: address, chainId: ChainId.mainnet }); + ethereumUtils.openAddressInBlockExplorer(address, ChainId.mainnet); } if (actionKey === ACTIONS.ADD_CONTACT) { navigate(Routes.MODAL_SCREEN, { diff --git a/src/screens/points/content/PointsContent.tsx b/src/screens/points/content/PointsContent.tsx index c90c006f7af..e6e6012c8ed 100644 --- a/src/screens/points/content/PointsContent.tsx +++ b/src/screens/points/content/PointsContent.tsx @@ -62,7 +62,7 @@ import { format, intervalToDuration, isToday } from 'date-fns'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ETH_REWARDS, useExperimentalFlag } from '@/config'; import { RewardsActionButton } from '../components/RewardsActionButton'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const InfoCards = ({ points }: { points: GetPointsDataForWalletQuery | undefined }) => { const labelSecondary = useForegroundColor('labelSecondary'); diff --git a/src/screens/points/content/ReferralContent.tsx b/src/screens/points/content/ReferralContent.tsx index f9719f9fd16..e17d4aa6c58 100644 --- a/src/screens/points/content/ReferralContent.tsx +++ b/src/screens/points/content/ReferralContent.tsx @@ -87,7 +87,7 @@ export function ReferralContent() { setStatus('invalid'); haptics.notificationError(); } else { - logger.error(new RainbowError('[ReferralContent]: Error validating referral code'), { + logger.error(new RainbowError('Error validating referral code'), { referralCode: code, }); Alert.alert(i18n.t(i18n.l.points.referral.error)); diff --git a/src/screens/points/contexts/PointsProfileContext.tsx b/src/screens/points/contexts/PointsProfileContext.tsx index bed940f82a9..193d206760e 100644 --- a/src/screens/points/contexts/PointsProfileContext.tsx +++ b/src/screens/points/contexts/PointsProfileContext.tsx @@ -13,10 +13,10 @@ import { loadWallet, signPersonalMessage } from '@/model/wallet'; import { RainbowError, logger } from '@/logger'; import { queryClient } from '@/react-query'; import { useNavigation } from '@/navigation'; -import { getProvider } from '@/handlers/web3'; +import { getProviderForNetwork } from '@/handlers/web3'; +import { Network } from '@/networks/types'; import { analyticsV2 } from '@/analytics'; import { delay } from '@/utils/delay'; -import { ChainId } from '@/networks/types'; type PointsProfileContext = { step: RainbowPointsFlowSteps; @@ -134,7 +134,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); throw new RainbowError('Points: Error getting onboard challenge'); } - const provider = getProvider({ chainId: ChainId.mainnet }); + const provider = getProviderForNetwork(Network.mainnet); const wallet = await loadWallet({ address: accountAddress, provider }); if (!wallet) { Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); @@ -176,7 +176,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode Alert.alert(i18n.t(i18n.l.points.console.generic_alert)); break; } - logger.error(new RainbowError('[PointsProfileContext]: Failed to onboard user'), { errorType }); + logger.info('Points: Failed to onboard user', { errorType }); analyticsV2.track(analyticsV2.event.pointsOnboardingScreenFailedToSignIn, { deeplinked, referralCode: !!referralCode, @@ -201,7 +201,7 @@ export const PointsProfileProvider = ({ children }: { children: React.ReactNode hardwareWallet: isHardwareWallet, errorType: undefined, }); - logger.error(new RainbowError('[PointsProfileContext]: signIn error'), { error }); + logger.error(new RainbowError('Points: signIn error'), { error }); } }, [accountAddress, deeplinked, goBack, isHardwareWallet, referralCode]); diff --git a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx index 9040b9a8e1b..223fa5d7d06 100644 --- a/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsHashAndActionsSection.tsx @@ -101,7 +101,7 @@ export const TransactionDetailsHashAndActionsSection: React.FC = ({ trans weight="heavy" onPress={onViewOnBlockExplorerPress} label={i18n.t(i18n.l.wallet.action.view_on, { - blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer({ chainId })), + blockExplorerName: transaction.explorerLabel ?? startCase(ethereumUtils.getBlockExplorer(chainId)), })} lightShadows /> diff --git a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx index 5672b52ae02..d0790ebe0e6 100644 --- a/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsValueAndFeeSection.tsx @@ -6,6 +6,8 @@ import { Box, Stack, globalColors } from '@/design-system'; import { TransactionDetailsDivider } from '@/screens/transaction-details/components/TransactionDetailsDivider'; import * as i18n from '@/languages'; +import { Network } from '@/networks/types'; + import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { convertAmountAndPriceToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; import { useAccountSettings } from '@/hooks'; @@ -15,7 +17,7 @@ import ImgixImage from '@/components/images/ImgixImage'; import { View } from 'react-native'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; type Props = { transaction: RainbowTransaction; diff --git a/src/screens/transaction-details/components/TransactionMasthead.tsx b/src/screens/transaction-details/components/TransactionMasthead.tsx index 92c16db5100..7461caddc5d 100644 --- a/src/screens/transaction-details/components/TransactionMasthead.tsx +++ b/src/screens/transaction-details/components/TransactionMasthead.tsx @@ -34,7 +34,7 @@ import ImageAvatar from '@/components/contacts/ImageAvatar'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import * as lang from '@/languages'; import { checkForPendingSwap } from '../helpers/checkForPendingSwap'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; const TransactionMastheadHeight = android ? 153 : 135; @@ -128,7 +128,7 @@ function CurrencyTile({ } }); } - }, [accountName, address, addressContact?.nickname]); + }, []); useEffect(() => { if (!addressAccount?.image && (fetchedEnsName || addressContact?.ens)) { diff --git a/src/state/appSessions/index.test.ts b/src/state/appSessions/index.test.ts index 02e8cf06905..6ff71512df9 100644 --- a/src/state/appSessions/index.test.ts +++ b/src/state/appSessions/index.test.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@/networks/types'; +import { Network } from '@/networks/types'; import { useAppSessionsStore } from '.'; const UNISWAP_HOST = 'uniswap.org'; @@ -15,13 +15,13 @@ test('should be able to add session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - chainId: ChainId.mainnet, + network: Network.mainnet, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.mainnet }, + sessions: { [ADDRESS_1]: Network.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -33,13 +33,13 @@ test('should be able to add session to an existent host', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - chainId: ChainId.arbitrum, + network: Network.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, + sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -51,19 +51,19 @@ test('should be able to add session to a new host', async () => { url: OPENSEA_URL, host: OPENSEA_HOST, address: ADDRESS_2, - chainId: ChainId.arbitrum, + network: Network.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, + sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, activeSessionAddress: ADDRESS_2, }, [OPENSEA_HOST]: { url: OPENSEA_URL, host: OPENSEA_HOST, - sessions: { [ADDRESS_2]: ChainId.arbitrum }, + sessions: { [ADDRESS_2]: Network.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -76,7 +76,7 @@ test('should be able to remove app session for a host', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.mainnet, [ADDRESS_2]: ChainId.arbitrum }, + sessions: { [ADDRESS_1]: Network.mainnet, [ADDRESS_2]: Network.arbitrum }, activeSessionAddress: ADDRESS_2, }, }); @@ -89,7 +89,7 @@ test('should be able to remove a session for a host and address', async () => { [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.mainnet }, + sessions: { [ADDRESS_1]: Network.mainnet }, activeSessionAddress: ADDRESS_1, }, }); @@ -101,7 +101,7 @@ test('should be able to update active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_2, - chainId: ChainId.arbitrum, + network: Network.arbitrum, }); updateActiveSession({ host: UNISWAP_HOST, address: ADDRESS_1 }); expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress).toStrictEqual(ADDRESS_1); @@ -110,9 +110,9 @@ test('should be able to update active session', async () => { test('should be able to update active session network', async () => { const { updateActiveSessionNetwork } = useAppSessionsStore.getState(); - updateActiveSessionNetwork({ host: UNISWAP_HOST, chainId: ChainId.base }); + updateActiveSessionNetwork({ host: UNISWAP_HOST, network: Network.base }); const activeSessionAddress = useAppSessionsStore.getState().appSessions[UNISWAP_HOST].activeSessionAddress; - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(ChainId.base); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[activeSessionAddress]).toStrictEqual(Network.base); }); test('should be able to update session network', async () => { @@ -121,9 +121,9 @@ test('should be able to update session network', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - chainId: ChainId.zora, + network: Network.zora, }); - expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(ChainId.zora); + expect(useAppSessionsStore.getState().appSessions[UNISWAP_HOST].sessions[ADDRESS_1]).toStrictEqual(Network.zora); }); test('should be able to clear all sessions', async () => { @@ -139,14 +139,14 @@ test('should be able to check if host has an active session', async () => { url: UNISWAP_URL, host: UNISWAP_HOST, address: ADDRESS_1, - chainId: ChainId.mainnet, + network: Network.mainnet, }); const activeSession = getActiveSession({ host: UNISWAP_HOST }); expect(activeSession).toStrictEqual({ activeSessionAddress: ADDRESS_1, host: UNISWAP_HOST, sessions: { - '0x123': ChainId.mainnet, + '0x123': Network.mainnet, }, url: UNISWAP_URL, }); @@ -157,13 +157,13 @@ test('should be able to update session chain id', async () => { updateSessionNetwork({ host: UNISWAP_HOST, address: ADDRESS_1, - chainId: ChainId.arbitrum, + network: Network.arbitrum, }); expect(useAppSessionsStore.getState().appSessions).toStrictEqual({ [UNISWAP_HOST]: { url: UNISWAP_URL, host: UNISWAP_HOST, - sessions: { [ADDRESS_1]: ChainId.arbitrum }, + sessions: { [ADDRESS_1]: Network.arbitrum }, activeSessionAddress: ADDRESS_1, }, }); diff --git a/src/state/appSessions/index.ts b/src/state/appSessions/index.ts index 5a2ae7dd35c..46ae89ee9b3 100644 --- a/src/state/appSessions/index.ts +++ b/src/state/appSessions/index.ts @@ -1,32 +1,25 @@ import { Address } from 'viem'; -import { Network, ChainId, networkToIdMapping } from '@/networks/types'; +import { Network } from '@/networks/types'; import { createRainbowStore } from '../internal/createRainbowStore'; -export interface AppSessionV0 { - activeSessionAddress: Address; - host: string; - sessions: Record; - url: string; -} - export interface AppSession { activeSessionAddress: Address; host: string; - sessions: Record; + sessions: Record; url: string; } -export interface AppSessionsStore { +export interface AppSessionsStore { appSessions: Record; getActiveSession: ({ host }: { host: string }) => AppSession; removeAddressSessions: ({ address }: { address: Address }) => void; - addSession: ({ host, address, chainId, url }: { host: string; address: Address; chainId: ChainId; url: string }) => void; - removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; chainId: ChainId } | null; + addSession: ({ host, address, network, url }: { host: string; address: Address; network: Network; url: string }) => void; + removeSession: ({ host, address }: { host: string; address: Address }) => { address: Address; network: Network } | null; removeAppSession: ({ host }: { host: string }) => void; updateActiveSession: ({ host, address }: { host: string; address: Address }) => void; - updateActiveSessionNetwork: ({ host, chainId }: { host: string; chainId: ChainId }) => void; - updateSessionNetwork: ({ address, host, chainId }: { address: Address; host: string; chainId: ChainId }) => void; + updateActiveSessionNetwork: ({ host, network }: { host: string; network: Network }) => void; + updateSessionNetwork: ({ address, host, network }: { address: Address; host: string; network: Network }) => void; clearSessions: () => void; } @@ -54,18 +47,18 @@ export const useAppSessionsStore = createRainbowStore { + addSession: ({ host, address, network, url }) => { const appSessions = get().appSessions; const existingSession = appSessions[host]; if (!existingSession || !existingSession.sessions) { appSessions[host] = { host, - sessions: { [address]: chainId }, + sessions: { [address]: network }, activeSessionAddress: address, url, }; } else { - appSessions[host].sessions[address] = chainId; + appSessions[host].sessions[address] = network; appSessions[host].activeSessionAddress = address; } set({ @@ -100,7 +93,7 @@ export const useAppSessionsStore = createRainbowStore { + updateActiveSessionNetwork: ({ host, network }) => { const appSessions = get().appSessions; const appSession = appSessions[host] || {}; set({ @@ -137,13 +130,13 @@ export const useAppSessionsStore = createRainbowStore { + updateSessionNetwork: ({ host, address, network }) => { const appSessions = get().appSessions; const appSession = appSessions[host]; if (!appSession) return; @@ -154,7 +147,7 @@ export const useAppSessionsStore = createRainbowStore { - if (version === 0) { - const oldState = persistedState as AppSessionsStore; - const appSessions: AppSessionsStore['appSessions'] = {}; - for (const [host, session] of Object.entries(oldState.appSessions)) { - const sessions = session.sessions; - const newSessions = Object.keys(sessions).reduce( - (acc, addr) => { - const address = addr as Address; - const network = sessions[address]; - acc[address] = networkToIdMapping[network]; - return acc as Record; - }, - {} as Record - ); - appSessions[host] = { - activeSessionAddress: session.activeSessionAddress, - host: session.host, - sessions: newSessions, - url: session.url, - }; - } - return { - ...oldState, - appSessions, - }; - } - return persistedState as AppSessionsStore; - }, + version: 0, } ); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 2a97a31f284..90834dde2e0 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -1,12 +1,11 @@ -import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; import { Address } from 'viem'; import { RainbowError, logger } from '@/logger'; import store from '@/redux/store'; import { ETH_ADDRESS, SUPPORTED_CHAIN_IDS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; -import { swapsStore } from '@/state/swaps/swapsStore'; -import { ChainId } from '@/networks/types'; -import { useConnectedToHardhatStore } from '../connectedToHardhat'; +import { ParsedSearchAsset, UniqueId, UserAssetFilter } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; +import { swapsStore } from '../swaps/swapsStore'; const SEARCH_CACHE_MAX_ENTRIES = 50; @@ -67,7 +66,7 @@ function serializeUserAssetsState(state: Partial, version?: num version, }); } catch (error) { - logger.error(new RainbowError(`[userAssetsStore]: Failed to serialize state for user assets storage`), { error }); + logger.error(new RainbowError('Failed to serialize state for user assets storage'), { error }); throw error; } } @@ -77,7 +76,7 @@ function deserializeUserAssetsState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError(`[userAssetsStore]: Failed to parse serialized state from user assets storage`), { error }); + logger.error(new RainbowError('Failed to parse serialized state from user assets storage'), { error }); throw error; } @@ -89,7 +88,7 @@ function deserializeUserAssetsState(serializedState: string) { chainBalances = new Map(state.chainBalances); } } catch (error) { - logger.error(new RainbowError(`[userAssetsStore]: Failed to convert chainBalances from user assets storage`), { error }); + logger.error(new RainbowError('Failed to convert chainBalances from user assets storage'), { error }); } let idsByChain = new Map(); @@ -98,7 +97,7 @@ function deserializeUserAssetsState(serializedState: string) { idsByChain = new Map(state.idsByChain); } } catch (error) { - logger.error(new RainbowError(`[userAssetsStore]: Failed to convert idsByChain from user assets storage`), { error }); + logger.error(new RainbowError('Failed to convert idsByChain from user assets storage'), { error }); } let userAssetsData: Map = new Map(); @@ -107,7 +106,7 @@ function deserializeUserAssetsState(serializedState: string) { userAssetsData = new Map(state.userAssets); } } catch (error) { - logger.error(new RainbowError(`[userAssetsStore]: Failed to convert userAssets from user assets storage`), { error }); + logger.error(new RainbowError('Failed to convert userAssets from user assets storage'), { error }); } return { @@ -180,6 +179,7 @@ export const userAssetsStore = createRainbowStore( return filteredIds; } }, + getHighestValueEth: () => { const preferredNetwork = swapsStore.getState().preferredNetwork; const assets = get().userAssets; @@ -278,7 +278,7 @@ export const userAssetsStore = createRainbowStore( }); // Ensure all supported chains are in the map with a fallback value of 0 - SUPPORTED_CHAIN_IDS({ testnetMode: useConnectedToHardhatStore.getState().connectedToHardhat }).forEach(chainId => { + SUPPORTED_CHAIN_IDS({ testnetMode: false }).forEach(chainId => { if (!unsortedChainBalances.has(chainId)) { unsortedChainBalances.set(chainId, 0); idsByChain.set(chainId, []); diff --git a/src/state/browser/browserStore.ts b/src/state/browser/browserStore.ts index 041913d506e..f6c7340c3da 100644 --- a/src/state/browser/browserStore.ts +++ b/src/state/browser/browserStore.ts @@ -19,7 +19,7 @@ const lazyPersist = debounce( const serializedValue = serializeBrowserState(value.state, value.version ?? BROWSER_STORAGE_VERSION); browserStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError(`[browserStore]: Failed to serialize persisted browser data`), { error }); + logger.error(new RainbowError('Failed to serialize persisted browser data'), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -36,7 +36,7 @@ function serializeBrowserState(state: BrowserState, version: number): string { version, }); } catch (error) { - logger.error(new RainbowError(`[browserStore]: Failed to serialize state for browser storage`), { error }); + logger.error(new RainbowError('Failed to serialize state for browser storage'), { error }); throw error; } } @@ -46,7 +46,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError(`[browserStore]: Failed to parse serialized state from browser storage`), { error }); + logger.error(new RainbowError('Failed to parse serialized state from browser storage'), { error }); throw error; } @@ -56,7 +56,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState try { tabsData = new Map(state.tabsData); } catch (error) { - logger.error(new RainbowError(`[browserStore]: Failed to convert tabsData from browser storage`), { error }); + logger.error(new RainbowError('Failed to convert tabsData from browser storage'), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeBrowserState(serializedState: string): { state: BrowserState if (tabData) { tabData.url = persistedUrl; } else { - logger.warn(`[browserStore]: No tabData found for tabId ${tabId} during URL restoration`); + logger.warn(`No tabData found for tabId ${tabId} during URL restoration`); } } } diff --git a/src/state/connectedToHardhat/index.ts b/src/state/connectedToHardhat/index.ts deleted file mode 100644 index 8d6b2c5a4ae..00000000000 --- a/src/state/connectedToHardhat/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import create from 'zustand'; -import { createRainbowStore } from '../internal/createRainbowStore'; - -export interface ConnectedToHardhatState { - connectedToHardhat: boolean; - setConnectedToHardhat: (connectedToHardhat: boolean) => void; - - connectedToHardhatOp: boolean; - setConnectedToHardhatOp: (connectedToHardhatOp: boolean) => void; -} - -export const useConnectedToHardhatStore = createRainbowStore( - set => ({ - connectedToHardhat: false, - setConnectedToHardhat: connectedToHardhat => { - set({ connectedToHardhat }); - }, - - connectedToHardhatOp: false, - setConnectedToHardhatOp: connectedToHardhatOp => { - set({ connectedToHardhatOp }); - }, - }), - { - storageKey: 'connectedToHardhat', - version: 0, - } -); diff --git a/src/state/featuredResults/featuredResults.ts b/src/state/featuredResults/featuredResults.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts index df1a4d11df0..3e504ced84e 100644 --- a/src/state/internal/createRainbowStore.ts +++ b/src/state/internal/createRainbowStore.ts @@ -38,11 +38,6 @@ interface RainbowPersistConfig { * @default 0 */ version?: number; - /** - * A function to perform persisted state migration. - * This function will be called when persisted state versions mismatch with the one specified here. - */ - migrate?: (persistedState: unknown, version: number) => S | Promise; } /** @@ -51,7 +46,7 @@ interface RainbowPersistConfig { * @returns An object containing the persist storage and version. */ function createPersistStorage(config: RainbowPersistConfig) { - const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0, migrate } = config; + const { deserializer = defaultDeserializeState, serializer = defaultSerializeState, storageKey, version = 0 } = config; const persistStorage: PersistOptions>['storage'] = { getItem: (name: string) => { @@ -97,7 +92,7 @@ const lazyPersist = ({ name, serializer, storageKey, value }: LazyPersistPara const serializedValue = serializer(value.state, value.version ?? 0); rainbowStorage.set(key, serializedValue); } catch (error) { - logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize persisted store data`), { error }); + logger.error(new RainbowError('Failed to serialize persisted store data'), { error }); } }, PERSIST_RATE_LIMIT_MS, @@ -114,7 +109,7 @@ function defaultSerializeState(state: StorageValue>['state'], vers try { return JSON.stringify({ state, version }); } catch (error) { - logger.error(new RainbowError(`[createRainbowStore]: Failed to serialize Rainbow store data`), { error }); + logger.error(new RainbowError('Failed to serialize Rainbow store data'), { error }); throw error; } } @@ -128,7 +123,7 @@ function defaultDeserializeState(serializedState: string): StorageValue( partialize: persistConfig.partialize || (state => state), storage: persistStorage, version, - migrate: persistConfig.migrate, }) ) ); diff --git a/src/state/nonces/index.ts b/src/state/nonces/index.ts index d9b41e79781..8383d5ada1e 100644 --- a/src/state/nonces/index.ts +++ b/src/state/nonces/index.ts @@ -1,7 +1,7 @@ import create from 'zustand'; import { createStore } from '../internal/createStore'; -import { Network, ChainId, networkToIdMapping } from '@/networks/types'; -import { getProvider } from '@/handlers/web3'; +import { Network } from '@/networks/types'; +import { getProviderForNetwork } from '@/handlers/web3'; type NonceData = { currentNonce?: number; @@ -10,49 +10,41 @@ type NonceData = { type GetNonceArgs = { address: string; - chainId: ChainId; + network: Network; }; type UpdateNonceArgs = NonceData & GetNonceArgs; -export async function getNextNonce({ address, chainId }: { address: string; chainId: ChainId }) { +export async function getNextNonce({ address, network }: { address: string; network: Network }) { const { getNonce } = nonceStore.getState(); - const localNonceData = getNonce({ address, chainId }); + const localNonceData = getNonce({ address, network }); const localNonce = localNonceData?.currentNonce || 0; - const provider = getProvider({ chainId }); + const provider = getProviderForNetwork(network); const txCountIncludingPending = await provider.getTransactionCount(address, 'pending'); if (!localNonce && !txCountIncludingPending) return 0; const ret = Math.max(localNonce + 1, txCountIncludingPending); return ret; } -type NoncesV0 = { - [network in Network]: NonceData; -}; - -type Nonces = { - [chainId in ChainId]: NonceData; -}; - -export interface CurrentNonceState { - nonces: Record; - setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }: UpdateNonceArgs) => void; - getNonce: ({ address, chainId }: GetNonceArgs) => NonceData | null; +export interface CurrentNonceState { + nonces: Record>; + setNonce: ({ address, currentNonce, latestConfirmedNonce, network }: UpdateNonceArgs) => void; + getNonce: ({ address, network }: GetNonceArgs) => NonceData | null; clearNonces: () => void; } -export const nonceStore = createStore>( +export const nonceStore = createStore( (set, get) => ({ nonces: {}, - setNonce: ({ address, currentNonce, latestConfirmedNonce, chainId }) => { + setNonce: ({ address, currentNonce, latestConfirmedNonce, network }) => { const { nonces: oldNonces } = get(); - const addressAndChainIdNonces = oldNonces?.[address]?.[chainId] || {}; + const addressAndChainIdNonces = oldNonces?.[address]?.[network] || {}; set({ nonces: { ...oldNonces, [address]: { ...oldNonces[address], - [chainId]: { + [network]: { currentNonce: currentNonce ?? addressAndChainIdNonces?.currentNonce, latestConfirmedNonce: latestConfirmedNonce ?? addressAndChainIdNonces?.latestConfirmedNonce, }, @@ -60,9 +52,9 @@ export const nonceStore = createStore>( }, }); }, - getNonce: ({ address, chainId }) => { + getNonce: ({ address, network }) => { const { nonces } = get(); - return nonces[address]?.[chainId] ?? null; + return nonces[address]?.[network] ?? null; }, clearNonces: () => { set({ nonces: {} }); @@ -71,26 +63,7 @@ export const nonceStore = createStore>( { persist: { name: 'nonces', - version: 1, - migrate: (persistedState: unknown, version: number) => { - if (version === 0) { - const oldState = persistedState as CurrentNonceState; - const newNonces: CurrentNonceState['nonces'] = {}; - for (const [address, networkNonces] of Object.entries(oldState.nonces)) { - for (const [network, nonceData] of Object.entries(networkNonces)) { - if (!newNonces[address]) { - newNonces[address] = {} as Record; - } - newNonces[address][networkToIdMapping[network as Network]] = nonceData; - } - } - return { - ...oldState, - nonces: newNonces, - }; - } - return persistedState as CurrentNonceState; - }, + version: 0, }, } ); diff --git a/src/state/pendingTransactions/index.ts b/src/state/pendingTransactions/index.ts index 3eb8e2db2e5..fd5ac70063b 100644 --- a/src/state/pendingTransactions/index.ts +++ b/src/state/pendingTransactions/index.ts @@ -2,8 +2,8 @@ import { RainbowTransaction, NewTransaction } from '@/entities/transactions'; import { createStore } from '../internal/createStore'; import create from 'zustand'; import { parseNewTransaction } from '@/parsers/transactions'; +import { Network } from '@/networks/types'; import { nonceStore } from '../nonces'; -import { ChainId } from '@/networks/types'; export interface PendingTransactionsState { pendingTransactions: Record; @@ -35,7 +35,7 @@ export const pendingTransactionsStore = createStore( ...currentPendingTransactions, [address]: [ ...addressPendingTransactions.filter(tx => { - if (tx.chainId === pendingTransaction.chainId) { + if (tx.network === pendingTransaction.network) { return tx.nonce !== pendingTransaction.nonce; } return true; @@ -70,11 +70,11 @@ export const usePendingTransactionsStore = create(pendingTransactionsStore); export const addNewTransaction = ({ address, - chainId, + network, transaction, }: { address: string; - chainId: ChainId; + network: Network; transaction: NewTransaction; }) => { const { addPendingTransaction } = pendingTransactionsStore.getState(); @@ -83,18 +83,18 @@ export const addNewTransaction = ({ addPendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - chainId, + network, currentNonce: transaction.nonce, }); }; export const updateTransaction = ({ address, - chainId, + network, transaction, }: { address: string; - chainId: ChainId; + network: Network; transaction: NewTransaction; }) => { const { updatePendingTransaction } = pendingTransactionsStore.getState(); @@ -103,7 +103,7 @@ export const updateTransaction = ({ updatePendingTransaction({ address, pendingTransaction: parsedTransaction }); setNonce({ address, - chainId, + network, currentNonce: transaction.nonce, }); }; diff --git a/src/state/remoteCards/remoteCards.ts b/src/state/remoteCards/remoteCards.ts index aff2b348b96..bd354a10588 100644 --- a/src/state/remoteCards/remoteCards.ts +++ b/src/state/remoteCards/remoteCards.ts @@ -28,7 +28,7 @@ function serializeState(state: Partial, version?: number) { const validCards = Array.from(state.cards?.entries() ?? []).filter(([, card]) => card && card.sys?.id); if (state.cards && validCards.length < state.cards.size) { - logger.error(new RainbowError(`[remoteCardsStore]: filtered cards without sys.id during serialization`), { + logger.error(new RainbowError('remoteCardsStore: filtered cards without sys.id during serialization'), { filteredCount: state.cards.size - validCards.length, }); } @@ -44,7 +44,7 @@ function serializeState(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError(`[remoteCardsStore]: Failed to serialize state for remote cards storage`), { error }); + logger.error(new RainbowError('Failed to serialize state for remote cards storage'), { error }); throw error; } } @@ -54,7 +54,7 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError(`[remoteCardsStore]: Failed to parse serialized state from remote cards storage`), { error }); + logger.error(new RainbowError('Failed to parse serialized state from remote cards storage'), { error }); throw error; } @@ -66,7 +66,7 @@ function deserializeState(serializedState: string) { cardsByIdData = new Set(state.cardsById.filter(id => typeof id === 'string' && id.length > 0)); } } catch (error) { - logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cardsById from remote cards storage`), { error }); + logger.error(new RainbowError('Failed to convert cardsById from remote cards storage'), { error }); throw error; } @@ -76,7 +76,7 @@ function deserializeState(serializedState: string) { const validCards = state.cards.filter(([, card]) => card && card.sys?.id); if (validCards.length < state.cards.length) { - logger.error(new RainbowError(`[remoteCardsStore]: Filtered out cards without sys.id during deserialization`), { + logger.error(new RainbowError('Filtered out cards without sys.id during deserialization'), { filteredCount: state.cards.length - validCards.length, }); } @@ -84,7 +84,7 @@ function deserializeState(serializedState: string) { cardsData = new Map(validCards); } } catch (error) { - logger.error(new RainbowError(`[remoteCardsStore]: Failed to convert cards from remote cards storage`), { error }); + logger.error(new RainbowError('Failed to convert cards from remote cards storage'), { error }); throw error; } diff --git a/src/state/remotePromoSheets/remotePromoSheets.ts b/src/state/remotePromoSheets/remotePromoSheets.ts index 1edd98a3a27..9e364bbf91e 100644 --- a/src/state/remotePromoSheets/remotePromoSheets.ts +++ b/src/state/remotePromoSheets/remotePromoSheets.ts @@ -55,7 +55,7 @@ function serializeState(state: Partial, version?: number version, }); } catch (error) { - logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to serialize state for remote promo sheets storage`), { error }); + logger.error(new RainbowError('Failed to serialize state for remote promo sheets storage'), { error }); throw error; } } @@ -65,9 +65,7 @@ function deserializeState(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to parse serialized state from remote promo sheets storage`), { - error, - }); + logger.error(new RainbowError('Failed to parse serialized state from remote promo sheets storage'), { error }); throw error; } @@ -79,7 +77,7 @@ function deserializeState(serializedState: string) { sheetsByIdData = new Set(state.sheetsById); } } catch (error) { - logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheetsById from remote promo sheets storage`), { error }); + logger.error(new RainbowError('Failed to convert sheetsById from remote promo sheets storage'), { error }); throw error; } @@ -89,7 +87,7 @@ function deserializeState(serializedState: string) { sheetsData = new Map(state.sheets); } } catch (error) { - logger.error(new RainbowError(`[remotePromoSheetsStore]: Failed to convert sheets from remote promo sheets storage`), { error }); + logger.error(new RainbowError('Failed to convert sheets from remote promo sheets storage'), { error }); throw error; } diff --git a/src/state/staleBalances/index.test.ts b/src/state/staleBalances/index.test.ts deleted file mode 100644 index b07e73acb7b..00000000000 --- a/src/state/staleBalances/index.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Address } from 'viem'; - -import { staleBalancesStore } from '.'; -import { DAI_ADDRESS, OP_ADDRESS } from '@/references'; -import { ETH_ADDRESS } from '@rainbow-me/swaps'; -import { ChainId } from '@/networks/types'; - -const TEST_ADDRESS_1 = '0xFOO'; -const TEST_ADDRESS_2 = '0xBAR'; -const THEN = Date.now() - 700000; -const WHEN = Date.now() + 60000; - -test('should be able to add asset information to the staleBalances object', async () => { - const { addStaleBalance, staleBalances } = staleBalancesStore.getState(); - expect(staleBalances).toStrictEqual({}); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.mainnet, - info: { - address: DAI_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: THEN, - }, - }); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.mainnet, - info: { - address: ETH_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }); - const newStaleBalances = staleBalancesStore.getState().staleBalances; - expect(newStaleBalances).toStrictEqual({ - [TEST_ADDRESS_1]: { - [ChainId.mainnet]: { - [DAI_ADDRESS]: { - address: DAI_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: THEN, - }, - [ETH_ADDRESS]: { - address: ETH_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }, - }, - }); -}); - -test('should generate accurate stale balance query params and clear expired data - case #1', async () => { - const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); - clearExpiredData(TEST_ADDRESS_1); - const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); - expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); -}); - -test('should be able to remove expired stale balance and preserve unexpired data', async () => { - const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.mainnet, - info: { - address: DAI_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: THEN, - }, - }); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.mainnet, - info: { - address: ETH_ADDRESS as Address, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }); - clearExpiredData(TEST_ADDRESS_1); - const newStaleBalances = staleBalancesStore.getState().staleBalances; - expect(newStaleBalances).toStrictEqual({ - [TEST_ADDRESS_1]: { - [ChainId.mainnet]: { - [ETH_ADDRESS]: { - address: ETH_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }, - }, - }); -}); - -test('should preserve data from other addresses when clearing expired data', async () => { - const { addStaleBalance, clearExpiredData } = staleBalancesStore.getState(); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.mainnet, - info: { - address: DAI_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: THEN, - }, - }); - addStaleBalance({ - address: TEST_ADDRESS_2, - chainId: ChainId.mainnet, - info: { - address: ETH_ADDRESS as Address, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }); - clearExpiredData(TEST_ADDRESS_1); - const newStaleBalances = staleBalancesStore.getState().staleBalances; - expect(newStaleBalances).toStrictEqual({ - [TEST_ADDRESS_1]: { - [ChainId.mainnet]: { - [ETH_ADDRESS]: { - address: ETH_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }, - }, - [TEST_ADDRESS_2]: { - [ChainId.mainnet]: { - [ETH_ADDRESS]: { - address: ETH_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }, - }, - }); -}); - -test('should generate accurate stale balance query params and clear expired data - case #2', async () => { - const { getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); - clearExpiredData(TEST_ADDRESS_2); - const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_2); - expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); -}); - -test('should generate accurate stale balance query params and clear expired data - case #3', async () => { - const { addStaleBalance, getStaleBalancesQueryParam, clearExpiredData } = staleBalancesStore.getState(); - addStaleBalance({ - address: TEST_ADDRESS_1, - chainId: ChainId.optimism, - info: { - address: OP_ADDRESS, - transactionHash: '0xFOOBAR', - expirationTime: WHEN, - }, - }); - - clearExpiredData(TEST_ADDRESS_1); - const queryParam = getStaleBalancesQueryParam(TEST_ADDRESS_1); - expect(queryParam).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}&token=${ChainId.optimism}.${OP_ADDRESS}`); - - clearExpiredData(TEST_ADDRESS_2); - const queryParam2 = getStaleBalancesQueryParam(TEST_ADDRESS_2); - expect(queryParam2).toStrictEqual(`&token=${ChainId.mainnet}.${ETH_ADDRESS}`); -}); diff --git a/src/state/staleBalances/index.ts b/src/state/staleBalances/index.ts deleted file mode 100644 index 8a9928aaacb..00000000000 --- a/src/state/staleBalances/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { createRainbowStore } from '../internal/createRainbowStore'; - -const TIME_TO_WATCH = 600000; - -interface StaleBalanceInfo { - address: string; - expirationTime?: number; - transactionHash: string; -} - -interface StaleBalances { - [key: string]: StaleBalanceInfo; -} -interface StaleBalancesByChainId { - [key: number]: StaleBalances; -} - -export interface StaleBalancesState { - addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => void; - clearExpiredData: (address: string) => void; - getStaleBalancesQueryParam: (address: string) => string; - staleBalances: Record; -} - -export const staleBalancesStore = createRainbowStore( - (set, get) => ({ - addStaleBalance: ({ address, chainId, info }: { address: string; chainId: number; info: StaleBalanceInfo }) => { - set(state => { - const { staleBalances } = state; - const staleBalancesForUser = staleBalances[address] || {}; - const staleBalancesForChain = staleBalancesForUser[chainId] || {}; - const newStaleBalancesForChain = { - ...staleBalancesForChain, - [info.address]: { - ...info, - expirationTime: info.expirationTime || Date.now() + TIME_TO_WATCH, - }, - }; - const newStaleBalancesForUser = { - ...staleBalancesForUser, - [chainId]: newStaleBalancesForChain, - }; - return { - staleBalances: { - ...staleBalances, - [address]: newStaleBalancesForUser, - }, - }; - }); - }, - clearExpiredData: (address: string) => { - set(state => { - const { staleBalances } = state; - const staleBalancesForUser = staleBalances[address] || {}; - const newStaleBalancesForUser: StaleBalancesByChainId = { - ...staleBalancesForUser, - }; - for (const c of Object.keys(staleBalancesForUser)) { - const chainId = parseInt(c, 10); - const newStaleBalancesForChain = { - ...(staleBalancesForUser[chainId] || {}), - }; - for (const staleBalance of Object.values(newStaleBalancesForChain)) { - if (typeof staleBalance.expirationTime === 'number' && staleBalance.expirationTime <= Date.now()) { - delete newStaleBalancesForChain[staleBalance.address]; - } - } - newStaleBalancesForUser[chainId] = newStaleBalancesForChain; - } - return { - staleBalances: { - ...staleBalances, - [address]: newStaleBalancesForUser, - }, - }; - }); - }, - getStaleBalancesQueryParam: (address: string) => { - let queryStringFragment = ''; - const { staleBalances } = get(); - const staleBalancesForUser = staleBalances[address]; - for (const c of Object.keys(staleBalancesForUser)) { - const chainId = parseInt(c, 10); - const staleBalancesForChain = staleBalancesForUser[chainId]; - for (const staleBalance of Object.values(staleBalancesForChain)) { - if (typeof staleBalance.expirationTime === 'number') { - queryStringFragment += `&token=${chainId}.${staleBalance.address}`; - } - } - } - return queryStringFragment; - }, - staleBalances: {}, - }), - { - storageKey: 'staleBalances', - version: 0, - } -); diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts index df49c0f32a2..ac5fb83d92f 100644 --- a/src/state/swaps/swapsStore.ts +++ b/src/state/swaps/swapsStore.ts @@ -2,7 +2,7 @@ import { MIN_FLASHBOTS_PRIORITY_FEE } from '@/__swaps__/screens/Swap/constants'; import { getCustomGasSettings, setCustomMaxPriorityFee } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; import { getSelectedGasSpeed } from '@/__swaps__/screens/Swap/hooks/useSelectedGas'; import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; -import { ChainId } from '@/networks/types'; +import { ChainId } from '@/__swaps__/types/chains'; import { GasSpeed } from '@/__swaps__/types/gas'; import { RecentSwap } from '@/__swaps__/types/swap'; import { getCachedGasSuggestions } from '@/__swaps__/utils/meteorology'; @@ -66,7 +66,7 @@ function serialize(state: Partial, version?: number) { version, }); } catch (error) { - logger.error(new RainbowError(`[swapsStore]: Failed to serialize state for swaps storage`), { error }); + logger.error(new RainbowError('Failed to serialize state for swaps storage'), { error }); throw error; } } @@ -76,7 +76,7 @@ function deserialize(serializedState: string) { try { parsedState = JSON.parse(serializedState); } catch (error) { - logger.error(new RainbowError(`[swapsStore]: Failed to parse serialized state from swaps storage`), { error }); + logger.error(new RainbowError('Failed to parse serialized state from swaps storage'), { error }); throw error; } @@ -88,7 +88,7 @@ function deserialize(serializedState: string) { recentSwaps = new Map(state.recentSwaps); } } catch (error) { - logger.error(new RainbowError(`[swapsStore]: Failed to convert recentSwaps from swaps storage`), { error }); + logger.error(new RainbowError('Failed to convert recentSwaps from swaps storage'), { error }); } let latestSwapAt: Map = new Map(); @@ -97,7 +97,7 @@ function deserialize(serializedState: string) { latestSwapAt = new Map(state.latestSwapAt); } } catch (error) { - logger.error(new RainbowError(`[swapsStore]: Failed to convert latestSwapAt from swaps storage`), { error }); + logger.error(new RainbowError('Failed to convert latestSwapAt from swaps storage'), { error }); } return { diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 4c8c379c775..f4573bb7834 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -1,25 +1,23 @@ +import { memo } from 'react'; import { Address } from 'viem'; import { useAccountSettings } from '@/hooks'; import { userAssetsStore } from '@/state/assets/userAssets'; import { useSwapsStore } from '@/state/swaps/swapsStore'; import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/screens/Swap/resources/_selectors/assets'; import { ParsedSearchAsset } from '@/__swaps__/types/assets'; +import { ChainId } from '@/__swaps__/types/chains'; import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets'; -import { ChainId } from '@/networks/types'; -import { useConnectedToHardhatStore } from '../connectedToHardhat'; -export const UserAssetsSync = function UserAssetsSync() { +export const UserAssetsSync = memo(function UserAssetsSync() { const { accountAddress: currentAddress, nativeCurrency: currentCurrency } = useAccountSettings(); const userAssetsWalletAddress = userAssetsStore(state => state.associatedWalletAddress); const isSwapsOpen = useSwapsStore(state => state.isSwapsOpen); - const { connectedToHardhat } = useConnectedToHardhatStore(); useUserAssets( { address: currentAddress as Address, currency: currentCurrency, - testnetMode: connectedToHardhat, }, { enabled: !isSwapsOpen || userAssetsWalletAddress !== currentAddress, @@ -43,4 +41,4 @@ export const UserAssetsSync = function UserAssetsSync() { ); return null; -}; +}); diff --git a/src/storage/legacy.ts b/src/storage/legacy.ts index 64adfbec6e6..2246c2bc2c4 100644 --- a/src/storage/legacy.ts +++ b/src/storage/legacy.ts @@ -32,7 +32,7 @@ class LegacyStorage extends Storage; @@ -187,18 +186,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; let networkColors = { - [ChainId.arbitrum]: '#2D374B', - [ChainId.base]: '#0052FF', - [ChainId.goerli]: '#f6c343', - [ChainId.gnosis]: '#479E9C', - [ChainId.mainnet]: '#25292E', - [ChainId.optimism]: '#FF4040', - [ChainId.polygon]: '#8247E5', - [ChainId.bsc]: '#F0B90B', - [ChainId.zora]: '#2B5DF0', - [ChainId.avalanche]: '#E84142', - [ChainId.degen]: '#A36EFD', - [ChainId.blast]: '#25292E', + arbitrum: '#2D374B', + base: '#0052FF', + goerli: '#f6c343', + gnosis: '#479E9C', + mainnet: '#25292E', + optimism: '#FF4040', + polygon: '#8247E5', + bsc: '#F0B90B', + zora: '#2B5DF0', + avalanche: '#E84142', + degen: '#A36EFD', + blast: '#25292E', }; let gradients = { @@ -329,18 +328,18 @@ const getColorsByTheme = (darkMode?: boolean) => { }; networkColors = { - [ChainId.arbitrum]: '#ADBFE3', - [ChainId.base]: '#3979FF', - [ChainId.goerli]: '#f6c343', - [ChainId.gnosis]: '#479E9C', - [ChainId.mainnet]: '#E0E8FF', - [ChainId.optimism]: '#FF6A6A', - [ChainId.polygon]: '#A275EE', - [ChainId.bsc]: '#F0B90B', - [ChainId.zora]: '#6183F0', - [ChainId.avalanche]: '#FF5D5E', - [ChainId.degen]: '#A36EFD', - [ChainId.blast]: '#FCFC03', + arbitrum: '#ADBFE3', + base: '#3979FF', + goerli: '#f6c343', + gnosis: '#479E9C', + mainnet: '#E0E8FF', + optimism: '#FF6A6A', + polygon: '#A275EE', + bsc: '#F0B90B', + zora: '#6183F0', + avalanche: '#FF5D5E', + degen: '#A36EFD', + blast: '#FCFC03', }; } diff --git a/src/utils/actionsheet.ts b/src/utils/actionsheet.ts index 30b38eac3d9..0b524e532d1 100644 --- a/src/utils/actionsheet.ts +++ b/src/utils/actionsheet.ts @@ -3,10 +3,10 @@ import ActionSheet from 'react-native-action-sheet'; export default function showActionSheetWithOptions(...args: any[]) { if (ios) { - // @ts-ignore + //@ts-ignore ActionSheetIOS.showActionSheetWithOptions(...args); } else { - // @ts-ignore + //@ts-ignore ActionSheet.showActionSheetWithOptions(...args); } } diff --git a/src/utils/bluetoothPermissions.ts b/src/utils/bluetoothPermissions.ts index 48373a56cd3..7983fd1fc8f 100644 --- a/src/utils/bluetoothPermissions.ts +++ b/src/utils/bluetoothPermissions.ts @@ -60,7 +60,7 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { ]; const res = await checkForMultiplePermissions(ANDROID_BT_PERMISSION); - logger.debug('[bluetoothPermissions]: Android Permission status: ', { res }); + logger.debug('[Bluetooth] Android Permission status: ', { res }); const deniedPermissions: AndroidPermission[] = []; @@ -72,13 +72,13 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { } if (deniedPermissions.length === 0) { - logger.debug('[bluetoothPermissions]: Android Permissions all granted'); + logger.debug('[Bluetooth] Android Permissions all granted'); return true; } // if we're only missing one, only request one else if (deniedPermissions.length === 1) { const askResult = await requestPermission(deniedPermissions[0]); - logger.debug('[bluetoothPermissions]: Android Permission single askResult: ', { + logger.debug('[Bluetooth] Android Permission single askResult: ', { askResult, }); if (askResult === RESULTS.GRANTED) { @@ -97,7 +97,7 @@ export const checkAndRequestAndroidBluetooth = async (): Promise => { // else request in a group } else if (deniedPermissions.length > 1) { const askResults = await requestMultiplePermissions(deniedPermissions); - logger.debug('[bluetoothPermissions]: Android Bluetooth Permission multiple askResult: ', { askResults }); + logger.debug('[Bluetooth] Android Bluetooth Permission multiple askResult: ', { askResults }); const deniedOrBlockedPermissions: AndroidPermission[] = []; // check if we are missing any permissions diff --git a/src/utils/branch.ts b/src/utils/branch.ts index f7c50bb79fe..474eaca32a6 100644 --- a/src/utils/branch.ts +++ b/src/utils/branch.ts @@ -8,7 +8,7 @@ import * as ls from '@/storage'; import { logger, RainbowError } from '@/logger'; export const branchListener = async (handleOpenLinkingURL: (url: any) => void) => { - logger.debug(`[branchListener]: setting up listener`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: setting up listener`, {}, logger.DebugContext.deeplinks); /* * This is run every time the app is opened, whether from a cold start of from the background. @@ -19,30 +19,30 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = case 'Trouble reaching the Branch servers, please try again shortly.': break; default: - logger.error(new RainbowError(`[branchListener]: error when handling event`), { + logger.error(new RainbowError('Branch: error when handling event'), { error, }); } } - logger.debug(`[branchListener]: handling event`, { params, uri }, logger.DebugContext.deeplinks); + logger.debug(`Branch: handling event`, { params, uri }, logger.DebugContext.deeplinks); if (!params && uri) { - logger.debug(`[branchListener]: no params but we have a URI`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: no params but we have a URI`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } else if (!params) { // We got absolutely nothing to work with. - logger.warn(`[branchListener]: received no params or URI when handling event`, { + logger.warn(`Branch: received no params or URI when handling event`, { params, uri, }); } else if (params['+non_branch_link']) { const nonBranchUrl = params['+non_branch_link']; - logger.debug(`[branchListener]: handling non-Branch link`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: handling non-Branch link`, {}, logger.DebugContext.deeplinks); if (typeof nonBranchUrl === 'string' && nonBranchUrl?.startsWith('rainbow://open')) { - logger.debug(`[branchListener]: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: aggressive Safari redirect mode`, {}, logger.DebugContext.deeplinks); /** * This happens when the user hits the Branch-hosted fallback page in @@ -64,7 +64,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = handleOpenLinkingURL(url); } } else { - logger.debug(`[branchListener]: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: non-Branch link handled directly`, {}, logger.DebugContext.deeplinks); /** * This can happen when the user clicks on a deeplink and we pass its handling on to Branch. @@ -80,7 +80,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * * No link was opened, so we don't typically need to do anything. */ - logger.debug(`[branchListener]: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: handling event where no link was opened`, {}, logger.DebugContext.deeplinks); if (IS_TESTING === 'true' && !!uri) { handleOpenLinkingURL(uri); @@ -91,14 +91,14 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = * should use `params.uri`. This happens about 8k times per week, so it's * very expected. */ - logger.debug(`[branchListener]: using preferred URI value from params`, { + logger.debug(`Branch: using preferred URI value from params`, { params, uri, }); handleOpenLinkingURL(params.uri); } else if (uri) { - logger.debug(`[branchListener]: handling event default case`, {}, logger.DebugContext.deeplinks); + logger.debug(`Branch: handling event default case`, {}, logger.DebugContext.deeplinks); handleOpenLinkingURL(uri); } @@ -112,7 +112,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: any) => void) = .getFirstReferringParams() .then(branchParams => branchParams) .catch(e => { - logger.error(new RainbowError(`[branchListener]: error calling branch.getFirstReferringParams()`), e); + logger.error(new RainbowError('error calling branch.getFirstReferringParams()'), e); return null; }); diff --git a/src/utils/contenthash.ts b/src/utils/contenthash.ts index 948ad38ab13..6ae3225ba73 100644 --- a/src/utils/contenthash.ts +++ b/src/utils/contenthash.ts @@ -12,7 +12,7 @@ export function encodeContenthash(text: string) { let encoded = ''; let error; if (text) { - const matched = matchProtocol(text); + let matched = matchProtocol(text); if (matched) { contentType = matched[1]; content = matched[2]; diff --git a/src/utils/deviceUtils.ts b/src/utils/deviceUtils.ts index e4186a4366e..efa5290c1f5 100644 --- a/src/utils/deviceUtils.ts +++ b/src/utils/deviceUtils.ts @@ -1,10 +1,8 @@ -import { Dimensions, PixelRatio, Platform, NativeModules } from 'react-native'; -const { NavbarHeight } = NativeModules; +import { Dimensions, PixelRatio, Platform } from 'react-native'; -import { IS_ANDROID, IS_IOS } from '@/env'; +import { IS_IOS } from '@/env'; const { height, width } = Dimensions.get('window'); -const scale = Dimensions.get('screen').scale; const deviceUtils = (function () { const iPhone15ProHeight = 852, @@ -41,10 +39,5 @@ const deviceUtils = (function () { export const DEVICE_WIDTH = deviceUtils.dimensions.width; export const DEVICE_HEIGHT = deviceUtils.dimensions.height; export const PIXEL_RATIO = PixelRatio.get(); -export const NAVIGATION_BAR_HEIGHT = IS_ANDROID ? NavbarHeight.getNavigationBarHeight() / scale : 0; -export default deviceUtils; -export const isUsingButtonNavigation = () => { - if (!IS_ANDROID) return false; - return NAVIGATION_BAR_HEIGHT > 40; -}; +export default deviceUtils; diff --git a/src/utils/doesWalletsContainAddress.ts b/src/utils/doesWalletsContainAddress.ts index 088a7bb2953..4929399d672 100644 --- a/src/utils/doesWalletsContainAddress.ts +++ b/src/utils/doesWalletsContainAddress.ts @@ -5,7 +5,7 @@ export default function doesWalletsContainAddress({ address, wallets }: { addres for (let i = 0; i < Object.keys(wallets).length; i++) { const key = Object.keys(wallets)[i]; const someWallet = wallets[key]; - const found = someWallet.addresses?.find((account: any) => account.visible && account.address !== address); + const found = someWallet.addresses.find((account: any) => account.visible && account.address !== address); if (found) { return { key, wallet: found }; diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index 00ac9f63577..f0f685191f4 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -24,7 +24,8 @@ import { SelectedGasFee, } from '@/entities'; import { getOnchainAssetBalance } from '@/handlers/assets'; -import { getProvider, isTestnetChain, toHex } from '@/handlers/web3'; +import { getIsHardhatConnected, getProviderForNetwork, isTestnetNetwork, toHex } from '@/handlers/web3'; +import { Network } from '@/helpers/networkTypes'; import { convertRawAmountToDecimalFormat, fromWei, greaterThan, isZero, subtract, add } from '@/helpers/utilities'; import { Navigation } from '@/navigation'; import { parseAssetNative } from '@/parsers'; @@ -42,40 +43,32 @@ import { import Routes from '@/navigation/routesNames'; import { logger, RainbowError } from '@/logger'; import { IS_IOS } from '@/env'; -import { RainbowNetworkObjects, getNetworkObject } from '@/networks'; +import { RainbowNetworks, getNetworkObj, getNetworkObject } from '@/networks'; import { externalTokenQueryKey, FormattedExternalAsset, fetchExternalToken, useExternalToken, } from '@/resources/assets/externalAssetsQuery'; -import { ChainId, Network } from '@/networks/types'; -import { AddressOrEth } from '@/__swaps__/types/assets'; -import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; +import { ChainId } from '@/__swaps__/types/chains'; -const getNetworkNativeAsset = ({ chainId }: { chainId: ChainId }) => { +const getNetworkNativeAsset = (chainId: ChainId): ParsedAddressAsset | undefined => { const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId); return getAccountAsset(nativeAssetUniqueId); }; -export const getNativeAssetForNetwork = async ({ - chainId, - address, -}: { - chainId: ChainId; - address?: EthereumAddress; -}): Promise => { - const networkNativeAsset = getNetworkNativeAsset({ chainId }); +export const getNativeAssetForNetwork = async (chainId: ChainId, address?: EthereumAddress): Promise => { + const network = getNetworkFromChainId(chainId); + const networkNativeAsset = getNetworkNativeAsset(chainId); const { accountAddress, nativeCurrency } = store.getState().settings; const differentWallet = address?.toLowerCase() !== accountAddress?.toLowerCase(); let nativeAsset = differentWallet ? undefined : networkNativeAsset; // If the asset is on a different wallet, or not available in this wallet if (differentWallet || !nativeAsset) { - const networkObject = getNetworkObject({ chainId }); - const mainnetAddress = networkObject?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; - const nativeAssetAddress = networkObject.nativeCurrency.address as AddressOrEth; + const mainnetAddress = getNetworkObject({ chainId })?.nativeCurrency?.mainnetAddress || ETH_ADDRESS; + const nativeAssetAddress = getNetworkObject({ chainId }).nativeCurrency.address; const externalAsset = await queryClient.fetchQuery( externalTokenQueryKey({ address: nativeAssetAddress, chainId, currency: nativeCurrency }), @@ -88,20 +81,20 @@ export const getNativeAssetForNetwork = async ({ // @ts-ignore nativeAsset = { ...externalAsset, - network: networkObject.value, - uniqueId: getUniqueId(networkObject.nativeCurrency.address, chainId), - address: networkObject.nativeCurrency.address, - decimals: networkObject.nativeCurrency.decimals, - symbol: networkObject.nativeCurrency.symbol, + network, + uniqueId: getUniqueId(getNetworkObject({ chainId }).nativeCurrency.address, chainId), + address: getNetworkObject({ chainId }).nativeCurrency.address, + decimals: getNetworkObject({ chainId }).nativeCurrency.decimals, + symbol: getNetworkObject({ chainId }).nativeCurrency.symbol, }; } - const provider = getProvider({ chainId }); + const provider = getProviderForNetwork(network); if (nativeAsset) { nativeAsset.mainnet_address = mainnetAddress; - nativeAsset.address = networkObject.nativeCurrency.address; + nativeAsset.address = getNetworkObject({ chainId }).nativeCurrency.address; - const balance = await getOnchainAssetBalance(nativeAsset, address, chainId, provider); + const balance = await getOnchainAssetBalance(nativeAsset, address, network, provider); if (balance) { const assetWithBalance = { @@ -122,7 +115,7 @@ const getAsset = (accountAssets: Record, uniqueId: E const getUserAssetFromCache = (uniqueId: string) => { const { accountAddress, nativeCurrency } = store.getState().settings; - const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat; + const connectedToHardhat = getIsHardhatConnected(); const cache = queryClient.getQueryCache(); @@ -175,11 +168,11 @@ const getAssetPrice = (address: EthereumAddress = ETH_ADDRESS): number => { }; export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { - let address = (getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS) as AddressOrEth; + let address = getNetworkObject({ chainId }).nativeCurrency?.mainnetAddress || ETH_ADDRESS; let internalChainId = ChainId.mainnet; const { nativeCurrency } = store.getState().settings; if (chainId === ChainId.avalanche || chainId === ChainId.degen) { - address = getNetworkObject({ chainId }).nativeCurrency?.address as AddressOrEth; + address = getNetworkObject({ chainId }).nativeCurrency?.address; internalChainId = chainId; } const { data: nativeAsset } = useExternalToken({ @@ -192,14 +185,14 @@ export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { }; // anotha 1 -const getPriceOfNativeAssetForNetwork = ({ chainId }: { chainId: ChainId }) => { - if (chainId === ChainId.polygon) { +const getPriceOfNativeAssetForNetwork = (network: Network) => { + if (network === Network.polygon) { return getMaticPriceUnit(); - } else if (chainId === ChainId.bsc) { + } else if (network === Network.bsc) { return getBnbPriceUnit(); - } else if (chainId === ChainId.avalanche) { + } else if (network === Network.avalanche) { return getAvaxPriceUnit(); - } else if (chainId === ChainId.degen) { + } else if (network === Network.degen) { return getDegenPriceUnit(); } return getEthPriceUnit(); @@ -281,7 +274,7 @@ const getDataString = (func: string, arrVals: string[]) => { * @param {Number} chainId */ export const getNetworkFromChainId = (chainId: ChainId): Network => { - return RainbowNetworkObjects.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; + return RainbowNetworks.find(network => network.id === chainId)?.value || getNetworkObject({ chainId: ChainId.mainnet }).value; }; /** @@ -289,7 +282,7 @@ export const getNetworkFromChainId = (chainId: ChainId): Network => { * @param {Number} chainId */ const getNetworkNameFromChainId = (chainId: ChainId): string => { - return RainbowNetworkObjects.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; + return RainbowNetworks.find(network => network.id === chainId)?.name || getNetworkObject({ chainId: ChainId.mainnet }).name; }; /** @@ -297,20 +290,20 @@ const getNetworkNameFromChainId = (chainId: ChainId): string => { * @param {String} network */ const getChainIdFromNetwork = (network?: Network): ChainId => { - return RainbowNetworkObjects.find(networkObject => networkObject.value === network)?.id || ChainId.mainnet; + return network ? getNetworkObj(network).id : ChainId.mainnet; }; /** * @desc get etherscan host from network string * @param {String} network */ -function getEtherscanHostForNetwork({ chainId }: { chainId: ChainId }): string { +function getEtherscanHostForNetwork(chainId: ChainId): string { const base_host = 'etherscan.io'; const networkObject = getNetworkObject({ chainId }); const blockExplorer = networkObject.blockExplorers?.default?.url; const network = networkObject.network as Network; - if (network && isTestnetChain({ chainId })) { + if (network && isTestnetNetwork(network)) { return `${network}.${base_host}`; } else { return blockExplorer || base_host; @@ -382,11 +375,11 @@ export const getFirstTransactionTimestamp = async (address: EthereumAddress): Pr return timestamp ? timestamp * 1000 : undefined; }; -function getBlockExplorer({ chainId }: { chainId: ChainId }) { +function getBlockExplorer(chainId: ChainId) { return getNetworkObject({ chainId }).blockExplorers?.default.name || 'etherscan'; } -function openAddressInBlockExplorer({ address, chainId }: { address: EthereumAddress; chainId: ChainId }) { +function openAddressInBlockExplorer(address: EthereumAddress, chainId: ChainId) { const explorer = getNetworkObject({ chainId })?.blockExplorers?.default?.url; Linking.openURL(`${explorer}/address/${address}`); } @@ -432,7 +425,7 @@ async function parseEthereumUrl(data: string) { if (!functionName) { // Send native asset const chainId = getChainIdFromNetwork(network); - asset = getNetworkNativeAsset({ chainId }); + asset = getNetworkNativeAsset(chainId); // @ts-ignore if (!asset || asset?.balance.amount === 0) { @@ -476,17 +469,17 @@ export const getUniqueIdNetwork = (address: EthereumAddress, network: Network) = export const getUniqueId = (address: EthereumAddress, chainId: ChainId) => `${address}_${chainId}`; -export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: AddressOrEth; chainId: ChainId } => { +export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: EthereumAddress; chainId: ChainId } => { const parts = uniqueId.split('_'); // If the unique ID does not contain '_', it's a mainnet address if (parts.length === 1) { - return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; + return { address: parts[0], chainId: ChainId.mainnet }; } // If the unique ID contains '_', the last part is the network and the rest is the address const network = parts[1] as Network; // Assuming the last part is a valid Network enum value - const address = parts[0] as AddressOrEth; + const address = parts[0]; const chainId = getChainIdFromNetwork(network); return { address, chainId }; @@ -529,7 +522,7 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const l1FeeInWei = await OVM_GasPriceOracle.getL1Fee(serializedTx); return l1FeeInWei; } catch (e: any) { - logger.error(new RainbowError(`[ethereumUtils]: error calculating l1 fee`), { + logger.error(new RainbowError('error calculating l1 fee'), { message: e.message, }); } @@ -537,13 +530,13 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider const getBasicSwapGasLimit = (chainId: ChainId) => { switch (chainId) { - case ChainId.arbitrum: + case getChainIdFromNetwork(Network.arbitrum): return ethUnits.basic_swap_arbitrum; - case ChainId.polygon: + case getChainIdFromNetwork(Network.polygon): return ethUnits.basic_swap_polygon; - case ChainId.bsc: + case getChainIdFromNetwork(Network.bsc): return ethUnits.basic_swap_bsc; - case ChainId.optimism: + case getChainIdFromNetwork(Network.optimism): return ethUnits.basic_swap_optimism; default: return ethUnits.basic_swap; diff --git a/src/utils/getTokenMetadata.ts b/src/utils/getTokenMetadata.ts new file mode 100644 index 00000000000..fe376441c80 --- /dev/null +++ b/src/utils/getTokenMetadata.ts @@ -0,0 +1,7 @@ +import { TokenMetadata } from '@/entities/tokens'; +import { omitFlatten } from '@/helpers/utilities'; +import { rainbowTokenList } from '@/references'; + +export default function getTokenMetadata(tokenAddress: string | undefined): Omit | undefined { + return undefined; +} diff --git a/src/utils/getUrlForTrustIconFallback.ts b/src/utils/getUrlForTrustIconFallback.ts index 49030949001..c248639dfc9 100644 --- a/src/utils/getUrlForTrustIconFallback.ts +++ b/src/utils/getUrlForTrustIconFallback.ts @@ -1,19 +1,18 @@ -import { ChainId } from '@/networks/types'; import { EthereumAddress } from '@/entities'; -import ethereumUtils from './ethereumUtils'; +import { Network } from '@/networks/types'; -export default function getUrlForTrustIconFallback(address: EthereumAddress, chainId: ChainId): string | null { +export default function getUrlForTrustIconFallback(address: EthereumAddress, network: Network): string | null { if (!address) return null; let networkPath = 'ethereum'; - switch (chainId) { - case ChainId.mainnet: + switch (network) { + case Network.mainnet: networkPath = 'ethereum'; break; - case ChainId.bsc: + case Network.bsc: networkPath = 'smartchain'; break; default: - networkPath = ethereumUtils.getNetworkFromChainId(chainId); + networkPath = network; } return `https://rainbowme-res.cloudinary.com/image/upload/assets/${networkPath}/${address}.png`; } diff --git a/src/utils/index.ts b/src/utils/index.ts index aad92e7e346..0ebd4ada6b7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,12 +12,14 @@ export { default as ethereumUtils } from './ethereumUtils'; export { default as formatURLForDisplay } from './formatURLForDisplay'; export { default as gasUtils } from './gas'; export { default as getDominantColorFromImage } from './getDominantColorFromImage'; +export { default as getTokenMetadata } from './getTokenMetadata'; export { getUniqueTokenFormat, getUniqueTokenType } from './uniqueTokens'; export { default as getUrlForTrustIconFallback } from './getUrlForTrustIconFallback'; export { default as haptics } from './haptics'; export { default as isETH } from './isETH'; export { default as isLowerCaseMatch } from './isLowerCaseMatch'; export { default as labelhash } from './labelhash'; +export { default as logger } from './logger'; export { default as magicMemo } from './magicMemo'; export { default as measureText } from './measureText'; export { default as neverRerender } from './neverRerender'; diff --git a/src/utils/languageLocaleToCountry.ts b/src/utils/languageLocaleToCountry.ts deleted file mode 100644 index b8edace143b..00000000000 --- a/src/utils/languageLocaleToCountry.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Language } from '@/languages'; - -/** - * Converts a language locale to a country code. - * @param languageLocale - The language locale to convert. - * @returns The country code. - */ -export const languageLocaleToCountry = (languageLocale: keyof typeof Language) => { - const [languageCode, countryCode] = languageLocale.split('_'); - - // e.g. - ES_419 we want to return ES instead of 419 - if (Number(countryCode)) { - return languageCode; - } - return countryCode; -}; diff --git a/src/utils/ledger.ts b/src/utils/ledger.ts index c7da0a7657f..af9bb6cd725 100644 --- a/src/utils/ledger.ts +++ b/src/utils/ledger.ts @@ -25,14 +25,14 @@ export const ledgerErrorHandler = (error: Error) => { return LEDGER_ERROR_CODES.OFF_OR_LOCKED; } if (error.name.includes('Disconnected')) { - logger.error(new RainbowError(`[ledger]: Disconnected Error`), { + logger.error(new RainbowError('[Ledger] - Disconnected Error'), { name: error.name, message: error.message, }); return LEDGER_ERROR_CODES.DISCONNECTED; } - logger.error(new RainbowError(`[ledger]: Unknown Error`), { + logger.error(new RainbowError('[LedgerConnect] - Unknown Error'), { name: error.name, message: error.message, }); @@ -62,12 +62,12 @@ export const checkLedgerConnection = async ({ ethApp .getAddress(path) .then(res => { - logger.debug(`[ledger]: ledger is ready`, {}); + logger.info('[checkLedgerConnection] - ledger is ready', {}); successCallback?.(deviceId); }) .catch(e => { const errorType = ledgerErrorHandler(e); - logger.warn('[ledger] - ledger is not ready', { + logger.warn('[checkLedgerConnection] - ledger is not ready', { errorType: errorType, error: e, }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 00000000000..c69ff91a70f --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,117 @@ +import { captureException } from '@sentry/react-native'; +import { QUIET_OLD_LOGGER } from 'react-native-dotenv'; +import sentryUtils from './sentry'; + +/** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ +const Logger = { + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + debug(...args: any[]) { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + const date = new Date().toLocaleTimeString(); + Array.prototype.unshift.call(args, `[${date}] ⚡⚡⚡ `); + console.log(...args); // eslint-disable-line no-console + } + }, + + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + error(...args: any[]) { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + console.error(...args); // eslint-disable-line no-console + } + }, + + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + log(...args: any[]) { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + const date = new Date().toLocaleTimeString(); + Array.prototype.unshift.call(args, `[${date}]`); + console.log(...args); // eslint-disable-line no-console + } + }, + + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + prettyLog() { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + const allArgs = Array.prototype.slice.call(arguments).map(arg => { + try { + if (typeof arg === 'object') { + return JSON.stringify(arg, null, 2); + } else { + return arg; + } + } catch (e) { + return arg; + } + }); + console.log(allArgs.length > 0 ? allArgs : allArgs[0]); // eslint-disable-line no-console + } + }, + + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + sentry(...args: any[]) { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + const date = new Date().toLocaleTimeString(); + Array.prototype.unshift.call(args, `[${date}]`); + console.log(...args); // eslint-disable-line no-console + } + if (args.length === 1 && typeof args[0] === 'string') { + sentryUtils.addInfoBreadcrumb.apply(null, [args[0]]); + } else { + const safeData = safelyStringifyWithFormat(args[1]); + sentryUtils.addDataBreadcrumb(args[0], safeData); + } + }, + + /** + * @deprecated use `@/logger` instead, and see `@/logger/README` for documentation + */ + warn(...args: any[]) { + if (QUIET_OLD_LOGGER) return; + if (__DEV__) { + console.warn(...args); // eslint-disable-line no-console + } + }, +}; + +const safelyStringifyWithFormat = (data: any) => { + try { + const seen: any = []; + const newData = JSON.stringify( + data, + // Required to ignore cyclic structures + (key, val) => { + if (val != null && typeof val == 'object') { + if (seen.indexOf(val) >= 0) { + return; + } + seen.push(val); + } + return val; + }, + 2 + ); + return { data: newData }; + } catch (e) { + captureException(e); + return {}; + } +}; + +export default Logger; diff --git a/src/utils/memoFn.ts b/src/utils/memoFn.ts index da14394b00e..d279ae5feb6 100644 --- a/src/utils/memoFn.ts +++ b/src/utils/memoFn.ts @@ -27,7 +27,7 @@ export function memoFn( // if no arguments used we just want the developer and run the function as is if (args.length === 0) { if (IS_DEV) { - logger.warn(`[memoFn]: memoized function ${fn.name} was called with no arguments`); + logger.warn(`memoized function ${fn.name} was called with no arguments`); } // Call it anyway to not break stuff @@ -41,7 +41,7 @@ export function memoFn( if (typeof arg !== 'number' && typeof arg !== 'boolean' && typeof arg !== 'string') { if (IS_DEV) { logger.warn( - `[memoFn]: memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( + `memoized function ${fn.name} was called with non-supported arguments: ${JSON.stringify( args )}. Typeof of ${i + 1} argument is ${typeof arg}` ); @@ -57,8 +57,8 @@ export function memoFn( if (cache.has(key)) { // For debugging - // logger.debug(`[memoFn]: Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); - // logger.debug('[memoFn]: Total cached', cachedCalls++); + // logger.debug(`Used cached ${cachedCall++} times result for function ${fn.name} with arguments ${key}); + // logger.debug('Total cached', cachedCalls++); // return cached result return cache.get(key)!; // we did a check for that key already diff --git a/src/utils/methodRegistry.ts b/src/utils/methodRegistry.ts index 21abc903164..082b1b7c39c 100644 --- a/src/utils/methodRegistry.ts +++ b/src/utils/methodRegistry.ts @@ -1,5 +1,5 @@ import { Contract } from '@ethersproject/contracts'; -import { getProvider } from '../handlers/web3'; +import { web3Provider } from '../handlers/web3'; import namesOverrides from '../references/method-names-overrides.json'; import methodRegistryABI from '../references/method-registry-abi.json'; import { metadataClient } from '@/graphql'; @@ -17,8 +17,7 @@ export const methodRegistryLookupAndParse = async (methodSignatureBytes: any, ch if (data?.contractFunction?.text) { signature = data.contractFunction.text; } else { - const provider = getProvider({ chainId }); - const registry = new Contract(METHOD_REGISTRY_ADDRESS, methodRegistryABI, provider); + const registry = new Contract(METHOD_REGISTRY_ADDRESS, methodRegistryABI, web3Provider); signature = await registry.entries(methodSignatureBytes); } diff --git a/src/utils/poaps.ts b/src/utils/poaps.ts index d0020a031ad..555a5a13da3 100644 --- a/src/utils/poaps.ts +++ b/src/utils/poaps.ts @@ -20,7 +20,7 @@ export const getPoapAndOpenSheetWithSecretWord = async (secretWord: string, goBa }); } } catch (e) { - logger.warn('[poaps]: Error getting POAP with secret word'); + logger.warn('Error getting POAP with secret word'); } }; @@ -39,6 +39,6 @@ export const getPoapAndOpenSheetWithQRHash = async (qrHash: string, goBack: bool }); } } catch { - logger.warn('[poaps]: Error getting POAP with qrHash'); + logger.warn('Error getting POAP with qrHash'); } }; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 52f5a2a334f..8a6f193a480 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -15,15 +15,13 @@ import { SEND_TRANSACTION } from './signingMethods'; import { handleSessionRequestResponse } from '@/walletConnect'; import ethereumUtils from './ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers'; -import { RainbowNetworkObjects } from '@/networks'; +import { RainbowNetworks } from '@/networks'; import { maybeSignUri } from '@/handlers/imgix'; import { getActiveRoute } from '@/navigation/Navigation'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; -import { Address } from 'viem'; -import { ChainId } from '@/networks/types'; export type RequestSource = 'walletconnect' | 'browser'; @@ -37,13 +35,9 @@ export interface DappConnectionData { address?: string; } -export const handleDappBrowserConnectionPrompt = ( - dappData: DappConnectionData -): Promise<{ chainId: ChainId; address: Address } | Error> => { +export const handleDappBrowserConnectionPrompt = (dappData: DappConnectionData): Promise<{ chainId: number; address: string } | Error> => { return new Promise((resolve, reject) => { - const chainIds = RainbowNetworkObjects.filter(network => network.enabled && network.networkType !== 'testnet').map( - network => network.id - ); + const chainIds = RainbowNetworks.filter(network => network.enabled && network.networkType !== 'testnet').map(network => network.id); const receivedTimestamp = Date.now(); const routeParams: WalletconnectApprovalSheetRouteParams = { receivedTimestamp, diff --git a/src/utils/reviewAlert.ts b/src/utils/reviewAlert.ts index 4deef6eb9c9..44b3113b6f8 100644 --- a/src/utils/reviewAlert.ts +++ b/src/utils/reviewAlert.ts @@ -30,7 +30,7 @@ export const numberOfTimesBeforePrompt: { }; export const handleReviewPromptAction = async (action: ReviewPromptAction) => { - logger.debug(`[reviewAlert]: handleReviewPromptAction: ${action}`); + logger.debug(`handleReviewPromptAction: ${action}`); if (IS_TESTING === 'true') { return; @@ -53,10 +53,10 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } const timeOfLastPrompt = ls.review.get(['timeOfLastPrompt']) || 0; - logger.debug(`[reviewAlert]: timeOfLastPrompt: ${timeOfLastPrompt}`); + logger.debug(`timeOfLastPrompt: ${timeOfLastPrompt}`); actionToDispatch.numOfTimesDispatched += 1; - logger.debug(`[reviewAlert]: numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); + logger.debug(`numOfTimesDispatched: ${actionToDispatch.numOfTimesDispatched}`); const hasReachedAmount = actionToDispatch.numOfTimesDispatched >= numberOfTimesBeforePrompt[action]; @@ -66,7 +66,7 @@ export const handleReviewPromptAction = async (action: ReviewPromptAction) => { } if (hasReachedAmount && timeOfLastPrompt + TWO_MONTHS <= Date.now()) { - logger.debug(`[reviewAlert]: Prompting for review`); + logger.debug(`Prompting for review`); actionToDispatch.numOfTimesDispatched = 0; ls.review.set(['timeOfLastPrompt'], Date.now()); promptForReview(); diff --git a/src/utils/simplifyChartData.ts b/src/utils/simplifyChartData.ts index d5d5e151116..a6397322946 100644 --- a/src/utils/simplifyChartData.ts +++ b/src/utils/simplifyChartData.ts @@ -3,13 +3,13 @@ import { maxBy, minBy } from 'lodash'; export default function simplifyChartData(data: any, destinatedNumberOfPoints: number) { if (!data) return null; - const allSegmentDividers: any = []; + let allSegmentDividers: any = []; let allSegmentsPoints: any = []; - const colors = []; - const lines = []; - const dividers = []; - const lastPoints = []; - const createdLastPoints: any = []; + let colors = []; + let lines = []; + let dividers = []; + let lastPoints = []; + let createdLastPoints: any = []; if (data.segments.length > 0) { for (let i = 0; i < 1; i++) { @@ -25,13 +25,13 @@ export default function simplifyChartData(data: any, destinatedNumberOfPoints: n } } if (allSegmentsPoints.length > destinatedNumberOfPoints) { - const destMul = allSegmentsPoints.length / destinatedNumberOfPoints; + let destMul = allSegmentsPoints.length / destinatedNumberOfPoints; const maxValue = maxBy(allSegmentsPoints, 'y'); const minValue = minBy(allSegmentsPoints, 'y'); const dataDiff = allSegmentsPoints[allSegmentsPoints.length - 1].x - allSegmentsPoints[0].x; const xMul = Math.floor(dataDiff / allSegmentsPoints.length); - const newData = []; + let newData = []; newData.push({ isImportant: true, x: allSegmentsPoints[0].x - xMul * 2, diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 4eee7e3a9c6..b653aa9406e 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -8,7 +8,7 @@ import { isAddress, getAddress } from '@ethersproject/address'; import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; -import WalletConnectCore, { Core } from '@walletconnect/core'; +import { Core } from '@walletconnect/core'; import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -37,17 +37,16 @@ import * as explain from '@/screens/Explain'; import { Box } from '@/design-system'; import { AuthRequestAuthenticateSignature, AuthRequestResponseErrorReason, RPCMethod, RPCPayload } from '@/walletConnect/types'; import { AuthRequest } from '@/walletConnect/sheets/AuthRequest'; -import { getProvider } from '@/handlers/web3'; -import { RainbowNetworkObjects } from '@/networks'; +import { getProviderForNetwork } from '@/handlers/web3'; +import { RainbowNetworks } from '@/networks'; import { uniq } from 'lodash'; import { fetchDappMetadata } from '@/resources/metadata/dapp'; import { DAppStatus } from '@/graphql/__generated__/metadata'; import { handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; import { PerformanceTracking } from '@/performance/tracking'; -import { ChainId } from '@/networks/types'; -const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworkObjects.filter(({ features }) => features.walletconnect).map(({ id }) => id); +const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworks.filter(({ features }) => features.walletconnect).map(({ id }) => id); const SUPPORTED_SESSION_EVENTS = ['chainChanged', 'accountsChanged']; @@ -70,7 +69,7 @@ let hasDeeplinkPendingRedirect = false; * listeners. BE CAREFUL WITH THIS. */ export function setHasPendingDeeplinkPendingRedirect(value: boolean) { - logger.debug(`[walletConnect]: setHasPendingDeeplinkPendingRedirect`, { value }); + logger.info(`setHasPendingDeeplinkPendingRedirect`, { value }); hasDeeplinkPendingRedirect = value; } @@ -96,46 +95,32 @@ export function maybeGoBackAndClearHasPendingRedirect({ delay = 0 }: { delay?: n */ let syncWeb3WalletClient: Awaited> | undefined; -let lastConnector: string | undefined = undefined; - -let walletConnectCore: WalletConnectCore | undefined; - -let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; +const walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); + +const web3WalletClient = Web3Wallet.init({ + core: walletConnectCore, + metadata: { + name: '🌈 Rainbow', + description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', + url: 'https://rainbow.me', + icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], + redirect: { + native: 'rainbow://wc', + universal: 'https://rnbwapp.com/wc', + }, + }, +}); let initPromise: ReturnType<(typeof Web3Wallet)['init']> | null = null; -export const initializeWCv2 = async () => { - walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); - - web3WalletClient = Web3Wallet.init({ - core: walletConnectCore, - metadata: { - name: '🌈 Rainbow', - description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', - url: 'https://rainbow.me', - icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], - redirect: { - native: 'rainbow://wc', - universal: 'https://rnbwapp.com/wc', - }, - }, - }); - return web3WalletClient; -}; - // this function ensures we only initialize the client once export async function getWeb3WalletClient() { if (!syncWeb3WalletClient) { if (!initPromise) { - if (web3WalletClient) { - initPromise = web3WalletClient.then(client => { - syncWeb3WalletClient = client; - return client; - }); - } else { - await initializeWCv2(); - return getWeb3WalletClient(); - } + initPromise = web3WalletClient.then(client => { + syncWeb3WalletClient = client; + return client; + }); } // Wait for the initialization promise to resolve return initPromise; @@ -164,11 +149,7 @@ export function parseRPCParams({ method, params }: RPCPayload): { decodedMessage = toUtf8String(message); } } catch (err) { - logger.debug( - `[walletConnect]: parsing RPC params unable to decode hex message to UTF8 string`, - {}, - logger.DebugContext.walletconnect - ); + logger.debug('WC v2: parsing RPC params unable to decode hex message to UTF8 string', {}, logger.DebugContext.walletconnect); } return { @@ -235,7 +216,7 @@ export function getApprovedNamespaces(props: Parameters id === chainId && features.walletconnect); + return !!RainbowNetworks.find(({ id, features }) => id === chainId && features.walletconnect); } /** @@ -319,7 +300,7 @@ async function rejectProposal({ proposal: Web3WalletTypes.SessionProposal; reason: Parameters[0]; }) { - logger.warn(`[walletConnect]: session approval denied`, { + logger.warn(`WC v2: session approval denied`, { reason, proposal, }); @@ -335,23 +316,9 @@ async function rejectProposal({ }); } -// listen for THIS topic pairing, and clear timeout if received -function trackTopicHandler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { - logger.debug(`[walletConnect]: pair: handler`, { proposal }); - - const { metadata } = - (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; - - analytics.track(analytics.event.wcNewPairing, { - dappName: metadata.name, - dappUrl: metadata.url, - connector: lastConnector || 'unknown', - }); -} - export async function pair({ uri, connector }: { uri: string; connector?: string }) { - logger.debug(`[walletConnect]: pair`, { uri }, logger.DebugContext.walletconnect); - lastConnector = connector; + logger.debug(`WC v2: pair`, { uri }, logger.DebugContext.walletconnect); + /** * Make sure this is cleared if we get multiple pairings in rapid succession */ @@ -359,10 +326,27 @@ export async function pair({ uri, connector }: { uri: string; connector?: string const { topic, ...rest } = parseUri(uri); const client = await getWeb3WalletClient(); - logger.debug(`[walletConnect]: pair: parsed uri`, { topic, rest }); + logger.debug(`WC v2: pair: parsed uri`, { topic, rest }); + + // listen for THIS topic pairing, and clear timeout if received + function handler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { + logger.debug(`WC v2: pair: handler`, { proposal }); + + const { metadata } = + (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; + analytics.track(analytics.event.wcNewPairing, { + dappName: metadata.name, + dappUrl: metadata.url, + connector, + }); + } + + // CAN get fired on subsequent pairs, so need to make sure we clean up + client.on('session_proposal', handler); + client.on('auth_request', handler); // init pairing - await client.pair({ uri }); + await client.core.pairing.pair({ uri }); } export async function initListeners() { @@ -373,13 +357,13 @@ export async function initListeners() { syncWeb3WalletClient = client; - logger.debug(`[walletConnect]: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); client.on('session_proposal', onSessionProposal); client.on('session_request', onSessionRequest); client.on('auth_request', onAuthRequest); client.on('session_delete', () => { - logger.debug(`[walletConnect]: session_delete`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session_delete`, {}, logger.DebugContext.walletconnect); setTimeout(() => { events.emit('walletConnectV2SessionDeleted'); @@ -398,7 +382,7 @@ export async function initListeners() { /** * Ensure that if the FCM token changes we update the echo server */ - messaging().onTokenRefresh(async (token: string) => { + messaging().onTokenRefresh(async token => { await subscribeToEchoServer({ token, client_id }); }); } else { @@ -409,11 +393,11 @@ export async function initListeners() { * which could be due to network flakiness, SSL server error (has * happened), etc. Things out of our control. */ - logger.warn(`[walletConnect]: FCM token not found, push notifications will not be received`); + logger.warn(`WC v2: FCM token not found, push notifications will not be received`); } } } catch (e) { - logger.error(new RainbowError(`[walletConnect]: initListeners failed`), { error: e }); + logger.error(new RainbowError(`WC v2: initListeners failed`), { error: e }); } } @@ -433,7 +417,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; * should report these to Datadog, and we can leave this as a warn to * continue to monitor. */ - logger.warn(`[walletConnect]: echo server subscription failed`, { + logger.warn(`WC v2: echo server subscription failed`, { error: res.error, }); } @@ -441,9 +425,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposal) { try { - trackTopicHandler(proposal); - - logger.debug(`[walletConnect]: session_proposal`, { proposal }, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session_proposal`, { proposal }, logger.DebugContext.walletconnect); const verifiedData = proposal.verifyContext.verified; const receivedTimestamp = Date.now(); @@ -483,7 +465,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa if (approved) { logger.debug( - `[walletConnect]: session approved`, + `WC v2: session approved`, { approved, approvedChainId, @@ -509,7 +491,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa }, }); - logger.debug(`[walletConnect]: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session approved namespaces`, { namespaces }, logger.DebugContext.walletconnect); try { if (namespaces.success) { @@ -529,7 +511,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa // let the ConnectedDappsSheet know we've got a new one events.emit('walletConnectV2SessionCreated'); - logger.debug(`[walletConnect]: session created`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session created`, {}, logger.DebugContext.walletconnect); analytics.track(analytics.event.wcNewSessionApproved, { dappName: proposer.metadata.name, @@ -571,7 +553,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa title: lang.t(lang.l.walletconnect.connection_failed), }); - logger.error(new RainbowError(`[walletConnect]: session approval failed`), { + logger.error(new RainbowError(`WC v2: session approval failed`), { error: (e as Error).message, }); } @@ -592,7 +574,7 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa ); } catch (error) { logger.error( - new RainbowError(`[walletConnect]: session request catch all`, { + new RainbowError(`WC v2: session request catch all`, { ...(error as Error), }) ); @@ -604,12 +586,12 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se setHasPendingDeeplinkPendingRedirect(true); const client = await getWeb3WalletClient(); - logger.debug(`[walletConnect]: session_request`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session_request`, {}, logger.DebugContext.walletconnect); const { id, topic } = event; const { method, params } = event.params.request; - logger.debug(`[walletConnect]: session_request method`, { method, params }, logger.DebugContext.walletconnect); + logger.debug(`WC v2: session_request method`, { method, params }, logger.DebugContext.walletconnect); // we allow eth sign for connections but we dont want to support actual singing if (method === RPCMethod.Sign) { @@ -632,23 +614,19 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se params, }); if (!address) { - logger.error(new RainbowError('[walletConnect]: No Address in the RPC Params')); + logger.error(new RainbowError('No Address in the RPC Params')); return; } const allWallets = store.getState().wallets.wallets; - logger.debug( - `[walletConnect]: session_request method is supported`, - { method, params, address, message }, - logger.DebugContext.walletconnect - ); + logger.debug(`WC v2: session_request method is supported`, { method, params, address, message }, logger.DebugContext.walletconnect); if (isSigningMethod) { - logger.debug(`[walletConnect]: validating session_request signing method`); + logger.debug(`WC v2: validating session_request signing method`); if (!address || !message) { - logger.error(new RainbowError(`[walletConnect]: session_request exited, signing request had no address and/or messsage`), { + logger.error(new RainbowError(`WC v2: session_request exited, signing request had no address and/or messsage`), { address, message, }); @@ -669,7 +647,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // for TS only, should never happen if (!allWallets) { - logger.error(new RainbowError(`[walletConnect]: allWallets is null, this should never happen`)); + logger.error(new RainbowError(`WC v2: allWallets is null, this should never happen`)); return; } @@ -677,7 +655,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const isReadOnly = selectedWallet?.type === WalletTypes.readOnly; if (!selectedWallet || isReadOnly) { - logger.error(new RainbowError(`[walletConnect]: session_request exited, selectedWallet was falsy or read only`), { + logger.error(new RainbowError(`WC v2: session_request exited, selectedWallet was falsy or read only`), { selectedWalletType: selectedWallet?.type, }); @@ -704,7 +682,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se // mostly a TS guard, pry won't happen if (!session) { - logger.error(new RainbowError(`[walletConnect]: session_request topic was not found`)); + logger.error(new RainbowError(`WC v2: session_request topic was not found`)); await client.respondSessionRequest({ topic, @@ -717,9 +695,9 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se const { nativeCurrency, network } = store.getState().settings; const chainId = Number(event.params.chainId.split(':')[1]); - logger.debug(`[walletConnect]: getting session for topic`, { session }); + logger.debug(`WC v2: getting session for topic`, { session }); - logger.debug(`[walletConnect]: handling request`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: handling request`, {}, logger.DebugContext.walletconnect); const dappNetwork = ethereumUtils.getNetworkFromChainId(chainId); const displayDetails = getRequestDisplayDetails(event.params.request, nativeCurrency, dappNetwork); @@ -771,7 +749,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); saveLocalRequests(updatedRequests, address, network); - logger.debug(`[walletConnect]: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: navigating to CONFIRM_REQUEST sheet`, {}, logger.DebugContext.walletconnect); handleWalletConnectRequest(request); @@ -781,7 +759,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); } } else { - logger.error(new RainbowError(`[walletConnect]: received unsupported session_request RPC method`), { + logger.error(new RainbowError(`WC v2: received unsupported session_request RPC method`), { method, }); @@ -791,7 +769,7 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se response: formatJsonRpcError(id, `Method ${method} not supported`), }); } catch (e) { - logger.error(new RainbowError(`[walletConnect]: error rejecting session_request`), { + logger.error(new RainbowError(`WC v2: error rejecting session_request`), { error: (e as Error).message, }); } @@ -816,7 +794,7 @@ export async function handleSessionRequestResponse( }, { result, error }: { result: string | null; error: any } ) { - logger.debug(`[walletConnect]: handleSessionRequestResponse`, { + logger.info(`WC v2: handleSessionRequestResponse`, { success: Boolean(result), }); @@ -827,14 +805,14 @@ export async function handleSessionRequestResponse( topic, response: formatJsonRpcResult(id, result), }; - logger.debug(`[walletConnect]: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: handleSessionRequestResponse success`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } else { const payload = { topic, response: formatJsonRpcError(id, error), }; - logger.debug(`[walletConnect]: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); + logger.debug(`WC v2: handleSessionRequestResponse reject`, {}, logger.DebugContext.walletconnect); await client.respondSessionRequest(payload); } @@ -842,11 +820,9 @@ export async function handleSessionRequestResponse( } export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { - trackTopicHandler(event); - const client = await getWeb3WalletClient(); - logger.debug(`[walletConnect]: auth_request`, { event }, logger.DebugContext.walletconnect); + logger.debug(`WC v2: auth_request`, { event }, logger.DebugContext.walletconnect); const authenticate: AuthRequestAuthenticateSignature = async ({ address }) => { try { @@ -879,11 +855,11 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { * encapsulate reused code. */ const loadWalletAndSignMessage = async () => { - const provider = getProvider({ chainId: ChainId.arbitrum }); + const provider = getProviderForNetwork(); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, provider }); if (!wallet) { - logger.error(new RainbowError(`[walletConnect]: could not loadWallet to sign auth_request`)); + logger.error(new RainbowError(`WC v2: could not loadWallet to sign auth_request`)); return undefined; } @@ -932,7 +908,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { return { success: true }; } catch (e: any) { - logger.error(new RainbowError(`[walletConnect]: an unknown error occurred when signing auth_request`), { + logger.error(new RainbowError(`WC v2: an unknown error occurred when signing auth_request`), { message: e.message, }); return { success: false, reason: AuthRequestResponseErrorReason.Unknown }; @@ -1017,13 +993,13 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres } } } else { - logger.error(new RainbowError(`[walletConnect]: namespace is missing chains prop when updating`), { + logger.error(new RainbowError(`WC v2: namespace is missing chains prop when updating`), { requiredNamespaces: session.requiredNamespaces, }); } } - logger.debug(`[walletConnect]: updating session`, { + logger.debug(`WC v2: updating session`, { namespaces, }); @@ -1032,7 +1008,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres namespaces, }); } catch (e: any) { - logger.error(new RainbowError(`[walletConnect]: error adding account to session`), { + logger.error(new RainbowError(`WC v2: error adding account to session`), { message: e.message, }); } @@ -1050,12 +1026,12 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { for (const value of Object.values(session.requiredNamespaces)) { if (!value.chains) { - logger.debug(`[walletConnect]: changeAccount, no chains found for namespace`); + logger.debug(`WC v2: changeAccount, no chains found for namespace`); continue; } for (const chainId of value.chains) { - logger.debug(`[walletConnect]: changeAccount, updating accounts for chainId`, { + logger.debug(`WC v2: changeAccount, updating accounts for chainId`, { chainId, }); @@ -1069,15 +1045,15 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { chainId, }); - logger.debug(`[walletConnect]: changeAccount, updated accounts for chainId`, { + logger.debug(`WC v2: changeAccount, updated accounts for chainId`, { chainId, }); } } - logger.debug(`[walletConnect]: changeAccount complete`); + logger.debug(`WC v2: changeAccount complete`); } catch (e: any) { - logger.error(new RainbowError(`[walletConnect]: error changing account`), { + logger.error(new RainbowError(`WC v2: error changing account`), { message: e.message, }); } diff --git a/tsconfig.json b/tsconfig.json index 9d8fe1cca5a..2f2c192422a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,6 +29,7 @@ "@rainbow-me/model/*": ["src/model/*"], "@rainbow-me/navigation": ["src/navigation"], "@rainbow-me/navigation/*": ["src/navigation/*"], + "@rainbow-me/networkTypes": ["./src/helpers/networkTypes"], "@rainbow-me/parsers": ["src/parsers"], "@rainbow-me/raps": ["src/raps"], "@rainbow-me/react-query": ["./src/react-query"], diff --git a/yarn.lock b/yarn.lock index 9d7df69de4f..94b70d87693 100644 --- a/yarn.lock +++ b/yarn.lock @@ -115,18 +115,6 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/generator@npm:7.25.0" - dependencies: - "@babel/types": "npm:^7.25.0" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^2.5.1" - checksum: 10c0/d0e2dfcdc8bdbb5dded34b705ceebf2e0bc1b06795a1530e64fb6a3ccf313c189db7f60c1616effae48114e1a25adc75855bc4496f3779a396b3377bae718ce7 - languageName: node - linkType: hard - "@babel/helper-annotate-as-pure@npm:^7.0.0, @babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" @@ -367,19 +355,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-replace-supers@npm:7.25.0" - dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef - languageName: node - linkType: hard - "@babel/helper-simple-access@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-simple-access@npm:7.24.7" @@ -491,17 +466,6 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/parser@npm:7.25.3" - dependencies: - "@babel/types": "npm:^7.25.2" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/874b01349aedb805d6694f867a752fdc7469778fad76aca4548d2cc6ce96087c3ba5fb917a6f8d05d2d1a74aae309b5f50f1a4dba035f5a2c9fcfe6e106d2c4e - languageName: node - linkType: hard - "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.7" @@ -999,7 +963,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.0.0-0, @babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": +"@babel/plugin-transform-class-properties@npm:^7.22.0, @babel/plugin-transform-class-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" dependencies: @@ -1042,22 +1006,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0-0": - version: 7.25.0 - resolution: "@babel/plugin-transform-classes@npm:7.25.0" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - globals: "npm:^11.1.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4451dccf8a7979427ae042afe381233f30764a8072faf0de1337a4fc297c6d7cb40df9e28931ac096e5b56392d0cd97d3ce10aee68288150a8701624d362a791 - languageName: node - linkType: hard - "@babel/plugin-transform-computed-properties@npm:^7.0.0, @babel/plugin-transform-computed-properties@npm:^7.21.5, @babel/plugin-transform-computed-properties@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" @@ -1628,7 +1576,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.0.0-0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": +"@babel/plugin-transform-unicode-regex@npm:^7.0.0, @babel/plugin-transform-unicode-regex@npm:^7.18.6, @babel/plugin-transform-unicode-regex@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" dependencies: @@ -1939,17 +1887,6 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/template@npm:7.25.0" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/parser": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b - languageName: node - linkType: hard - "@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.2.3, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.0, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.4.5": version: 7.24.8 resolution: "@babel/traverse@npm:7.24.8" @@ -1968,21 +1905,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.25.0": - version: 7.25.3 - resolution: "@babel/traverse@npm:7.25.3" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" - "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.2" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10c0/4c8a1966fa90b53a783a4afd2fcdaa6ab1a912e6621dca9fcc6633e80ccb9491620e88caf73b537da4e16cefd537b548c87d7087868d5b0066414dea375c0e9b - languageName: node - linkType: hard - "@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.1.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.24.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.24.9 resolution: "@babel/types@npm:7.24.9" @@ -1994,17 +1916,6 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/types@npm:7.25.2" - dependencies: - "@babel/helper-string-parser": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/e489435856be239f8cc1120c90a197e4c2865385121908e5edb7223cfdff3768cba18f489adfe0c26955d9e7bbb1fb10625bc2517505908ceb0af848989bd864 - languageName: node - linkType: hard - "@bankify/react-native-animate-number@npm:0.2.1": version: 0.2.1 resolution: "@bankify/react-native-animate-number@npm:0.2.1" @@ -2032,13 +1943,13 @@ __metadata: languageName: node linkType: hard -"@candlefinance/faster-image@npm:1.6.2": - version: 1.6.2 - resolution: "@candlefinance/faster-image@npm:1.6.2" +"@candlefinance/faster-image@npm:1.5.0": + version: 1.5.0 + resolution: "@candlefinance/faster-image@npm:1.5.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/d9d5a2ead7351fe5305fe9b298cddd7f7debe8c8d2524faf4e0dc66a5f1dd275f3ef73464b69cda792ce636578a3f5c8ef9a1b047f4e8ae9941001fb5b8f6f26 + checksum: 10c0/34666d7abb079d13209b9a16adf0758d6fb7d9041e8cb9a112dcca4d8c128f14017a5c34a33eeb93dd08c2f0e5802da134a185d40620225c10dc0049d08a5d79 languageName: node linkType: hard @@ -5584,9 +5495,9 @@ __metadata: languageName: node linkType: hard -"@shopify/react-native-skia@npm:1.3.11": - version: 1.3.11 - resolution: "@shopify/react-native-skia@npm:1.3.11" +"@shopify/react-native-skia@npm:1.3.8": + version: 1.3.8 + resolution: "@shopify/react-native-skia@npm:1.3.8" dependencies: canvaskit-wasm: "npm:0.39.1" react-reconciler: "npm:0.27.0" @@ -5601,7 +5512,7 @@ __metadata: optional: true bin: setup-skia-web: scripts/setup-canvaskit.js - checksum: 10c0/b069d05ff1bf3599cc16eadc492614ab55b9e9130ba1aac22d83f19e6b7f37e57260471cf8bda86b9bbdebb71834bf4b83f33da2f7c0b18b31df104015b72fef + checksum: 10c0/df665797f0948265432a3c3786a9861b117ebf8945702fc2d552c4b6a4e48edb685584050165d382db162e2dcf84f5ee166f46737131342e4e9e93c5f95f1e6b languageName: node linkType: hard @@ -5819,7 +5730,7 @@ __metadata: languageName: node linkType: hard -"@stablelib/x25519@npm:1.0.3": +"@stablelib/x25519@npm:1.0.3, @stablelib/x25519@npm:^1.0.3": version: 1.0.3 resolution: "@stablelib/x25519@npm:1.0.3" dependencies: @@ -6229,7 +6140,27 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.5": +"@types/eslint-scope@npm:^3.7.3": + version: 3.7.7 + resolution: "@types/eslint-scope@npm:3.7.7" + dependencies: + "@types/eslint": "npm:*" + "@types/estree": "npm:*" + checksum: 10c0/a0ecbdf2f03912679440550817ff77ef39a30fa8bfdacaf6372b88b1f931828aec392f52283240f0d648cf3055c5ddc564544a626bcf245f3d09fcb099ebe3cc + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 8.56.10 + resolution: "@types/eslint@npm:8.56.10" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10c0/674349d6c342c3864d70f4d5a9965f96fb253801532752c8c500ad6a1c2e8b219e01ccff5dc8791dcb58b5483012c495708bb9f3ff929f5c9322b3da126c15d3 + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d @@ -6311,7 +6242,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -6407,11 +6338,11 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.45 - resolution: "@types/node@npm:18.19.45" + version: 18.19.40 + resolution: "@types/node@npm:18.19.40" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/79c324176411dcfa92f76b0ffc0673aa4bd8da82d003b44633e927c9493cdc46c35f04c0873b096b23b12bab090a6bbdea21242b3bbb2ea5dc1d9bf72adaa04f + checksum: 10c0/e91c139cbfa7593d9634fc75fa73de787c35c0ca949f629bf65a66bb0909c6de508b974c5953bf3ab910474847287b92366bed632ac6bc14d78fa81b1ddf049f languageName: node linkType: hard @@ -7058,27 +6989,28 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/core@npm:2.15.1" +"@walletconnect/core@npm:2.11.2": + version: 2.11.2 + resolution: "@walletconnect/core@npm:2.11.2" dependencies: - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/heartbeat": "npm:1.2.1" + "@walletconnect/jsonrpc-provider": "npm:1.0.13" + "@walletconnect/jsonrpc-types": "npm:1.0.3" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - events: "npm:3.3.0" + "@walletconnect/keyvaluestorage": "npm:^1.1.1" + "@walletconnect/logger": "npm:^2.0.1" + "@walletconnect/relay-api": "npm:^1.0.9" + "@walletconnect/relay-auth": "npm:^1.0.4" + "@walletconnect/safe-json": "npm:^1.0.2" + "@walletconnect/time": "npm:^1.0.2" + "@walletconnect/types": "npm:2.11.2" + "@walletconnect/utils": "npm:2.11.2" + events: "npm:^3.3.0" + isomorphic-unfetch: "npm:3.1.0" lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:3.1.0" - checksum: 10c0/3c831303bffcc360bb7d2f6e71b9928e73039e34e582e8da3f03dbc67e7876cf3ff89491ec9bad7ae31c3c3706ea6e44d2cd1be501349e882739a5184e917797 + uint8arrays: "npm:^3.1.0" + checksum: 10c0/a1bd4f028c08b668b5b5ddd9adbf136010b55dea90f1b7203b45c7149acdb7a8f03e1310f4c7bbd9580b6ba163962914a86a5a3360359a32f7932de0963e8cb3 languageName: node linkType: hard @@ -7162,6 +7094,17 @@ __metadata: languageName: node linkType: hard +"@walletconnect/heartbeat@npm:1.2.1": + version: 1.2.1 + resolution: "@walletconnect/heartbeat@npm:1.2.1" + dependencies: + "@walletconnect/events": "npm:^1.0.1" + "@walletconnect/time": "npm:^1.0.2" + tslib: "npm:1.14.1" + checksum: 10c0/5ad46f26dcb7b9b3227f004cd74b18741d4cd32c21825a036eb03985c67a0cf859c285bc5635401966a99129e854d72de3458ff592370575ef7e52f5dd12ebbc + languageName: node + linkType: hard + "@walletconnect/heartbeat@npm:1.2.2, @walletconnect/heartbeat@npm:^1.2.1": version: 1.2.2 resolution: "@walletconnect/heartbeat@npm:1.2.2" @@ -7184,6 +7127,17 @@ __metadata: languageName: node linkType: hard +"@walletconnect/jsonrpc-provider@npm:1.0.13": + version: 1.0.13 + resolution: "@walletconnect/jsonrpc-provider@npm:1.0.13" + dependencies: + "@walletconnect/jsonrpc-utils": "npm:^1.0.8" + "@walletconnect/safe-json": "npm:^1.0.2" + tslib: "npm:1.14.1" + checksum: 10c0/9b5b2f0ce516d2ddebe2cd1a2c8ea18a6b765b0d068162caf39745c18534e264a0cc6198adb869ba8684d0efa563be30956a3b9a7cc82b80b9e263f6211e30ab + languageName: node + linkType: hard + "@walletconnect/jsonrpc-provider@npm:1.0.14": version: 1.0.14 resolution: "@walletconnect/jsonrpc-provider@npm:1.0.14" @@ -7195,6 +7149,16 @@ __metadata: languageName: node linkType: hard +"@walletconnect/jsonrpc-types@npm:1.0.3": + version: 1.0.3 + resolution: "@walletconnect/jsonrpc-types@npm:1.0.3" + dependencies: + keyvaluestorage-interface: "npm:^1.0.0" + tslib: "npm:1.14.1" + checksum: 10c0/a0fc8a88c62795bf4bf83d4e98a4e2cdd659ef70c73642582089fdf0994c54fd8050aa6cca85cfdcca6b77994e71334895e7a19649c325a8c822b059c2003884 + languageName: node + linkType: hard + "@walletconnect/jsonrpc-types@npm:1.0.4, @walletconnect/jsonrpc-types@npm:^1.0.2, @walletconnect/jsonrpc-types@npm:^1.0.3": version: 1.0.4 resolution: "@walletconnect/jsonrpc-types@npm:1.0.4" @@ -7228,7 +7192,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/keyvaluestorage@npm:1.1.1": +"@walletconnect/keyvaluestorage@npm:1.1.1, @walletconnect/keyvaluestorage@npm:^1.1.1": version: 1.1.1 resolution: "@walletconnect/keyvaluestorage@npm:1.1.1" dependencies: @@ -7269,6 +7233,16 @@ __metadata: languageName: node linkType: hard +"@walletconnect/logger@npm:2.0.1": + version: 2.0.1 + resolution: "@walletconnect/logger@npm:2.0.1" + dependencies: + pino: "npm:7.11.0" + tslib: "npm:1.14.1" + checksum: 10c0/1778686f608f03bc8a67fb560a2694e8aef74b392811508e98cc158d1839a1bb0a0256eb2ed719c4ee17e65a11543ddc4f9059d3bdd5dddcca6359ba1bab18bd + languageName: node + linkType: hard + "@walletconnect/logger@npm:2.1.2, @walletconnect/logger@npm:^2.0.1": version: 2.1.2 resolution: "@walletconnect/logger@npm:2.1.2" @@ -7291,13 +7265,13 @@ __metadata: languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/react-native-compat@npm:2.15.1" +"@walletconnect/react-native-compat@npm:2.11.2": + version: 2.11.2 + resolution: "@walletconnect/react-native-compat@npm:2.11.2" dependencies: events: "npm:3.3.0" - fast-text-encoding: "npm:1.0.6" - react-native-url-polyfill: "npm:2.0.0" + fast-text-encoding: "npm:^1.0.6" + react-native-url-polyfill: "npm:^2.0.0" peerDependencies: "@react-native-async-storage/async-storage": "*" "@react-native-community/netinfo": "*" @@ -7306,11 +7280,11 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: 10c0/996452a58797b811b4417c649943602decadb5cbe4dd2717f1aaaed42df0bebcbaeda81e43754d62c319d335702d9e1cb8161e9102ce1bd25354e41588b26b97 + checksum: 10c0/b8a9d63de7e57078203c08a70f3b406d21582495cbea089160f5fd670d9c4db9afb9dee5507acb534507d4651cd6e1e38231930996cad6d8d5115d9b0d92124a languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.10": +"@walletconnect/relay-api@npm:1.0.10, @walletconnect/relay-api@npm:^1.0.9": version: 1.0.10 resolution: "@walletconnect/relay-api@npm:1.0.10" dependencies: @@ -7319,16 +7293,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.11": - version: 1.0.11 - resolution: "@walletconnect/relay-api@npm:1.0.11" - dependencies: - "@walletconnect/jsonrpc-types": "npm:^1.0.2" - checksum: 10c0/2595d7e68d3a93e7735e0b6204811762898b0ce1466e811d78be5bcec7ac1cde5381637615a99104099165bf63695da5ef9381d6ded29924a57a71b10712a91d - languageName: node - linkType: hard - -"@walletconnect/relay-auth@npm:1.0.4": +"@walletconnect/relay-auth@npm:1.0.4, @walletconnect/relay-auth@npm:^1.0.4": version: 1.0.4 resolution: "@walletconnect/relay-auth@npm:1.0.4" dependencies: @@ -7358,20 +7323,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/sign-client@npm:2.15.1" +"@walletconnect/sign-client@npm:2.11.2": + version: 2.11.2 + resolution: "@walletconnect/sign-client@npm:2.11.2" dependencies: - "@walletconnect/core": "npm:2.15.1" - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/core": "npm:2.11.2" + "@walletconnect/events": "npm:^1.0.1" + "@walletconnect/heartbeat": "npm:1.2.1" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - events: "npm:3.3.0" - checksum: 10c0/4ff66239c2994cb501cd1830b2cbc2ecf854799cfc4d100cc6af3523f8524daead7c08daa7dba944def79640515ccabe2250f700e0be1a07dfee5c3668aa2dee + "@walletconnect/logger": "npm:^2.0.1" + "@walletconnect/time": "npm:^1.0.2" + "@walletconnect/types": "npm:2.11.2" + "@walletconnect/utils": "npm:2.11.2" + events: "npm:^3.3.0" + checksum: 10c0/c6ecf82bb247a5ae72e78a1f122c2458c5f7a36811125223bf394aaac92994db94782775cbbe3957a121c49c404cfd72ed09f648882407ccc1c180618ca6124c languageName: node linkType: hard @@ -7395,23 +7360,23 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.13.3": - version: 2.13.3 - resolution: "@walletconnect/types@npm:2.13.3" +"@walletconnect/types@npm:2.11.2": + version: 2.11.2 + resolution: "@walletconnect/types@npm:2.11.2" dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - events: "npm:3.3.0" - checksum: 10c0/b12e92e39fa2fd9dbdfbef62265c7f5e5aa1d824b6db68ea5ce4fa9ed56c9fcdf6d93fbc6ffeb57169cff4082d475899739377b91f2b13f5209c8ccb66c47d6e + "@walletconnect/events": "npm:^1.0.1" + "@walletconnect/heartbeat": "npm:1.2.1" + "@walletconnect/jsonrpc-types": "npm:1.0.3" + "@walletconnect/keyvaluestorage": "npm:^1.1.1" + "@walletconnect/logger": "npm:^2.0.1" + events: "npm:^3.3.0" + checksum: 10c0/3d0027e1b4bb6f281a237e4f887aa1ab36f17d713f8dce5987e5b5ca6890f50025d163fdf874d7057a554bdaaf19305c7afeefa7e7a6512037525263f64c2784 languageName: node linkType: hard -"@walletconnect/types@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/types@npm:2.15.1" +"@walletconnect/types@npm:2.13.3": + version: 2.13.3 + resolution: "@walletconnect/types@npm:2.13.3" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -7419,7 +7384,7 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 10c0/0666874a4acd9326f2554542936a88f6df50ca958440254ab605dca81d57cdaf839b05cac983ad194f4ed44734d07d564f4277156556cadac07302e8dd86aa4d + checksum: 10c0/b12e92e39fa2fd9dbdfbef62265c7f5e5aa1d824b6db68ea5ce4fa9ed56c9fcdf6d93fbc6ffeb57169cff4082d475899739377b91f2b13f5209c8ccb66c47d6e languageName: node linkType: hard @@ -7430,47 +7395,47 @@ __metadata: languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": - version: 2.13.3 - resolution: "@walletconnect/utils@npm:2.13.3" +"@walletconnect/utils@npm:2.11.2": + version: 2.11.2 + resolution: "@walletconnect/utils@npm:2.11.2" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:1.0.2" + "@stablelib/random": "npm:^1.0.2" "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.10" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.3" - "@walletconnect/window-getters": "npm:1.0.1" - "@walletconnect/window-metadata": "npm:1.0.1" + "@stablelib/x25519": "npm:^1.0.3" + "@walletconnect/relay-api": "npm:^1.0.9" + "@walletconnect/safe-json": "npm:^1.0.2" + "@walletconnect/time": "npm:^1.0.2" + "@walletconnect/types": "npm:2.11.2" + "@walletconnect/window-getters": "npm:^1.0.1" + "@walletconnect/window-metadata": "npm:^1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" - uint8arrays: "npm:3.1.0" - checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad + uint8arrays: "npm:^3.1.0" + checksum: 10c0/0adec7e6e88805d74b7bd44a12cf6765bae86678dd152dd23f120f5c183e10fad35d8257e1c5ae5a55f9b28fe0e2a9b07486e70280cd07b1267abb899b0a9ec1 languageName: node linkType: hard -"@walletconnect/utils@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/utils@npm:2.15.1" +"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": + version: 2.13.3 + resolution: "@walletconnect/utils@npm:2.13.3" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-api": "npm:1.0.10" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" + "@walletconnect/types": "npm:2.13.3" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 10c0/bde087f530f91502ba26c9553abd464234adb5738c7e10cfbdd275699d630a81601b5928abeeb77abe29e88238a4067a1bd1c5159f04b49178452066c82d99ae + checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad languageName: node linkType: hard @@ -7489,19 +7454,19 @@ __metadata: languageName: node linkType: hard -"@walletconnect/web3wallet@npm:1.14.1": - version: 1.14.1 - resolution: "@walletconnect/web3wallet@npm:1.14.1" +"@walletconnect/web3wallet@npm:1.10.2": + version: 1.10.2 + resolution: "@walletconnect/web3wallet@npm:1.10.2" dependencies: "@walletconnect/auth-client": "npm:2.1.2" - "@walletconnect/core": "npm:2.15.1" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/core": "npm:2.11.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.13" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.15.1" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - checksum: 10c0/1918cb3319036fef3ed5565a607b230e083ea37b12a067df22f1bc81ab34fcf9a48d7ff2f4eb9dbd16066432f0e53460db727466b976e009bab378db5b4da1f6 + "@walletconnect/logger": "npm:2.0.1" + "@walletconnect/sign-client": "npm:2.11.2" + "@walletconnect/types": "npm:2.11.2" + "@walletconnect/utils": "npm:2.11.2" + checksum: 10c0/9f6950a1d49f8fbc14ba560bd98654f0d1cdaac5e2fd64560fe9cbfdc6fea98258c117fdf47c53754c44cae92a62dc29af24d2853afdca58d9d7e9e0a922ab0a languageName: node linkType: hard @@ -7540,7 +7505,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: @@ -7626,7 +7591,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.12.1": +"@webassemblyjs/wasm-edit@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: @@ -7667,7 +7632,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5": version: 1.12.1 resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: @@ -7808,7 +7773,7 @@ __metadata: "@babel/runtime": "npm:7.22.0" "@bankify/react-native-animate-number": "npm:0.2.1" "@bradgarropy/use-countdown": "npm:1.4.1" - "@candlefinance/faster-image": "npm:1.6.2" + "@candlefinance/faster-image": "npm:1.5.0" "@capsizecss/core": "npm:3.0.0" "@ensdomains/address-encoder": "npm:0.2.16" "@ensdomains/content-hash": "npm:2.5.7" @@ -7869,7 +7834,7 @@ __metadata: "@rudderstack/rudder-sdk-react-native": "npm:1.12.1" "@sentry/react-native": "npm:5.22.0" "@shopify/flash-list": "npm:1.7.0" - "@shopify/react-native-skia": "npm:1.3.11" + "@shopify/react-native-skia": "npm:1.3.8" "@tanstack/query-async-storage-persister": "npm:4.2.1" "@tanstack/react-query": "npm:4.2.1" "@tanstack/react-query-persist-client": "npm:4.2.1" @@ -7896,12 +7861,12 @@ __metadata: "@unstoppabledomains/resolution": "npm:7.1.4" "@wagmi/chains": "npm:1.8.0" "@walletconnect/client": "npm:1.8.0" - "@walletconnect/core": "npm:2.15.1" + "@walletconnect/core": "npm:2.11.2" "@walletconnect/legacy-utils": "npm:2.0.0" - "@walletconnect/react-native-compat": "npm:2.15.1" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - "@walletconnect/web3wallet": "npm:1.14.1" + "@walletconnect/react-native-compat": "npm:2.11.2" + "@walletconnect/types": "npm:2.11.2" + "@walletconnect/utils": "npm:2.11.2" + "@walletconnect/web3wallet": "npm:1.10.2" assert: "npm:1.5.0" ast-parser: "npm:0.0.5" async-mutex: "npm:0.3.2" @@ -7911,7 +7876,7 @@ __metadata: babel-plugin-date-fns: "npm:2.0.0" babel-plugin-graphql-tag: "npm:2.5.0" babel-plugin-lodash: "npm:3.3.4" - babel-plugin-module-resolver: "npm:5.0.2" + babel-plugin-module-resolver: "npm:4.0.0" babel-plugin-rewire: "npm:1.2.0" babel-plugin-styled-components: "npm:1.11.1" babel-plugin-transform-remove-console: "npm:6.9.4" @@ -7976,6 +7941,7 @@ __metadata: multiformats: "npm:9.6.2" nanoid: "npm:3.2.0" node-vibrant: "npm:3.2.1-alpha.1" + p-queue: "npm:7.2.0" p-wait-for: "npm:4.1.0" pako: "npm:2.0.4" parse-ms: "npm:2.1.0" @@ -8003,7 +7969,7 @@ __metadata: react-native-branch: "npm:5.3.1" react-native-change-icon: "npm:4.0.0" react-native-circular-progress: "npm:1.3.8" - react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a" + react-native-cloud-fs: "rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" react-native-crypto: "npm:2.2.0" react-native-dark-mode: "npm:0.2.2" react-native-device-info: "npm:5.3.1" @@ -8011,7 +7977,7 @@ __metadata: react-native-extra-dimensions-android: "npm:1.2.2" react-native-fast-image: "npm:8.5.11" react-native-fs: "npm:2.16.6" - react-native-gesture-handler: "npm:2.18.1" + react-native-gesture-handler: "npm:2.17.1" react-native-get-random-values: "npm:1.5.0" react-native-haptic-feedback: "npm:2.2.0" react-native-image-crop-picker: "npm:0.41.0" @@ -8032,25 +7998,25 @@ __metadata: react-native-quick-md5: "npm:3.0.6" react-native-radial-gradient: "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c" react-native-randombytes: "npm:3.5.3" - react-native-reanimated: "npm:3.15.0" + react-native-reanimated: "npm:3.14.0" react-native-redash: "npm:16.3.0" react-native-restart: "npm:0.0.22" react-native-safe-area-context: "npm:4.10.1" react-native-safe-area-view: rainbow-me/react-native-safe-area-view react-native-screen-corner-radius: "npm:0.2.2" - react-native-screens: "npm:3.34.0" + react-native-screens: "npm:3.32.0" react-native-section-list-get-item-layout: "npm:2.2.3" react-native-share: "npm:8.2.1" react-native-sound: "npm:0.11.2" react-native-splash-screen: "npm:3.3.0" react-native-storage: "npm:1.0.1" - react-native-svg: "npm:15.6.0" + react-native-svg: "npm:15.3.0" react-native-tab-view: "npm:3.5.1" react-native-tcp: "npm:3.3.2" react-native-text-input-mask: "npm:2.0.0" react-native-text-size: "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a" react-native-tooltip: "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590" - react-native-tooltips: "rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" + react-native-tooltips: "rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3" react-native-udp: "npm:2.7.0" react-native-url-polyfill: "npm:2.0.0" react-native-version-number: "npm:0.3.6" @@ -8097,7 +8063,7 @@ __metadata: viem: "npm:2.9.16" vm-browserify: "npm:0.0.4" w2t: "npm:3.0.2" - webpack: "npm:5.94.0" + webpack: "npm:5.90.3" webpack-cli: "npm:5.1.4" zod: "npm:3.23.8" zustand: "npm:4.5.4" @@ -8207,12 +8173,12 @@ __metadata: languageName: node linkType: hard -"acorn-import-attributes@npm:^1.9.5": - version: 1.9.5 - resolution: "acorn-import-attributes@npm:1.9.5" +"acorn-import-assertions@npm:^1.9.0": + version: 1.9.0 + resolution: "acorn-import-assertions@npm:1.9.0" peerDependencies: acorn: ^8 - checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d + checksum: 10c0/3b4a194e128efdc9b86c2b1544f623aba4c1aa70d638f8ab7dc3971a5b4aa4c57bd62f99af6e5325bb5973c55863b4112e708a6f408bad7a138647ca72283afe languageName: node linkType: hard @@ -8992,16 +8958,16 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:5.0.2": - version: 5.0.2 - resolution: "babel-plugin-module-resolver@npm:5.0.2" +"babel-plugin-module-resolver@npm:4.0.0": + version: 4.0.0 + resolution: "babel-plugin-module-resolver@npm:4.0.0" dependencies: - find-babel-config: "npm:^2.1.1" - glob: "npm:^9.3.3" + find-babel-config: "npm:^1.2.0" + glob: "npm:^7.1.6" pkg-up: "npm:^3.1.0" - reselect: "npm:^4.1.7" - resolve: "npm:^1.22.8" - checksum: 10c0/ccbb9e673c4219f68937349267521becb72be292cf30bf70b861c3e709d24fbfa589da0bf6c100a0def799d38199299171cb6eac3fb00b1ea740373e2c1fe54c + reselect: "npm:^4.0.0" + resolve: "npm:^1.13.1" + checksum: 10c0/3b3eda8487aa56d768032763d653eab87eb7a3199e2cbf606e5c6883233f1b668c838bc751164bd28af5e12c566f9291bc928f845795b038805c227b2691e193 languageName: node linkType: hard @@ -12260,7 +12226,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0": +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.10.0, enhanced-resolve@npm:^5.15.0": version: 5.17.0 resolution: "enhanced-resolve@npm:5.17.0" dependencies: @@ -12270,16 +12236,6 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.17.1": - version: 5.17.1 - resolution: "enhanced-resolve@npm:5.17.1" - dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10c0/81a0515675eca17efdba2cf5bad87abc91a528fc1191aad50e275e74f045b41506167d420099022da7181c8d787170ea41e4a11a0b10b7a16f6237daecb15370 - languageName: node - linkType: hard - "enquirer@npm:^2.3.0, enquirer@npm:^2.3.5, enquirer@npm:^2.3.6": version: 2.4.1 resolution: "enquirer@npm:2.4.1" @@ -13162,6 +13118,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^4.0.7": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + "events@npm:3.3.0, events@npm:^3.0.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" @@ -13432,7 +13395,7 @@ __metadata: languageName: node linkType: hard -"fast-text-encoding@npm:1.0.6": +"fast-text-encoding@npm:^1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" checksum: 10c0/e1d0381bda229c92c7906f63308f3b9caca8c78b732768b1ee16f560089ed21bc159bbe1434138ccd3815931ec8d4785bdade1ad1c45accfdf27ac6606ac67d2 @@ -13608,13 +13571,13 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^2.1.1": - version: 2.1.1 - resolution: "find-babel-config@npm:2.1.1" +"find-babel-config@npm:^1.2.0": + version: 1.2.2 + resolution: "find-babel-config@npm:1.2.2" dependencies: - json5: "npm:^2.2.3" - path-exists: "npm:^4.0.0" - checksum: 10c0/fb1f348027b83e9fe3b4163ec9c45719adab1205b2807708d660fe2a7beb5f812e0fabb6b21746cf0ef9c9fbf67bcc8e37f6cddb9d43229f0524767522d6110b + json5: "npm:^1.0.2" + path-exists: "npm:^3.0.0" + checksum: 10c0/c82631323b055a3ea8d2dbc42593d243dddf39ec20e83bb6aad847d77676829f4a2bdf507c5177bc9d2d4509a5e239a6023631f1e8b8011ab16d44d227c65639 languageName: node linkType: hard @@ -14356,18 +14319,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^9.3.3": - version: 9.3.5 - resolution: "glob@npm:9.3.5" - dependencies: - fs.realpath: "npm:^1.0.0" - minimatch: "npm:^8.0.2" - minipass: "npm:^4.2.4" - path-scurry: "npm:^1.6.1" - checksum: 10c0/2f6c2b9ee019ee21dc258ae97a88719614591e4c979cb4580b1b9df6f0f778a3cb38b4bdaf18dfa584637ea10f89a3c5f2533a5e449cf8741514ad18b0951f2e - languageName: node - linkType: hard - "global-modules@npm:^1.0.0": version: 1.0.0 resolution: "global-modules@npm:1.0.0" @@ -14475,7 +14426,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -18222,15 +18173,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^8.0.2": - version: 8.0.4 - resolution: "minimatch@npm:8.0.4" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/a0a394c356dd5b4cb7f821720841a82fa6f07c9c562c5b716909d1b6ec5e56a7e4c4b5029da26dd256b7d2b3a3f38cbf9ddd8680e887b9b5282b09c05501c1ca - languageName: node - linkType: hard - "minimist@npm:1.2.0": version: 1.2.0 resolution: "minimist@npm:1.2.0" @@ -18329,13 +18271,6 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.2.4": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 10c0/4ea76b030d97079f4429d6e8a8affd90baf1b6a1898977c8ccce4701c5a2ba2792e033abc6709373f25c2c4d4d95440d9d5e9464b46b7b76ca44d2ce26d939ce - languageName: node - linkType: hard - "minipass@npm:^5.0.0": version: 5.0.0 resolution: "minipass@npm:5.0.0" @@ -19582,7 +19517,17 @@ __metadata: languageName: node linkType: hard -"p-timeout@npm:^5.0.0": +"p-queue@npm:7.2.0": + version: 7.2.0 + resolution: "p-queue@npm:7.2.0" + dependencies: + eventemitter3: "npm:^4.0.7" + p-timeout: "npm:^5.0.2" + checksum: 10c0/0dad31488d6afe5c27a84ed00a703eef1ed4387338e0debe8155d36172808c6ae0451be5d88a12aa41f1deb4d3583ecd19e5f6ded5f06c937b01ff828d18c6cb + languageName: node + linkType: hard + +"p-timeout@npm:^5.0.0, p-timeout@npm:^5.0.2": version: 5.1.0 resolution: "p-timeout@npm:5.1.0" checksum: 10c0/1b026cf9d5878c64bec4341ca9cda8ec6b8b3aea8a57885ca0fe2b35753a20d767fb6f9d3aa41e1252f42bc95432c05ea33b6b18f271fb10bfb0789591850a41 @@ -19832,7 +19777,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": +"path-scurry@npm:^1.11.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -20883,10 +20828,10 @@ __metadata: languageName: node linkType: hard -"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#9b204615b76cf3d29bd86a9094dbd95d717b6a7a": - version: 2.6.2 - resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=9b204615b76cf3d29bd86a9094dbd95d717b6a7a" - checksum: 10c0/db1c719b90475201aa1e1177209723598ac38689a827d387dd281ea5190ad09f3e6c8fee77caff70b46a228b6552459d9a9e73e4159c18a29d19d235e17d7907 +"react-native-cloud-fs@rainbow-me/react-native-cloud-fs#c4ed2d78a7c401f628248a4e45eaf5bf9319a31a": + version: 2.6.1 + resolution: "react-native-cloud-fs@https://github.com/rainbow-me/react-native-cloud-fs.git#commit=c4ed2d78a7c401f628248a4e45eaf5bf9319a31a" + checksum: 10c0/74fe20b46f13a502176bb4bbc78ea24f5ec35d09c37a9404bc15a16c085d8459c184c948841bd8b0b1e5d649609fec2b8279ea31c18c70b24835609e34bbc253 languageName: node linkType: hard @@ -20981,9 +20926,9 @@ __metadata: languageName: node linkType: hard -"react-native-gesture-handler@npm:2.18.1": - version: 2.18.1 - resolution: "react-native-gesture-handler@npm:2.18.1" +"react-native-gesture-handler@npm:2.17.1": + version: 2.17.1 + resolution: "react-native-gesture-handler@npm:2.17.1" dependencies: "@egjs/hammerjs": "npm:^2.0.17" hoist-non-react-statics: "npm:^3.3.0" @@ -20992,7 +20937,7 @@ __metadata: peerDependencies: react: "*" react-native: "*" - checksum: 10c0/6c0f69de1f31eb92cf858903223cbe37b5a71b9e943b70a284e564507067539c5381956b0f832a874da5e1185d01a0a56f06f11ea79985973235eaf3d469274b + checksum: 10c0/01ea97f347df2505c58be8551c62e9fd0bae7814346a41f3367ec0b4836877fd8b9b710619f8bc10c6cdbb0e2670dd6c131124d27aabc4745383aab690dae6b3 languageName: node linkType: hard @@ -21204,18 +21149,15 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:3.15.0": - version: 3.15.0 - resolution: "react-native-reanimated@npm:3.15.0" +"react-native-reanimated@npm:3.14.0": + version: 3.14.0 + resolution: "react-native-reanimated@npm:3.14.0" dependencies: "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" - "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-classes": "npm:^7.0.0-0" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" - "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" "@babel/preset-typescript": "npm:^7.16.7" convert-source-map: "npm:^2.0.0" invariant: "npm:^2.2.4" @@ -21223,7 +21165,7 @@ __metadata: "@babel/core": ^7.0.0-0 react: "*" react-native: "*" - checksum: 10c0/86420a616ed7d80404847b584c9fa8f31b516f05401274f32eee908727bcf3fdfa0f09e0b195cd1d11cd0efaa4264ee42d47e2d2000bdab6626f53a795edb6a8 + checksum: 10c0/a62ba3e4475c474bc7359d5175f192e86bbe86378a61041247b81beec6e93b4806cd8a5a8a61d1458d56eeef991803e5a128db69d260fa53d78050216b17d014 languageName: node linkType: hard @@ -21285,16 +21227,16 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-screens@npm:3.34.0": - version: 3.34.0 - resolution: "react-native-screens@npm:3.34.0" +"react-native-screens@npm:3.32.0": + version: 3.32.0 + resolution: "react-native-screens@npm:3.32.0" dependencies: react-freeze: "npm:^1.0.0" warn-once: "npm:^0.1.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/568af49a1d79797ebecf5b8c815d44b651b0affdd82d7a8024b9e5e71250a23e108d73562200ef571b806c6a9ebba0484510e710246165dbdc50ad18e4d77619 + checksum: 10c0/38dc2e2bf3072c544d3bacceedbcc8ffaee5a588f4984c8d7186a236bd436f761c9b987a37fb3af8e0df2e5980db6155c5f6b6d4a3983001d0f45aa704a20b93 languageName: node linkType: hard @@ -21340,17 +21282,16 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-svg@npm:15.6.0": - version: 15.6.0 - resolution: "react-native-svg@npm:15.6.0" +"react-native-svg@npm:15.3.0": + version: 15.3.0 + resolution: "react-native-svg@npm:15.3.0" dependencies: css-select: "npm:^5.1.0" css-tree: "npm:^1.1.3" - warn-once: "npm:0.1.1" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/213b6ee33651bb7d2dd17a51c62981264fc519283dc14493361e7816005e2a7b5f2784af11076129bd7402ae2dc0104478f5bd8f894d55f8d4c82c65060bbac6 + checksum: 10c0/72ac639de834d943c3a52f1ac1703e0858eefe59f32d8db4c7e4736e117afea8b88efe8eef9fd1051367d601076090e733fcb93e70df0ec8c6ff6df4859874af languageName: node linkType: hard @@ -21406,10 +21347,12 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-tooltips@rainbow-me/react-native-tooltips#fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa": - version: 1.0.4 - resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=fdafbc7ba33ee231229f5d3f58b29d0d1c55ddfa" - checksum: 10c0/7a1f79d8d381532b41eff2b6e8dbd818a4cbd05c21c28419cba8bfb63047555d5e10ee93687a56c2e093eff7921503971942f14a9b5584e6be93cd21ab334805 +"react-native-tooltips@rainbow-me/react-native-tooltips#77338abadbef8225870aea5cfc0dacf94a1448e3": + version: 1.0.3 + resolution: "react-native-tooltips@https://github.com/rainbow-me/react-native-tooltips.git#commit=77338abadbef8225870aea5cfc0dacf94a1448e3" + dependencies: + deprecated-react-native-prop-types: "npm:2.2.0" + checksum: 10c0/b97f5891e583b15a37362f0b0bb2657a41728347bbf1bb4dc692a620db0ea87b02c11c633594e2c6dddcc3b116f6d186616761352f8f803768af3a9b98f9caab languageName: node linkType: hard @@ -21426,7 +21369,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-native-url-polyfill@npm:2.0.0": +"react-native-url-polyfill@npm:2.0.0, react-native-url-polyfill@npm:^2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0" dependencies: @@ -22129,7 +22072,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"reselect@npm:^4.1.7": +"reselect@npm:^4.0.0": version: 4.1.8 resolution: "reselect@npm:4.1.8" checksum: 10c0/06a305a504affcbb67dd0561ddc8306b35796199c7e15b38934c80606938a021eadcf68cfd58e7bb5e17786601c37602a3362a4665c7bf0a96c1041ceee9d0b7 @@ -22213,7 +22156,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -22248,7 +22191,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -24771,7 +24714,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"uint8arrays@npm:^3.0.0": +"uint8arrays@npm:^3.0.0, uint8arrays@npm:^3.1.0": version: 3.1.1 resolution: "uint8arrays@npm:3.1.1" dependencies: @@ -25433,7 +25376,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"warn-once@npm:0.1.1, warn-once@npm:^0.1.0": +"warn-once@npm:^0.1.0": version: 0.1.1 resolution: "warn-once@npm:0.1.1" checksum: 10c0/f531e7b2382124f51e6d8f97b8c865246db8ab6ff4e53257a2d274e0f02b97d7201eb35db481843dc155815e154ad7afb53b01c4d4db15fb5aa073562496aff7 @@ -25449,13 +25392,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.2 - resolution: "watchpack@npm:2.4.2" +"watchpack@npm:^2.4.0": + version: 2.4.1 + resolution: "watchpack@npm:2.4.1" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/ec60a5f0e9efaeca0102fd9126346b3b2d523e01c34030d3fddf5813a7125765121ebdc2552981136dcd2c852deb1af0b39340f2fcc235f292db5399d0283577 + checksum: 10c0/c694de0a61004e587a8a0fdc9cfec20ee692c52032d9ab2c2e99969a37fdab9e6e1bd3164ed506f9a13f7c83e65563d563e0d6b87358470cdb7309b83db78683 languageName: node linkType: hard @@ -25545,24 +25488,25 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"webpack@npm:5.94.0": - version: 5.94.0 - resolution: "webpack@npm:5.94.0" +"webpack@npm:5.90.3": + version: 5.90.3 + resolution: "webpack@npm:5.90.3" dependencies: + "@types/eslint-scope": "npm:^3.7.3" "@types/estree": "npm:^1.0.5" - "@webassemblyjs/ast": "npm:^1.12.1" - "@webassemblyjs/wasm-edit": "npm:^1.12.1" - "@webassemblyjs/wasm-parser": "npm:^1.12.1" + "@webassemblyjs/ast": "npm:^1.11.5" + "@webassemblyjs/wasm-edit": "npm:^1.11.5" + "@webassemblyjs/wasm-parser": "npm:^1.11.5" acorn: "npm:^8.7.1" - acorn-import-attributes: "npm:^1.9.5" + acorn-import-assertions: "npm:^1.9.0" browserslist: "npm:^4.21.10" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.1" + enhanced-resolve: "npm:^5.15.0" es-module-lexer: "npm:^1.2.1" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" + graceful-fs: "npm:^4.2.9" json-parse-even-better-errors: "npm:^2.3.1" loader-runner: "npm:^4.2.0" mime-types: "npm:^2.1.27" @@ -25570,14 +25514,14 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: schema-utils: "npm:^3.2.0" tapable: "npm:^2.1.1" terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.1" + watchpack: "npm:^2.4.0" webpack-sources: "npm:^3.2.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10c0/b4d1b751f634079bd177a89eef84d80fa5bb8d6fc15d72ab40fc2b9ca5167a79b56585e1a849e9e27e259803ee5c4365cb719e54af70a43c06358ec268ff4ebf + checksum: 10c0/f737aa871cadbbae89833eb85387f1bf9ee0768f039100a3c8134f2fdcc78c3230ca775c373b1aa467b272f74c6831e119f7a8a1c14dcac97327212be9c93eeb languageName: node linkType: hard From 01a0c9f8f4b2ba0003acd5bad69a6ce7d6967599 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 4 Sep 2024 00:47:07 -0300 Subject: [PATCH 08/64] fix types --- .../screens/Swap/components/GasButton.tsx | 10 +--- .../profile-header/ProfileAvatarRow.tsx | 6 +- src/components/cards/MintsCard/Menu.tsx | 2 +- src/components/change-wallet/AddressRow.tsx | 12 ++-- .../ens-profile/ActionButtons/MoreButton.tsx | 23 +++---- .../UniqueTokenExpandedStateHeader.tsx | 13 ++-- src/components/list/ListHeaderMenu.tsx | 3 +- ...enu.android.js => contextMenu.android.tsx} | 33 +++++----- .../native-context-menu/contextMenu.tsx | 56 ++++++++++++++++- src/screens/ENSIntroSheet.tsx | 3 +- .../components/Backups/ViewWalletBackup.tsx | 3 +- .../points/components/LeaderboardRow.tsx | 23 +++---- .../TransactionDetailsAddressRow.tsx | 2 +- ...etailsStatusActionsAndTimestampSection.tsx | 60 +++++++++---------- 14 files changed, 143 insertions(+), 106 deletions(-) rename src/components/native-context-menu/{contextMenu.android.js => contextMenu.android.tsx} (56%) diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 6f96e02db53..099a7353d3e 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -14,7 +14,6 @@ import { swapsStore } from '@/state/swaps/swapsStore'; import { gasUtils } from '@/utils'; import React, { PropsWithChildren, ReactNode, useCallback, useMemo } from 'react'; import { StyleSheet } from 'react-native'; -import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; import Animated, { runOnUI, useAnimatedStyle } from 'react-native-reanimated'; import { THICK_BORDER_WIDTH } from '../constants'; import { GasSettings, useCustomGasSettings } from '../hooks/useCustomGas'; @@ -133,7 +132,7 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c ); const handlePressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => handlePressSpeedOption(actionKey as GasSpeed), + ({ nativeEvent: { actionKey } }: { nativeEvent: { actionKey: GasSpeed } }) => handlePressSpeedOption(actionKey), [handlePressSpeedOption] ); @@ -147,8 +146,6 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c const menuConfig = useMemo(() => { const menuItems = menuOptions.map(gasOption => { - if (IS_ANDROID) return gasOption; - const currentBaseFee = getCachedCurrentBaseFee(chainId); const gasSettings = gasOption === GasSpeed.CUSTOM ? customGasSettings : metereologySuggestions.data?.[gasOption]; const subtitle = getEstimatedFeeRangeInGwei(gasSettings, currentBaseFee); @@ -174,7 +171,7 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c isAnchoredToRight isMenuPrimaryAction onPressActionSheet={handlePressActionSheet} - options={menuConfig.menuItems} + options={menuOptions} useActionSheetFallback={false} wrapNativeComponent={false} > @@ -184,14 +181,11 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c ) : ( {children} diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx index 0e6e1fb85a0..e61620556a0 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx @@ -118,11 +118,7 @@ export function ProfileAvatarRow({ size = ProfileAvatarSize }: { size?: number } - + { haptics.selection(); diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index a67479f22a6..13499fc7746 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -23,6 +23,7 @@ import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { useForegroundColor } from '@/design-system'; +import { MenuActionConfig } from 'react-native-ios-context-menu'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; const NOOP = () => undefined; @@ -157,7 +158,7 @@ export default function AddressRow({ contextMenuActions, data, editMode, onPress }, ...(notificationsEnabled - ? [ + ? ([ { actionKey: ContextMenuKeys.Notifications, actionTitle: lang.t('wallet.action.notifications.action_title'), @@ -166,18 +167,15 @@ export default function AddressRow({ contextMenuActions, data, editMode, onPress iconValue: 'bell.fill', }, }, - ] + ] as const) : []), { actionKey: ContextMenuKeys.Remove, actionTitle: lang.t('wallet.action.remove'), - icon: { - iconType: 'SYSTEM', - iconValue: 'trash.fill', - }, + icon: { iconType: 'SYSTEM', iconValue: 'trash.fill' }, menuAttributes: ['destructive'], }, - ]; + ] satisfies MenuActionConfig[]; const menuConfig = { menuItems: contextMenuItems, diff --git a/src/components/ens-profile/ActionButtons/MoreButton.tsx b/src/components/ens-profile/ActionButtons/MoreButton.tsx index dd695ea4e9e..4c1329ef100 100644 --- a/src/components/ens-profile/ActionButtons/MoreButton.tsx +++ b/src/components/ens-profile/ActionButtons/MoreButton.tsx @@ -2,7 +2,6 @@ import { useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; import { Keyboard, Share } from 'react-native'; -import { MenuActionConfig } from 'react-native-ios-context-menu'; import { showDeleteContactActionSheet } from '../../contacts'; import More from '../MoreButton/MoreButton'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; @@ -45,14 +44,18 @@ export default function MoreButton({ address, ensName }: { address?: string; ens const menuItems = useMemo(() => { return [ - isWatching && { - actionKey: ACTIONS.OPEN_WALLET, - actionTitle: lang.t('profiles.details.open_wallet'), - icon: { - iconType: 'SYSTEM', - iconValue: 'iphone.and.arrow.forward', - }, - }, + ...(isWatching + ? [ + { + actionKey: ACTIONS.OPEN_WALLET, + actionTitle: lang.t('profiles.details.open_wallet'), + icon: { + iconType: 'SYSTEM', + iconValue: 'iphone.and.arrow.forward', + }, + }, + ] + : []), { actionKey: ACTIONS.COPY_ADDRESS, actionTitle: lang.t('profiles.details.copy_address'), @@ -95,7 +98,7 @@ export default function MoreButton({ address, ensName }: { address?: string; ens iconValue: 'square.and.arrow.up', }, }, - ].filter(Boolean) as MenuActionConfig[]; + ].filter(Boolean); }, [isWatching, formattedAddress, contact]); const handlePressMenuItem = useCallback( diff --git a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx index 205b88c4bff..8eda8f3e5e9 100644 --- a/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx +++ b/src/components/expanded-state/unique-token/UniqueTokenExpandedStateHeader.tsx @@ -21,7 +21,6 @@ import isSVGImage from '@/utils/isSVG'; import { refreshNFTContractMetadata, reportNFT } from '@/resources/nfts/simplehash'; import { ContextCircleButton } from '@/components/context-menu'; import { IS_ANDROID, IS_IOS } from '@/env'; -import { MenuActionConfig, MenuConfig } from 'react-native-ios-context-menu'; import { ChainId } from '@/networks/types'; const AssetActionsEnum = { @@ -262,7 +261,7 @@ const UniqueTokenExpandedStateHeader = ({ const isENS = asset.asset_contract?.address?.toLowerCase() === ENS_NFT_CONTRACT_ADDRESS.toLowerCase(); const isPhotoDownloadAvailable = !isSVG && !isENS; - const assetMenuConfig: MenuConfig = useMemo(() => { + const assetMenuConfig = useMemo(() => { const AssetActions = getAssetActions({ chainId: asset.chainId }); return { @@ -443,11 +442,7 @@ const UniqueTokenExpandedStateHeader = ({ const familyNameHitSlop: Space = '19px (Deprecated)'; const assetMenuOptions = useMemo(() => { - return ( - assetMenuConfig?.menuItems - ?.filter((item): item is MenuActionConfig => 'actionTitle' in item) - .map((item: MenuActionConfig) => item.actionTitle) ?? [] - ); + return assetMenuConfig?.menuItems?.filter(item => 'actionTitle' in item).map(item => item.actionTitle) ?? []; }, [assetMenuConfig]); return ( @@ -463,8 +458,8 @@ const UniqueTokenExpandedStateHeader = ({ { - const actionItems = (assetMenuConfig?.menuItems || []).filter((item): item is MenuActionConfig => 'actionTitle' in item); - const actionKey: MenuActionConfig = actionItems[index]; + const actionItems = (assetMenuConfig?.menuItems || []).filter(item => 'actionTitle' in item); + const actionKey = actionItems[index]; if (!actionKey) return; handlePressAssetMenuItem({ nativeEvent: { actionKey: actionKey.actionKey }, diff --git a/src/components/list/ListHeaderMenu.tsx b/src/components/list/ListHeaderMenu.tsx index 48ad813b2b6..e59b335a940 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import ContextMenuButton from '@/components/native-context-menu/contextMenu'; +import ContextMenuButton, { MenuConfig } from '@/components/native-context-menu/contextMenu'; import { ButtonPressAnimation } from '@/components/animations'; import { Bleed, Box, Inline, Text, useForegroundColor } from '@/design-system'; import { NftSort } from '@/hooks/useNFTsSortBy'; import { haptics } from '@/utils'; -import { MenuConfig } from 'react-native-ios-context-menu'; type ListHeaderMenuProps = { selected: NftSort; diff --git a/src/components/native-context-menu/contextMenu.android.js b/src/components/native-context-menu/contextMenu.android.tsx similarity index 56% rename from src/components/native-context-menu/contextMenu.android.js rename to src/components/native-context-menu/contextMenu.android.tsx index f46b7c83e06..a5d18d00dc9 100644 --- a/src/components/native-context-menu/contextMenu.android.js +++ b/src/components/native-context-menu/contextMenu.android.tsx @@ -1,6 +1,8 @@ -import { MenuView } from '@react-native-menu/menu'; -import React, { useMemo } from 'react'; +import { MenuView, NativeActionEvent } from '@react-native-menu/menu'; +import React, { PropsWithChildren, useMemo } from 'react'; import { useLatestCallback } from '@/hooks'; +import { NativeMenuComponentProps } from '@react-native-menu/menu/lib/typescript/src/types'; +import { MenuConfig } from './contextMenu'; export default function ContextMenuAndroid({ children, @@ -9,16 +11,19 @@ export default function ContextMenuAndroid({ onPressMenuItem, shouldOpenOnLongPress, style, - testID, -}) { +}: PropsWithChildren<{ + menuConfig: MenuConfig; + isAnchoredToRight?: boolean; + onPressMenuItem: (event: { nativeEvent: { actionKey: string } }) => void; + shouldOpenOnLongPress?: boolean; + style?: NativeMenuComponentProps['style']; +}>) { const actions = useMemo(() => { const items = []; if (menuTitle) { items.push({ - attributes: { - disabled: true, - }, + attributes: { disabled: true }, id: 'title', title: menuTitle, }); @@ -29,13 +34,13 @@ export default function ContextMenuAndroid({ ...(menuItems || []).map(item => ({ id: item.actionKey, image: item.icon?.iconValue, - title: item.actionTitle || item.menuTitle, + title: item.actionTitle || item.menuTitle || '', ...(item.menuTitle && { titleColor: 'black', subactions: item.menuItems.map(item => ({ id: item.actionKey, image: item.icon?.iconValue, - title: item.actionTitle, + title: item.actionTitle || '', })), }), })) @@ -45,12 +50,9 @@ export default function ContextMenuAndroid({ return items; }, [menuItems, menuTitle]); - const onPressAction = useLatestCallback( - ({ nativeEvent: { event } }) => { - return onPressMenuItem({ nativeEvent: { actionKey: event } }); - }, - [onPressMenuItem] - ); + const onPressAction = useLatestCallback<({ nativeEvent }: NativeActionEvent) => void>(({ nativeEvent: { event } }) => { + return onPressMenuItem({ nativeEvent: { actionKey: event } }); + }); return ( {children} diff --git a/src/components/native-context-menu/contextMenu.tsx b/src/components/native-context-menu/contextMenu.tsx index 95a134cb757..b825f74b491 100644 --- a/src/components/native-context-menu/contextMenu.tsx +++ b/src/components/native-context-menu/contextMenu.tsx @@ -1,7 +1,57 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React from 'react'; -import { ContextMenuButton, ContextMenuButtonProps } from 'react-native-ios-context-menu'; +import React, { PropsWithChildren } from 'react'; +import { + ContextMenuButton, + ContextMenuButtonProps, + DynamicColor, + MenuAttributes, + MenuElementSize, + MenuState, + UIMenuOptions, +} from 'react-native-ios-context-menu'; -export default function ContextMenu(props: ContextMenuButtonProps) { +/** + * This components was widely used throughout the app but without types (.js file all as any) + * even tho it works fine, transitioning to typescript makes a lot scream + * so I recreate the 'react-native-ios-context-menu' types here in a less strict way + * just to not have to deal with the strictness of the original types + */ + +// eslint-disable-next-line @typescript-eslint/ban-types +type IconConfig = { iconType: 'ASSET' | 'SYSTEM' | (string & {}); iconValue: string; iconTint?: string | DynamicColor }; + +type MenuActionConfig = Readonly< + { + actionSubtitle?: string; + // eslint-disable-next-line @typescript-eslint/ban-types + menuState?: MenuState | (string & {}); + menuAttributes?: Array; + discoverabilityTitle?: string; + icon?: IconConfig; + } & ( + | { menuTitle: string; menuItems: Readonly>; actionKey?: never; actionTitle?: never } + | { menuTitle?: never; menuItems?: never; actionKey: string; actionTitle: string } + ) +>; + +export type MenuConfig = Readonly<{ + menuTitle?: string; + menuItems: Readonly>; + menuSubtitle?: string; + menuOptions?: Array; + menuPreferredElementSize?: MenuElementSize; + icon?: IconConfig; +}>; + +export default function ContextMenu( + props: PropsWithChildren< + Omit & { + menuConfig: MenuConfig; + onPressMenuItem: (e: { nativeEvent: Omit & { actionKey: any } }) => void; + } + > +) { + // @ts-expect-error `activeOpacity` and `wrapNativeComponent` are missing in the `ContextMenuButtonProps` type but are valid + // https://www.npmjs.com/package/react-native-ios-context-menu/v/1.2.1#312-contextmenubutton-component return ; } diff --git a/src/screens/ENSIntroSheet.tsx b/src/screens/ENSIntroSheet.tsx index 2cd29eb9a00..a180eb67dae 100644 --- a/src/screens/ENSIntroSheet.tsx +++ b/src/screens/ENSIntroSheet.tsx @@ -4,7 +4,6 @@ import { IS_TESTING } from 'react-native-dotenv'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; import { InteractionManager, View } from 'react-native'; -import { MenuActionConfig } from 'react-native-ios-context-menu'; import LinearGradient from 'react-native-linear-gradient'; import ActivityIndicator from '../components/ActivityIndicator'; import IntroMarquee from '../components/ens-registration/IntroMarquee/IntroMarquee'; @@ -55,7 +54,7 @@ const ContextMenuRenderer = ({ children, handleSelectExistingName, handleNavigat iconValue: 'magnifyingglass', }, }, - ] as MenuActionConfig[], + ], menuTitle: '', }; }, []); diff --git a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx index 6ea5ddbc87a..d085c3f62fd 100644 --- a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx +++ b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx @@ -40,7 +40,6 @@ import { login, } from '@/handlers/cloudBackup'; import { logger, RainbowError } from '@/logger'; -import { captureException } from '@sentry/react-native'; import { RainbowAccount, createWallet } from '@/model/wallet'; import { PROFILES, useExperimentalFlag } from '@/config'; import showWalletErrorAlert from '@/helpers/support'; @@ -108,7 +107,7 @@ type ContextMenuWrapperProps = { const ContextMenuWrapper = ({ children, account, menuConfig, onPressMenuItem }: ContextMenuWrapperProps) => { return IS_IOS ? ( - onPressMenuItem({ ...e, account })}> + onPressMenuItem({ ...e, account })}> {children} ) : ( diff --git a/src/screens/points/components/LeaderboardRow.tsx b/src/screens/points/components/LeaderboardRow.tsx index 915e6c22a8f..f0565e176e1 100644 --- a/src/screens/points/components/LeaderboardRow.tsx +++ b/src/screens/points/components/LeaderboardRow.tsx @@ -1,7 +1,6 @@ import * as i18n from '@/languages'; import React, { memo, useCallback, useMemo } from 'react'; import { Keyboard, Share } from 'react-native'; -import { MenuActionConfig } from 'react-native-ios-context-menu'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { useClipboard, useContacts, useSwitchWallet, useWallets, useWatchWallet } from '@/hooks'; import { useNavigation } from '@/navigation'; @@ -61,14 +60,18 @@ export const LeaderboardRow = memo(function LeaderboardRow({ const menuItems = useMemo(() => { return [ - isWatching && { - actionKey: ACTIONS.OPEN_WALLET, - actionTitle: i18n.t(i18n.l.profiles.details.open_wallet), - icon: { - iconType: 'SYSTEM', - iconValue: 'iphone.and.arrow.forward', - }, - }, + ...(isWatching + ? [ + { + actionKey: ACTIONS.OPEN_WALLET, + actionTitle: i18n.t(i18n.l.profiles.details.open_wallet), + icon: { + iconType: 'SYSTEM', + iconValue: 'iphone.and.arrow.forward', + }, + }, + ] + : []), { actionKey: ACTIONS.COPY_ADDRESS, actionTitle: i18n.t(i18n.l.profiles.details.copy_address), @@ -111,7 +114,7 @@ export const LeaderboardRow = memo(function LeaderboardRow({ iconValue: 'square.and.arrow.up', }, }, - ].filter(Boolean) as MenuActionConfig[]; + ]; }, [isWatching, formattedAddress, contact]); const handlePressMenuItem = useCallback( diff --git a/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx b/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx index c3fec380ce7..8dd64832ddd 100644 --- a/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx @@ -169,7 +169,7 @@ const ContextMenuRenderer = ({ } return ( - + {children} ); diff --git a/src/screens/transaction-details/components/TransactionDetailsStatusActionsAndTimestampSection.tsx b/src/screens/transaction-details/components/TransactionDetailsStatusActionsAndTimestampSection.tsx index d3df660b79d..fab6b9abcca 100644 --- a/src/screens/transaction-details/components/TransactionDetailsStatusActionsAndTimestampSection.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsStatusActionsAndTimestampSection.tsx @@ -2,11 +2,10 @@ import React, { useCallback, useMemo } from 'react'; import { RainbowTransaction, TransactionStatusTypes } from '@/entities'; import { Box, Stack, Text } from '@/design-system'; import { formatTransactionDetailsDate } from '@/screens/transaction-details/helpers/formatTransactionDetailsDate'; -import { capitalize } from 'lodash'; import { getIconColorAndGradientForTransactionStatus } from '@/screens/transaction-details/helpers/getIconColorAndGradientForTransactionStatus'; import RadialGradient from 'react-native-radial-gradient'; import { useTheme } from '@/theme'; -import ContextMenuButton from '@/components/native-context-menu/contextMenu'; +import ContextMenuButton, { MenuConfig } from '@/components/native-context-menu/contextMenu'; import { StyleSheet } from 'react-native'; import { ButtonPressAnimation } from '@/components/animations'; import { haptics } from '@/utils'; @@ -36,36 +35,37 @@ export const TransactionDetailsStatusActionsAndTimestampSection: React.FC const canBeCancelled = canBeResubmitted && status !== TransactionStatusTypes.cancelling; const menuConfig = useMemo( - () => ({ - menuTitle: '', - menuItems: [ - ...(canBeResubmitted - ? [ - { - actionKey: 'speedUp', - actionTitle: i18n.t(i18n.l.transaction_details.actions_menu.speed_up), - icon: { - iconType: 'SYSTEM', - iconValue: 'speedometer', + () => + ({ + menuTitle: '', + menuItems: [ + ...(canBeResubmitted + ? [ + { + actionKey: 'speedUp', + actionTitle: i18n.t(i18n.l.transaction_details.actions_menu.speed_up), + icon: { + iconType: 'SYSTEM', + iconValue: 'speedometer', + }, }, - }, - ] - : []), - ...(canBeCancelled - ? [ - { - actionKey: 'cancel', - actionTitle: i18n.t(i18n.l.transaction_details.actions_menu.cancel), - menuAttributes: ['destructive'], - icon: { - iconType: 'SYSTEM', - iconValue: 'xmark.circle', + ] + : []), + ...(canBeCancelled + ? [ + { + actionKey: 'cancel', + actionTitle: i18n.t(i18n.l.transaction_details.actions_menu.cancel), + menuAttributes: ['destructive' as const], + icon: { + iconType: 'SYSTEM', + iconValue: 'xmark.circle', + }, }, - }, - ] - : []), - ], - }), + ] + : []), + ], + }) satisfies MenuConfig, [canBeCancelled, canBeResubmitted] ); From f8b2d4c5e87cbcb84c4c45d954555c87606568f3 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 4 Sep 2024 13:47:04 -0300 Subject: [PATCH 09/64] update ts --- package.json | 2 +- .../search/results/SearchResults.tsx | 4 ---- yarn.lock | 18 +++++++++--------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 52c229cff23..f415f7a86bd 100644 --- a/package.json +++ b/package.json @@ -369,7 +369,7 @@ "ts-jest": "29.1.1", "ts-loader": "9.5.1", "ts-migrate": "0.1.26", - "typescript": "5.1.6", + "typescript": "5.5.4", "typescript-coverage-report": "0.6.1", "webpack": "5.94.0", "webpack-cli": "5.1.4" diff --git a/src/components/DappBrowser/search/results/SearchResults.tsx b/src/components/DappBrowser/search/results/SearchResults.tsx index e7bf159980f..44203c49bec 100644 --- a/src/components/DappBrowser/search/results/SearchResults.tsx +++ b/src/components/DappBrowser/search/results/SearchResults.tsx @@ -54,12 +54,9 @@ const search = (query: string, dapps: Dapp[], numberOfResults = 4): Dapp[] => { if (b?.trending === true && a?.trending !== true) return 1; if (a?.trending === true && b?.trending !== true) return -1; - // @ts-expect-error: Need to fix these types const relevanceDiff = b.relevance - a.relevance; if (relevanceDiff === 0) { - // @ts-expect-error: Same here const aWordCount = a.name.split(' ').length; - // @ts-expect-error: Same here const bWordCount = b.name.split(' ').length; return aWordCount - bWordCount; } @@ -74,7 +71,6 @@ const search = (query: string, dapps: Dapp[], numberOfResults = 4): Dapp[] => { return [{ url: query, urlDisplay: query, name: query, isDirect: true } as unknown as Dapp, ...(dappResults as Dapp[])]; } - // @ts-expect-error: Same here return filteredDapps; }; diff --git a/yarn.lock b/yarn.lock index 9d7df69de4f..d0f43fa730a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8085,7 +8085,7 @@ __metadata: ts-loader: "npm:9.5.1" ts-migrate: "npm:0.1.26" tty-browserify: "npm:0.0.0" - typescript: "npm:5.1.6" + typescript: "npm:5.5.4" typescript-coverage-report: "npm:0.6.1" url: "npm:0.10.3" url-join: "npm:4.0.1" @@ -24691,13 +24691,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"typescript@npm:5.1.6": - version: 5.1.6 - resolution: "typescript@npm:5.1.6" +"typescript@npm:5.5.4": + version: 5.5.4 + resolution: "typescript@npm:5.5.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/45ac28e2df8365fd28dac42f5d62edfe69a7203d5ec646732cadc04065331f34f9078f81f150fde42ed9754eed6fa3b06a8f3523c40b821e557b727f1992e025 + checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c languageName: node linkType: hard @@ -24721,13 +24721,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.1.6#optional!builtin": - version: 5.1.6 - resolution: "typescript@patch:typescript@npm%3A5.1.6#optional!builtin::version=5.1.6&hash=5da071" +"typescript@patch:typescript@npm%3A5.5.4#optional!builtin": + version: 5.5.4 + resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=b45daf" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/c2bded58ab897a8341fdbb0c1d92ea2362f498cfffebdc8a529d03e15ea2454142dfbf122dabbd9a5cb79b7123790d27def16e11844887d20636226773ed329a + checksum: 10c0/10dd9881baba22763de859e8050d6cb6e2db854197495c6f1929b08d1eb2b2b00d0b5d9b0bcee8472f1c3f4a7ef6a5d7ebe0cfd703f853aa5ae465b8404bc1ba languageName: node linkType: hard From a048dd965185dc6ffe229fe7a6720af1f772fd0d Mon Sep 17 00:00:00 2001 From: gregs Date: Mon, 16 Sep 2024 23:49:25 -0300 Subject: [PATCH 10/64] add back isAnchoredToRight --- src/__swaps__/screens/Swap/components/GasButton.tsx | 1 + src/components/list/ListHeaderMenu.tsx | 2 +- src/components/native-context-menu/contextMenu.tsx | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index 099a7353d3e..c1815f3e9b0 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -182,6 +182,7 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c ) : ( - + & { menuConfig: MenuConfig; onPressMenuItem: (e: { nativeEvent: Omit & { actionKey: any } }) => void; + isAnchoredToRight?: boolean; // this only used in android check contextMenu.android.tsx in this same folder } > ) { From 8f84fd66055326f977ad51a20650375f72ce26ed Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 17 Sep 2024 00:04:18 -0300 Subject: [PATCH 11/64] ?? --- src/__swaps__/screens/Swap/components/GasButton.tsx | 1 - src/components/list/ListHeaderMenu.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/GasButton.tsx b/src/__swaps__/screens/Swap/components/GasButton.tsx index c1815f3e9b0..099a7353d3e 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -182,7 +182,6 @@ const GasMenu = ({ backToReview = false, children }: { backToReview?: boolean; c ) : ( - + Date: Tue, 17 Sep 2024 13:55:49 -0300 Subject: [PATCH 12/64] grey out the selected option --- .../RecyclerAssetList2/WrappedCollectiblesHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 2e446c3780d..09830c1a73d 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -70,7 +70,7 @@ const CollectiblesHeader = () => { icon: { iconType: 'SYSTEM', iconValue: 'arrow.up.circle', - iconTint: nftSortDirection === SortDirection.Asc ? undefined : colors.grey, + iconTint: nftSortDirection === SortDirection.Asc ? colors.grey : undefined, }, }, { @@ -79,7 +79,7 @@ const CollectiblesHeader = () => { icon: { iconType: 'SYSTEM', iconValue: 'arrow.down.circle', - iconTint: nftSortDirection === SortDirection.Desc ? undefined : colors.grey, + iconTint: nftSortDirection === SortDirection.Desc ? colors.grey : undefined, }, }, ], From a2e3231975eb3c30ff18d96d68ddcd3018a2d9a5 Mon Sep 17 00:00:00 2001 From: gregs Date: Thu, 10 Oct 2024 12:21:20 -0300 Subject: [PATCH 13/64] android --- .../WrappedCollectiblesHeader.tsx | 17 ++++++++++++++--- src/hooks/useNFTsSortBy.ts | 13 ++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 09830c1a73d..5d39be24b32 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -3,10 +3,11 @@ import { Box, Inline, Text } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; -import { NftSort, useNftSort } from '@/hooks/useNFTsSortBy'; +import { NftSort, parseNftSort, useNftSort } from '@/hooks/useNFTsSortBy'; import { colors } from '@/styles'; import { useRemoteConfig } from '@/model/remoteConfig'; import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_ANDROID, IS_IOS } from '@/env'; const TokenFamilyHeaderHeight = 48; @@ -58,7 +59,7 @@ const CollectiblesHeader = () => { menuItems={Object.values(NftCollectionSortCriterion).map(sortCriterion => { return { icon: { iconType: 'SYSTEM', iconValue: getMenuItemIcon(sortCriterion) }, - ...(nftSort === sortCriterion + ...(nftSort === sortCriterion && IS_IOS // submenus look weird in android, so it toggles when clicking the same item ? { menuTitle: i18n.t(i18n.l.nfts.sort[sortCriterion]), menuPreferredElementSize: 'small', @@ -91,7 +92,17 @@ const CollectiblesHeader = () => { }), }; })} - selectItem={string => updateNFTSort(string as NftSort)} + selectItem={actionKey => { + const sort = actionKey as NftSort; + if (IS_ANDROID) { + const [criterion, direction] = parseNftSort(sort); + if (criterion !== nftSort) return updateNFTSort(sort); + const toggledDirection = direction === SortDirection.Asc ? SortDirection.Desc : SortDirection.Asc; + updateNFTSort(`${criterion}|${toggledDirection}`); + } else { + updateNFTSort(sort); + } + }} icon={getIconForSortType(nftSort)} text={i18n.t(i18n.l.nfts.sort[nftSort])} /> diff --git a/src/hooks/useNFTsSortBy.ts b/src/hooks/useNFTsSortBy.ts index a37f69b6a16..2fb5f83531b 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -1,20 +1,15 @@ import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; -import { MMKV, useMMKVString } from 'react-native-mmkv'; +import { useMMKVString } from 'react-native-mmkv'; import useAccountSettings from './useAccountSettings'; -const mmkv = new MMKV(); const getStorageKey = (accountAddress: string) => `nfts-sort-${accountAddress}`; -const parseNftSort = (s: string | undefined) => { +export const parseNftSort = (s: string | undefined) => { const [sortBy = NftCollectionSortCriterion.MostRecent, sortDirection = SortDirection.Desc] = (s?.split('|') || []) as [ sortBy?: NftCollectionSortCriterion, sortDirection?: SortDirection, ]; - return { sortBy, sortDirection } as const; -}; - -export const getNftSortForAddress = (accountAddress: string) => { - return parseNftSort(mmkv.getString(getStorageKey(accountAddress))); + return [sortBy, sortDirection] as const; }; export type NftSort = `${NftCollectionSortCriterion}|${SortDirection}`; @@ -22,7 +17,7 @@ export type NftSort = `${NftCollectionSortCriterion}|${SortDirection}`; export function useNftSort() { const { accountAddress } = useAccountSettings(); const [nftSortData, setNftSortData] = useMMKVString(getStorageKey(accountAddress)); - const { sortBy, sortDirection } = parseNftSort(nftSortData); + const [sortBy, sortDirection] = parseNftSort(nftSortData); return { updateNFTSort: (nftSort: NftSort) => setNftSortData(nftSort), From 5f1ee0101e03e41bd0d1db267bbb1b8a3d2bc408 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:13:46 -0400 Subject: [PATCH 14/64] Fix iOS 18 cool-modals context menu bug (#6112) --- src/react-native-cool-modals/ios/RNCMScreen.m | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/react-native-cool-modals/ios/RNCMScreen.m b/src/react-native-cool-modals/ios/RNCMScreen.m index fdd546b3143..36598655c64 100644 --- a/src/react-native-cool-modals/ios/RNCMScreen.m +++ b/src/react-native-cool-modals/ios/RNCMScreen.m @@ -9,7 +9,6 @@ // lib #import "Rainbow-Swift.h" - @interface RNCMScreenView () @end @@ -66,7 +65,6 @@ - (void)willDismiss { - (void) setIsShortFormEnabled:(BOOL)isShortFormEnabled { _isShortFormEnabled = isShortFormEnabled; [(PanModalViewController*) [_controller parentVC] panModalSetNeedsLayoutUpdateWrapper]; - } - (void) layout { @@ -83,7 +81,6 @@ - (void) setHidden:(BOOL)hidden { } [(PanModalViewController*) [_controller parentVC] hide]; }); - } } @@ -131,7 +128,6 @@ - (void)updateBounds [_bridge.uiManager setSize:self.bounds.size forView:self]; } - - (void)setPointerEvents:(RCTPointerEvents)pointerEvents { // pointer events settings are managed by the parent screen container, we ignore @@ -335,11 +331,14 @@ - (instancetype)initWithView:(UIView *)view - (void)presentModally:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion slackStack:(BOOL)slackStack { return [_parentVC presentModally:viewControllerToPresent animated:flag completion:completion slackStack:slackStack]; - } - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion { - return [_parentVC dismissViewControllerAnimated:flag completion:completion]; + if (self.parentViewController) { + [self.parentViewController dismissViewControllerAnimated:flag completion:completion]; + } else { + [super dismissViewControllerAnimated:flag completion:completion]; + } } - (UIViewController *)presentedViewController { @@ -361,6 +360,16 @@ - (void)viewDidLayoutSubviews } } +- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { + BOOL isContextMenu = [viewControllerToPresent isKindOfClass:NSClassFromString(@"_UIContextMenuActionsOnlyViewController")]; + + if (isContextMenu) { + [_parentVC presentViewController:viewControllerToPresent animated:flag completion:completion]; + } else { + [super presentViewController:viewControllerToPresent animated:flag completion:completion]; + } +} + - (id)findFirstResponder:(UIView*)parent { if (parent.isFirstResponder) { @@ -426,7 +435,6 @@ @implementation RNCMScreenManager } [(RNCMScreenView *) view jumpTo:point]; }]; - } RCT_EXPORT_METHOD(layout:(nonnull NSNumber*) reactTag) { @@ -438,10 +446,8 @@ @implementation RNCMScreenManager } [(RNCMScreenView *) view layout]; }]; - } - RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL) @@ -476,7 +482,6 @@ @implementation RNCMScreenManager RCT_EXPORT_VIEW_PROPERTY(ignoreBottomOffset, BOOL) RCT_EXPORT_VIEW_PROPERTY(hidden, BOOL) - - (UIView *)view { return [[RNCMScreenView alloc] initWithBridge:self.bridge]; @@ -494,15 +499,13 @@ @implementation RCTConvert (RNSScreen) @"containedModal": @(RNSScreenStackPresentationContainedModal), @"transparentModal": @(RNSScreenStackPresentationTransparentModal), @"containedTransparentModal": @(RNSScreenStackPresentationContainedTransparentModal) - }), RNSScreenStackPresentationPush, integerValue) +}), RNSScreenStackPresentationPush, integerValue) RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{ @"default": @(RNSScreenStackAnimationDefault), @"none": @(RNSScreenStackAnimationNone), @"fade": @(RNSScreenStackAnimationFade), @"flip": @(RNSScreenStackAnimationFlip), - }), RNSScreenStackAnimationDefault, integerValue) - +}), RNSScreenStackAnimationDefault, integerValue) @end - From c10b4f2a9bafe2d99b539fe1026fa50361b93363 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 18 Sep 2024 18:36:16 -0400 Subject: [PATCH 15/64] Fix send crashes / blank screen (#6116) --- src/hooks/useColorForAsset.ts | 26 +++++++++----------- src/utils/pseudoRandomArrayItemFromString.ts | 4 ++- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/hooks/useColorForAsset.ts b/src/hooks/useColorForAsset.ts index 5f497b7fd19..bfaf954e388 100644 --- a/src/hooks/useColorForAsset.ts +++ b/src/hooks/useColorForAsset.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import { lightModeThemeColors } from '../styles/colors'; import { ParsedAddressAsset } from '@/entities'; -import { useTheme } from '@/theme'; import { ethereumUtils, isETH, pseudoRandomArrayItemFromString } from '@/utils'; import { usePersistentDominantColorFromImage } from './usePersistentDominantColorFromImage'; @@ -11,28 +10,27 @@ export default function useColorForAsset( forceLightMode = false, forceETHColor = false ) { + // @ts-expect-error ts-migrate(2304) FIXME: Cannot find name 'useTheme'. const { isDarkMode: isDarkModeTheme, colors } = useTheme(); - const accountAsset = ethereumUtils.getAssetFromAllAssets(asset.uniqueId || asset.mainnet_address || asset.address); - const resolvedAddress = asset.mainnet_address || asset.address || accountAsset.address; + const accountAsset = ethereumUtils.getAssetFromAllAssets(asset?.uniqueId || asset?.mainnet_address || asset?.address); + const resolvedAddress = asset?.mainnet_address || asset?.address || accountAsset?.address; const derivedColor = usePersistentDominantColorFromImage(accountAsset?.icon_url || asset?.icon_url); const isDarkMode = forceLightMode || isDarkModeTheme; const colorDerivedFromAddress = useMemo(() => { - if (!isETH(resolvedAddress)) { - return pseudoRandomArrayItemFromString(resolvedAddress, colors.avatarBackgrounds); - } - - if (isDarkMode) { - if (forceETHColor) return colors.appleBlue; - return colors.brighten(lightModeThemeColors.dark); - } - - return colors.dark; + const color = isETH(resolvedAddress) + ? isDarkMode + ? forceETHColor + ? colors.appleBlue + : colors.brighten(lightModeThemeColors.dark) + : colors.dark + : pseudoRandomArrayItemFromString(resolvedAddress, colors.avatarBackgrounds); + return color; }, [colors, forceETHColor, isDarkMode, resolvedAddress]); return useMemo(() => { - let color2Return: string; + let color2Return; // we have special handling for eth color if (isETH(resolvedAddress)) { diff --git a/src/utils/pseudoRandomArrayItemFromString.ts b/src/utils/pseudoRandomArrayItemFromString.ts index fe47a62fb29..050499b7a90 100644 --- a/src/utils/pseudoRandomArrayItemFromString.ts +++ b/src/utils/pseudoRandomArrayItemFromString.ts @@ -1,7 +1,9 @@ const cache: { [key: string]: number } = {}; // For a given string, will always return same item from an array. -export default function pseudoRandomArrayItemFromString(string: string, array: T[]): T { +export default function pseudoRandomArrayItemFromString(string: string, array: T[]): T | null { + if (!string) return null; + if (!cache[string]) { cache[string] = [...string].map(char => char.toLowerCase().charCodeAt(0)).reduce((acc, v) => acc + v, 0); } From 955e21a7fc439b05cd78584d61a50c67b91b1a69 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:43:59 -0400 Subject: [PATCH 16/64] Fix disabled paste button on Android (#6118) --- src/__swaps__/screens/Swap/components/SearchInputButton.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx index 30c5a60f673..887d367c3d2 100644 --- a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx @@ -10,6 +10,7 @@ import { THICK_BORDER_WIDTH } from '../constants'; import { useClipboard } from '@/hooks'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; import { triggerHapticFeedback } from '@/screens/points/constants'; +import { IS_ANDROID } from '@/env'; const CANCEL_LABEL = i18n.t(i18n.l.button.cancel); const CLOSE_LABEL = i18n.t(i18n.l.button.close); @@ -73,10 +74,11 @@ export const SearchInputButton = ({ const isInputSearchFocused = inputProgress.value === NavigationSteps.SEARCH_FOCUSED; const isOutputSearchFocused = outputProgress.value === NavigationSteps.SEARCH_FOCUSED; const isOutputTokenListFocused = outputProgress.value === NavigationSteps.TOKEN_LIST_FOCUSED; + + const clipboardDataAvailable = hasClipboardData || IS_ANDROID; + const isPasteDisabled = output && !internalSelectedOutputAsset.value && isOutputTokenListFocused && !clipboardDataAvailable; const isVisible = isInputSearchFocused || isOutputSearchFocused || output || (!output && !!internalSelectedInputAsset.value); - - const isPasteDisabled = output && !internalSelectedOutputAsset.value && isOutputTokenListFocused && !hasClipboardData; const visibleOpacity = isPasteDisabled ? 0.4 : 1; return { From 9617bdc93b10197640814d7f86d2baa78f1e9cf9 Mon Sep 17 00:00:00 2001 From: gregs Date: Thu, 19 Sep 2024 12:52:37 -0300 Subject: [PATCH 17/64] imageVariants (#6114) --- src/components/DappBrowser/Homepage.tsx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/DappBrowser/Homepage.tsx b/src/components/DappBrowser/Homepage.tsx index 4053671b59c..76ca66fb3bf 100644 --- a/src/components/DappBrowser/Homepage.tsx +++ b/src/components/DappBrowser/Homepage.tsx @@ -1,6 +1,6 @@ import { BlurView } from '@react-native-community/blur'; import React, { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { ScrollView, StyleSheet, View } from 'react-native'; +import { PixelRatio, ScrollView, StyleSheet, View } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; import { ButtonPressAnimation } from '@/components/animations'; @@ -499,6 +499,17 @@ const Card = memo(function Card({ ); }); +const getImageForDevicePixelRatio = ({ imageUrl, imageVariants }: FeaturedResult) => { + if (!imageVariants) return imageUrl; + + const pixelRatio = PixelRatio.get(); + const { x1, x2, x3 } = imageVariants; + + if (pixelRatio < 1.5) return x1?.url || imageUrl; + if (pixelRatio < 3) return x2?.url || x1?.url || imageUrl; + return x3?.url || x2?.url || x1?.url || imageUrl; +}; + export const DappBrowserFeaturedResultsCard = memo(function Card({ handlePress, featuredResult, @@ -508,6 +519,8 @@ export const DappBrowserFeaturedResultsCard = memo(function Card({ }) { const { isDarkMode } = useColorMode(); + const imageUrl = getImageForDevicePixelRatio(featuredResult); + return ( - + {IS_IOS && ( Date: Thu, 19 Sep 2024 20:37:30 -0400 Subject: [PATCH 18/64] Fix send sheet stuck on loading (#6119) * fix stuck loading phase * Update src/screens/SendSheet.js * fix deleting contact resetting toAddress to undefined * fix some shit with ens names * rm logs * fix failed to send * fix lint * prefer contact name over wallet label * force linter 2 re-rerun --------- Co-authored-by: Bruno Barbieri --- .../Swap/components/SearchInputButton.tsx | 3 ++- src/components/fields/CheckboxField.tsx | 2 +- src/components/send/SendHeader.js | 22 +++++++++++++------ src/screens/SendConfirmationSheet.tsx | 2 +- src/screens/SendSheet.js | 15 ++++++++----- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx index 887d367c3d2..aa89ce65023 100644 --- a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx @@ -36,6 +36,7 @@ export const SearchInputButton = ({ outputSearchRef, AnimatedSwapStyles, } = useSwapContext(); + const { hasClipboardData } = useClipboard(); const btnText = useDerivedValue(() => { @@ -74,7 +75,7 @@ export const SearchInputButton = ({ const isInputSearchFocused = inputProgress.value === NavigationSteps.SEARCH_FOCUSED; const isOutputSearchFocused = outputProgress.value === NavigationSteps.SEARCH_FOCUSED; const isOutputTokenListFocused = outputProgress.value === NavigationSteps.TOKEN_LIST_FOCUSED; - + const clipboardDataAvailable = hasClipboardData || IS_ANDROID; const isPasteDisabled = output && !internalSelectedOutputAsset.value && isOutputTokenListFocused && !clipboardDataAvailable; diff --git a/src/components/fields/CheckboxField.tsx b/src/components/fields/CheckboxField.tsx index 4b79dbfc815..3f9d5fb1148 100644 --- a/src/components/fields/CheckboxField.tsx +++ b/src/components/fields/CheckboxField.tsx @@ -48,7 +48,7 @@ export default function CheckboxField({ - + {label} diff --git a/src/components/send/SendHeader.js b/src/components/send/SendHeader.js index f93e6b7fdbc..fe4ef3c7047 100644 --- a/src/components/send/SendHeader.js +++ b/src/components/send/SendHeader.js @@ -117,17 +117,15 @@ export default function SendHeader({ const isPreExistingContact = (contact?.nickname?.length || 0) > 0; const name = - removeFirstEmojiFromString(userWallet?.label || contact?.nickname || nickname) || userWallet?.ens || contact?.ens || recipient; + removeFirstEmojiFromString(contact?.nickname || userWallet?.label || nickname) || userWallet?.ens || contact?.ens || recipient; const handleNavigateToContact = useCallback(() => { - let nickname = profilesEnabled ? (!isHexString(recipient) ? recipient : null) : recipient; let color = ''; + const nickname = !isHexString(name) ? name : ''; if (!profilesEnabled) { color = contact?.color; if (color !== 0 && !color) { - const emoji = profileUtils.addressHashedEmoji(hexAddress); color = profileUtils.addressHashedColorIndex(hexAddress) || 0; - nickname = isHexString(recipient) ? emoji : `${emoji} ${recipient}`; } } @@ -142,7 +140,7 @@ export default function SendHeader({ onRefocusInput, type: 'contact_profile', }); - }, [contact, hexAddress, navigate, onRefocusInput, profilesEnabled, recipient]); + }, [contact, hexAddress, name, navigate, onRefocusInput, profilesEnabled, recipient]); const handleOpenContactActionSheet = useCallback(async () => { return showActionSheetWithOptions( @@ -162,7 +160,7 @@ export default function SendHeader({ address: hexAddress, nickname: name, onDelete: () => { - onChangeAddressInput(contact?.ens); + onChangeAddressInput(contact?.ens ? contact?.ens : contact?.address); }, removeContact: removeContact, }); @@ -175,7 +173,17 @@ export default function SendHeader({ } } ); - }, [contact?.ens, handleNavigateToContact, hexAddress, onRefocusInput, removeContact, setClipboard, name, onChangeAddressInput]); + }, [ + hexAddress, + name, + removeContact, + onChangeAddressInput, + contact?.ens, + contact?.address, + handleNavigateToContact, + onRefocusInput, + setClipboard, + ]); const onChange = useCallback( text => { diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 411434521cc..579946fb0b7 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -420,7 +420,7 @@ export const SendConfirmationSheet = () => { return existingAcct; }, [toAddress, userAccounts, watchedAccounts]); - let avatarName = removeFirstEmojiFromString(existingAccount?.label || contact?.nickname); + let avatarName = removeFirstEmojiFromString(contact?.nickname || existingAccount?.label); if (!avatarName) { if (isValidDomainFormat(to)) { diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index ad0f897c0be..330776eb0f4 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -588,7 +588,7 @@ export default function SendSheet(props) { })(); } }, - [amountDetails.assetAmount, goBack, isENS, isHardwareWallet, navigate, onSubmit, recipient, selected?.name, selected?.network] + [amountDetails, goBack, isENS, isHardwareWallet, navigate, onSubmit, recipient, selected?.name, selected?.network] ); const { buttonDisabled, buttonLabel } = useMemo(() => { @@ -713,8 +713,8 @@ export default function SendSheet(props) { const isValid = checkIsValidAddressOrDomainFormat(text); if (!isValid) { setIsValidAddress(); + setToAddress(); } - setToAddress(); setCurrentInput(text); setRecipient(text); setNickname(text); @@ -764,7 +764,6 @@ export default function SendSheet(props) { if ( !!accountAddress && - amountDetails.assetAmount !== '' && Object.entries(selected).length && assetChainId === currentChainId && currentProviderChainId === currentChainId && @@ -813,6 +812,12 @@ export default function SendSheet(props) { const isEmptyWallet = !sortedAssets?.length && !sendableUniqueTokens?.length; + const filteredUserAccountsFromContacts = useMemo(() => { + return userAccounts.filter( + account => !filteredContacts.some(contact => contact.address.toLowerCase() === account.address.toLowerCase()) + ); + }, [userAccounts, filteredContacts]); + return ( @@ -832,7 +837,7 @@ export default function SendSheet(props) { recipientFieldRef={recipientFieldRef} removeContact={onRemoveContact} showAssetList={showAssetList} - userAccounts={userAccounts} + userAccounts={filteredUserAccountsFromContacts} watchedAccounts={watchedAccounts} /> {showEmptyState && ( @@ -848,7 +853,7 @@ export default function SendSheet(props) { setNickname(nickname); }} removeContact={onRemoveContact} - userAccounts={userAccounts} + userAccounts={filteredUserAccountsFromContacts} watchedAccounts={watchedAccounts} /> )} From f9423acf13de4136c989688013e1656cd44e5d9f Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 19 Sep 2024 22:44:47 -0400 Subject: [PATCH 19/64] Support typing into native inputs in swap (#6100) * Remove unused TODO as build quote params should always rely on the corresponding input output amounts even if native values are updated * Support input native changes in swap inputs controller animated reaction * Temp cleanup on SwapInputAsset to pull out SwapInputAmountCaret and SwapInputNativeAmount * Temp cleanup on SwapOutputAsset to pull out SwapOutputAmountCaret and SwapOutputNativeAmount * Placeholder: generic SwapInputValuesCaret that still need to be hooked up with native inputs * Add caret animatedStyle to SwapInputValuesCaret * Use SwapInputValuesCaret component in SwapInputAsset and SwapOutputAsset * Remove old input and output caret styles from useSwapTextStyles * Cleanup of unused imports in swaps types * Add assetToSellCaretStyle and assetToBuyCaretStyle to caret component * Remove now unused assetToSell/BuyCaretStyles from useAnimatedSwapStyles * Update native input in SwapInputAsset to be typeable * Define size style for native caret in SwapInputValuesCaret * Update swap number pad Remove formatting that isn't a decimal or number on inputs. Previously, we were removing only commas, but now that we support native inputs, we want to remove the native currency symbols. Prevent updating of the native inputs if their is no corresponding price for the input / output asset. * Create separate SwapNativeInput component * Remove unused caret styles from SwapInputAsset and SwapOutputAsset * Add param to ignore alignment for native currency formatting and add handler for basic currency formatting * Update width on nativeCaret * Add support for changes to native output value * Fix missing checks for inputNativeValue and outputNativeValue * Split native currency symbol from value in native input component * Disable caret when correponding asset does not have price for native input * Update formatting for input amount if input method is native inputs * Disable pointer events when no price on native inputs * Distinguish between placeholder values vs typed inputs for native input values * Update checks for color for zero value in swap text styles * Disable focus and pointer events on native output if output based quotes disabled * Support showing explainer if output quotes disabled and user tries to update output native amount * Ignore native input changes if native currency decimals exceeded * Update add decimal in swap number pad to also handle scenarios where there are no native currency decimals * Update South Korea and Japan currencies as they do not use decimals * Fix numbers test after JPY decimal changes * Update numbers test value which should get rounded up * Fix: check for native placeholder value while checking native input decimal places --- .../Swap/components/SwapInputAsset.tsx | 30 +---- .../Swap/components/SwapInputValuesCaret.tsx | 95 +++++++++++++ .../Swap/components/SwapNativeInput.tsx | 82 ++++++++++++ .../screens/Swap/components/SwapNumberPad.tsx | 86 ++++++++++-- .../Swap/components/SwapOutputAsset.tsx | 73 ++++------ .../Swap/hooks/useAnimatedSwapStyles.ts | 14 -- .../Swap/hooks/useSwapInputsController.ts | 99 ++++++++++++-- .../screens/Swap/hooks/useSwapTextStyles.ts | 125 ++++-------------- .../screens/Swap/providers/swap-provider.tsx | 5 - src/__swaps__/types/swap.ts | 3 +- src/__swaps__/utils/__tests__/numbers.test.ts | 2 +- src/__swaps__/utils/numbers.ts | 5 +- src/__swaps__/utils/swaps.ts | 20 ++- src/references/supportedCurrencies.ts | 10 +- 14 files changed, 419 insertions(+), 230 deletions(-) create mode 100644 src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx create mode 100644 src/__swaps__/screens/Swap/components/SwapNativeInput.tsx diff --git a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx index 179ef8defdf..60dd457215d 100644 --- a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx @@ -11,6 +11,8 @@ import { FadeMask } from '@/__swaps__/screens/Swap/components/FadeMask'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { SwapActionButton } from '@/__swaps__/screens/Swap/components/SwapActionButton'; import { SwapInput } from '@/__swaps__/screens/Swap/components/SwapInput'; +import { SwapNativeInput } from '@/__swaps__/screens/Swap/components/SwapNativeInput'; +import { SwapInputValuesCaret } from '@/__swaps__/screens/Swap/components/SwapInputValuesCaret'; import { TokenList } from '@/__swaps__/screens/Swap/components/TokenList/TokenList'; import { BASE_INPUT_WIDTH, INPUT_INNER_WIDTH, INPUT_PADDING, THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants'; @@ -50,7 +52,7 @@ function SwapInputActionButton() { } function SwapInputAmount() { - const { focusedInput, SwapTextStyles, SwapInputController, AnimatedSwapStyles } = useSwapContext(); + const { focusedInput, SwapTextStyles, SwapInputController } = useSwapContext(); return ( {SwapInputController.formattedInputAmount} - - - + @@ -119,15 +119,7 @@ function InputAssetBalanceBadge() { } export function SwapInputAsset() { - const { - outputProgress, - inputProgress, - AnimatedSwapStyles, - SwapTextStyles, - SwapInputController, - internalSelectedInputAsset, - SwapNavigation, - } = useSwapContext(); + const { outputProgress, inputProgress, AnimatedSwapStyles, internalSelectedInputAsset, SwapNavigation } = useSwapContext(); return ( @@ -143,9 +135,7 @@ export function SwapInputAsset() { - - {SwapInputController.formattedInputNativeValue} - + @@ -174,14 +164,6 @@ export const styles = StyleSheet.create({ backgroundOverlay: { backgroundColor: 'rgba(0, 0, 0, 0.88)', }, - caret: { - height: 32, - width: 2, - }, - caretContainer: { - flexGrow: 100, - flexShrink: 0, - }, flipButton: { borderRadius: 15, height: 30, diff --git a/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx b/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx new file mode 100644 index 00000000000..99c57675935 --- /dev/null +++ b/src/__swaps__/screens/Swap/components/SwapInputValuesCaret.tsx @@ -0,0 +1,95 @@ +import { Box, useColorMode } from '@/design-system'; +import React from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { Easing, SharedValue, useAnimatedStyle, withRepeat, withSequence, withTiming } from 'react-native-reanimated'; +import { SLIDER_COLLAPSED_HEIGHT, SLIDER_HEIGHT, caretConfig } from '@/__swaps__/screens/Swap/constants'; +import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { NavigationSteps } from '@/__swaps__/screens/Swap/hooks/useSwapNavigation'; +import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; +import { inputKeys } from '@/__swaps__/types/swap'; +import { getColorValueForThemeWorklet } from '@/__swaps__/utils/swaps'; + +export function SwapInputValuesCaret({ inputCaretType, disabled }: { inputCaretType: inputKeys; disabled?: SharedValue }) { + const { isDarkMode } = useColorMode(); + const { + configProgress, + focusedInput, + inputProgress, + internalSelectedInputAsset, + internalSelectedOutputAsset, + isQuoteStale, + outputProgress, + SwapInputController, + sliderPressProgress, + } = useSwapContext(); + + const inputMethod = SwapInputController.inputMethod; + const inputValues = SwapInputController.inputValues; + + const caretStyle = useAnimatedStyle(() => { + const shouldShow = + !disabled?.value && + configProgress.value === NavigationSteps.INPUT_ELEMENT_FOCUSED && + focusedInput.value === inputCaretType && + inputProgress.value === 0 && + outputProgress.value === 0 && + (inputMethod.value !== 'slider' || + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) || + (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); + + const opacity = shouldShow + ? withRepeat( + withSequence( + withTiming(1, { duration: 0 }), + withTiming(1, { duration: 400, easing: Easing.bezier(0.87, 0, 0.13, 1) }), + withTiming(0, caretConfig), + withTiming(1, caretConfig) + ), + -1, + true + ) + : withTiming(0, caretConfig); + + const isZero = + (inputMethod.value !== 'slider' && inputValues.value[inputCaretType] === 0) || + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); + + return { + display: shouldShow ? 'flex' : 'none', + opacity, + position: isZero ? 'absolute' : 'relative', + }; + }); + + const assetCaretStyle = useAnimatedStyle(() => { + const selectedAsset = + inputCaretType === 'inputAmount' || inputCaretType === 'inputNativeValue' ? internalSelectedInputAsset : internalSelectedOutputAsset; + return { + backgroundColor: getColorValueForThemeWorklet(selectedAsset.value?.highContrastColor, isDarkMode, true), + }; + }); + + const caretSizeStyle = + inputCaretType === 'inputNativeValue' || inputCaretType === 'outputNativeValue' ? styles.nativeCaret : styles.inputCaret; + + return ( + + + + ); +} + +export const styles = StyleSheet.create({ + nativeCaret: { + height: 19, + width: 1.5, + }, + inputCaret: { + height: 32, + width: 2, + }, + caretContainer: { + flexGrow: 100, + flexShrink: 0, + }, +}); diff --git a/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx b/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx new file mode 100644 index 00000000000..7432d641750 --- /dev/null +++ b/src/__swaps__/screens/Swap/components/SwapNativeInput.tsx @@ -0,0 +1,82 @@ +import { AnimatedText, Box } from '@/design-system'; +import React from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { runOnJS, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated'; + +import { SwapInputValuesCaret } from '@/__swaps__/screens/Swap/components/SwapInputValuesCaret'; +import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; +import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; +import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; + +export function SwapNativeInput({ + nativeInputType, + handleTapWhileDisabled, +}: { + nativeInputType: 'inputNativeValue' | 'outputNativeValue'; + handleTapWhileDisabled?: () => void; +}) { + const { + focusedInput, + internalSelectedInputAsset, + internalSelectedOutputAsset, + outputQuotesAreDisabled, + SwapTextStyles, + SwapInputController, + } = useSwapContext(); + + const formattedNativeInput = + nativeInputType === 'inputNativeValue' ? SwapInputController.formattedInputNativeValue : SwapInputController.formattedOutputNativeValue; + + const textStyle = nativeInputType === 'inputNativeValue' ? SwapTextStyles.inputNativeValueStyle : SwapTextStyles.outputNativeValueStyle; + + const nativeCurrencySymbol = formattedNativeInput.value.slice(0, 1); + const formattedNativeValue = useDerivedValue(() => { + return formattedNativeInput.value.slice(1); + }); + + const disabled = useDerivedValue(() => { + if (nativeInputType === 'outputNativeValue' && outputQuotesAreDisabled.value) return true; + + // disable caret and pointer events for native inputs when corresponding asset is missing price + const asset = nativeInputType === 'inputNativeValue' ? internalSelectedInputAsset : internalSelectedOutputAsset; + const assetPrice = asset.value?.nativePrice || asset.value?.price?.value || 0; + return !assetPrice || equalWorklet(assetPrice, 0); + }); + + const pointerEventsStyle = useAnimatedStyle(() => { + return { + pointerEvents: disabled.value ? 'none' : 'box-only', + }; + }); + + return ( + { + 'worklet'; + if (outputQuotesAreDisabled.value && handleTapWhileDisabled && nativeInputType === 'outputNativeValue') { + runOnJS(handleTapWhileDisabled)(); + } else { + focusedInput.value = nativeInputType; + } + }} + > + + + {nativeCurrencySymbol} + + + + {formattedNativeValue} + + + + + + ); +} + +export const styles = StyleSheet.create({ + nativeContainer: { alignItems: 'center', flexDirection: 'row', height: 17, pointerEvents: 'box-only' }, + nativeRowContainer: { alignItems: 'center', flexDirection: 'row' }, +}); diff --git a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx index a90b5dace9d..b9b9a5286a4 100644 --- a/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx +++ b/src/__swaps__/screens/Swap/components/SwapNumberPad.tsx @@ -11,9 +11,10 @@ import Animated, { withDelay, withTiming, } from 'react-native-reanimated'; - +import { supportedNativeCurrencies } from '@/references'; import { Bleed, Box, Columns, HitSlop, Separator, Text, useColorMode, useForegroundColor } from '@/design-system'; -import { stripCommas } from '@/__swaps__/utils/swaps'; +import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { stripNonDecimalNumbers } from '@/__swaps__/utils/swaps'; import { CUSTOM_KEYBOARD_HEIGHT, LIGHT_SEPARATOR_COLOR, @@ -30,6 +31,7 @@ import { colors } from '@/styles'; import { NavigationSteps, useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; import { IS_IOS } from '@/env'; import { inputKeys } from '@/__swaps__/types/swap'; +import { useAccountSettings } from '@/hooks'; type numberPadCharacter = number | 'backspace' | '.'; @@ -49,23 +51,81 @@ const getFormattedInputKey = (inputKey: inputKeys) => { export const SwapNumberPad = () => { const { isDarkMode } = useColorMode(); - const { focusedInput, isQuoteStale, SwapInputController, configProgress, outputQuotesAreDisabled } = useSwapContext(); + const { nativeCurrency } = useAccountSettings(); + const { + focusedInput, + internalSelectedInputAsset, + internalSelectedOutputAsset, + isQuoteStale, + SwapInputController, + configProgress, + outputQuotesAreDisabled, + } = useSwapContext(); const longPressTimer = useSharedValue(0); - const addNumber = (number?: number) => { + const removeFormatting = (inputKey: inputKeys) => { 'worklet'; + return stripNonDecimalNumbers(SwapInputController[getFormattedInputKey(inputKey)].value); + }; + + const ignoreChange = ({ currentValue, addingDecimal = false }: { currentValue?: string; addingDecimal?: boolean }) => { + 'worklet'; + // ignore when: outputQuotesAreDisabled and we are updating the output amount or output native value if ((focusedInput.value === 'outputAmount' || focusedInput.value === 'outputNativeValue') && outputQuotesAreDisabled.value) { + return true; + } + + // ignore when: corresponding asset does not have a price and we are updating native inputs + const inputAssetPrice = internalSelectedInputAsset.value?.nativePrice || internalSelectedInputAsset.value?.price?.value || 0; + const outputAssetPrice = internalSelectedOutputAsset.value?.nativePrice || internalSelectedOutputAsset.value?.price?.value || 0; + const outputAssetHasNoPrice = !outputAssetPrice || equalWorklet(outputAssetPrice, 0); + const inputAssetHasNoPrice = !inputAssetPrice || equalWorklet(inputAssetPrice, 0); + if ( + (focusedInput.value === 'outputNativeValue' && outputAssetHasNoPrice) || + (focusedInput.value === 'inputNativeValue' && inputAssetHasNoPrice) + ) { + return true; + } + + // ignore when: decimals exceed native currency decimals + if (currentValue) { + const currentValueDecimals = currentValue.split('.')?.[1]?.length ?? -1; + const nativeCurrencyDecimals = supportedNativeCurrencies[nativeCurrency].decimals; + + const isNativePlaceholderValue = equalWorklet(currentValue, 0) && SwapInputController.inputMethod.value !== focusedInput.value; + + if (addingDecimal && nativeCurrencyDecimals === 0) { + return true; + } else if ( + (focusedInput.value === 'inputNativeValue' || focusedInput.value === 'outputNativeValue') && + !isNativePlaceholderValue && + currentValueDecimals >= nativeCurrencyDecimals + ) { + return true; + } + } + return false; + }; + + const addNumber = (number?: number) => { + 'worklet'; + const inputKey = focusedInput.value; + const currentValue = removeFormatting(inputKey); + + if (ignoreChange({ currentValue })) { return; } // Immediately stop the quote fetching interval SwapInputController.quoteFetchingInterval.stop(); - const inputKey = focusedInput.value; - const currentValue = stripCommas(SwapInputController[getFormattedInputKey(inputKey)].value); + const inputMethod = SwapInputController.inputMethod.value; - const newValue = currentValue === '0' ? `${number}` : `${currentValue}${number}`; + const isNativePlaceholderValue = + equalWorklet(currentValue, 0) && inputMethod !== inputKey && (inputKey === 'inputNativeValue' || inputKey === 'outputNativeValue'); + + const newValue = currentValue === '0' || isNativePlaceholderValue ? `${number}` : `${currentValue}${number}`; // For a uint256, the maximum value is: // 2e256 − 1 =115792089237316195423570985008687907853269984665640564039457584007913129639935 @@ -79,7 +139,7 @@ export const SwapNumberPad = () => { isQuoteStale.value = 1; } - if (SwapInputController.inputMethod.value !== inputKey) { + if (inputMethod !== inputKey) { SwapInputController.inputMethod.value = inputKey; } @@ -94,7 +154,11 @@ export const SwapNumberPad = () => { const addDecimalPoint = () => { 'worklet'; const inputKey = focusedInput.value; - const currentValue = stripCommas(SwapInputController[getFormattedInputKey(inputKey)].value); + const currentValue = removeFormatting(inputKey); + + if (ignoreChange({ currentValue, addingDecimal: true })) { + return; + } if (!currentValue.includes('.')) { if (SwapInputController.inputMethod.value !== inputKey) { @@ -115,7 +179,7 @@ export const SwapNumberPad = () => { const deleteLastCharacter = () => { 'worklet'; - if ((focusedInput.value === 'outputAmount' || focusedInput.value === 'outputNativeValue') && outputQuotesAreDisabled.value) { + if (ignoreChange({})) { return; } @@ -125,7 +189,7 @@ export const SwapNumberPad = () => { SwapInputController.inputMethod.value = inputKey; } - const currentValue = stripCommas(SwapInputController[getFormattedInputKey(inputKey)].value); + const currentValue = removeFormatting(inputKey); // Handle deletion, ensuring a placeholder zero remains if the entire number is deleted const newValue = currentValue.length > 1 ? currentValue.slice(0, -1) : 0; diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index 3ccde4f4748..e3a5557ead5 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -7,6 +7,8 @@ import { ScreenCornerRadius } from 'react-native-screen-corner-radius'; import { AnimatedSwapCoinIcon } from '@/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon'; import { BalanceBadge } from '@/__swaps__/screens/Swap/components/BalanceBadge'; +import { SwapNativeInput } from '@/__swaps__/screens/Swap/components/SwapNativeInput'; +import { SwapInputValuesCaret } from '@/__swaps__/screens/Swap/components/SwapInputValuesCaret'; import { FadeMask } from '@/__swaps__/screens/Swap/components/FadeMask'; import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton'; import { SwapActionButton } from '@/__swaps__/screens/Swap/components/SwapActionButton'; @@ -20,7 +22,6 @@ import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; import { useSwapsStore } from '@/state/swaps/swapsStore'; -import { ethereumUtils } from '@/utils'; import Clipboard from '@react-native-clipboard/clipboard'; import { CopyPasteMenu } from './CopyPasteMenu'; @@ -51,27 +52,8 @@ function SwapOutputActionButton() { ); } -function SwapOutputAmount() { - const { navigate } = useNavigation(); - const { focusedInput, SwapTextStyles, SwapInputController, AnimatedSwapStyles, outputQuotesAreDisabled } = useSwapContext(); - - const handleTapWhileDisabled = useCallback(() => { - const { inputAsset, outputAsset } = useSwapsStore.getState(); - const inputTokenSymbol = inputAsset?.symbol; - const outputTokenSymbol = outputAsset?.symbol; - const isCrosschainSwap = inputAsset?.chainId !== outputAsset?.chainId; - const isBridgeSwap = inputTokenSymbol === outputTokenSymbol; - - navigate(Routes.EXPLAIN_SHEET, { - inputToken: inputTokenSymbol, - fromChainId: inputAsset?.chainId ?? ChainId.mainnet, - toChainId: outputAsset?.chainId ?? ChainId.mainnet, - isCrosschainSwap, - isBridgeSwap, - outputToken: outputTokenSymbol, - type: 'output_disabled', - }); - }, [navigate]); +function SwapOutputAmount({ handleTapWhileDisabled }: { handleTapWhileDisabled: () => void }) { + const { focusedInput, SwapTextStyles, SwapInputController, outputQuotesAreDisabled } = useSwapContext(); const [isPasteEnabled, setIsPasteEnabled] = useState(() => !outputQuotesAreDisabled.value); useAnimatedReaction( @@ -115,9 +97,7 @@ function SwapOutputAmount() { {SwapInputController.formattedOutputAmount} - - - + @@ -147,15 +127,26 @@ function OutputAssetBalanceBadge() { } export function SwapOutputAsset() { - const { - outputProgress, - inputProgress, - AnimatedSwapStyles, - SwapTextStyles, - SwapInputController, - internalSelectedOutputAsset, - SwapNavigation, - } = useSwapContext(); + const { outputProgress, inputProgress, AnimatedSwapStyles, internalSelectedOutputAsset, SwapNavigation } = useSwapContext(); + const { navigate } = useNavigation(); + + const handleTapWhileDisabled = useCallback(() => { + const { inputAsset, outputAsset } = useSwapsStore.getState(); + const inputTokenSymbol = inputAsset?.symbol; + const outputTokenSymbol = outputAsset?.symbol; + const isCrosschainSwap = inputAsset?.chainId !== outputAsset?.chainId; + const isBridgeSwap = inputTokenSymbol === outputTokenSymbol; + + navigate(Routes.EXPLAIN_SHEET, { + inputToken: inputTokenSymbol, + fromChainId: inputAsset?.chainId ?? ChainId.mainnet, + toChainId: outputAsset?.chainId ?? ChainId.mainnet, + isCrosschainSwap, + isBridgeSwap, + outputToken: outputTokenSymbol, + type: 'output_disabled', + }); + }, [navigate]); return ( @@ -165,15 +156,13 @@ export function SwapOutputAsset() { - + - - {SwapInputController.formattedOutputNativeValue} - + @@ -203,14 +192,6 @@ export const styles = StyleSheet.create({ backgroundOverlay: { backgroundColor: 'rgba(0, 0, 0, 0.88)', }, - caret: { - height: 32, - width: 2, - }, - caretContainer: { - flexGrow: 100, - flexShrink: 0, - }, flipButton: { borderRadius: 15, height: 30, diff --git a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts index 6e74ff1b4f1..244dd9f993e 100644 --- a/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useAnimatedSwapStyles.ts @@ -212,18 +212,6 @@ export function useAnimatedSwapStyles({ }; }); - const assetToSellCaretStyle = useAnimatedStyle(() => { - return { - backgroundColor: getColorValueForThemeWorklet(internalSelectedInputAsset.value?.highContrastColor, isDarkMode, true), - }; - }); - - const assetToBuyCaretStyle = useAnimatedStyle(() => { - return { - backgroundColor: getColorValueForThemeWorklet(internalSelectedOutputAsset.value?.highContrastColor, isDarkMode, true), - }; - }); - const flipButtonFetchingStyle = useAnimatedStyle(() => { if (IS_ANDROID) return { borderWidth: 0 }; return { @@ -309,9 +297,7 @@ export function useAnimatedSwapStyles({ outputTokenListStyle, swapActionWrapperStyle, assetToSellIconStyle, - assetToSellCaretStyle, assetToBuyIconStyle, - assetToBuyCaretStyle, hideWhileReviewingOrConfiguringGas, flipButtonFetchingStyle, searchInputAssetButtonStyle, diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index e82fa75fc39..030f7297458 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -1,4 +1,11 @@ -import { divWorklet, equalWorklet, greaterThanWorklet, isNumberStringWorklet, mulWorklet } from '@/__swaps__/safe-math/SafeMath'; +import { + divWorklet, + equalWorklet, + greaterThanWorklet, + isNumberStringWorklet, + mulWorklet, + toFixedWorklet, +} from '@/__swaps__/safe-math/SafeMath'; import { SCRUBBER_WIDTH, SLIDER_WIDTH, snappySpringConfig } from '@/__swaps__/screens/Swap/constants'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; @@ -10,7 +17,14 @@ import { convertRawAmountToDecimalFormat, handleSignificantDecimalsWorklet, } from '@/__swaps__/utils/numbers'; -import { addCommasToNumber, buildQuoteParams, clamp, getDefaultSlippageWorklet, trimTrailingZeros } from '@/__swaps__/utils/swaps'; +import { + addCommasToNumber, + addSymbolToNativeDisplayWorklet, + buildQuoteParams, + clamp, + getDefaultSlippageWorklet, + trimTrailingZeros, +} from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { useAccountSettings } from '@/hooks'; @@ -102,7 +116,7 @@ export function useSwapInputsController({ return addCommasToNumber(inputValues.value.inputAmount, '0'); } - if (inputMethod.value === 'outputAmount') { + if (inputMethod.value === 'outputAmount' || inputMethod.value === 'inputNativeValue' || inputMethod.value === 'outputNativeValue') { return valueBasedDecimalFormatter({ amount: inputValues.value.inputAmount, nativePrice: inputNativePrice.value, @@ -121,16 +135,19 @@ export function useSwapInputsController({ }); const formattedInputNativeValue = useDerivedValue(() => { + if (inputMethod.value === 'inputNativeValue') { + return addSymbolToNativeDisplayWorklet(inputValues.value.inputNativeValue, currentCurrency); + } if ( (inputMethod.value === 'slider' && percentageToSwap.value === 0) || !inputValues.value.inputNativeValue || !isNumberStringWorklet(inputValues.value.inputNativeValue.toString()) || equalWorklet(inputValues.value.inputNativeValue, 0) ) { - return convertAmountToNativeDisplayWorklet(0, currentCurrency); + return convertAmountToNativeDisplayWorklet(0, currentCurrency, false, true); } - return convertAmountToNativeDisplayWorklet(inputValues.value.inputNativeValue, currentCurrency); + return convertAmountToNativeDisplayWorklet(inputValues.value.inputNativeValue, currentCurrency, false, true); }); const formattedOutputAmount = useDerivedValue(() => { @@ -154,16 +171,19 @@ export function useSwapInputsController({ }); const formattedOutputNativeValue = useDerivedValue(() => { + if (inputMethod.value === 'outputNativeValue') { + return addSymbolToNativeDisplayWorklet(inputValues.value.outputNativeValue, currentCurrency); + } if ( (inputMethod.value === 'slider' && percentageToSwap.value === 0) || !inputValues.value.outputNativeValue || !isNumberStringWorklet(inputValues.value.outputNativeValue.toString()) || equalWorklet(inputValues.value.outputNativeValue, 0) ) { - return convertAmountToNativeDisplayWorklet(0, currentCurrency); + return convertAmountToNativeDisplayWorklet(0, currentCurrency, false, true); } - return convertAmountToNativeDisplayWorklet(inputValues.value.outputNativeValue, currentCurrency); + return convertAmountToNativeDisplayWorklet(inputValues.value.outputNativeValue, currentCurrency, false, true); }); const updateNativePriceForAsset = useCallback( @@ -206,7 +226,7 @@ export function useSwapInputsController({ } // NOTE: if we encounter a quote error, let's make sure to update the outputAmount and inputAmount to 0 accordingly - if (lastTypedInput.value === 'inputAmount') { + if (lastTypedInput.value === 'inputAmount' || lastTypedInput.value === 'inputNativeValue') { inputValues.modify(prev => { return { ...prev, @@ -214,7 +234,7 @@ export function useSwapInputsController({ outputNativeValue: 0, }; }); - } else if (lastTypedInput.value === 'outputAmount') { + } else if (lastTypedInput.value === 'outputAmount' || lastTypedInput.value === 'outputNativeValue') { inputValues.modify(prev => { return { ...prev, @@ -246,7 +266,6 @@ export function useSwapInputsController({ quoteFetchingInterval: ReturnType; }) => { 'worklet'; - // Check whether the quote has been superseded by new user input so we don't introduce conflicting updates const isLastTypedInputStillValid = originalQuoteParams.lastTypedInput === lastTypedInput.value; @@ -259,7 +278,9 @@ export function useSwapInputsController({ const isInputAmountStillValid = originalQuoteParams.inputAmount === inputValues.value.inputAmount; const isOutputAmountStillValid = originalQuoteParams.outputAmount === inputValues.value.outputAmount; const areInputAmountsStillValid = - originalQuoteParams.lastTypedInput === 'inputAmount' ? isInputAmountStillValid : isOutputAmountStillValid; + originalQuoteParams.lastTypedInput === 'inputAmount' || originalQuoteParams.lastTypedInput === 'inputNativeValue' + ? isInputAmountStillValid + : isOutputAmountStillValid; // Set prices first regardless of the quote status, as long as the same assets are still selected if (inputPrice && isInputUniqueIdStillValid) { @@ -476,7 +497,7 @@ export function useSwapInputsController({ } const quotedInputAmount = - lastTypedInputParam === 'outputAmount' + lastTypedInputParam === 'outputAmount' || lastTypedInputParam === 'outputNativeValue' ? Number( convertRawAmountToDecimalFormat( quoteResponse.sellAmount.toString(), @@ -486,7 +507,7 @@ export function useSwapInputsController({ : undefined; const quotedOutputAmount = - lastTypedInputParam === 'inputAmount' + lastTypedInputParam === 'inputAmount' || lastTypedInputParam === 'inputNativeValue' ? Number( convertRawAmountToDecimalFormat( quoteResponse.buyAmountMinusFees.toString(), @@ -762,7 +783,7 @@ export function useSwapInputsController({ /** * Observes value changes in the active inputMethod, which can be any of the following: * - inputAmount - * - inputNativeValue (TODO) + * - inputNativeValue * - outputAmount * - outputNativeValue (TODO) * - sliderXPosition @@ -870,6 +891,56 @@ export function useSwapInputsController({ }; }); + runOnJS(debouncedFetchQuote)(); + } + } + const inputMethodValue = inputMethod.value; + const isNativeInputMethod = inputMethodValue === 'inputNativeValue'; + const isNativeOutputMethod = inputMethodValue === 'outputNativeValue'; + if ( + (isNativeInputMethod || isNativeOutputMethod) && + !equalWorklet(current.values[inputMethodValue], previous.values[inputMethodValue]) + ) { + // If the number in the native field changes + lastTypedInput.value = inputMethodValue; + if (equalWorklet(current.values[inputMethodValue], 0)) { + // If the native amount was set to 0 + resetValuesToZeroWorklet({ updateSlider: true, inputKey: inputMethodValue }); + } else { + // If the native amount was set to a non-zero value + if (isNativeInputMethod && !internalSelectedInputAsset.value) return; + if (isNativeOutputMethod && !internalSelectedOutputAsset.value) return; + + // If the asset price is zero + if (isNativeInputMethod && equalWorklet(inputNativePrice.value, 0)) return; + if (isNativeOutputMethod && equalWorklet(outputNativePrice.value, 0)) return; + + if (isQuoteStale.value !== 1) isQuoteStale.value = 1; + const nativePrice = isNativeInputMethod ? inputNativePrice.value : outputNativePrice.value; + const decimalPlaces = isNativeInputMethod + ? internalSelectedInputAsset.value?.decimals + : internalSelectedOutputAsset.value?.decimals; + const amount = toFixedWorklet(divWorklet(current.values[inputMethodValue], nativePrice), decimalPlaces || 18); + const amountKey = isNativeInputMethod ? 'inputAmount' : 'outputAmount'; + + inputValues.modify(values => { + return { + ...values, + [amountKey]: amount, + }; + }); + + if (isNativeInputMethod) { + const inputAssetBalance = internalSelectedInputAsset.value?.maxSwappableAmount || '0'; + + if (equalWorklet(inputAssetBalance, 0)) { + sliderXPosition.value = withSpring(0, snappySpringConfig); + } else { + const updatedSliderPosition = clamp(Number(divWorklet(amount, inputAssetBalance)) * SLIDER_WIDTH, 0, SLIDER_WIDTH); + sliderXPosition.value = withSpring(updatedSliderPosition, snappySpringConfig); + } + } + runOnJS(debouncedFetchQuote)(); } } diff --git a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts index 92facea2c77..be633447abb 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts @@ -1,5 +1,4 @@ import { - Easing, SharedValue, interpolateColor, useAnimatedStyle, @@ -11,45 +10,27 @@ import { } from 'react-native-reanimated'; import { useColorMode, useForegroundColor } from '@/design-system'; -import { - ETH_COLOR_DARK, - ETH_COLOR_DARK_ACCENT, - SLIDER_COLLAPSED_HEIGHT, - SLIDER_HEIGHT, - caretConfig, - pulsingConfig, -} from '@/__swaps__/screens/Swap/constants'; -import { inputKeys, inputMethods, inputValuesType } from '@/__swaps__/types/swap'; +import { ETH_COLOR_DARK, ETH_COLOR_DARK_ACCENT, pulsingConfig } from '@/__swaps__/screens/Swap/constants'; +import { inputMethods, inputValuesType } from '@/__swaps__/types/swap'; import { getColorValueForThemeWorklet, opacity } from '@/__swaps__/utils/swaps'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { equalWorklet } from '@/__swaps__/safe-math/SafeMath'; import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs'; -import { NavigationSteps } from './useSwapNavigation'; export function useSwapTextStyles({ - configProgress, inputMethod, inputValues, internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, - focusedInput, - inputProgress, - outputProgress, - sliderPressProgress, }: { - configProgress: SharedValue; inputMethod: SharedValue; inputValues: SharedValue; internalSelectedInputAsset: SharedValue; internalSelectedOutputAsset: SharedValue; isFetching: SharedValue; isQuoteStale: SharedValue; - focusedInput: SharedValue; - inputProgress: SharedValue; - outputProgress: SharedValue; - sliderPressProgress: SharedValue; }) { const { isDarkMode } = useColorMode(); @@ -79,19 +60,30 @@ export function useSwapTextStyles({ }); const isInputZero = useDerivedValue(() => { - const isZero = - !internalSelectedInputAsset.value || - (inputValues.value.inputAmount === 0 && inputMethod.value !== 'slider') || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); - return isZero; + const isInputAmountZero = inputValues.value.inputAmount === 0; + if (!internalSelectedInputAsset.value) return true; + + if (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) return true; + + if (inputMethod.value === 'inputNativeValue' && isInputAmountZero) { + return inputValues.value.inputNativeValue === 0; + } + + return isInputAmountZero; }); const isOutputZero = useDerivedValue(() => { - const isZero = - !internalSelectedOutputAsset.value || - (inputValues.value.outputAmount === 0 && inputMethod.value !== 'slider') || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.outputAmount, 0)); - return isZero; + const isOutputAmountZero = inputValues.value.outputAmount === 0; + + if (!internalSelectedOutputAsset.value) return true; + + if (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) return true; + + if (inputMethod.value === 'outputNativeValue' && isOutputAmountZero) { + return inputValues.value.outputNativeValue === 0; + } + + return isOutputAmountZero; }); const inputAssetColor = useDerivedValue(() => { @@ -170,81 +162,10 @@ export function useSwapTextStyles({ }; }); - // TODO: Create a reusable InputCaret component - const inputCaretStyle = useAnimatedStyle(() => { - const shouldShow = - configProgress.value === NavigationSteps.INPUT_ELEMENT_FOCUSED && - focusedInput.value === 'inputAmount' && - inputProgress.value === 0 && - outputProgress.value === 0 && - (inputMethod.value !== 'slider' || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) || - (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); - - const opacity = shouldShow - ? withRepeat( - withSequence( - withTiming(1, { duration: 0 }), - withTiming(1, { duration: 400, easing: Easing.bezier(0.87, 0, 0.13, 1) }), - withTiming(0, caretConfig), - withTiming(1, caretConfig) - ), - -1, - true - ) - : withTiming(0, caretConfig); - - const isZero = - (inputMethod.value !== 'slider' && inputValues.value.inputAmount === 0) || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); - - return { - display: shouldShow ? 'flex' : 'none', - opacity, - position: isZero ? 'absolute' : 'relative', - }; - }); - - const outputCaretStyle = useAnimatedStyle(() => { - const shouldShow = - configProgress.value === NavigationSteps.INPUT_ELEMENT_FOCUSED && - focusedInput.value === 'outputAmount' && - inputProgress.value === 0 && - outputProgress.value === 0 && - (inputMethod.value !== 'slider' || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)) || - (sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT && isQuoteStale.value === 0)); - - const opacity = shouldShow - ? withRepeat( - withSequence( - withTiming(1, { duration: 0 }), - withTiming(1, { duration: 400, easing: Easing.bezier(0.87, 0, 0.13, 1) }), - withTiming(0, caretConfig), - withTiming(1, caretConfig) - ), - -1, - true - ) - : withTiming(0, caretConfig); - - const isZero = - (inputMethod.value !== 'slider' && inputValues.value.outputAmount === 0) || - (inputMethod.value === 'slider' && equalWorklet(inputValues.value.inputAmount, 0)); - - return { - display: shouldShow ? 'flex' : 'none', - opacity, - position: isZero ? 'absolute' : 'relative', - }; - }); - return { inputAmountTextStyle, - inputCaretStyle, inputNativeValueStyle, outputAmountTextStyle, - outputCaretStyle, outputNativeValueStyle, }; } diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 7e1698d7f3d..abefed50f50 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -439,17 +439,12 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }); const SwapTextStyles = useSwapTextStyles({ - configProgress, - focusedInput, inputMethod: SwapInputController.inputMethod, - inputProgress, inputValues: SwapInputController.inputValues, internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, - outputProgress, - sliderPressProgress, }); const SwapNavigation = useSwapNavigation({ diff --git a/src/__swaps__/types/swap.ts b/src/__swaps__/types/swap.ts index 3bf0134c6d3..6a513b68024 100644 --- a/src/__swaps__/types/swap.ts +++ b/src/__swaps__/types/swap.ts @@ -1,5 +1,4 @@ -import { ExtendedAnimatedAssetWithColors, UniqueId } from './assets'; -import { SearchAsset } from './search'; +import { ExtendedAnimatedAssetWithColors } from './assets'; export type inputKeys = 'inputAmount' | 'inputNativeValue' | 'outputAmount' | 'outputNativeValue'; export type inputMethods = inputKeys | 'slider'; diff --git a/src/__swaps__/utils/__tests__/numbers.test.ts b/src/__swaps__/utils/__tests__/numbers.test.ts index 609ebc1186b..faf3be13c6b 100644 --- a/src/__swaps__/utils/__tests__/numbers.test.ts +++ b/src/__swaps__/utils/__tests__/numbers.test.ts @@ -13,7 +13,7 @@ const testCases = [ { value: 1234.56, currency: supportedCurrencies.NZD, expected: 'NZ$1,234.56' }, { value: 1234.56, currency: supportedCurrencies.GBP, expected: '£1,234.56' }, { value: 1234.56, currency: supportedCurrencies.CNY, expected: '¥1,234.56' }, - { value: 1234.56, currency: supportedCurrencies.JPY, expected: '¥1,234.56' }, + { value: 1234.56, currency: supportedCurrencies.JPY, expected: '¥1,235' }, { value: 1234.56, currency: supportedCurrencies.INR, expected: '₹1,234.56' }, { value: 1234.56, currency: supportedCurrencies.TRY, expected: '₺1,234.56' }, { value: 1234.56, currency: supportedCurrencies.ZAR, expected: 'R1,234.56' }, diff --git a/src/__swaps__/utils/numbers.ts b/src/__swaps__/utils/numbers.ts index 38969306888..c6c3e11a115 100644 --- a/src/__swaps__/utils/numbers.ts +++ b/src/__swaps__/utils/numbers.ts @@ -314,7 +314,8 @@ export const convertBipsToPercentage = (value: BigNumberish, decimals = 2): stri export const convertAmountToNativeDisplayWorklet = ( value: number | string, nativeCurrency: keyof nativeCurrencyType, - useThreshold = false + useThreshold = false, + ignoreAlignment = false ) => { 'worklet'; @@ -338,7 +339,7 @@ export const convertAmountToNativeDisplayWorklet = ( maximumFractionDigits: decimals, }); - const nativeDisplay = `${thresholdReached ? '<' : ''}${alignment === 'left' ? symbol : ''}${nativeValue}${alignment === 'right' ? symbol : ''}`; + const nativeDisplay = `${thresholdReached ? '<' : ''}${alignment === 'left' || ignoreAlignment ? symbol : ''}${nativeValue}${!ignoreAlignment && alignment === 'right' ? symbol : ''}`; return nativeDisplay; }; diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 6b70afdf030..fc81b10cb35 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -17,7 +17,7 @@ import { TokenColors } from '@/graphql/__generated__/metadata'; import * as i18n from '@/languages'; import { RainbowConfig } from '@/model/remoteConfig'; import store from '@/redux/store'; -import { ETH_ADDRESS } from '@/references'; +import { ETH_ADDRESS, supportedNativeCurrencies } from '@/references'; import { userAssetsStore } from '@/state/assets/userAssets'; import { colors } from '@/styles'; import { BigNumberish } from '@ethersproject/bignumber'; @@ -197,6 +197,8 @@ export const findNiceIncrement = (availableBalance: string | number | undefined) // /---- 🔵 Worklet utils 🔵 ----/ // // +type nativeCurrencyType = typeof supportedNativeCurrencies; + export function addCommasToNumber(number: string | number, fallbackValue: T = 0 as T): T | string { 'worklet'; if (isNaN(Number(number))) { @@ -217,14 +219,25 @@ export function addCommasToNumber(number: string | n } } +export const addSymbolToNativeDisplayWorklet = (value: number | string, nativeCurrency: keyof nativeCurrencyType): string => { + 'worklet'; + + const nativeSelected = supportedNativeCurrencies?.[nativeCurrency]; + const { symbol } = nativeSelected; + + const nativeValueWithCommas = addCommasToNumber(value, '0'); + + return `${symbol}${nativeValueWithCommas}`; +}; + export function clamp(value: number, lowerBound: number, upperBound: number) { 'worklet'; return Math.min(Math.max(lowerBound, value), upperBound); } -export function stripCommas(value: string) { +export function stripNonDecimalNumbers(value: string) { 'worklet'; - return value.replace(/,/g, ''); + return value.replace(/[^0-9.]/g, ''); } export function trimTrailingZeros(value: string) { @@ -628,7 +641,6 @@ export const buildQuoteParams = ({ fromAddress: currentAddress, sellTokenAddress: inputAsset.isNativeAsset ? ETH_ADDRESS_AGGREGATOR : inputAsset.address, buyTokenAddress: outputAsset.isNativeAsset ? ETH_ADDRESS_AGGREGATOR : outputAsset.address, - // TODO: Handle native input cases below sellAmount: lastTypedInput === 'inputAmount' || lastTypedInput === 'inputNativeValue' ? convertAmountToRawAmount(inputAmount.toString(), inputAsset.decimals) diff --git a/src/references/supportedCurrencies.ts b/src/references/supportedCurrencies.ts index 9c356d87fd4..ef1588a377a 100644 --- a/src/references/supportedCurrencies.ts +++ b/src/references/supportedCurrencies.ts @@ -98,8 +98,8 @@ export const supportedCurrencies = { emoji: '🇰🇷', emojiName: 'south_korea', label: i18n.t(i18n.l.settings.currency.KRW), - mask: '[099999999999]{.}[00]', - placeholder: '0.00', + mask: '[099999999999]', + placeholder: '0', userAssetsSmallThreshold: 100, smallThreshold: 1000, symbol: '₩', @@ -139,12 +139,12 @@ export const supportedCurrencies = { alignment: 'left', assetLimit: 1, currency: 'JPY', - decimals: 2, + decimals: 0, emoji: '🇯🇵', emojiName: 'japan', label: i18n.t(i18n.l.settings.currency.JPY), - mask: '[099999999999]{.}[00]', - placeholder: '0.00', + mask: '[099999999999]', + placeholder: '0', userAssetsSmallThreshold: 10, smallThreshold: 100, symbol: '¥', From e696f6021c224aa70fcecfdf97bfc7062f8ffe36 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 20 Sep 2024 14:00:41 -0400 Subject: [PATCH 20/64] Fix chainId not being passed in for dapp browser getProvider function (#6121) * Update rainbow provider package to latest 0.1.1 * Fix: getProvider function needs to use an object param * Cleanup --- ios/Podfile.lock | 2 +- ios/Rainbow.xcodeproj/project.pbxproj | 284 +++++++++--------- package.json | 2 +- .../DappBrowser/handleProviderRequest.ts | 9 +- yarn.lock | 10 +- 5 files changed, 153 insertions(+), 154 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f28ca0bae70..a0d7fa6eba0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2425,7 +2425,7 @@ SPEC CHECKSUMS: TOCropViewController: b9b2905938c0424f289ea2d9964993044913fac6 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c + Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d PODFILE CHECKSUM: 0839e4141c8f26133bf9a961f5ded1ea3127af54 diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 8f98c3f4058..8bbbe54755d 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 15E531D5242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */; }; 15E531DA242DAB7100797B89 /* NotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 15E531D9242DAB7100797B89 /* NotificationManager.m */; }; 24979E8920F84250007EB0DA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24979E7720F84004007EB0DA /* GoogleService-Info.plist */; }; + 3F5576215214EE23607F6CA9 /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FC59C3544C0FBE4328F0600 /* libPods-PriceWidgetExtension.a */; }; 4D098C2F2811A9A5006A801A /* RNStartTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D098C2E2811A9A5006A801A /* RNStartTime.m */; }; 6630540924A38A1900E5B030 /* RainbowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 6630540824A38A1900E5B030 /* RainbowText.m */; }; 6635730624939991006ACFA6 /* SafeStoreReview.m in Sources */ = {isa = PBXBuildFile; fileRef = 6635730524939991006ACFA6 /* SafeStoreReview.m */; }; @@ -34,8 +35,7 @@ 66A1FEB624AB641100C3F539 /* RNCMScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEB324AB641100C3F539 /* RNCMScreen.m */; }; 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */; }; 66A28EB024CAF1B500410A88 /* TestFlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A28EAF24CAF1B500410A88 /* TestFlight.m */; }; - 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */; }; - 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */; }; + 7E667C2059B8CDB73F45605F /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C37D3C9100B762F75F550BB5 /* libPods-ImageNotification.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -68,7 +68,6 @@ B5CE8FFF29A5758100EB1EFA /* pooly@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */; }; B5D7F2F029E8D41E003D6A54 /* finiliar@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */; }; B5D7F2F129E8D41E003D6A54 /* finiliar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */; }; - B8CD55BCF7A0AB6EFF7E2107 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */; }; C04D10F025AFC8C1003BEF7A /* Extras.json in Resources */ = {isa = PBXBuildFile; fileRef = C04D10EF25AFC8C1003BEF7A /* Extras.json */; }; C1038325273C2D0C00B18210 /* PriceWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF75272BA7AA00FF5C78 /* PriceWidgetView.swift */; }; C1038337273C5C4200B18210 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DCF62272BA6EF00FF5C78 /* PriceWidget.swift */; }; @@ -139,8 +138,9 @@ C9B378BB2C515A860085E5D0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B378BA2C515A860085E5D0 /* ShareViewController.swift */; }; C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */; }; + CC651D36962A724E81524D7F /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6742E1A099DF0612378089CD /* libPods-SelectTokenIntent.a */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; + EDE3164FD32FFCE390544F9B /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,14 +203,12 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; - 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE772886202800B5C7E7 /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0299CE792886202800B5C7E7 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; - 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; + 11F3C03FC45727D52FAE847D /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -218,6 +216,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Rainbow/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Rainbow/main.m; sourceTree = ""; }; 152643462B9AD97E004AC9AA /* InjectedJSBundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = InjectedJSBundle.js; path = ../InjectedJSBundle.js; sourceTree = ""; }; + 1539A52384919E5109C2C9B2 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 157155032418733F009B698B /* RainbowRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RainbowRelease.entitlements; path = Rainbow/RainbowRelease.entitlements; sourceTree = ""; }; 157155042418734C009B698B /* RainbowDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RainbowDebug.entitlements; path = Rainbow/RainbowDebug.entitlements; sourceTree = ""; }; 15C3987D2880EDFF006033AC /* og@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "og@3x.png"; sourceTree = ""; }; @@ -236,6 +235,7 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; + 1FC59C3544C0FBE4328F0600 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -248,19 +248,19 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; - 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; + 2FF98F997797E047DCAF7234 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; + 380324A440FD8D773D7F1074 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; - 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; + 62CAF1459EC3D2C1F76CAAA6 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + 62DD03EC74A1637EEB329C87 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; 664612ED274848B000B43F5A /* SelectTokenIntent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SelectTokenIntent.entitlements; sourceTree = ""; }; 6655FFB325BB2B0700642961 /* ThemeModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThemeModule.m; sourceTree = ""; }; - 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; 668ADB2C25A4E3A40050859D /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = ""; }; 668ADB2E25A4E3A40050859D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66A1FEAF24AB641100C3F539 /* RNCMScreenStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCMScreenStack.h; path = "../src/react-native-cool-modals/ios/RNCMScreenStack.h"; sourceTree = ""; }; @@ -271,17 +271,16 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; - 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; - 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; - 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; + 6742E1A099DF0612378089CD /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 75256BD3661D2D38F0A34282 /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + 883A50680E4D8A275AFBB48C /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ButtonManager.m; sourceTree = ""; }; + A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; AA0B1CB82B00C5E100EAF77D /* SF-Mono-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Semibold.otf"; path = "../src/assets/fonts/SF-Mono-Semibold.otf"; sourceTree = ""; }; AA0B1CB92B00C5E100EAF77D /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Bold.otf"; path = "../src/assets/fonts/SF-Mono-Bold.otf"; sourceTree = ""; }; AA0B1CBA2B00C5E100EAF77D /* SF-Pro-Rounded-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Black.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Black.otf"; sourceTree = ""; }; @@ -291,7 +290,7 @@ AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; + AB3065204B58D1CAF6AB5BFB /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; @@ -313,8 +312,7 @@ B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pooly@3x.png"; sourceTree = ""; }; B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@3x.png"; sourceTree = ""; }; B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@2x.png"; sourceTree = ""; }; - B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; - BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B652202E744A23B17A022084 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; C04D10EF25AFC8C1003BEF7A /* Extras.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Extras.json; sourceTree = ""; }; C11640E7274DC10B00C9120A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; C1272389274EBBB6006AC743 /* CurrencyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyDetails.swift; sourceTree = ""; }; @@ -342,6 +340,9 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; + C37D3C9100B762F75F550BB5 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C3AB85CEFE6AA92587D648DA /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; + C83648862B87E1FB90335EFA /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -354,14 +355,13 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; - E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; - E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; + E5F43237FF3957D40BFC7963 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; + EBC7F3A7CB4D39D0EB49A89B /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; + F222FAC72622CB2711326815 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; + FCB6238842FEFC329B82EB3C /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B8CD55BCF7A0AB6EFF7E2107 /* libPods-ImageNotification.a in Frameworks */, + 7E667C2059B8CDB73F45605F /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */, + EDE3164FD32FFCE390544F9B /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */, + 3F5576215214EE23607F6CA9 /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */, + CC651D36962A724E81524D7F /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */, - D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */, - 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */, - BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */, + C37D3C9100B762F75F550BB5 /* libPods-ImageNotification.a */, + 1FC59C3544C0FBE4328F0600 /* libPods-PriceWidgetExtension.a */, + A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */, + 6742E1A099DF0612378089CD /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */, - 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */, - 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */, - E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */, - E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */, - 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */, - B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */, - 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */, - 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */, - 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */, - 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */, - 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */, - E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */, - 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */, - FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */, + 11F3C03FC45727D52FAE847D /* Pods-ImageNotification.debug.xcconfig */, + 883A50680E4D8A275AFBB48C /* Pods-ImageNotification.release.xcconfig */, + FCB6238842FEFC329B82EB3C /* Pods-ImageNotification.localrelease.xcconfig */, + 62CAF1459EC3D2C1F76CAAA6 /* Pods-ImageNotification.staging.xcconfig */, + C3AB85CEFE6AA92587D648DA /* Pods-PriceWidgetExtension.debug.xcconfig */, + C83648862B87E1FB90335EFA /* Pods-PriceWidgetExtension.release.xcconfig */, + AB3065204B58D1CAF6AB5BFB /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + EBC7F3A7CB4D39D0EB49A89B /* Pods-PriceWidgetExtension.staging.xcconfig */, + B652202E744A23B17A022084 /* Pods-Rainbow.debug.xcconfig */, + 62DD03EC74A1637EEB329C87 /* Pods-Rainbow.release.xcconfig */, + 1539A52384919E5109C2C9B2 /* Pods-Rainbow.localrelease.xcconfig */, + F222FAC72622CB2711326815 /* Pods-Rainbow.staging.xcconfig */, + 380324A440FD8D773D7F1074 /* Pods-SelectTokenIntent.debug.xcconfig */, + 75256BD3661D2D38F0A34282 /* Pods-SelectTokenIntent.release.xcconfig */, + 2FF98F997797E047DCAF7234 /* Pods-SelectTokenIntent.localrelease.xcconfig */, + E5F43237FF3957D40BFC7963 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */, + 7C2BB7E8EA2574B3A74BB808 /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - BB2F78BEBE245D7D48C41820 /* [CP] Copy Pods Resources */, + F202C751E27E638202288B79 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */, + 58D46DFCC897D560DF7238EC /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */, - F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */, - 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */, + 712E4B41786AE847D8D82804 /* [CP] Embed Pods Frameworks */, + B645329271EC2E2C170CA75B /* [CP] Copy Pods Resources */, + 09A7DC74FE15490E6EC30E26 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */, + 6B031E223F6AC5E261E2209C /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */, + 99C0F14AAA799EF5C78E3FB9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */, + 5367F9783545F28460727F96 /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - EE77FD4F5751D4E858F705B1 /* [CP] Copy Pods Resources */, + 897D6F92EBB33792380F5E33 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1098,7 +1098,20 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */ = { + 09A7DC74FE15490E6EC30E26 /* [CP-User] [RNFB] Core Configuration */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + name = "[CP-User] [RNFB] Core Configuration"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + }; + 5367F9783545F28460727F96 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1113,27 +1126,36 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */ = { + 58D46DFCC897D560DF7238EC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", ); - name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */ = { + 6B031E223F6AC5E261E2209C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1148,14 +1170,14 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */ = { + 712E4B41786AE847D8D82804 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1173,7 +1195,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */ = { + 7C2BB7E8EA2574B3A74BB808 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1188,35 +1210,20 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + 897D6F92EBB33792380F5E33 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - ); - name = "Upload Debug Symbols to Sentry"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; - }; - BB2F78BEBE245D7D48C41820 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1227,7 +1234,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1241,14 +1247,13 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */ = { + 99C0F14AAA799EF5C78E3FB9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1284,43 +1289,22 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EE77FD4F5751D4E858F705B1 /* [CP] Copy Pods Resources */ = { + 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", ); - name = "[CP] Copy Pods Resources"; + name = "Upload Debug Symbols to Sentry"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; }; - F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */ = { + B645329271EC2E2C170CA75B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1376,26 +1360,42 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */ = { + F202C751E27E638202288B79 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = 11F3C03FC45727D52FAE847D /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1633,7 +1633,7 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */; + baseConfigurationReference = 883A50680E4D8A275AFBB48C /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1684,7 +1684,7 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */; + baseConfigurationReference = FCB6238842FEFC329B82EB3C /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1732,7 +1732,7 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */; + baseConfigurationReference = 62CAF1459EC3D2C1F76CAAA6 /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1780,7 +1780,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */; + baseConfigurationReference = B652202E744A23B17A022084 /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1857,7 +1857,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = 62DD03EC74A1637EEB329C87 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1977,7 +1977,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = F222FAC72622CB2711326815 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2093,7 +2093,7 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */; + baseConfigurationReference = 1539A52384919E5109C2C9B2 /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2266,7 +2266,7 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */; + baseConfigurationReference = C3AB85CEFE6AA92587D648DA /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2315,7 +2315,7 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */; + baseConfigurationReference = C83648862B87E1FB90335EFA /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2365,7 +2365,7 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */; + baseConfigurationReference = AB3065204B58D1CAF6AB5BFB /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2412,7 +2412,7 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */; + baseConfigurationReference = EBC7F3A7CB4D39D0EB49A89B /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2459,7 +2459,7 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */; + baseConfigurationReference = 380324A440FD8D773D7F1074 /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2506,7 +2506,7 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */; + baseConfigurationReference = 75256BD3661D2D38F0A34282 /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2554,7 +2554,7 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */; + baseConfigurationReference = 2FF98F997797E047DCAF7234 /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2599,7 +2599,7 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */; + baseConfigurationReference = E5F43237FF3957D40BFC7963 /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; diff --git a/package.json b/package.json index 3cce127e547..dbedca91ca5 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "@ledgerhq/react-native-hw-transport-ble": "6.27.7", "@metamask/eth-sig-util": "7.0.2", "@notifee/react-native": "7.8.2", - "@rainbow-me/provider": "0.0.12", + "@rainbow-me/provider": "0.1.1", "@rainbow-me/react-native-animated-number": "0.0.2", "@rainbow-me/swaps": "0.26.0", "@react-native-async-storage/async-storage": "1.23.1", diff --git a/src/components/DappBrowser/handleProviderRequest.ts b/src/components/DappBrowser/handleProviderRequest.ts index 8b432f05c79..79b64e05abf 100644 --- a/src/components/DappBrowser/handleProviderRequest.ts +++ b/src/components/DappBrowser/handleProviderRequest.ts @@ -14,8 +14,7 @@ import { getDappMetadata } from '@/resources/metadata/dapp'; import { useAppSessionsStore } from '@/state/appSessions'; import { BigNumber } from '@ethersproject/bignumber'; import { ChainId } from '@/chains/types'; -import { defaultChains, SUPPORTED_CHAIN_IDS } from '@/chains'; -import { Chain } from '@wagmi/chains'; +import { chainsNativeAsset, defaultChains, SUPPORTED_CHAIN_IDS } from '@/chains'; export type ProviderRequestPayload = RequestArguments & { id: number; @@ -178,11 +177,11 @@ const getActiveSession = ({ host }: { host: string }): ActiveSession => { : null; if (!appSession) return null; + return { address: appSession?.address || '', chainId: appSession.chainId, }; - // return null; }; const checkRateLimitFn = async (host: string) => { @@ -344,9 +343,9 @@ export const handleProviderRequestApp = ({ messenger, data, meta }: { messenger: checkRateLimit, onSwitchEthereumChainNotSupported, onSwitchEthereumChainSupported, - getProvider: chainId => getProvider({ chainId: chainId as number }) as unknown as Provider, + getProvider, getActiveSession, - getChain: chainId => defaultChains[chainId] as Chain, + getChainNativeCurrency: chainId => chainsNativeAsset[chainId], }); // @ts-ignore diff --git a/yarn.lock b/yarn.lock index 9f900a96610..3213b14274d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4377,9 +4377,9 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/provider@npm:0.0.12": - version: 0.0.12 - resolution: "@rainbow-me/provider@npm:0.0.12" +"@rainbow-me/provider@npm:0.1.1": + version: 0.1.1 + resolution: "@rainbow-me/provider@npm:0.1.1" dependencies: "@ethersproject/abstract-provider": "npm:5.7.0" "@ethersproject/bignumber": "npm:5.7.0" @@ -4387,7 +4387,7 @@ __metadata: "@metamask/eth-sig-util": "npm:7.0.1" eventemitter3: "npm:5.0.1" viem: "npm:1.21.4" - checksum: 10c0/532e06fa477996f2f56758d8c68da91bc678eaa72e200fcd4fe589498e453e908033e0858a039aed1af66db3431d7dd9dcd7283b874516c2ebaea9cace2c3291 + checksum: 10c0/dcc62cbaf3e6a999e89d794bfbc95c62b2e3debb3c9370aef0d59a52dda623b64bc15a1f6eafc7eb8e15a6a37797e13f2b49c539efeb582b3d2a3dd12e2f8c60 languageName: node linkType: hard @@ -7860,7 +7860,7 @@ __metadata: "@nomiclabs/hardhat-ethers": "npm:2.2.3" "@nomiclabs/hardhat-waffle": "npm:2.0.6" "@notifee/react-native": "npm:7.8.2" - "@rainbow-me/provider": "npm:0.0.12" + "@rainbow-me/provider": "npm:0.1.1" "@rainbow-me/react-native-animated-number": "npm:0.0.2" "@rainbow-me/swaps": "npm:0.26.0" "@react-native-async-storage/async-storage": "npm:1.23.1" From dcdf796a210e32c0e82fe1533b70858c79400ce9 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 20 Sep 2024 14:07:36 -0400 Subject: [PATCH 21/64] Update arbitrum default back to mainnet for WC message signing (#6122) --- src/walletConnect/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index e85860bcbbc..a34dcf2e1b0 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -24,7 +24,6 @@ import * as lang from '@/languages'; import store from '@/redux/store'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import WalletTypes from '@/helpers/walletTypes'; -import ethereumUtils from '@/utils/ethereumUtils'; import { getRequestDisplayDetails } from '@/parsers/requests'; import { WalletconnectRequestData, REQUESTS_UPDATE_REQUESTS_TO_APPROVE, removeRequest } from '@/redux/requests'; import { saveLocalRequests } from '@/handlers/localstorage/walletconnectRequests'; @@ -868,7 +867,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { * encapsulate reused code. */ const loadWalletAndSignMessage = async () => { - const provider = getProvider({ chainId: ChainId.arbitrum }); + const provider = getProvider({ chainId: ChainId.mainnet }); const wallet = await loadWallet({ address, showErrorIfNotLoaded: false, provider }); if (!wallet) { From 75453351a11ab8509b2d699e3366b6c7241b7231 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:52:11 -0400 Subject: [PATCH 22/64] Bump deps for Xcode 16 compatibility (#6110) * RN upgrade * update ios files * fixes for android * iOS working fine * bump sentry again * patches and missing dep bump * bump more deps * update lockfile * fix android * changes to pbxproj * revert RN upgrade * new line missing * force linter 2 re-rerun * update pod lock --- ios/Gemfile | 5 +- ios/Gemfile.lock | 4 +- ios/Podfile | 2 +- ios/Podfile.lock | 50 ++- package.json | 6 +- ...atch => react-native-cloud-fs+2.6.2.patch} | 0 ...act-native-image-crop-picker+0.41.2.patch} | 0 .../Swap/components/SearchInputButton.tsx | 1 + yarn.lock | 316 ++++++++++-------- 9 files changed, 223 insertions(+), 161 deletions(-) rename patches/{react-native-cloud-fs+2.6.1.patch => react-native-cloud-fs+2.6.2.patch} (100%) rename patches/{react-native-image-crop-picker+0.41.0.patch => react-native-image-crop-picker+0.41.2.patch} (100%) diff --git a/ios/Gemfile b/ios/Gemfile index 4d4d8ecb223..56d9ef00e81 100644 --- a/ios/Gemfile +++ b/ios/Gemfile @@ -4,8 +4,9 @@ source 'https://rubygems.org' ruby '>= 2.7.0' gem "fastlane" -gem 'cocoapods', '>= 1.13', '< 1.15' -gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' +# Exclude problematic versions of cocoapods and activesupport that causes build failures. +gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' +gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock index 1af4554f95c..db0f6145d07 100644 --- a/ios/Gemfile.lock +++ b/ios/Gemfile.lock @@ -276,8 +276,8 @@ PLATFORMS ruby DEPENDENCIES - activesupport (>= 6.1.7.5, < 7.1.0) - cocoapods (>= 1.13, < 1.15) + activesupport (>= 6.1.7.5, != 7.1.0) + cocoapods (>= 1.13, != 1.15.1, != 1.15.0) fastlane RUBY VERSION diff --git a/ios/Podfile b/ios/Podfile index fff78719d51..bf1cf540459 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -69,7 +69,7 @@ target 'Rainbow' do pod 'SRSRadialGradient', :path => '../node_modules/react-native-radial-gradient/ios' pod 'react-native-palette-full', :path => '../node_modules/react-native-palette-full' pod 'react-native-ble-plx', :path => '../node_modules/react-native-ble-plx' - pod 'TOCropViewController', '2.7.3' + pod 'TOCropViewController', '~> 2.7.4' use_native_modules! diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a0d7fa6eba0..32d0852c36a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1651,15 +1651,15 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNImageCropPicker (0.41.0): + - RNImageCropPicker (0.41.2): - React-Core - React-RCTImage - - RNImageCropPicker/QBImagePickerController (= 0.41.0) - - TOCropViewController (~> 2.7.3) - - RNImageCropPicker/QBImagePickerController (0.41.0): + - RNImageCropPicker/QBImagePickerController (= 0.41.2) + - TOCropViewController (~> 2.7.4) + - RNImageCropPicker/QBImagePickerController (0.41.2): - React-Core - React-RCTImage - - TOCropViewController (~> 2.7.3) + - TOCropViewController (~> 2.7.4) - RNInputMask (4.1.0) - RNKeyboard (1.0.6): - React @@ -1766,11 +1766,29 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNSentry (5.22.0): + - RNSentry (5.31.1): + - DoubleConversion + - glog - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics - React-hermes - - Sentry/HybridSDK (= 8.24.0) + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Sentry/HybridSDK (= 8.36.0) + - Yoga - RNShare (8.2.1): - React-Core - RNSound (0.11.2): @@ -1792,7 +1810,7 @@ PODS: - SDWebImageWebPCoder (0.14.6): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) - - Sentry/HybridSDK (8.24.0) + - Sentry/HybridSDK (8.36.0) - Shimmer (1.0.2) - SocketRocket (0.7.0) - SRSRadialGradient (1.0.10): @@ -1801,7 +1819,7 @@ PODS: - TcpSockets (3.3.2): - CocoaAsyncSocket - React - - TOCropViewController (2.7.3) + - TOCropViewController (2.7.4) - ToolTipMenu (5.2.1): - React - VisionCamera (4.4.2): @@ -1954,7 +1972,7 @@ DEPENDENCIES: - SRSRadialGradient (from `../node_modules/react-native-radial-gradient/ios`) - swift-vibrant - TcpSockets (from `../node_modules/react-native-tcp`) - - TOCropViewController (= 2.7.3) + - TOCropViewController (~> 2.7.4) - ToolTipMenu (from `../node_modules/react-native-tooltip`) - VisionCamera (from `../node_modules/react-native-vision-camera`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -2395,7 +2413,7 @@ SPEC CHECKSUMS: RNFlashList: e9b57a5553639f9b528cc50ab53f25831722ed62 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df RNGestureHandler: efed690b8493a00b99654043daeb1335276ac4a2 - RNImageCropPicker: 13eab07a785c7a8f8047a1146f7e59d1911c7bb8 + RNImageCropPicker: 771e2ca319d2cf92e04ebf334ece892ee9a6728f RNInputMask: 815461ebdf396beb62cf58916c35cf6930adb991 RNKeyboard: 14793d75953d99c6d950090b8e9698b234c5d08c RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94 @@ -2406,7 +2424,7 @@ SPEC CHECKSUMS: RNReanimated: 45553a3ae29a75a76269595f8554d07d4090e392 RNRudderSdk: 805d4b7064714f3295bf5f152d3812cc67f67a93 RNScreens: aa943ad421c3ced3ef5a47ede02b0cbfc43a012e - RNSentry: 7ae2a06a5563de39ec09dcb1e751ac0c67f1883c + RNSentry: 2c78e72aaa6e55d61ce60d98a62f01b666f76513 RNShare: eaee3dd5a06dad397c7d3b14762007035c5de405 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b @@ -2416,17 +2434,17 @@ SPEC CHECKSUMS: RudderKit: f272f9872183946452ac94cd7bb2244a71e6ca8f SDWebImage: 2d6d229046fea284d62e36bfb8ebe8287dfc5b10 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 - Sentry: 2f6baed15a3f8056b875fc903dc3dcb2903117f4 + Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 Shimmer: c5374be1c2b0c9e292fb05b339a513cf291cac86 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d SRSRadialGradient: e5a34825dff88c9cf773b58bf39ed6a02e59a084 swift-vibrant: 3def73c5c281db74f420ec386590d9c1c5b0995c TcpSockets: bd31674146c0931a064fc254a59812dfd1a73ae0 - TOCropViewController: b9b2905938c0424f289ea2d9964993044913fac6 + TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d + Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c -PODFILE CHECKSUM: 0839e4141c8f26133bf9a961f5ded1ea3127af54 +PODFILE CHECKSUM: 40f081f682acbe2d1d2129eb3335352ea01aab70 COCOAPODS: 1.14.3 diff --git a/package.json b/package.json index dbedca91ca5..bda6b026086 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "install-pods-fast": "cd ios && bundle exec pod install && cd ..", "install-pods-fast-jsc": "cd ios && bundle exec env USE_HERMES=NO pod install && cd ..", "install-pods-no-flipper": "cd ios && bundle exec env SKIP_FLIPPER=true pod install --repo-update && cd ..", - "ios": "react-native run-ios --simulator='iPhone 15 Pro'", + "ios": "react-native run-ios --simulator='iPhone 16 Pro'", "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", @@ -125,7 +125,7 @@ "@react-navigation/stack": "6.3.29", "@reservoir0x/reservoir-sdk": "2.3.0", "@rudderstack/rudder-sdk-react-native": "1.12.1", - "@sentry/react-native": "5.22.0", + "@sentry/react-native": "5.31.1", "@shopify/flash-list": "1.7.0", "@shopify/react-native-skia": "1.3.11", "@tanstack/query-async-storage-persister": "4.2.1", @@ -234,7 +234,7 @@ "react-native-gesture-handler": "2.18.1", "react-native-get-random-values": "1.5.0", "react-native-haptic-feedback": "2.2.0", - "react-native-image-crop-picker": "0.41.0", + "react-native-image-crop-picker": "0.41.2", "react-native-indicators": "0.17.0", "react-native-ios-context-menu": "1.15.3", "react-native-keyboard-area": "1.0.6", diff --git a/patches/react-native-cloud-fs+2.6.1.patch b/patches/react-native-cloud-fs+2.6.2.patch similarity index 100% rename from patches/react-native-cloud-fs+2.6.1.patch rename to patches/react-native-cloud-fs+2.6.2.patch diff --git a/patches/react-native-image-crop-picker+0.41.0.patch b/patches/react-native-image-crop-picker+0.41.2.patch similarity index 100% rename from patches/react-native-image-crop-picker+0.41.0.patch rename to patches/react-native-image-crop-picker+0.41.2.patch diff --git a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx index aa89ce65023..503764f05ef 100644 --- a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx @@ -143,4 +143,5 @@ export const SearchInputButton = ({ ); + }; diff --git a/yarn.lock b/yarn.lock index 3213b14274d..2c6c4d02922 100644 --- a/yarn.lock +++ b/yarn.lock @@ -341,7 +341,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.18.9, @babel/helper-remap-async-to-generator@npm:^7.24.7": +"@babel/helper-remap-async-to-generator@npm:^7.18.9": + version: 7.25.0 + resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" + dependencies: + "@babel/helper-annotate-as-pure": "npm:^7.24.7" + "@babel/helper-wrap-function": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0d17b5f7bb6a607edc9cc62fff8056dd9f341bf2f919884f97b99170d143022a5e7ae57922c4891e4fc360ad291e708d2f8cd8989f1d3cd7a17600159984f5a6 + languageName: node + linkType: hard + +"@babel/helper-remap-async-to-generator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-remap-async-to-generator@npm:7.24.7" dependencies: @@ -451,6 +464,17 @@ __metadata: languageName: node linkType: hard +"@babel/helper-wrap-function@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helper-wrap-function@npm:7.25.0" + dependencies: + "@babel/template": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10c0/d54601a98384c191cbc1ff07b03a19e288ef8d5c6bfafe270b2a303d96e7304eb296002921ed464cc1b105a547d1db146eb86b0be617924dee1ba1b379cdc216 + languageName: node + linkType: hard + "@babel/helpers@npm:^7.22.0, @babel/helpers@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helpers@npm:7.24.8" @@ -5267,115 +5291,123 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/feedback@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry-internal/feedback@npm:7.110.1" +"@sentry-internal/feedback@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry-internal/feedback@npm:7.119.0" dependencies: - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/5429a7dcd14986770647392558ed0d09f31d33d6771688304f90e88feff9b0af6edaa0195e60c5bdce6a8518409f6314b9d03293b661fc3e1b74ef1c154e4595 + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/02ef3f157312c81dd8a2b9504aebf7b4ba5a81c395d529427ee521cd85b5026e847e5f37f3cc11b52229596f7a6f20c34a97ee3d8525a4d6c28aff0c32f1e615 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry-internal/replay-canvas@npm:7.110.1" +"@sentry-internal/replay-canvas@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry-internal/replay-canvas@npm:7.119.0" dependencies: - "@sentry/core": "npm:7.110.1" - "@sentry/replay": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/125b8247db4631e298f3c8c6d8b486669dfa6356490cc7464a23131a5518d9d7ef314f2c1cbbbadad4f9738ba451fbf5f9457170f4bc899b8275e15ae28cb610 + "@sentry/core": "npm:7.119.0" + "@sentry/replay": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/a51124c6709d57b245ad9d16e446601f9fd42bebcf3a10e6ea04e1e5f91e490a9c815761ed71a6e837daad3b1d7da8be91a4f99b5f258e15c0eb906ee3411e73 languageName: node linkType: hard -"@sentry-internal/tracing@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry-internal/tracing@npm:7.110.1" +"@sentry-internal/tracing@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry-internal/tracing@npm:7.119.0" dependencies: - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/ee901b99b838a932f2adb1b085364c816fb0b3f61643ca368af37dd2aa9faf702ea1f3d28f7b0e2e79c57794e3260c8ce34376da0f8658b6ee844b28676ecd55 + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/3cf855bdb6a6dae2b12626d74c0d6c03f9db749e7da7a9871929277a17fbaa411139ef6e892fe86b2836c706850fd165628954a6de6c1f44fdca2f82bde97ff2 + languageName: node + linkType: hard + +"@sentry/babel-plugin-component-annotate@npm:2.20.1": + version: 2.20.1 + resolution: "@sentry/babel-plugin-component-annotate@npm:2.20.1" + checksum: 10c0/83d9e203902ae2601a1b8fcb528c044b42a7275996df2dc775e67804b516f1467b132014d799322871777e40b1ee7d09605c8a1572d67541c1b7556049514d0d languageName: node linkType: hard -"@sentry/browser@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/browser@npm:7.110.1" +"@sentry/browser@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/browser@npm:7.119.0" dependencies: - "@sentry-internal/feedback": "npm:7.110.1" - "@sentry-internal/replay-canvas": "npm:7.110.1" - "@sentry-internal/tracing": "npm:7.110.1" - "@sentry/core": "npm:7.110.1" - "@sentry/replay": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/085a2cbd2b60dbb596b68d3cf7d0059e226bc8892aa90a50680fa9a9256889a715ff234e9210738a3205ce5513e52290e440ffa4f2d2e2429471ea584d075493 + "@sentry-internal/feedback": "npm:7.119.0" + "@sentry-internal/replay-canvas": "npm:7.119.0" + "@sentry-internal/tracing": "npm:7.119.0" + "@sentry/core": "npm:7.119.0" + "@sentry/integrations": "npm:7.119.0" + "@sentry/replay": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/8a29d1149a02f3f1b01723b0008517dfce34315b7803cd8d7f1543d401d4aba53d5eec20d1d9b879985973cddce0b29de62fe18d8da210e24cf73fd2b12a3be9 languageName: node linkType: hard -"@sentry/cli-darwin@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-darwin@npm:2.30.4" +"@sentry/cli-darwin@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-darwin@npm:2.34.0" conditions: os=darwin languageName: node linkType: hard -"@sentry/cli-linux-arm64@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-linux-arm64@npm:2.30.4" +"@sentry/cli-linux-arm64@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-linux-arm64@npm:2.34.0" conditions: (os=linux | os=freebsd) & cpu=arm64 languageName: node linkType: hard -"@sentry/cli-linux-arm@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-linux-arm@npm:2.30.4" +"@sentry/cli-linux-arm@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-linux-arm@npm:2.34.0" conditions: (os=linux | os=freebsd) & cpu=arm languageName: node linkType: hard -"@sentry/cli-linux-i686@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-linux-i686@npm:2.30.4" +"@sentry/cli-linux-i686@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-linux-i686@npm:2.34.0" conditions: (os=linux | os=freebsd) & (cpu=x86 | cpu=ia32) languageName: node linkType: hard -"@sentry/cli-linux-x64@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-linux-x64@npm:2.30.4" +"@sentry/cli-linux-x64@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-linux-x64@npm:2.34.0" conditions: (os=linux | os=freebsd) & cpu=x64 languageName: node linkType: hard -"@sentry/cli-win32-i686@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-win32-i686@npm:2.30.4" +"@sentry/cli-win32-i686@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-win32-i686@npm:2.34.0" conditions: os=win32 & (cpu=x86 | cpu=ia32) languageName: node linkType: hard -"@sentry/cli-win32-x64@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli-win32-x64@npm:2.30.4" +"@sentry/cli-win32-x64@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli-win32-x64@npm:2.34.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@sentry/cli@npm:2.30.4": - version: 2.30.4 - resolution: "@sentry/cli@npm:2.30.4" - dependencies: - "@sentry/cli-darwin": "npm:2.30.4" - "@sentry/cli-linux-arm": "npm:2.30.4" - "@sentry/cli-linux-arm64": "npm:2.30.4" - "@sentry/cli-linux-i686": "npm:2.30.4" - "@sentry/cli-linux-x64": "npm:2.30.4" - "@sentry/cli-win32-i686": "npm:2.30.4" - "@sentry/cli-win32-x64": "npm:2.30.4" +"@sentry/cli@npm:2.34.0": + version: 2.34.0 + resolution: "@sentry/cli@npm:2.34.0" + dependencies: + "@sentry/cli-darwin": "npm:2.34.0" + "@sentry/cli-linux-arm": "npm:2.34.0" + "@sentry/cli-linux-arm64": "npm:2.34.0" + "@sentry/cli-linux-i686": "npm:2.34.0" + "@sentry/cli-linux-x64": "npm:2.34.0" + "@sentry/cli-win32-i686": "npm:2.34.0" + "@sentry/cli-win32-x64": "npm:2.34.0" https-proxy-agent: "npm:^5.0.0" node-fetch: "npm:^2.6.7" progress: "npm:^2.0.3" @@ -5398,7 +5430,7 @@ __metadata: optional: true bin: sentry-cli: bin/sentry-cli - checksum: 10c0/2be7bef39f9740f1b4e126a66733077da990297c9988a3cbff89a00596cef6e71e9d44852fe313f3c07e4e5b771eb91f930464f1779bdf8a222a6ef734bee23a + checksum: 10c0/20507fa7cfd09e12f533926cc2270abe25cdc454d8e4787b698284011cc9d42387805ed725d989ccf9bb4a27329ee6a3a994c7134f44f82a9fe93d9472ac171b languageName: node linkType: hard @@ -5415,13 +5447,13 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/core@npm:7.110.1" +"@sentry/core@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/core@npm:7.119.0" dependencies: - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/8eee39697a1173f233a83fa07b5099c2fdc1f345ec0ac0304ed38c7159214e6a0615fb2fe4dcd4152dbe11cf5ec14bfe3d7da19706e90b13a8bd7984256c4b2f + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/6f2246a9a6ea5dbec8422fccd265aaf8e20582eaf03f1199ef526a41e5939b861cf5050a07975f17c0920c86b4c8cf3416e805131c713899da49d74d529d0697 languageName: node linkType: hard @@ -5436,26 +5468,26 @@ __metadata: languageName: node linkType: hard -"@sentry/hub@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/hub@npm:7.110.1" +"@sentry/hub@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/hub@npm:7.119.0" dependencies: - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/f1cee31fcd6a4fc7745eed0e6c900c3f82bce532d8fe5e503ed6e270ced020b2f1cade660c45531c4e97cd5136ba4bf3cf50c1c2e436c2ad053c18685f6ed1e9 + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/92d5799a9d8775c4d4a54629eee11c1d63d8c8f9cd08ac2a79f63a93e89292e5b21b8812e3ebd9e33eeb5713208d4540f74250f7ad62e0097843e734ec2b8495 languageName: node linkType: hard -"@sentry/integrations@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/integrations@npm:7.110.1" +"@sentry/integrations@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/integrations@npm:7.119.0" dependencies: - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" localforage: "npm:^1.8.1" - checksum: 10c0/276c163411fb7ca0354a4316855de3448a9ff3f14fac4057cd02c39f8aff54fb462b69cb1df9e155b5118d21f66ea7c14439960307c9f0d4bcb6950ac009e3b4 + checksum: 10c0/1e539f0234067587380fbedb132ad4b11f715d229f1fe1571c4c545f23e6dae867d8484d825c271599e1f22d62dc66e481a78e86e6a8653fe60ddcb11b04d93b languageName: node linkType: hard @@ -5487,18 +5519,19 @@ __metadata: languageName: node linkType: hard -"@sentry/react-native@npm:5.22.0": - version: 5.22.0 - resolution: "@sentry/react-native@npm:5.22.0" +"@sentry/react-native@npm:5.31.1": + version: 5.31.1 + resolution: "@sentry/react-native@npm:5.31.1" dependencies: - "@sentry/browser": "npm:7.110.1" - "@sentry/cli": "npm:2.30.4" - "@sentry/core": "npm:7.110.1" - "@sentry/hub": "npm:7.110.1" - "@sentry/integrations": "npm:7.110.1" - "@sentry/react": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" + "@sentry/babel-plugin-component-annotate": "npm:2.20.1" + "@sentry/browser": "npm:7.119.0" + "@sentry/cli": "npm:2.34.0" + "@sentry/core": "npm:7.119.0" + "@sentry/hub": "npm:7.119.0" + "@sentry/integrations": "npm:7.119.0" + "@sentry/react": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" peerDependencies: expo: ">=49.0.0" react: ">=17.0.0" @@ -5508,34 +5541,34 @@ __metadata: optional: true bin: sentry-expo-upload-sourcemaps: scripts/expo-upload-sourcemaps.js - checksum: 10c0/0eaafbaa4b697ffdddb7fcd298655d7c4b582bb2c4bdb5acd8a7c67d0ef48bf01ed04094cc49a2bb33ba6a017950778208486a13949d6cca7ede5e450278c0cf + checksum: 10c0/19e452181ad20f248ca96b2f24f4830e3d58abb9db6ae928ffecaefb10050f7835083c154e80b09f86cadb1e19d05afa0c33c31e1dee45fed51562e58e0a091f languageName: node linkType: hard -"@sentry/react@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/react@npm:7.110.1" +"@sentry/react@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/react@npm:7.119.0" dependencies: - "@sentry/browser": "npm:7.110.1" - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" + "@sentry/browser": "npm:7.119.0" + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" hoist-non-react-statics: "npm:^3.3.2" peerDependencies: react: 15.x || 16.x || 17.x || 18.x - checksum: 10c0/f1ef241a53e3d676f65b53810af8203b7ad8a4eb665c6f50318bcb2bfc2888eb080eac838f701ccce7195863b9c85f6eac69ce5ae0a53a9807a04d1a36076774 + checksum: 10c0/4bfc7f2c4abc5495306baac192d4ec9bb324e35c8690a64148b19f1f7b3448285df0e124e1aaaebaa6282925ac77acfe2dc4b675afb4ed7aac789d354ab2507a languageName: node linkType: hard -"@sentry/replay@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/replay@npm:7.110.1" +"@sentry/replay@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/replay@npm:7.119.0" dependencies: - "@sentry-internal/tracing": "npm:7.110.1" - "@sentry/core": "npm:7.110.1" - "@sentry/types": "npm:7.110.1" - "@sentry/utils": "npm:7.110.1" - checksum: 10c0/ffb300d229d6447ea0f2b99a21ab3d6fae6136cdef33fb9daaac417b1a6b070f385139c934a2acff68562a6f5acdcf96a6e606ddf3893b7a9d44955597343841 + "@sentry-internal/tracing": "npm:7.119.0" + "@sentry/core": "npm:7.119.0" + "@sentry/types": "npm:7.119.0" + "@sentry/utils": "npm:7.119.0" + checksum: 10c0/fa861d0b0912ac55a462899931e238d957048cddf91e7ccbaf58afc94580fea147079d777993c63212aa923d36dd253daf595cf866144a92e1c7860900cb3348 languageName: node linkType: hard @@ -5559,10 +5592,10 @@ __metadata: languageName: node linkType: hard -"@sentry/types@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/types@npm:7.110.1" - checksum: 10c0/6892ae1bed6426ef10dba075d0bd68247bac7c024a7860f3385d320759e152bd9e7bb4b3eeec2600a58d81bb30084fd01e9586da09e372fb48d615c24c86fd10 +"@sentry/types@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/types@npm:7.119.0" + checksum: 10c0/b2ca256ef1d7fb156d455908aa3e021cb2f58100355961fca454d0c5e6c1b8d1a949c479c136dbceca2b76dabcd71cd950e089c576f7e424effbe60f0c0bbaa6 languageName: node linkType: hard @@ -5576,12 +5609,12 @@ __metadata: languageName: node linkType: hard -"@sentry/utils@npm:7.110.1": - version: 7.110.1 - resolution: "@sentry/utils@npm:7.110.1" +"@sentry/utils@npm:7.119.0": + version: 7.119.0 + resolution: "@sentry/utils@npm:7.119.0" dependencies: - "@sentry/types": "npm:7.110.1" - checksum: 10c0/ab84862283881808e94e68d4abe19a38024fbe8de0bd55ddbb1238123772ce633fd507af7f636427333c6376767a85f6e665ece647c43183fdcac6a8e7ba3c77 + "@sentry/types": "npm:7.119.0" + checksum: 10c0/5fd5e5bb0a45548d65baa6c4a04481e750433daa53ce2caa0baa0db19a912b74168290043f8605ed5c10cef7db744efdca2130020d9f59dca3a89d818492daf5 languageName: node linkType: hard @@ -6422,11 +6455,11 @@ __metadata: linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.45 - resolution: "@types/node@npm:18.19.45" + version: 18.19.50 + resolution: "@types/node@npm:18.19.50" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/79c324176411dcfa92f76b0ffc0673aa4bd8da82d003b44633e927c9493cdc46c35f04c0873b096b23b12bab090a6bbdea21242b3bbb2ea5dc1d9bf72adaa04f + checksum: 10c0/36e6bc9eb47213ce94a868dad9504465ad89fba6af9f7954e22bb27fb17a32ac495f263d0cf4fdaee74becd7b2629609a446ec8c2b59b7a07bd587567c8a4782 languageName: node linkType: hard @@ -7883,7 +7916,7 @@ __metadata: "@reservoir0x/reservoir-sdk": "npm:2.3.0" "@rnx-kit/align-deps": "npm:2.2.4" "@rudderstack/rudder-sdk-react-native": "npm:1.12.1" - "@sentry/react-native": "npm:5.22.0" + "@sentry/react-native": "npm:5.31.1" "@shopify/flash-list": "npm:1.7.0" "@shopify/react-native-skia": "npm:1.3.11" "@tanstack/query-async-storage-persister": "npm:4.2.1" @@ -8030,7 +8063,7 @@ __metadata: react-native-gesture-handler: "npm:2.18.1" react-native-get-random-values: "npm:1.5.0" react-native-haptic-feedback: "npm:2.2.0" - react-native-image-crop-picker: "npm:0.41.0" + react-native-image-crop-picker: "npm:0.41.2" react-native-indicators: "npm:0.17.0" react-native-ios-context-menu: "npm:1.15.3" react-native-keyboard-area: "npm:1.0.6" @@ -12341,7 +12374,16 @@ __metadata: languageName: node linkType: hard -"envinfo@npm:^7.10.0, envinfo@npm:^7.7.3": +"envinfo@npm:^7.10.0": + version: 7.14.0 + resolution: "envinfo@npm:7.14.0" + bin: + envinfo: dist/cli.js + checksum: 10c0/059a031eee101e056bd9cc5cbfe25c2fab433fe1780e86cf0a82d24a000c6931e327da6a8ffb3dce528a24f83f256e7efc0b36813113eff8fdc6839018efe327 + languageName: node + linkType: hard + +"envinfo@npm:^7.7.3": version: 7.13.0 resolution: "envinfo@npm:7.13.0" bin: @@ -13470,13 +13512,13 @@ __metadata: linkType: hard "fast-xml-parser@npm:^4.0.12, fast-xml-parser@npm:^4.2.4": - version: 4.4.1 - resolution: "fast-xml-parser@npm:4.4.1" + version: 4.5.0 + resolution: "fast-xml-parser@npm:4.5.0" dependencies: strnum: "npm:^1.0.5" bin: fxparser: src/cli/cli.js - checksum: 10c0/7f334841fe41bfb0bf5d920904ccad09cefc4b5e61eaf4c225bf1e1bb69ee77ef2147d8942f783ee8249e154d1ca8a858e10bda78a5d78b8bed3f48dcee9bf33 + checksum: 10c0/71d206c9e137f5c26af88d27dde0108068a5d074401901d643c500c36e95dfd828333a98bda020846c41f5b9b364e2b0e9be5b19b0bdcab5cf31559c07b80a95 languageName: node linkType: hard @@ -21039,12 +21081,12 @@ __metadata: languageName: node linkType: hard -"react-native-image-crop-picker@npm:0.41.0": - version: 0.41.0 - resolution: "react-native-image-crop-picker@npm:0.41.0" +"react-native-image-crop-picker@npm:0.41.2": + version: 0.41.2 + resolution: "react-native-image-crop-picker@npm:0.41.2" peerDependencies: react-native: ">=0.40.0" - checksum: 10c0/04f8186ba3f721a9a7dccc0d3e1931bb6bb7c3c52ae5ff18f66a72aea9b5996b59174b0982378bae254966f1384a0ebb65bd23a11930f22da0c5adb86266f1fe + checksum: 10c0/8df9baa7107ce5e6f1f9f84e6a0766e3019c6df3bbc95eb88a4d261065959df81c81dcb566a097ca780f8d745e489bed7e390d4fdfedde207b102080bdce81ad languageName: node linkType: hard From 7f8c8b05729d5b0d7b11d1ce9ca4703ecc4d538a Mon Sep 17 00:00:00 2001 From: Ibrahim Taveras Date: Mon, 23 Sep 2024 13:10:25 -0400 Subject: [PATCH 23/64] bump iOS and Android to v1.9.40 (#6131) --- CHANGELOG.md | 33 +++++++++++++++++++++++++++ android/app/build.gradle | 4 ++-- ios/Rainbow.xcodeproj/project.pbxproj | 8 +++---- package.json | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36233adc07a..9c7a13738bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.39] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.39) + +### Added + +- Added rc-push script for release tracking and cleanup (#6088) +- Built the react query for addys claimables endpoint along with wallet screen UI (#6071) + +### Changed + +- Swaps performance improvements (#6050) +- Improved CI jobs for build and tests for Tophat (#6043, #6089) +- Removed some test env for some vars that aren’t needed anymore (#6077) +- userAssetsStore refactor (#6015) +- Bumped swaps sdk to 0.26 (#6098) +- Final implementation for network to chainId migration (#6039) + +### Fixed + +- Fixed swaps spec in e2e so that all assets balances will update correctly (#6060) +- Fixed an issue with charts where it was using USD for points instead of user’s selected currency (#6051) +- Fixed an issue on Android nav bar where it was covered by the systems navigation bar (#6053) +- Fixed e2e flakiness (#6084, #6090) +- Fixed an issue with opacity on mwp sign txn sheet (#6083) +- Fixed a crash that happened when searching input token in swaps (#6104) +- Fixed and issue with degen native asset address, degen ↔ wdegen (#6087, #6091) +- Fixed a crash on token details chart for cannot read property ‘y’ of undefined (#6009) +- Fixed issues with remote promo sheets (#6085) +- Fixed a bug on iOS 18 which caused context menu dismissals (#6112) +- Fixed a crash that was happening on send flow (#6116) +- Fixed a bug where the paste button was disabled on swaps flow for android devices (#6118) +- Fixed an issue where deleting a contact would cause loading issues on send flow (#6119) +- Fixed a bug where chainId wasn’t being passed in the dapp browser (#6121) + ## [1.9.38] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.36) ### Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index 7a56f87e913..7f6d6b97cbd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -130,8 +130,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 231 - versionName "1.9.39" + versionCode 232 + versionName "1.9.40" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 8bbbe54755d..2fb347cef59 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1836,7 +1836,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.39; + MARKETING_VERSION = 1.9.40; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1900,7 +1900,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.39; + MARKETING_VERSION = 1.9.40; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2017,7 +2017,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.39; + MARKETING_VERSION = 1.9.40; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2133,7 +2133,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.39; + MARKETING_VERSION = 1.9.40; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index bda6b026086..26530053395 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.39-1", + "version": "1.9.40-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen && yarn fetch:networks", From e6f4b2f7a062c273abd4bca5d8f98f23e19e06c3 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:35:46 -0400 Subject: [PATCH 24/64] Update CI to work with Xcode 16 / iOS 18 (#6129) --- .detoxrc.js | 2 +- .github/workflows/macstadium-builds.yml | 3 +- .github/workflows/macstadium-tests.yml | 14 ++-- e2e/9_swaps.spec.ts | 4 +- e2e/helpers.ts | 3 + package.json | 6 +- .../Swap/components/SearchInputButton.tsx | 1 - yarn.lock | 76 +++---------------- 8 files changed, 31 insertions(+), 78 deletions(-) diff --git a/.detoxrc.js b/.detoxrc.js index efcacc30b6c..0b5b837895f 100644 --- a/.detoxrc.js +++ b/.detoxrc.js @@ -9,7 +9,7 @@ module.exports = { devices: { 'ios.simulator': { type: 'ios.simulator', - device: { type: 'iPhone 15 Pro' }, + device: { type: 'iPhone 16 Pro' }, }, 'android.attached': { type: 'android.attached', diff --git a/.github/workflows/macstadium-builds.yml b/.github/workflows/macstadium-builds.yml index eb66d59324a..4de26e67e9b 100644 --- a/.github/workflows/macstadium-builds.yml +++ b/.github/workflows/macstadium-builds.yml @@ -45,7 +45,8 @@ jobs: ${{ steps.yarn-cache-dir-path.outputs.dir }} .yarn/cache .yarn/install-state.gz - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + !.eslintcache # Exclude eslint cache + key: ${{ runner.os }}-yarn-${{ github.sha }} # Cache per commit restore-keys: | ${{ runner.os }}-yarn- diff --git a/.github/workflows/macstadium-tests.yml b/.github/workflows/macstadium-tests.yml index b06e42ae654..237d1614507 100644 --- a/.github/workflows/macstadium-tests.yml +++ b/.github/workflows/macstadium-tests.yml @@ -18,7 +18,7 @@ jobs: run: git config core.sshCommand "ssh -i ~/.ssh/id_ed25519 -F /dev/null" - name: Clean iOS app - run: yarn clean:ios > /dev/null 2>&1 || true + run: yarn cache clean && yarn clean:ios > /dev/null 2>&1 || true - name: Set up ENV vars & scripts env: @@ -40,7 +40,8 @@ jobs: ${{ steps.yarn-cache-dir-path.outputs.dir }} .yarn/cache .yarn/install-state.gz - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + !.eslintcache # Exclude eslint cache + key: ${{ runner.os }}-yarn-${{ github.sha }} # Cache per commit restore-keys: | ${{ runner.os }}-yarn- @@ -49,7 +50,7 @@ jobs: yarn install && yarn setup - name: Upload Yarn cache - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: yarn-cache path: | @@ -62,7 +63,7 @@ jobs: needs: install-deps steps: - name: Download Yarn cache - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: yarn-cache path: .yarn @@ -73,6 +74,9 @@ jobs: - name: Audit CI run: yarn audit-ci --config audit-ci.jsonc + - name: Remove ESLint cache + run: rm -f .eslintcache + - name: Lint run: yarn lint:ci @@ -86,7 +90,7 @@ jobs: needs: install-deps steps: - name: Download Yarn cache - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: yarn-cache path: .yarn diff --git a/e2e/9_swaps.spec.ts b/e2e/9_swaps.spec.ts index a9d4a5577d1..67d866e2326 100644 --- a/e2e/9_swaps.spec.ts +++ b/e2e/9_swaps.spec.ts @@ -62,9 +62,7 @@ describe('Swap Sheet Interaction Flow', () => { await tap('swap-button'); await delayTime('very-long'); - // flaky - // await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100); - await swipe('token-to-buy-list', 'up', 'slow', 0.2); + await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100); await tap('token-to-buy-dai-1'); await delayTime('very-long'); diff --git a/e2e/helpers.ts b/e2e/helpers.ts index 7ecb9811fc3..441a9d0144d 100644 --- a/e2e/helpers.ts +++ b/e2e/helpers.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable no-await-in-loop */ import { exec } from 'child_process'; import { JsonRpcProvider } from '@ethersproject/providers'; @@ -201,6 +202,7 @@ export async function clearField(elementId: string | RegExp) { export async function tapAndLongPress(elementId: string | RegExp, duration?: number) { try { + // @ts-expect-error return await element(by.id(elementId)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by id "${elementId}": ${error}`); @@ -209,6 +211,7 @@ export async function tapAndLongPress(elementId: string | RegExp, duration?: num export async function tapAndLongPressByText(text: string | RegExp, duration?: number) { try { + // @ts-expect-error return await element(by.text(text)).longPress(duration); } catch (error) { throw new Error(`Error long-pressing element by text "${text}": ${error}`); diff --git a/package.json b/package.json index 26530053395..f9073f9ca43 100644 --- a/package.json +++ b/package.json @@ -45,8 +45,9 @@ "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", - "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js", + "lint:ci": "yarn format:check && yarn lint:ts && yarn lint:js:ci", "lint:js": "eslint --cache . --quiet", + "lint:js:ci": "eslint --quiet", "lint:ts": "yarn tsc --skipLibCheck --noEmit", "postinstall": "./scripts/postinstall.sh", "start": "react-native start", @@ -132,7 +133,6 @@ "@tanstack/react-query": "4.2.1", "@tanstack/react-query-persist-client": "4.2.1", "@tradle/react-native-http": "2.0.1", - "@types/detox": "18.1.0", "@types/i18n-js": "3.0.3", "@types/lodash": "4.14.168", "@types/react-redux": "7.1.9", @@ -354,7 +354,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.2.0", "depcheck": "1.4.7", - "detox": "20.19.3", + "detox": "20.26.2", "dotenv": "8.2.0", "eslint": "8.22.0", "eslint-config-rainbow": "4.3.0", diff --git a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx index 503764f05ef..aa89ce65023 100644 --- a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx @@ -143,5 +143,4 @@ export const SearchInputButton = ({ ); - }; diff --git a/yarn.lock b/yarn.lock index 2c6c4d02922..34a83377e27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6268,15 +6268,6 @@ __metadata: languageName: node linkType: hard -"@types/detox@npm:18.1.0": - version: 18.1.0 - resolution: "@types/detox@npm:18.1.0" - dependencies: - detox: "npm:*" - checksum: 10c0/bcee8734aeaf5ee3460e8236d606366a8b78816b1df46b6b41767efb62fc857cdefe457ecbbd866e036e447a264091b743a35fff5a08c25b6a4aac7e454673c9 - languageName: node - linkType: hard - "@types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" @@ -7928,7 +7919,6 @@ __metadata: "@types/d3-scale": "npm:4.0.2" "@types/d3-shape": "npm:3.1.6" "@types/dedent": "npm:0.7.0" - "@types/detox": "npm:18.1.0" "@types/i18n-js": "npm:3.0.3" "@types/jest": "npm:29.5.12" "@types/lodash": "npm:4.14.168" @@ -7984,7 +7974,7 @@ __metadata: delay: "npm:4.4.0" depcheck: "npm:1.4.7" deprecated-react-native-prop-types: "npm:2.2.0" - detox: "npm:20.19.3" + detox: "npm:20.26.2" dns.js: "npm:1.0.1" domain-browser: "npm:1.2.0" dotenv: "npm:8.2.0" @@ -11849,59 +11839,16 @@ __metadata: languageName: node linkType: hard -"detox@npm:*": - version: 20.25.1 - resolution: "detox@npm:20.25.1" - dependencies: - ajv: "npm:^8.6.3" - bunyan: "npm:^1.8.12" - bunyan-debug-stream: "npm:^3.1.0" - caf: "npm:^15.0.1" - chalk: "npm:^4.0.0" - child-process-promise: "npm:^2.2.0" - execa: "npm:^5.1.1" - find-up: "npm:^5.0.0" - fs-extra: "npm:^11.0.0" - funpermaproxy: "npm:^1.1.0" - glob: "npm:^8.0.3" - ini: "npm:^1.3.4" - jest-environment-emit: "npm:^1.0.8" - json-cycle: "npm:^1.3.0" - lodash: "npm:^4.17.11" - multi-sort-stream: "npm:^1.0.3" - multipipe: "npm:^4.0.0" - node-ipc: "npm:9.2.1" - proper-lockfile: "npm:^3.0.2" - resolve-from: "npm:^5.0.0" - sanitize-filename: "npm:^1.6.1" - semver: "npm:^7.0.0" - serialize-error: "npm:^8.0.1" - shell-quote: "npm:^1.7.2" - signal-exit: "npm:^3.0.3" - stream-json: "npm:^1.7.4" - strip-ansi: "npm:^6.0.1" - telnet-client: "npm:1.2.8" - tempfile: "npm:^2.0.0" - trace-event-lib: "npm:^1.3.1" - which: "npm:^1.3.1" - ws: "npm:^7.0.0" - yargs: "npm:^17.0.0" - yargs-parser: "npm:^21.0.0" - yargs-unparser: "npm:^2.0.0" - peerDependencies: - jest: 29.x.x || 28.x.x || ^27.2.5 - peerDependenciesMeta: - jest: - optional: true - bin: - detox: local-cli/cli.js - checksum: 10c0/fc84829d0e4a2b0d7c4c325949eae0915956df9642fa2365065e0a4275e9da7d2368bd5b31543ba5750ab27461ca84a66331ddecce5504c57fec85ac300e21e4 +"detox-copilot@npm:^0.0.0": + version: 0.0.0 + resolution: "detox-copilot@npm:0.0.0" + checksum: 10c0/9e29a017aa9a029f1bf235959c63f271612c20bfb55d254d6f3f88d62dc3c56479da7f0c3937484df70dac661ebd8f55572f3c148bedcb7ade464e02e6089f47 languageName: node linkType: hard -"detox@npm:20.19.3": - version: 20.19.3 - resolution: "detox@npm:20.19.3" +"detox@npm:20.26.2": + version: 20.26.2 + resolution: "detox@npm:20.26.2" dependencies: ajv: "npm:^8.6.3" bunyan: "npm:^1.8.12" @@ -11909,13 +11856,14 @@ __metadata: caf: "npm:^15.0.1" chalk: "npm:^4.0.0" child-process-promise: "npm:^2.2.0" + detox-copilot: "npm:^0.0.0" execa: "npm:^5.1.1" find-up: "npm:^5.0.0" fs-extra: "npm:^11.0.0" funpermaproxy: "npm:^1.1.0" glob: "npm:^8.0.3" ini: "npm:^1.3.4" - jest-environment-emit: "npm:^1.0.5" + jest-environment-emit: "npm:^1.0.8" json-cycle: "npm:^1.3.0" lodash: "npm:^4.17.11" multi-sort-stream: "npm:^1.0.3" @@ -11945,7 +11893,7 @@ __metadata: optional: true bin: detox: local-cli/cli.js - checksum: 10c0/69cd50f7efc62232a428b0955d2d79672e0cb49e66588507c01b4d63b0c4623a9911051f19aaf303321d15a45ac1a003f33650df473ca673e54ff0a13fbe3054 + checksum: 10c0/7601ed9678447c4d11e8c5ca5791a59d8a099bf53190aa3e224dd4ffe0b68af22ddefcbea618448ddf866a36c4ab9c2067225e7443cb2e95437ffdd2fc7738ba languageName: node linkType: hard @@ -16200,7 +16148,7 @@ __metadata: languageName: node linkType: hard -"jest-environment-emit@npm:^1.0.5, jest-environment-emit@npm:^1.0.8": +"jest-environment-emit@npm:^1.0.8": version: 1.0.8 resolution: "jest-environment-emit@npm:1.0.8" dependencies: From 6270098b23d34813299030ab601c30d406edde26 Mon Sep 17 00:00:00 2001 From: gregs Date: Tue, 24 Sep 2024 00:12:23 -0300 Subject: [PATCH 25/64] omit_from_total (#6103) --- src/resources/defi/types.ts | 3 +++ src/resources/defi/utils.ts | 51 +++++++++++++++---------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/resources/defi/types.ts b/src/resources/defi/types.ts index d2a50bc57dd..eac4fb62ea0 100644 --- a/src/resources/defi/types.ts +++ b/src/resources/defi/types.ts @@ -40,6 +40,7 @@ export type PositionsTotals = { export type Claimable = { asset: PositionAsset; quantity: string; + omit_from_total?: boolean; }; export type Deposit = { apr: string; @@ -47,6 +48,7 @@ export type Deposit = { asset: PositionAsset; quantity: string; total_asset: string; // what does this mean? + omit_from_total?: boolean; underlying: { asset: PositionAsset; quantity: string }[]; }; export type Borrow = { @@ -55,6 +57,7 @@ export type Borrow = { asset: PositionAsset; quantity: string; total_asset: string; // what does this mean? + omit_from_total?: boolean; underlying: { asset: PositionAsset; quantity: string }[]; }; diff --git a/src/resources/defi/utils.ts b/src/resources/defi/utils.ts index 436d7a20f6e..3adc0a45c2e 100644 --- a/src/resources/defi/utils.ts +++ b/src/resources/defi/utils.ts @@ -3,10 +3,7 @@ import { AddysPositionsResponse, Borrow, Claimable, - Deposit, - NativeDisplay, Position, - PositionAsset, PositionsTotals, RainbowBorrow, RainbowClaimable, @@ -21,66 +18,58 @@ import { chainsIdByName } from '@/chains'; export const parsePosition = (position: Position, currency: NativeCurrencyKey): RainbowPosition => { let totalDeposits = '0'; - const parsedDeposits = position.deposits?.map((deposit: Deposit): RainbowDeposit => { - deposit.underlying = deposit.underlying?.map( - (underlying: { - asset: PositionAsset; - quantity: string; - }): { - asset: PositionAsset; - quantity: string; - native: NativeDisplay; - } => { + const parsedDeposits = position.deposits?.map((deposit): RainbowDeposit => { + return { + ...deposit, + underlying: deposit.underlying?.map(underlying => { const nativeDisplay = convertRawAmountToNativeDisplay( underlying.quantity, underlying.asset.decimals, underlying.asset.price?.value!, currency ); - totalDeposits = add(totalDeposits, nativeDisplay.amount); + if (!deposit.omit_from_total) { + totalDeposits = add(totalDeposits, nativeDisplay.amount); + } return { ...underlying, native: nativeDisplay, }; - } - ); - return deposit as RainbowDeposit; + }), + }; }); let totalBorrows = '0'; const parsedBorrows = position.borrows?.map((borrow: Borrow): RainbowBorrow => { - borrow.underlying = borrow.underlying.map( - (underlying: { - asset: PositionAsset; - quantity: string; - }): { - asset: PositionAsset; - quantity: string; - native: NativeDisplay; - } => { + return { + ...borrow, + underlying: borrow.underlying.map(underlying => { const nativeDisplay = convertRawAmountToNativeDisplay( underlying.quantity, underlying.asset.decimals, underlying.asset.price?.value!, currency ); - totalBorrows = add(totalBorrows, nativeDisplay.amount); + if (!borrow.omit_from_total) { + totalBorrows = add(totalBorrows, nativeDisplay.amount); + } return { ...underlying, native: nativeDisplay, }; - } - ); - return borrow as RainbowBorrow; + }), + }; }); let totalClaimables = '0'; const parsedClaimables = position.claimables?.map((claim: Claimable): RainbowClaimable => { const nativeDisplay = convertRawAmountToNativeDisplay(claim.quantity, claim.asset.decimals, claim.asset.price?.value!, currency); - totalClaimables = add(totalClaimables, nativeDisplay.amount); + if (!claim.omit_from_total) { + totalClaimables = add(totalClaimables, nativeDisplay.amount); + } return { asset: claim.asset, quantity: claim.quantity, From bfc63088a5ed52bd6bcd5b576aa08d7f774c2b2d Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 24 Sep 2024 10:04:11 -0400 Subject: [PATCH 26/64] Fix speed up and cancel bug (#6133) * change txTo -> to * idk --- src/screens/SpeedUpAndCancelSheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/SpeedUpAndCancelSheet.js b/src/screens/SpeedUpAndCancelSheet.js index 00e6c71699a..bbbf1766df1 100644 --- a/src/screens/SpeedUpAndCancelSheet.js +++ b/src/screens/SpeedUpAndCancelSheet.js @@ -336,7 +336,7 @@ export default function SpeedUpAndCancelSheet() { setNonce(tx.nonce); setValue(hexValue); setData(hexData); - setTo(tx.txTo); + setTo(tx.to); setGasLimit(hexGasLimit); if (!isL2) { setTxType(GasFeeTypes.eip1559); From d80993125893088382d6c5e8d86670f231590ec3 Mon Sep 17 00:00:00 2001 From: Ibrahim Taveras Date: Tue, 24 Sep 2024 12:54:29 -0400 Subject: [PATCH 27/64] bump iOS and Android to v1.9.41 (#6136) --- CHANGELOG.md | 8 +++++++- android/app/build.gradle | 4 ++-- ios/Rainbow.xcodeproj/project.pbxproj | 8 ++++---- package.json | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7a13738bb..9446638f981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.40] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.40) + +### Fixed + +- Fixed a bug with speed up and cancel (#6133) + ## [1.9.39] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.39) ### Added @@ -48,7 +54,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/) - Fixed an issue where deleting a contact would cause loading issues on send flow (#6119) - Fixed a bug where chainId wasn’t being passed in the dapp browser (#6121) -## [1.9.38] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.36) +## [1.9.38] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.38) ### Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index 7f6d6b97cbd..a6572fd6ec5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -130,8 +130,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 232 - versionName "1.9.40" + versionCode 233 + versionName "1.9.41" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 2fb347cef59..abcaf71efa3 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1836,7 +1836,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.40; + MARKETING_VERSION = 1.9.41; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1900,7 +1900,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.40; + MARKETING_VERSION = 1.9.41; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2017,7 +2017,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.40; + MARKETING_VERSION = 1.9.41; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2133,7 +2133,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.40; + MARKETING_VERSION = 1.9.41; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index f9073f9ca43..8c11df2b871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.40-1", + "version": "1.9.41-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen && yarn fetch:networks", From 07010ef7d880ffff55911958e39de2f5db5f664e Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 25 Sep 2024 20:59:13 -0400 Subject: [PATCH 28/64] MWP (#6142) * mwp compat * prettier * log only if there is an error --- CHANGELOG.md | 2 +- src/model/wallet.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9446638f981..80f5b25a2b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ## [1.9.40] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.40) -### Fixed +### Fixed - Fixed a bug with speed up and cancel (#6133) diff --git a/src/model/wallet.ts b/src/model/wallet.ts index 64f7b31232e..3c3b61200ce 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -1006,13 +1006,22 @@ export const getPrivateKey = async ( lang.t('wallet.authenticate.alert.current_authentication_not_secure_enough') ); return kc.ErrorType.NotAuthenticated; - case kc.ErrorType.Unavailable: + case kc.ErrorType.Unavailable: { + // Retry with checksummed address if needed + // (This is to mimic the behavior of other wallets like CB) + const checksumAddress = toChecksumAddress(address); + if (address !== checksumAddress) { + return getPrivateKey(checksumAddress); + } // This means we couldn't find any matches for this key. logger.error(new RainbowError('KC unavailable for PKEY lookup'), { error }); break; + } default: // This is an unknown error - logger.error(new RainbowError('KC unknown error for PKEY lookup'), { error }); + if (error) { + logger.error(new RainbowError('KC unknown error for PKEY lookup'), { error }); + } break; } return pkey || null; From 7f820289fc473cd9b84d4672417820ed1b6c1e23 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:05:41 -0400 Subject: [PATCH 29/64] Dapp browser url updates fix (#6150) * remove unused import * restrict JS nav from SPA to same domain * clean up * Update src/components/DappBrowser/BrowserTab.tsx --- src/components/DappBrowser/BrowserTab.tsx | 30 +++++-------------- .../DappBrowser/handleProviderRequest.ts | 2 -- 2 files changed, 7 insertions(+), 25 deletions(-) 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 = ({ Date: Fri, 27 Sep 2024 13:42:55 -0400 Subject: [PATCH 30/64] Internal networks filtering (#6148) * filter internal networks out * rm apechains refs --- scripts/networks.js | 1 + src/chains/types.ts | 1 + src/chains/utils/backendNetworks.ts | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) 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/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)); } From 0d07edec839b7343a9552cc36b63a82aada8d563 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 30 Sep 2024 12:19:55 -0400 Subject: [PATCH 31/64] Fix token search crash for newly added chains (#6147) * fix token search crash * rm assets --- src/utils/ethereumUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index fa18c85fd9e..45272e37d94 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -278,7 +278,7 @@ const getDataString = (func: string, arrVals: string[]) => { */ 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 }) { From b5588cb467a3be6f6c9aecb6d74c291c7d10814e Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Mon, 30 Sep 2024 12:47:25 -0400 Subject: [PATCH 32/64] Claimables [PR #1]: updates to query, types, utils, wallet screen rendering logic + wallet screen components (#6140) * updates to query, types, & wallet screen components * rm onPress * thanks greg --- .../RecyclerAssetList2/Claimable.tsx | 9 ++- .../ClaimablesListHeader.tsx | 10 +-- .../RecyclerAssetList2/core/RowRenderer.tsx | 6 +- src/helpers/buildWalletSections.tsx | 32 +++++---- src/hooks/useWalletSectionsData.ts | 8 +++ src/resources/addys/claimables/query.ts | 8 ++- src/resources/addys/claimables/types.ts | 72 ++++++++++++++++--- src/resources/addys/claimables/utils.ts | 53 +++++++++++--- src/resources/defi/PositionsQuery.ts | 8 ++- src/screens/WalletScreen/index.tsx | 6 +- 10 files changed, 158 insertions(+), 54 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx index d543fb635ab..afc758e7531 100644 --- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx +++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx @@ -5,9 +5,13 @@ import { useClaimables } from '@/resources/addys/claimables/query'; import { FasterImageView } from '@candlefinance/faster-image'; import { ButtonPressAnimation } from '@/components/animations'; import { deviceUtils } from '@/utils'; +import Routes from '@/navigation/routesNames'; +import { ExtendedState } from './core/RawRecyclerList'; -export default function Claimable({ uniqueId }: { uniqueId: string }) { +export const Claimable = React.memo(function Claimable({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) { const { accountAddress, nativeCurrency } = useAccountSettings(); + const { navigate } = extendedState; + const { data = [] } = useClaimables( { address: accountAddress, @@ -25,6 +29,7 @@ export default function Claimable({ uniqueId }: { uniqueId: string }) { return ( navigate(Routes.CLAIM_CLAIMABLE_PANEL, { claimable })} scaleTo={0.96} paddingHorizontal="20px" justifyContent="space-between" @@ -68,4 +73,4 @@ export default function Claimable({ uniqueId }: { uniqueId: string }) { ); -} +}); diff --git a/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx b/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx index 964a58af799..c23cfc6cde9 100644 --- a/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/ClaimablesListHeader.tsx @@ -12,7 +12,7 @@ const AnimatedImgixImage = Animated.createAnimatedComponent(Image); const TokenFamilyHeaderAnimationDuration = 200; const TokenFamilyHeaderHeight = 48; -const ClaimablesListHeader = ({ total }: { total: string }) => { +export const ClaimablesListHeader = React.memo(function ClaimablesListHeader({ total }: { total: string }) { const { colors } = useTheme(); const { isClaimablesOpen, toggleOpenClaimables } = useOpenClaimables(); @@ -84,10 +84,4 @@ const ClaimablesListHeader = ({ total }: { total: string }) => { ); -}; - -ClaimablesListHeader.animationDuration = TokenFamilyHeaderAnimationDuration; - -ClaimablesListHeader.height = TokenFamilyHeaderHeight; - -export default ClaimablesListHeader; +}); diff --git a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx index b5805584840..7e14631d279 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RowRenderer.tsx @@ -35,8 +35,8 @@ import { RemoteCardCarousel } from '@/components/cards/remote-cards'; import WrappedCollectiblesHeader from '../WrappedCollectiblesHeader'; import NFTLoadingSkeleton from '../NFTLoadingSkeleton'; import { NFTEmptyState } from '../NFTEmptyState'; -import Claimable from '../Claimable'; -import ClaimablesListHeader from '../ClaimablesListHeader'; +import { ClaimablesListHeader } from '../ClaimablesListHeader'; +import { Claimable } from '../Claimable'; function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, extendedState: ExtendedState) { const data = extendedState.additionalData[uid]; @@ -175,7 +175,7 @@ function rowRenderer(type: CellType, { uid }: { uid: string }, _: unknown, exten case CellType.CLAIMABLE: { const { uniqueId } = data as ClaimableExtraData; - return ; + return ; } case CellType.LOADING_ASSETS: diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx index a612150f20d..833b154fad7 100644 --- a/src/helpers/buildWalletSections.tsx +++ b/src/helpers/buildWalletSections.tsx @@ -1,13 +1,10 @@ import { createSelector } from 'reselect'; import { buildBriefCoinsList, buildBriefUniqueTokenList } from './assets'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; -import { queryClient } from '@/react-query'; -import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; import store from '@/redux/store'; import { ClaimableExtraData, PositionExtraData } from '@/components/asset-list/RecyclerAssetList2/core/ViewTypes'; import { DEFI_POSITIONS, CLAIMABLES, ExperimentalValue } from '@/config/experimental'; import { RainbowPositions } from '@/resources/defi/types'; -import { claimablesQueryKey } from '@/resources/addys/claimables/query'; import { Claimable } from '@/resources/addys/claimables/types'; import { add, convertAmountToNativeDisplay } from './utilities'; import { RainbowConfig } from '@/model/remoteConfig'; @@ -60,20 +57,24 @@ const isFetchingNftsSelector = (state: any) => state.isFetchingNfts; const listTypeSelector = (state: any) => state.listType; const remoteConfigSelector = (state: any) => state.remoteConfig; const experimentalConfigSelector = (state: any) => state.experimentalConfig; +const positionsSelector = (state: any) => state.positions; +const claimablesSelector = (state: any) => state.claimables; const buildBriefWalletSections = ( balanceSectionData: any, uniqueTokenFamiliesSection: any, remoteConfig: RainbowConfig, - experimentalConfig: Record + experimentalConfig: Record, + positions: RainbowPositions | undefined, + claimables: Claimable[] | undefined ) => { const { balanceSection, isEmpty, isLoadingUserAssets } = balanceSectionData; const positionsEnabled = experimentalConfig[DEFI_POSITIONS] && !IS_TEST; const claimablesEnabled = (remoteConfig.claimables || experimentalConfig[CLAIMABLES]) && !IS_TEST; - const positionSection = positionsEnabled ? withPositionsSection(isLoadingUserAssets) : []; - const claimablesSection = claimablesEnabled ? withClaimablesSection(isLoadingUserAssets) : []; + const positionSection = positionsEnabled ? withPositionsSection(positions, isLoadingUserAssets) : []; + const claimablesSection = claimablesEnabled ? withClaimablesSection(claimables, isLoadingUserAssets) : []; const sections = [balanceSection, claimablesSection, positionSection, uniqueTokenFamiliesSection]; const filteredSections = sections.filter(section => section.length !== 0).flat(1); @@ -84,10 +85,7 @@ const buildBriefWalletSections = ( }; }; -const withPositionsSection = (isLoadingUserAssets: boolean) => { - const { accountAddress: address, nativeCurrency: currency } = store.getState().settings; - const positionsObj: RainbowPositions | undefined = queryClient.getQueryData(positionsQueryKey({ address, currency })); - +const withPositionsSection = (positionsObj: RainbowPositions | undefined, isLoadingUserAssets: boolean) => { const result: PositionExtraData[] = []; const sortedPositions = positionsObj?.positions?.sort((a, b) => (a.totals.totals.amount > b.totals.totals.amount ? -1 : 1)); sortedPositions?.forEach((position, index) => { @@ -118,9 +116,8 @@ const withPositionsSection = (isLoadingUserAssets: boolean) => { return []; }; -const withClaimablesSection = (isLoadingUserAssets: boolean) => { - const { accountAddress: address, nativeCurrency: currency } = store.getState().settings; - const claimables: Claimable[] | undefined = queryClient.getQueryData(claimablesQueryKey({ address, currency })); +const withClaimablesSection = (claimables: Claimable[] | undefined, isLoadingUserAssets: boolean) => { + const { nativeCurrency: currency } = store.getState().settings; const result: ClaimableExtraData[] = []; let totalNativeValue = '0'; @@ -285,6 +282,13 @@ const briefBalanceSectionSelector = createSelector( ); export const buildBriefWalletSectionsSelector = createSelector( - [briefBalanceSectionSelector, (state: any) => briefUniqueTokenDataSelector(state), remoteConfigSelector, experimentalConfigSelector], + [ + briefBalanceSectionSelector, + (state: any) => briefUniqueTokenDataSelector(state), + remoteConfigSelector, + experimentalConfigSelector, + positionsSelector, + claimablesSelector, + ], buildBriefWalletSections ); diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 98d0c594e0d..42a30f464b1 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -14,6 +14,8 @@ import { useLegacyNFTs } from '@/resources/nfts'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; import { useRemoteConfig } from '@/model/remoteConfig'; import { RainbowContext } from '@/helpers/RainbowContext'; +import { usePositions } from '@/resources/defi/PositionsQuery'; +import { useClaimables } from '@/resources/addys/claimables/query'; export default function useWalletSectionsData({ type, @@ -36,6 +38,8 @@ export default function useWalletSectionsData({ sortBy: nftSort, sortDirection: nftSortDirection, }); + const { data: positions } = usePositions({ address: accountAddress, currency: nativeCurrency }); + const { data: claimables } = useClaimables({ address: accountAddress, currency: nativeCurrency }); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); @@ -77,6 +81,8 @@ export default function useWalletSectionsData({ nftSort, remoteConfig, experimentalConfig, + positions, + claimables, }; const { briefSectionsData, isEmpty } = buildBriefWalletSectionsSelector(accountInfo); @@ -111,6 +117,8 @@ export default function useWalletSectionsData({ nftSort, remoteConfig, experimentalConfig, + positions, + claimables, ]); return walletSections; } diff --git a/src/resources/addys/claimables/query.ts b/src/resources/addys/claimables/query.ts index 10aaa2bfa16..137a5846caa 100644 --- a/src/resources/addys/claimables/query.ts +++ b/src/resources/addys/claimables/query.ts @@ -11,8 +11,10 @@ import { CLAIMABLES, useExperimentalFlag } from '@/config'; import { IS_TEST } from '@/env'; import { SUPPORTED_CHAIN_IDS } from '@/chains'; -const addysHttp = new RainbowFetchClient({ - baseURL: 'https://addys.p.rainbow.me/v3', +export const ADDYS_BASE_URL = 'https://addys.p.rainbow.me/v3'; + +export const addysHttp = new RainbowFetchClient({ + baseURL: ADDYS_BASE_URL, headers: { Authorization: `Bearer ${ADDYS_API_KEY}`, }, @@ -30,7 +32,7 @@ export type ClaimablesArgs = { // Query Key export const claimablesQueryKey = ({ address, currency }: ClaimablesArgs) => - createQueryKey('claimables', { address, currency }, { persisterVersion: 1 }); + createQueryKey('claimables', { address, currency }, { persisterVersion: 2 }); type ClaimablesQueryKey = ReturnType; diff --git a/src/resources/addys/claimables/types.ts b/src/resources/addys/claimables/types.ts index 814a4bfc869..28538833b69 100644 --- a/src/resources/addys/claimables/types.ts +++ b/src/resources/addys/claimables/types.ts @@ -1,6 +1,6 @@ -import { ChainId } from '@rainbow-me/swaps'; import { Address } from 'viem'; import { AddysAsset, AddysConsolidatedError, AddysResponseStatus } from '../types'; +import { ChainId } from '@/chains/types'; interface Colors { primary: string; @@ -28,20 +28,33 @@ interface DApp { colors: Colors; } -type ClaimableType = 'transaction' | 'sponsored'; - -export interface AddysClaimable { +interface AddysBaseClaimable { name: string; unique_id: string; - type: ClaimableType; + type: string; network: ChainId; asset: AddysAsset; amount: string; dapp: DApp; - claim_action_type?: string | null; +} + +interface AddysTransactionClaimable extends AddysBaseClaimable { + claim_action_type: 'transaction'; + claim_action: ClaimActionTransaction[]; +} + +interface AddysSponsoredClaimable extends AddysBaseClaimable { + claim_action_type: 'sponsored'; + claim_action: ClaimActionSponsored[]; +} + +interface AddysUnsupportedClaimable extends AddysBaseClaimable { + claim_action_type?: 'unknown' | null; claim_action?: ClaimAction[]; } +export type AddysClaimable = AddysTransactionClaimable | AddysSponsoredClaimable | AddysUnsupportedClaimable; + interface ConsolidatedClaimablesPayloadResponse { claimables: AddysClaimable[]; } @@ -61,8 +74,13 @@ export interface ConsolidatedClaimablesResponse { payload: ConsolidatedClaimablesPayloadResponse; } -// will add more attributes as needed -export interface Claimable { +interface BaseClaimable { + asset: { + iconUrl: string; + name: string; + symbol: string; + }; + chainId: ChainId; name: string; uniqueId: string; iconUrl: string; @@ -71,3 +89,41 @@ export interface Claimable { nativeAsset: { amount: string; display: string }; }; } + +export interface TransactionClaimable extends BaseClaimable { + type: 'transaction'; + action: { to: Address; data: string }; +} + +export interface SponsoredClaimable extends BaseClaimable { + type: 'sponsored'; + action: { url: string; method: string }; +} + +export type Claimable = TransactionClaimable | SponsoredClaimable; + +interface ClaimTransactionStatus { + network: ChainId; + transaction_hash: string; + explorer_url: string; + sponsored_status: string; +} + +interface ClaimPayloadResponse { + success: boolean; + claimable: Claimable | null; + claim_transaction_status: ClaimTransactionStatus | null; +} + +interface ClaimMetadataResponse { + address: string; + chain_id: ChainId; + currency: string; + claim_type: string; + error: string; +} + +export interface ClaimResponse { + metadata: ClaimMetadataResponse; + payload: ClaimPayloadResponse; +} diff --git a/src/resources/addys/claimables/utils.ts b/src/resources/addys/claimables/utils.ts index 30b0395cc87..71b6122a3f1 100644 --- a/src/resources/addys/claimables/utils.ts +++ b/src/resources/addys/claimables/utils.ts @@ -1,17 +1,50 @@ import { NativeCurrencyKey } from '@/entities'; import { AddysClaimable, Claimable } from './types'; -import { convertRawAmountToBalance, convertRawAmountToNativeDisplay, greaterThan, lessThan } from '@/helpers/utilities'; +import { convertRawAmountToBalance, convertRawAmountToNativeDisplay, greaterThan } from '@/helpers/utilities'; export const parseClaimables = (claimables: AddysClaimable[], currency: NativeCurrencyKey): Claimable[] => { return claimables - .map(claimable => ({ - name: claimable.name, - uniqueId: claimable.unique_id, - iconUrl: claimable.dapp.icon_url, - value: { - claimAsset: convertRawAmountToBalance(claimable.amount, claimable.asset), - nativeAsset: convertRawAmountToNativeDisplay(claimable.amount, claimable.asset.decimals, claimable.asset.price.value, currency), - }, - })) + .map(claimable => { + if ( + !(claimable.claim_action_type === 'transaction' || claimable.claim_action_type === 'sponsored') || + !claimable.claim_action?.length + ) { + return undefined; + } + + const baseClaimable = { + asset: { + iconUrl: claimable.asset.icon_url, + name: claimable.asset.name, + symbol: claimable.asset.symbol, + }, + chainId: claimable.network, + name: claimable.name, + uniqueId: claimable.unique_id, + iconUrl: claimable.dapp.icon_url, + value: { + claimAsset: convertRawAmountToBalance(claimable.amount, claimable.asset), + nativeAsset: convertRawAmountToNativeDisplay(claimable.amount, claimable.asset.decimals, claimable.asset.price.value, currency), + }, + }; + + if (claimable.claim_action_type === 'transaction') { + return { + ...baseClaimable, + type: 'transaction' as const, + action: { + to: claimable.claim_action[0].address_to, + data: claimable.claim_action[0].calldata, + }, + }; + } else if (claimable.claim_action_type === 'sponsored') { + return { + ...baseClaimable, + type: 'sponsored' as const, + action: { method: claimable.claim_action[0].method, url: claimable.claim_action[0].url }, + }; + } + }) + .filter((c): c is Claimable => !!c) .sort((a, b) => (greaterThan(a.value.claimAsset.amount ?? '0', b.value.claimAsset.amount ?? '0') ? -1 : 1)); }; diff --git a/src/resources/defi/PositionsQuery.ts b/src/resources/defi/PositionsQuery.ts index 46e935d00ed..0bd90cffb14 100644 --- a/src/resources/defi/PositionsQuery.ts +++ b/src/resources/defi/PositionsQuery.ts @@ -8,6 +8,8 @@ import { ADDYS_API_KEY } from 'react-native-dotenv'; import { AddysPositionsResponse, PositionsArgs } from './types'; import { parsePositions } from './utils'; import { SUPPORTED_CHAIN_IDS } from '@/chains'; +import { DEFI_POSITIONS, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; export const buildPositionsUrl = (address: string) => { const networkString = SUPPORTED_CHAIN_IDS.join(','); @@ -77,5 +79,9 @@ export async function fetchPositions( // Query Hook export function usePositions({ address, currency }: PositionsArgs, config: QueryConfig = {}) { - return useQuery(positionsQueryKey({ address, currency }), positionsQueryFunction, { ...config, enabled: !!address }); + const positionsEnabled = useExperimentalFlag(DEFI_POSITIONS); + return useQuery(positionsQueryKey({ address, currency }), positionsQueryFunction, { + ...config, + enabled: !!(address && positionsEnabled && !IS_TEST), + }); } diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 7b00c9e94e7..391dae64608 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -22,13 +22,11 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { analyticsV2 } from '@/analytics'; import { AppState } from '@/redux/store'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; -import { usePositions } from '@/resources/defi/PositionsQuery'; import styled from '@/styled-thing'; import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; -import { useClaimables } from '@/resources/addys/claimables/query'; import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; @@ -44,9 +42,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { const removeFirst = useRemoveFirst(); const [initialized, setInitialized] = useState(!!params?.initialized); const initializeWallet = useInitializeWallet(); - const { network: currentNetwork, accountAddress, appIcon, nativeCurrency } = useAccountSettings(); - usePositions({ address: accountAddress, currency: nativeCurrency }); - useClaimables({ address: accountAddress, currency: nativeCurrency }); + const { network: currentNetwork, accountAddress, appIcon } = useAccountSettings(); const loadAccountLateData = useLoadAccountLateData(); const loadGlobalLateData = useLoadGlobalLateData(); From caedd4632890e7eaea49c6dbd78ab1b536840604 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 30 Sep 2024 13:12:48 -0400 Subject: [PATCH 33/64] Fix improper gas fee calculation (#6125) * add missing mul to eip1559 * use preferred network if available * another NaN check guard --- src/__swaps__/screens/Swap/Swap.tsx | 5 +-- .../screens/Swap/components/GasButton.tsx | 6 ++-- .../screens/Swap/hooks/useEstimatedGasFee.ts | 34 ++++++------------- .../Swap/hooks/useNativeAssetForChain.ts | 3 +- .../SyncSwapStateAndSharedValues.tsx | 24 ++++++------- 5 files changed, 31 insertions(+), 41 deletions(-) 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 74814aedeab..384e3e2f232 100644 --- a/src/__swaps__/screens/Swap/components/GasButton.tsx +++ b/src/__swaps__/screens/Swap/components/GasButton.tsx @@ -57,7 +57,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 ( @@ -107,7 +108,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; } From 3d01175d8c1245a798abbaf4c28ef957411f8e93 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 1 Oct 2024 08:26:19 -0400 Subject: [PATCH 34/64] Claimables [PR #2]: claim panel ui (#6141) * raps * nit * comments * nit * nit * prettier * updates to query, types, & wallet screen components * rm onPress * rm logic * claim panel ui * nav * onPress -> onLongPress * i18n * pending state, i18n * comment * shimmer * tweak gas fee formatting * number formatting * wallet screen number format * route types * do not require long press for goBack * tweak button text * nit * formatting * thanks greg * revert * fix android button width * rm memo * try catch gas * rm functionality from tx * rm rapsv2 --- .../RecyclerAssetList2/Claimable.tsx | 12 +- src/languages/en_US.json | 16 + src/navigation/Routes.android.tsx | 2 + src/navigation/Routes.ios.tsx | 8 +- src/navigation/config.tsx | 2 +- src/navigation/routesNames.ts | 1 + src/navigation/types.ts | 4 + .../claimables/ClaimClaimablePanel.tsx | 17 ++ .../claimables/ClaimingClaimableSharedUI.tsx | 287 ++++++++++++++++++ .../claimables/ClaimingSponsoredClaimable.tsx | 9 + .../ClaimingTransactionClaimable.tsx | 149 +++++++++ 11 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 src/screens/claimables/ClaimClaimablePanel.tsx create mode 100644 src/screens/claimables/ClaimingClaimableSharedUI.tsx create mode 100644 src/screens/claimables/ClaimingSponsoredClaimable.tsx create mode 100644 src/screens/claimables/ClaimingTransactionClaimable.tsx diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx index afc758e7531..34f829c2fc8 100644 --- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx +++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Box, Inline, Stack, Text } from '@/design-system'; import { useAccountSettings } from '@/hooks'; import { useClaimables } from '@/resources/addys/claimables/query'; @@ -7,6 +7,7 @@ import { ButtonPressAnimation } from '@/components/animations'; import { deviceUtils } from '@/utils'; import Routes from '@/navigation/routesNames'; import { ExtendedState } from './core/RawRecyclerList'; +import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; export const Claimable = React.memo(function Claimable({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) { const { accountAddress, nativeCurrency } = useAccountSettings(); @@ -24,12 +25,17 @@ export const Claimable = React.memo(function Claimable({ uniqueId, extendedState const [claimable] = data; + const nativeDisplay = useMemo( + () => convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true), + [claimable.value.nativeAsset.amount, nativeCurrency] + ); + if (!claimable) return null; return ( navigate(Routes.CLAIM_CLAIMABLE_PANEL, { claimable })} + onPress={() => navigate(Routes.CLAIM_CLAIMABLE_PANEL, { claimable })} scaleTo={0.96} paddingHorizontal="20px" justifyContent="space-between" @@ -68,7 +74,7 @@ export const Claimable = React.memo(function Claimable({ uniqueId, extendedState style={{ backgroundColor: 'rgba(7, 17, 32, 0.02)' }} > - {claimable.value.nativeAsset.display} + {nativeDisplay} diff --git a/src/languages/en_US.json b/src/languages/en_US.json index fa6e25ce852..afefe1ab151 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -450,6 +450,22 @@ "action": "Continue" } }, + "claimables": { + "panel": { + "calculating_gas_fee": "Calculating gas fee...", + "amount_to_claim_on_network": "%{amount} to claim on %{network}", + "claim": "Claim", + "claiming": "Claiming", + "claim_in_progress": "Claim in Progress...", + "claimed": "Claimed!", + "claiming_failed": "Claim Failed", + "insufficient_funds": "Insufficient Funds", + "claim_amount": "Claim %{amount}", + "done": "Done", + "try_again": "Try Again", + "tokens_on_the_way": "Tokens on the way!" + } + }, "cloud": { "backup_success": "Your wallet has been backed up successfully!" }, diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 007652f2302..e66ca433ef5 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -90,6 +90,7 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { ClaimClaimablePanel } from '@/screens/claimables/ClaimClaimablePanel'; import { RootStackParamList } from './types'; const Stack = createStackNavigator(); @@ -247,6 +248,7 @@ function BSNavigator() { + {swapsV2Enabled && } diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index 4f1a8e96407..1ea1c9553ae 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -38,7 +38,6 @@ import { hardwareWalletTxNavigatorConfig, consoleSheetConfig, customGasSheetConfig, - dappBrowserControlPanelConfig, defaultScreenStackOptions, ensAdditionalRecordsSheetConfig, ensConfirmRegisterSheetConfig, @@ -50,6 +49,7 @@ import { nftOffersSheetConfig, nftSingleOfferSheetConfig, pairHardwareWalletNavigatorConfig, + panelConfig, profileConfig, profilePreviewConfig, qrScannerConfig, @@ -104,6 +104,7 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { ClaimClaimablePanel } from '@/screens/claimables/ClaimClaimablePanel'; import { RootStackParamList } from './types'; const Stack = createStackNavigator(); @@ -285,8 +286,9 @@ function NativeStackNavigator() { - - + + + {swapsV2Enabled && } diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 09b893005bb..fbe583c44b8 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -248,7 +248,7 @@ export const consoleSheetConfig = { }), }; -export const dappBrowserControlPanelConfig = { +export const panelConfig = { options: ({ route: { params = {} } }) => ({ ...buildCoolModalConfig({ ...params, diff --git a/src/navigation/routesNames.ts b/src/navigation/routesNames.ts index 1e5693b4c27..0356fe71a56 100644 --- a/src/navigation/routesNames.ts +++ b/src/navigation/routesNames.ts @@ -13,6 +13,7 @@ const Routes = { CHANGE_WALLET_SHEET: 'ChangeWalletSheet', CHANGE_WALLET_SHEET_NAVIGATOR: 'ChangeWalletSheetNavigator', CHECK_IDENTIFIER_SCREEN: 'CheckIdentifierScreen', + CLAIM_CLAIMABLE_PANEL: 'ClaimClaimablePanel', CONFIRM_REQUEST: 'ConfirmRequest', CONNECTED_DAPPS: 'ConnectedDapps', CONSOLE_SHEET: 'ConsoleSheet', diff --git a/src/navigation/types.ts b/src/navigation/types.ts index c5a2ccc2388..f8eae26f27d 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -5,6 +5,7 @@ import Routes from '@/navigation/routesNames'; import { PortalSheetProps } from '@/screens/Portal'; import { REGISTRATION_MODES } from '@/helpers/ens'; import { CampaignCheckResult } from '@/components/remote-promo-sheet/checkForRemotePromoSheet'; +import { Claimable } from '@/resources/addys/claimables/types'; export type PartialNavigatorConfigOptions = Pick['Screen']>[0]>, 'options'>; @@ -75,4 +76,7 @@ export type RootStackParamList = { [Routes.SWAP]: { action?: 'open_swap_settings'; }; + [Routes.CLAIM_CLAIMABLE_PANEL]: { + claimable: Claimable; + }; }; diff --git a/src/screens/claimables/ClaimClaimablePanel.tsx b/src/screens/claimables/ClaimClaimablePanel.tsx new file mode 100644 index 00000000000..7e0ca2392a9 --- /dev/null +++ b/src/screens/claimables/ClaimClaimablePanel.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { ClaimingTransactionClaimable } from './ClaimingTransactionClaimable'; +import { ClaimingSponsoredClaimable } from './ClaimingSponsoredClaimable'; +import { RootStackParamList } from '@/navigation/types'; + +export const ClaimClaimablePanel = () => { + const { + params: { claimable }, + } = useRoute>(); + + return claimable.type === 'transaction' ? ( + + ) : ( + + ); +}; diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx new file mode 100644 index 00000000000..e1f8972ad0e --- /dev/null +++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx @@ -0,0 +1,287 @@ +import React, { useEffect, useMemo } from 'react'; +import { AccentColorProvider, Bleed, Box, Inline, Text, TextShadow, globalColors, useColorMode } from '@/design-system'; +import * as i18n from '@/languages'; +import { ListHeader, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; +import { deviceUtils, safeAreaInsetValues } from '@/utils'; +import { View } from 'react-native'; +import { IS_IOS } from '@/env'; +import { ButtonPressAnimation, ShimmerAnimation } from '@/components/animations'; +import { SponsoredClaimable, TransactionClaimable } from '@/resources/addys/claimables/types'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { useTheme } from '@/theme'; +import { FasterImageView } from '@candlefinance/faster-image'; +import { chainsLabel } from '@/chains'; +import { useNavigation } from '@/navigation'; +import { TextColor } from '@/design-system/color/palettes'; +import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; +import { convertAmountToNativeDisplayWorklet, handleSignificantDecimalsWithThreshold } from '@/__swaps__/utils/numbers'; +import { useAccountSettings } from '@/hooks'; + +const BUTTON_WIDTH = deviceUtils.dimensions.width - 52; + +export type ClaimStatus = + | 'idle' // initial state + | 'claiming' // user has pressed the claim button + | 'pending' // claim has been submitted but we don't have a tx hash + | 'success' // claim has been submitted and we have a tx hash + | 'error'; // claim has failed + +export const ClaimingClaimableSharedUI = ({ + claim, + claimable, + claimStatus, + hasSufficientFunds, + isGasReady, + isTransactionReady, + nativeCurrencyGasFeeDisplay, + setClaimStatus, +}: + | { + claim: () => void; + claimable: TransactionClaimable; + claimStatus: ClaimStatus; + hasSufficientFunds: boolean; + isGasReady: boolean; + isTransactionReady: boolean; + nativeCurrencyGasFeeDisplay: string; + setClaimStatus: React.Dispatch>; + } + | { + claim: () => void; + claimable: SponsoredClaimable; + claimStatus: ClaimStatus; + hasSufficientFunds?: never; + isGasReady?: never; + isTransactionReady?: never; + nativeCurrencyGasFeeDisplay?: never; + setClaimStatus: React.Dispatch>; + }) => { + const { isDarkMode } = useColorMode(); + const { nativeCurrency } = useAccountSettings(); + const theme = useTheme(); + const { goBack } = useNavigation(); + + const isButtonDisabled = + claimStatus === 'claiming' || + (claimStatus !== 'success' && claimStatus !== 'pending' && claimable.type === 'transaction' && !isTransactionReady); + + const shouldShowClaimText = claimStatus === 'idle' && (claimable.type !== 'transaction' || hasSufficientFunds); + + const claimAmountDisplay = useMemo( + () => `${handleSignificantDecimalsWithThreshold(claimable.value.claimAsset.amount, 4, '0.001')} ${claimable.asset.symbol}`, + [claimable.asset.symbol, claimable.value.claimAsset.amount] + ); + + const claimAmountNativeDisplay = useMemo( + () => convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true), + [claimable.value.nativeAsset.amount, nativeCurrency] + ); + + const buttonLabel = useMemo(() => { + switch (claimStatus) { + case 'idle': + if (shouldShowClaimText) { + return i18n.t(i18n.l.claimables.panel.claim_amount, { amount: claimAmountDisplay }); + } else { + return i18n.t(i18n.l.claimables.panel.insufficient_funds); + } + case 'claiming': + return i18n.t(i18n.l.claimables.panel.claim_in_progress); + case 'pending': + case 'success': + return i18n.t(i18n.l.button.done); + case 'error': + default: + return i18n.t(i18n.l.points.points.try_again); + } + }, [claimAmountDisplay, claimStatus, shouldShowClaimText]); + + const panelTitle = useMemo(() => { + switch (claimStatus) { + case 'idle': + return i18n.t(i18n.l.claimables.panel.claim); + case 'claiming': + return i18n.t(i18n.l.claimables.panel.claiming); + case 'pending': + return i18n.t(i18n.l.claimables.panel.tokens_on_the_way); + case 'success': + return i18n.t(i18n.l.claimables.panel.claimed); + case 'error': + default: + return i18n.t(i18n.l.claimables.panel.claiming_failed); + } + }, [claimStatus]); + + const panelTitleColor: TextColor = useMemo(() => { + switch (claimStatus) { + case 'idle': + case 'claiming': + return 'label'; + case 'pending': + case 'success': + return 'green'; + case 'error': + default: + return 'red'; + } + }, [claimStatus]); + + const animationProgress = useSharedValue(0); + + useEffect(() => { + switch (claimStatus) { + case 'idle': + case 'error': + animationProgress.value = withTiming(0, { duration: 300 }); + break; + case 'claiming': + case 'pending': + case 'success': + default: + animationProgress.value = withTiming(1, { duration: 300 }); + break; + } + }, [claimStatus, animationProgress]); + + const gasAnimatedStyle = useAnimatedStyle(() => { + return { + height: (1 - animationProgress.value) * 30, + opacity: 1 - animationProgress.value, + }; + }); + + return ( + <> + + + + + + + {panelTitle} + + + + } + showBackButton={false} + /> + + + + + + + + + + {claimAmountNativeDisplay} + + + + + { + if (claimStatus === 'success' || claimStatus === 'pending') { + goBack(); + } + }} + onLongPress={() => { + if (claimStatus === 'idle' || claimStatus === 'error') { + setClaimStatus('claiming'); + claim(); + } + }} + > + + + + + {shouldShowClaimText && ( + + + 􀎽 + + + )} + + + {buttonLabel} + + + + + + + {claimable.type === 'transaction' && ( + + + {isGasReady ? ( + + + 􀵟 + + + {i18n.t(i18n.l.claimables.panel.amount_to_claim_on_network, { + amount: nativeCurrencyGasFeeDisplay, + network: chainsLabel[claimable.chainId], + })} + + + ) : ( + + {i18n.t(i18n.l.claimables.panel.calculating_gas_fee)} + + )} + + + )} + + + + + + + ); +}; diff --git a/src/screens/claimables/ClaimingSponsoredClaimable.tsx b/src/screens/claimables/ClaimingSponsoredClaimable.tsx new file mode 100644 index 00000000000..23c76b8a502 --- /dev/null +++ b/src/screens/claimables/ClaimingSponsoredClaimable.tsx @@ -0,0 +1,9 @@ +import React, { useState } from 'react'; +import { SponsoredClaimable } from '@/resources/addys/claimables/types'; +import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; + +export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: SponsoredClaimable }) => { + const [claimStatus, setClaimStatus] = useState('idle'); + + return {}} claimable={claimable} claimStatus={claimStatus} setClaimStatus={setClaimStatus} />; +}; diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx new file mode 100644 index 00000000000..303dfefcf2f --- /dev/null +++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx @@ -0,0 +1,149 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useAccountSettings, useGas } from '@/hooks'; +import { ethereumUtils } from '@/utils'; +import { TransactionClaimable } from '@/resources/addys/claimables/types'; +import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; +import { parseGasParamsForTransaction } from '@/parsers'; +import { getNextNonce } from '@/state/nonces'; +import { needsL1SecurityFeeChains } from '@/chains'; +import { logger, RainbowError } from '@/logger'; +import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; +import { TransactionRequest } from '@ethersproject/providers'; +import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; + +// supports legacy and new gas types +export type TransactionClaimableTxPayload = TransactionRequest & + ( + | { + to: string; + from: string; + nonce: number; + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + data: string; + value: '0x0'; + chainId: number; + } + | { + to: string; + from: string; + nonce: number; + gasLimit: string; + gasPrice: string; + data: string; + value: '0x0'; + chainId: number; + } + ); + +export const ClaimingTransactionClaimable = ({ claimable }: { claimable: TransactionClaimable }) => { + const { accountAddress, nativeCurrency } = useAccountSettings(); + const { isGasReady, isSufficientGas, isValidGas, selectedGasFee, startPollingGasFees, stopPollingGasFees, updateTxFee } = useGas(); + + const [baseTxPayload, setBaseTxPayload] = useState< + Omit | undefined + >(); + const [txPayload, setTxPayload] = useState(); + const [claimStatus, setClaimStatus] = useState('idle'); + + const provider = useMemo(() => getProvider({ chainId: claimable.chainId }), [claimable.chainId]); + + const buildTxPayload = useCallback(async () => { + const payload = { + value: '0x0' as const, + data: claimable.action.data, + from: accountAddress, + chainId: claimable.chainId, + nonce: await getNextNonce({ address: accountAddress, chainId: claimable.chainId }), + to: claimable.action.to, + }; + + setBaseTxPayload(payload); + }, [accountAddress, claimable.action.to, claimable.action.data, claimable.chainId, setBaseTxPayload]); + + useEffect(() => { + buildTxPayload(); + }, [buildTxPayload]); + + useEffect(() => { + startPollingGasFees(); + return () => { + stopPollingGasFees(); + }; + }, [startPollingGasFees, stopPollingGasFees]); + + const estimateGas = useCallback(async () => { + if (!baseTxPayload) { + logger.error(new RainbowError('[ClaimingTransactionClaimable]: attempted to estimate gas without a tx payload')); + return; + } + + const gasParams = parseGasParamsForTransaction(selectedGasFee); + const updatedTxPayload = { ...baseTxPayload, ...gasParams }; + + const gasLimit = await estimateGasWithPadding(updatedTxPayload, null, null, provider); + + if (!gasLimit) { + updateTxFee(null, null); + logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to estimate gas limit')); + return; + } + + if (needsL1SecurityFeeChains.includes(claimable.chainId)) { + const l1SecurityFee = await ethereumUtils.calculateL1FeeOptimism( + // @ts-expect-error - type mismatch, but this tx request structure is the same as in SendSheet.js + { + to: claimable.action.to, + from: accountAddress, + value: '0x0', + data: claimable.action.data, + }, + provider + ); + + if (!l1SecurityFee) { + updateTxFee(null, null); + logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to calculate L1 security fee')); + return; + } + + updateTxFee(gasLimit, null, l1SecurityFee); + } else { + updateTxFee(gasLimit, null); + } + + setTxPayload({ ...updatedTxPayload, gasLimit }); + }, [baseTxPayload, selectedGasFee, provider, claimable.chainId, claimable.action.to, claimable.action.data, updateTxFee, accountAddress]); + + useEffect(() => { + if (baseTxPayload) { + try { + estimateGas(); + } catch (e) { + logger.warn('[ClaimingTransactionClaimable]: Failed to estimate gas', { error: e }); + } + } + }, [baseTxPayload, estimateGas, selectedGasFee]); + + const isTransactionReady = !!(isGasReady && isSufficientGas && isValidGas && txPayload); + + const nativeCurrencyGasFeeDisplay = convertAmountToNativeDisplayWorklet( + selectedGasFee?.gasFee?.estimatedFee?.native?.value?.amount, + nativeCurrency, + true + ); + + return ( + {}} + claimable={claimable} + claimStatus={claimStatus} + hasSufficientFunds={isSufficientGas} + isGasReady={!!txPayload?.gasLimit} + isTransactionReady={isTransactionReady} + nativeCurrencyGasFeeDisplay={nativeCurrencyGasFeeDisplay} + setClaimStatus={setClaimStatus} + /> + ); +}; From 2a4e929fdceab32594071db1716a2ad769a39e44 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 1 Oct 2024 12:23:38 -0400 Subject: [PATCH 35/64] raps v2 (#6138) * raps * nit * comments * nit * nit * prettier * updates to query, types, & wallet screen components * rm onPress * rm logic * thanks greg --- .../claimTransactionClaimableAction.ts | 45 +++++++ src/rapsV2/actions/index.ts | 1 + src/rapsV2/claimTransactionClaimableRap.ts | 13 ++ src/rapsV2/common.ts | 20 +++ src/rapsV2/execute.ts | 121 ++++++++++++++++++ src/rapsV2/references.ts | 83 ++++++++++++ 6 files changed, 283 insertions(+) create mode 100644 src/rapsV2/actions/claimTransactionClaimableAction.ts create mode 100644 src/rapsV2/actions/index.ts create mode 100644 src/rapsV2/claimTransactionClaimableRap.ts create mode 100644 src/rapsV2/common.ts create mode 100644 src/rapsV2/execute.ts create mode 100644 src/rapsV2/references.ts diff --git a/src/rapsV2/actions/claimTransactionClaimableAction.ts b/src/rapsV2/actions/claimTransactionClaimableAction.ts new file mode 100644 index 00000000000..4cc138f650c --- /dev/null +++ b/src/rapsV2/actions/claimTransactionClaimableAction.ts @@ -0,0 +1,45 @@ +import { ActionProps } from '../references'; +import { sendTransaction } from '@/model/wallet'; +import { getProvider } from '@/handlers/web3'; +import { RainbowError } from '@/logger'; +import { addNewTransaction } from '@/state/pendingTransactions'; +import { NewTransaction } from '@/entities'; +import { chainsName } from '@/chains'; + +export async function claimTransactionClaimable({ parameters, wallet }: ActionProps<'claimTransactionClaimableAction'>) { + // will uncomment actual logic in follow-up PR, don't worry about it for now + return { nonce: undefined, hash: undefined }; + + // const { claimTx } = parameters; + + // const provider = getProvider({ chainId: claimTx.chainId }); + // const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider }); + + // if (!result?.result || !!result.error || !result.result.hash) { + // throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction'); + // } + + // const transaction = { + // amount: result.result.value.toString(), + // gasLimit: result.result.gasLimit, + // from: result.result.from ?? null, + // to: result.result.to ?? null, + // chainId: result.result.chainId, + // hash: result.result.hash, + // network: chainsName[result.result.chainId], + // status: 'pending', + // type: 'send', + // nonce: result.result.nonce, + // } satisfies NewTransaction; + + // addNewTransaction({ + // address: claimTx.from, + // chainId: claimTx.chainId, + // transaction, + // }); + + // return { + // nonce: result.result.nonce, + // hash: result.result.hash, + // }; +} diff --git a/src/rapsV2/actions/index.ts b/src/rapsV2/actions/index.ts new file mode 100644 index 00000000000..75323a946a5 --- /dev/null +++ b/src/rapsV2/actions/index.ts @@ -0,0 +1 @@ +export { claimTransactionClaimable } from './claimTransactionClaimableAction'; diff --git a/src/rapsV2/claimTransactionClaimableRap.ts b/src/rapsV2/claimTransactionClaimableRap.ts new file mode 100644 index 00000000000..f91b2b6950a --- /dev/null +++ b/src/rapsV2/claimTransactionClaimableRap.ts @@ -0,0 +1,13 @@ +import { createNewAction, createNewRap } from './common'; +import { RapAction, RapParameters } from './references'; + +export async function createClaimTransactionClaimableRap(parameters: Extract) { + let actions: RapAction<'claimTransactionClaimableAction'>[] = []; + + const claim = createNewAction('claimTransactionClaimableAction', parameters.claimTransactionClaimableActionParameters); + actions = actions.concat(claim); + + // create the overall rap + const newRap = createNewRap(actions); + return newRap; +} diff --git a/src/rapsV2/common.ts b/src/rapsV2/common.ts new file mode 100644 index 00000000000..506a165adcd --- /dev/null +++ b/src/rapsV2/common.ts @@ -0,0 +1,20 @@ +import { RapAction, RapActionParameterMap, RapActionTypes } from './references'; + +export interface RapActionTransaction { + hash: string | null; +} + +export function createNewAction(type: T, parameters: RapActionParameterMap[T]): RapAction { + const newAction = { + parameters, + transaction: { confirmed: null, hash: null }, + type, + }; + return newAction; +} + +export function createNewRap(actions: RapAction[]) { + return { + actions, + }; +} diff --git a/src/rapsV2/execute.ts b/src/rapsV2/execute.ts new file mode 100644 index 00000000000..cdbdc1ca8c7 --- /dev/null +++ b/src/rapsV2/execute.ts @@ -0,0 +1,121 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-async-promise-executor */ +/* eslint-disable no-promise-executor-return */ +import { Signer } from '@ethersproject/abstract-signer'; + +import { RainbowError, logger } from '@/logger'; + +import { ActionProps, RapResponse, Rap, RapAction, RapActionResponse, RapActionTypes, RapParameters } from './references'; +import { createClaimTransactionClaimableRap } from './claimTransactionClaimableRap'; +import { claimTransactionClaimable } from './actions/claimTransactionClaimableAction'; +import { delay } from '@/utils/delay'; + +// get the rap by type +export function createRap(parameters: RapParameters): Promise<{ actions: RapAction[] }> { + switch (parameters.type) { + case 'claimTransactionClaimableRap': + return createClaimTransactionClaimableRap(parameters); + default: + return Promise.resolve({ actions: [] }); + } +} + +// get the action executable by type +function getActionExecutableByType(type: T, props: ActionProps) { + switch (type) { + case 'claimTransactionClaimableAction': + return () => claimTransactionClaimable(props); + default: + throw new RainbowError(`[rapsV2/execute]: T - unknown action type ${type}`); + } +} + +// executes a single action in the rap +// if the action executes a tx on-chain, it will return the nonce it used +// if an error occurs, we return the error message +export async function executeAction({ + action, + wallet, + rap, + nonceToUse, + rapName, +}: { + action: RapAction; + wallet: Signer; + rap: Rap; + nonceToUse: number | undefined; + rapName: string; +}): Promise { + const { type, parameters } = action; + try { + const actionProps = { + wallet, + currentRap: rap, + parameters, + nonceToUse, + }; + const { nonce, hash } = await getActionExecutableByType(type, actionProps)(); + return { nonce, errorMessage: null, hash }; + } catch (error) { + logger.error(new RainbowError(`[rapsV2/execute]: ${rapName} - error execute action`), { + message: (error as Error)?.message, + }); + return { nonce: null, errorMessage: String(error), hash: null }; + } +} + +function getRapFullName(actions: RapAction[]) { + const actionTypes = actions.map(action => action.type); + return actionTypes.join(' + '); +} + +const waitForNodeAck = async (hash: string, provider: Signer['provider']): Promise => { + return new Promise(async resolve => { + const tx = await provider?.getTransaction(hash); + // This means the node is aware of the tx, we're good to go + if ((tx && tx.blockNumber === null) || (tx && tx?.blockNumber && tx?.blockNumber > 0)) { + resolve(); + } else { + // Wait for 1 second and try again + await delay(1000); + return waitForNodeAck(hash, provider); + } + }); +}; + +// goes through each action in the rap and executes it +// if an action executes a tx on-chain, increment the nonceToUse for the next tx +// if an action fails, it will return the error message +const executeRap = async (wallet: Signer, rap: Rap): Promise => { + const { actions } = rap; + const rapName = getRapFullName(rap.actions); + let nonceToUse: number | undefined; + + while (actions.length) { + const action = actions.shift(); + + if (!action) break; + + const { nonce, errorMessage, hash } = await executeAction({ + action, + wallet, + rap, + nonceToUse, + rapName, + }); + + if (errorMessage) return { errorMessage }; + + if (typeof nonce === 'number') { + actions.length >= 1 && hash && (await waitForNodeAck(hash, wallet.provider)); + nonceToUse = nonce + 1; + } + } + + return { errorMessage: null }; +}; + +export async function walletExecuteRap(wallet: Signer, rapParameters: RapParameters): Promise { + const rap = await createRap(rapParameters); + return executeRap(wallet, rap); +} diff --git a/src/rapsV2/references.ts b/src/rapsV2/references.ts new file mode 100644 index 00000000000..2f3acc7007f --- /dev/null +++ b/src/rapsV2/references.ts @@ -0,0 +1,83 @@ +import { Signer } from '@ethersproject/abstract-signer'; +import { TransactionRequest } from '@ethersproject/abstract-provider'; + +// supports legacy and new gas types +export type TransactionClaimableTxPayload = TransactionRequest & + ( + | { + to: string; + from: string; + nonce: number; + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + data: string; + value: '0x0'; + chainId: number; + } + | { + to: string; + from: string; + nonce: number; + gasLimit: string; + gasPrice: string; + data: string; + value: '0x0'; + chainId: number; + } + ); + +export interface ClaimTransactionClaimableActionParameters { + claimTx: TransactionClaimableTxPayload; +} + +export interface RapActionTransaction { + hash: string | null; +} + +export type RapActionParameterMap = { + claimTransactionClaimableAction: ClaimTransactionClaimableActionParameters; +}; + +export type RapParameters = { + type: 'claimTransactionClaimableRap'; + claimTransactionClaimableActionParameters: ClaimTransactionClaimableActionParameters; +}; + +export interface RapAction { + parameters: RapActionParameterMap[T]; + transaction: RapActionTransaction; + type: T; +} + +export interface Rap { + actions: RapAction<'claimTransactionClaimableAction'>[]; +} + +export enum rapActions { + claimTransactionClaimableAction = 'claimTransactionClaimableAction', +} + +export type RapActionTypes = keyof typeof rapActions; + +export enum rapTypes { + claimTransactionClaimableRap = 'claimTransactionClaimableRap', +} + +export type RapTypes = keyof typeof rapTypes; + +export interface RapActionResponse { + nonce: number | null | undefined; + errorMessage: string | null; + hash: string | null | undefined; +} + +export interface ActionProps { + nonceToUse: number | undefined; + parameters: RapActionParameterMap[T]; + wallet: Signer; +} + +export interface RapResponse { + errorMessage: string | null; +} From 9e93bdf64ef906f68c4372cfe91da246b2882b73 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Tue, 1 Oct 2024 14:17:16 -0400 Subject: [PATCH 36/64] Claimables [PR #3]: claim functionality (#6146) * raps * nit * comments * nit * nit * prettier * updates to query, types, & wallet screen components * rm onPress * rm logic * claim panel ui * nav * onPress -> onLongPress * i18n * pending state, i18n * comment * shimmer * functionality * tweak gas fee formatting * number formatting * wallet screen number format * route types * set pending state if no tx hash * do not require long press for goBack * tweak button text * nit * fix pending tx * disable read only wallet * formatting * thanks greg * revert * fix android button width * rm memo * try catch gas * rm functionality from tx --- .../claimTransactionClaimableAction.ts | 58 +++++++------- src/rapsV2/references.ts | 30 +------ src/resources/addys/claimables/query.ts | 2 +- src/resources/addys/claimables/types.ts | 7 +- src/resources/addys/claimables/utils.ts | 12 +-- src/resources/assets/types.ts | 2 +- .../claimables/ClaimingClaimableSharedUI.tsx | 18 +++-- .../claimables/ClaimingSponsoredClaimable.tsx | 80 ++++++++++++++++++- .../ClaimingTransactionClaimable.tsx | 59 +++++++++++++- 9 files changed, 190 insertions(+), 78 deletions(-) diff --git a/src/rapsV2/actions/claimTransactionClaimableAction.ts b/src/rapsV2/actions/claimTransactionClaimableAction.ts index 4cc138f650c..a62b4ff9478 100644 --- a/src/rapsV2/actions/claimTransactionClaimableAction.ts +++ b/src/rapsV2/actions/claimTransactionClaimableAction.ts @@ -7,39 +7,37 @@ import { NewTransaction } from '@/entities'; import { chainsName } from '@/chains'; export async function claimTransactionClaimable({ parameters, wallet }: ActionProps<'claimTransactionClaimableAction'>) { - // will uncomment actual logic in follow-up PR, don't worry about it for now - return { nonce: undefined, hash: undefined }; + const { claimTx, asset } = parameters; - // const { claimTx } = parameters; + const provider = getProvider({ chainId: claimTx.chainId }); + const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider }); - // const provider = getProvider({ chainId: claimTx.chainId }); - // const result = await sendTransaction({ transaction: claimTx, existingWallet: wallet, provider }); + if (!result?.result || !!result.error || !result.result.hash) { + throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction'); + } - // if (!result?.result || !!result.error || !result.result.hash) { - // throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: failed to execute claim transaction'); - // } + const transaction = { + amount: '0x0', + gasLimit: result.result.gasLimit, + from: result.result.from ?? null, + to: result.result.to ?? null, + chainId: result.result.chainId, + hash: result.result.hash, + network: chainsName[result.result.chainId], + status: 'pending', + type: 'send', + nonce: result.result.nonce, + asset, + } satisfies NewTransaction; - // const transaction = { - // amount: result.result.value.toString(), - // gasLimit: result.result.gasLimit, - // from: result.result.from ?? null, - // to: result.result.to ?? null, - // chainId: result.result.chainId, - // hash: result.result.hash, - // network: chainsName[result.result.chainId], - // status: 'pending', - // type: 'send', - // nonce: result.result.nonce, - // } satisfies NewTransaction; + addNewTransaction({ + address: claimTx.from, + chainId: claimTx.chainId, + transaction, + }); - // addNewTransaction({ - // address: claimTx.from, - // chainId: claimTx.chainId, - // transaction, - // }); - - // return { - // nonce: result.result.nonce, - // hash: result.result.hash, - // }; + return { + nonce: result.result.nonce, + hash: result.result.hash, + }; } diff --git a/src/rapsV2/references.ts b/src/rapsV2/references.ts index 2f3acc7007f..e6544fb4e61 100644 --- a/src/rapsV2/references.ts +++ b/src/rapsV2/references.ts @@ -1,34 +1,10 @@ +import { ParsedAddressAsset } from '@/entities'; +import { TransactionClaimableTxPayload } from '@/screens/claimables/ClaimingTransactionClaimable'; import { Signer } from '@ethersproject/abstract-signer'; -import { TransactionRequest } from '@ethersproject/abstract-provider'; - -// supports legacy and new gas types -export type TransactionClaimableTxPayload = TransactionRequest & - ( - | { - to: string; - from: string; - nonce: number; - gasLimit: string; - maxFeePerGas: string; - maxPriorityFeePerGas: string; - data: string; - value: '0x0'; - chainId: number; - } - | { - to: string; - from: string; - nonce: number; - gasLimit: string; - gasPrice: string; - data: string; - value: '0x0'; - chainId: number; - } - ); export interface ClaimTransactionClaimableActionParameters { claimTx: TransactionClaimableTxPayload; + asset: ParsedAddressAsset; } export interface RapActionTransaction { diff --git a/src/resources/addys/claimables/query.ts b/src/resources/addys/claimables/query.ts index 137a5846caa..c1db04818bd 100644 --- a/src/resources/addys/claimables/query.ts +++ b/src/resources/addys/claimables/query.ts @@ -32,7 +32,7 @@ export type ClaimablesArgs = { // Query Key export const claimablesQueryKey = ({ address, currency }: ClaimablesArgs) => - createQueryKey('claimables', { address, currency }, { persisterVersion: 2 }); + createQueryKey('claimables', { address, currency }, { persisterVersion: 3 }); type ClaimablesQueryKey = ReturnType; diff --git a/src/resources/addys/claimables/types.ts b/src/resources/addys/claimables/types.ts index 28538833b69..37852b6f2d8 100644 --- a/src/resources/addys/claimables/types.ts +++ b/src/resources/addys/claimables/types.ts @@ -1,6 +1,7 @@ import { Address } from 'viem'; import { AddysAsset, AddysConsolidatedError, AddysResponseStatus } from '../types'; import { ChainId } from '@/chains/types'; +import { ParsedAddressAsset } from '@/entities'; interface Colors { primary: string; @@ -75,11 +76,7 @@ export interface ConsolidatedClaimablesResponse { } interface BaseClaimable { - asset: { - iconUrl: string; - name: string; - symbol: string; - }; + asset: ParsedAddressAsset; chainId: ChainId; name: string; uniqueId: string; diff --git a/src/resources/addys/claimables/utils.ts b/src/resources/addys/claimables/utils.ts index 71b6122a3f1..90889a2668a 100644 --- a/src/resources/addys/claimables/utils.ts +++ b/src/resources/addys/claimables/utils.ts @@ -1,6 +1,9 @@ import { NativeCurrencyKey } from '@/entities'; import { AddysClaimable, Claimable } from './types'; import { convertRawAmountToBalance, convertRawAmountToNativeDisplay, greaterThan } from '@/helpers/utilities'; +import { parseAsset } from '@/resources/assets/assets'; +import { Network } from '@/chains/types'; +import { chainsName } from '@/chains'; export const parseClaimables = (claimables: AddysClaimable[], currency: NativeCurrencyKey): Claimable[] => { return claimables @@ -13,11 +16,10 @@ export const parseClaimables = (claimables: AddysClaimable[], currency: NativeCu } const baseClaimable = { - asset: { - iconUrl: claimable.asset.icon_url, - name: claimable.asset.name, - symbol: claimable.asset.symbol, - }, + asset: parseAsset({ + address: claimable.asset.asset_code, + asset: { ...claimable.asset, network: chainsName[claimable.network] as Network }, + }), chainId: claimable.network, name: claimable.name, uniqueId: claimable.unique_id, diff --git a/src/resources/assets/types.ts b/src/resources/assets/types.ts index cdb6d1b55b8..3c655c41980 100644 --- a/src/resources/assets/types.ts +++ b/src/resources/assets/types.ts @@ -28,7 +28,7 @@ export type AddysAddressAsset = { export type AddysAsset = { asset_code: string; - colors: TokenColors; + colors?: TokenColors; decimals: number; icon_url?: string; name: string; diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx index e1f8972ad0e..19bb2a6feff 100644 --- a/src/screens/claimables/ClaimingClaimableSharedUI.tsx +++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo } from 'react'; import { AccentColorProvider, Bleed, Box, Inline, Text, TextShadow, globalColors, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeader, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; -import { deviceUtils, safeAreaInsetValues } from '@/utils'; +import { deviceUtils, safeAreaInsetValues, watchingAlert } from '@/utils'; import { View } from 'react-native'; import { IS_IOS } from '@/env'; import { ButtonPressAnimation, ShimmerAnimation } from '@/components/animations'; @@ -15,7 +15,8 @@ import { useNavigation } from '@/navigation'; import { TextColor } from '@/design-system/color/palettes'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import { convertAmountToNativeDisplayWorklet, handleSignificantDecimalsWithThreshold } from '@/__swaps__/utils/numbers'; -import { useAccountSettings } from '@/hooks'; +import { useAccountSettings, useWallets } from '@/hooks'; +import { enableActionsOnReadOnlyWallet } from '@/config'; const BUTTON_WIDTH = deviceUtils.dimensions.width - 52; @@ -60,6 +61,7 @@ export const ClaimingClaimableSharedUI = ({ const { nativeCurrency } = useAccountSettings(); const theme = useTheme(); const { goBack } = useNavigation(); + const { isReadOnlyWallet } = useWallets(); const isButtonDisabled = claimStatus === 'claiming' || @@ -193,7 +195,7 @@ export const ClaimingClaimableSharedUI = ({ > { - if (claimStatus === 'idle' || claimStatus === 'error') { - setClaimStatus('claiming'); - claim(); + if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { + if (claimStatus === 'idle' || claimStatus === 'error') { + setClaimStatus('claiming'); + claim(); + } + } else { + watchingAlert(); } }} > diff --git a/src/screens/claimables/ClaimingSponsoredClaimable.tsx b/src/screens/claimables/ClaimingSponsoredClaimable.tsx index 23c76b8a502..58e8a75f259 100644 --- a/src/screens/claimables/ClaimingSponsoredClaimable.tsx +++ b/src/screens/claimables/ClaimingSponsoredClaimable.tsx @@ -1,9 +1,85 @@ import React, { useState } from 'react'; -import { SponsoredClaimable } from '@/resources/addys/claimables/types'; +import { ClaimResponse, SponsoredClaimable } from '@/resources/addys/claimables/types'; import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; +import { logger, RainbowError } from '@/logger'; +import { queryClient } from '@/react-query'; +import { ADDYS_BASE_URL, addysHttp, claimablesQueryKey } from '@/resources/addys/claimables/query'; +import { loadWallet } from '@/model/wallet'; +import { useMutation } from '@tanstack/react-query'; +import { getProvider } from '@/handlers/web3'; +import { useAccountSettings } from '@/hooks'; export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: SponsoredClaimable }) => { + const { accountAddress, nativeCurrency } = useAccountSettings(); + const [claimStatus, setClaimStatus] = useState('idle'); - return {}} claimable={claimable} claimStatus={claimStatus} setClaimStatus={setClaimStatus} />; + const { mutate: claimClaimable } = useMutation({ + mutationFn: async () => { + const provider = getProvider({ chainId: claimable.chainId }); + const wallet = await loadWallet({ + address: accountAddress, + showErrorIfNotLoaded: true, + provider, + }); + + if (!wallet) { + // Biometrics auth failure (retry possible) + setClaimStatus('error'); + return; + } + + const path = claimable.action.url.replace(ADDYS_BASE_URL, ''); + let response: { data: ClaimResponse }; + + if (claimable.action.method === 'GET') { + try { + response = await addysHttp.get(path); + } catch (e) { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call')); + return; + } + } else { + try { + response = await addysHttp.post(path); + } catch (e) { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call')); + return; + } + } + + if (!response.data.payload.success) { + setClaimStatus('error'); + logger.warn('[ClaimSponsoredClaimable]: sponsored claim api call returned unsuccessful response'); + } else { + if (response.data.payload.claim_transaction_status?.transaction_hash) { + setClaimStatus('success'); + } else { + setClaimStatus('pending'); + } + // Clear and refresh claimables data + queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency })); + } + }, + onError: e => { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimingSponsoredClaimable]: Failed to claim claimable due to unhandled error'), { + message: (e as Error)?.message, + }); + }, + onSuccess: () => { + if (claimStatus === 'claiming') { + logger.error( + new RainbowError('[ClaimingSponsoredClaimable]: claim function completed but never resolved status to success or error state') + ); + setClaimStatus('error'); + } + }, + }); + + return ( + + ); }; diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx index 303dfefcf2f..3f79915e656 100644 --- a/src/screens/claimables/ClaimingTransactionClaimable.tsx +++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx @@ -10,6 +10,11 @@ import { logger, RainbowError } from '@/logger'; import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; import { TransactionRequest } from '@ethersproject/providers'; import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; +import { useMutation } from '@tanstack/react-query'; +import { loadWallet } from '@/model/wallet'; +import { walletExecuteRap } from '@/rapsV2/execute'; +import { claimablesQueryKey } from '@/resources/addys/claimables/query'; +import { queryClient } from '@/react-query'; // supports legacy and new gas types export type TransactionClaimableTxPayload = TransactionRequest & @@ -134,9 +139,61 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac true ); + const { mutate: claimClaimable } = useMutation({ + mutationFn: async () => { + if (!txPayload) { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to missing tx payload')); + return; + } + + const wallet = await loadWallet({ + address: accountAddress, + showErrorIfNotLoaded: false, + provider, + }); + + if (!wallet) { + // Biometrics auth failure (retry possible) + setClaimStatus('error'); + return; + } + + const { errorMessage } = await walletExecuteRap(wallet, { + type: 'claimTransactionClaimableRap', + claimTransactionClaimableActionParameters: { claimTx: txPayload, asset: claimable.asset }, + }); + + if (errorMessage) { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to rap error'), { + message: errorMessage, + }); + } else { + setClaimStatus('success'); + // Clear and refresh claimables data + queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency })); + } + }, + onError: e => { + setClaimStatus('error'); + logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to unhandled error'), { + message: (e as Error)?.message, + }); + }, + onSuccess: () => { + if (claimStatus === 'claiming') { + logger.error( + new RainbowError('[ClaimingTransactionClaimable]: claim function completed but never resolved status to success or error state') + ); + setClaimStatus('error'); + } + }, + }); + return ( {}} + claim={claimClaimable} claimable={claimable} claimStatus={claimStatus} hasSufficientFunds={isSufficientGas} From 159822532ade263d64caf33e944cec728a1d825a Mon Sep 17 00:00:00 2001 From: Christopher Howard Date: Wed, 2 Oct 2024 10:02:16 -0400 Subject: [PATCH 37/64] [APP-1907] Add Claiming Status to Pending Claimable TX's (plus types and unused code cleanup) (#6155) * fix: use claim type for pending claimable tx * [APP-1909] Await Claimable TX Before Resolving Rap (#6156) * fix: await claimable tx mining before displaying success * fix: add pending tx prior to waiting for mining --- src/rapsV2/actions/claimTransactionClaimableAction.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rapsV2/actions/claimTransactionClaimableAction.ts b/src/rapsV2/actions/claimTransactionClaimableAction.ts index a62b4ff9478..b2d94597cd0 100644 --- a/src/rapsV2/actions/claimTransactionClaimableAction.ts +++ b/src/rapsV2/actions/claimTransactionClaimableAction.ts @@ -25,7 +25,7 @@ export async function claimTransactionClaimable({ parameters, wallet }: ActionPr hash: result.result.hash, network: chainsName[result.result.chainId], status: 'pending', - type: 'send', + type: 'claim', nonce: result.result.nonce, asset, } satisfies NewTransaction; @@ -36,6 +36,12 @@ export async function claimTransactionClaimable({ parameters, wallet }: ActionPr transaction, }); + const tx = await wallet?.provider?.getTransaction(result.result.hash); + const receipt = await tx?.wait(); + if (!receipt) { + throw new RainbowError('[CLAIM-TRANSACTION-CLAIMABLE]: tx not mined'); + } + return { nonce: result.result.nonce, hash: result.result.hash, From ac9ec83a544f3efdbac7acd485d425280d3245a4 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 2 Oct 2024 12:10:25 -0400 Subject: [PATCH 38/64] Claimables fixes (#6158) * number formatting * fix border radius for claim dapp icon * poll gas by chain id * haptics * adjust button enabled logic + shadows --- .../RecyclerAssetList2/Claimable.tsx | 15 +++++---- .../claimables/ClaimingClaimableSharedUI.tsx | 32 ++++++++----------- .../claimables/ClaimingSponsoredClaimable.tsx | 13 ++++++-- .../ClaimingTransactionClaimable.tsx | 14 +++++--- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx index 34f829c2fc8..3e422b53bfb 100644 --- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx +++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Box, Inline, Stack, Text } from '@/design-system'; import { useAccountSettings } from '@/hooks'; import { useClaimables } from '@/resources/addys/claimables/query'; @@ -25,10 +25,7 @@ export const Claimable = React.memo(function Claimable({ uniqueId, extendedState const [claimable] = data; - const nativeDisplay = useMemo( - () => convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true), - [claimable.value.nativeAsset.amount, nativeCurrency] - ); + const nativeDisplay = convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true); if (!claimable) return null; @@ -43,9 +40,13 @@ export const Claimable = React.memo(function Claimable({ uniqueId, extendedState flexDirection="row" > - `${handleSignificantDecimalsWithThreshold(claimable.value.claimAsset.amount, 4, '0.001')} ${claimable.asset.symbol}`, - [claimable.asset.symbol, claimable.value.claimAsset.amount] - ); - - const claimAmountNativeDisplay = useMemo( - () => convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true), - [claimable.value.nativeAsset.amount, nativeCurrency] - ); + const claimAmountNativeDisplay = convertAmountToNativeDisplayWorklet(claimable.value.nativeAsset.amount, nativeCurrency, true); const buttonLabel = useMemo(() => { switch (claimStatus) { case 'idle': if (shouldShowClaimText) { - return i18n.t(i18n.l.claimables.panel.claim_amount, { amount: claimAmountDisplay }); + return i18n.t(i18n.l.claimables.panel.claim_amount, { amount: claimable.value.claimAsset.display }); } else { return i18n.t(i18n.l.claimables.panel.insufficient_funds); } @@ -96,7 +88,7 @@ export const ClaimingClaimableSharedUI = ({ default: return i18n.t(i18n.l.points.points.try_again); } - }, [claimAmountDisplay, claimStatus, shouldShowClaimText]); + }, [claimable.value.claimAsset.display, claimStatus, shouldShowClaimText]); const panelTitle = useMemo(() => { switch (claimStatus) { @@ -167,7 +159,10 @@ export const ClaimingClaimableSharedUI = ({ @@ -220,6 +215,7 @@ export const ClaimingClaimableSharedUI = ({ } }} onLongPress={() => { + haptics.impactHeavy(); if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { if (claimStatus === 'idle' || claimStatus === 'error') { setClaimStatus('claiming'); @@ -230,9 +226,7 @@ export const ClaimingClaimableSharedUI = ({ } }} > - + {shouldShowClaimText && ( - + 􀎽 )} - + {buttonLabel} diff --git a/src/screens/claimables/ClaimingSponsoredClaimable.tsx b/src/screens/claimables/ClaimingSponsoredClaimable.tsx index 58e8a75f259..81aff49c5ce 100644 --- a/src/screens/claimables/ClaimingSponsoredClaimable.tsx +++ b/src/screens/claimables/ClaimingSponsoredClaimable.tsx @@ -8,6 +8,7 @@ import { loadWallet } from '@/model/wallet'; import { useMutation } from '@tanstack/react-query'; import { getProvider } from '@/handlers/web3'; import { useAccountSettings } from '@/hooks'; +import { haptics } from '@/utils'; export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: SponsoredClaimable }) => { const { accountAddress, nativeCurrency } = useAccountSettings(); @@ -25,6 +26,7 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored if (!wallet) { // Biometrics auth failure (retry possible) + haptics.notificationError(); setClaimStatus('error'); return; } @@ -36,6 +38,7 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored try { response = await addysHttp.get(path); } catch (e) { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call')); return; @@ -44,6 +47,7 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored try { response = await addysHttp.post(path); } catch (e) { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimSponsoredClaimable]: failed to execute sponsored claim api call')); return; @@ -51,12 +55,15 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored } if (!response.data.payload.success) { + haptics.notificationError(); setClaimStatus('error'); - logger.warn('[ClaimSponsoredClaimable]: sponsored claim api call returned unsuccessful response'); + logger.error(new RainbowError('[ClaimSponsoredClaimable]: sponsored claim api call returned unsuccessful response')); } else { if (response.data.payload.claim_transaction_status?.transaction_hash) { + haptics.notificationSuccess(); setClaimStatus('success'); } else { + haptics.notificationSuccess(); setClaimStatus('pending'); } // Clear and refresh claimables data @@ -64,6 +71,7 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored } }, onError: e => { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimingSponsoredClaimable]: Failed to claim claimable due to unhandled error'), { message: (e as Error)?.message, @@ -71,10 +79,11 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored }, onSuccess: () => { if (claimStatus === 'claiming') { + haptics.notificationError(); + setClaimStatus('error'); logger.error( new RainbowError('[ClaimingSponsoredClaimable]: claim function completed but never resolved status to success or error state') ); - setClaimStatus('error'); } }, }); diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx index 3f79915e656..9130abd7a46 100644 --- a/src/screens/claimables/ClaimingTransactionClaimable.tsx +++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useAccountSettings, useGas } from '@/hooks'; -import { ethereumUtils } from '@/utils'; +import { ethereumUtils, haptics } from '@/utils'; import { TransactionClaimable } from '@/resources/addys/claimables/types'; import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { parseGasParamsForTransaction } from '@/parsers'; @@ -72,11 +72,11 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac }, [buildTxPayload]); useEffect(() => { - startPollingGasFees(); + startPollingGasFees(claimable.chainId); return () => { stopPollingGasFees(); }; - }, [startPollingGasFees, stopPollingGasFees]); + }, [claimable.chainId, startPollingGasFees, stopPollingGasFees]); const estimateGas = useCallback(async () => { if (!baseTxPayload) { @@ -142,6 +142,7 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac const { mutate: claimClaimable } = useMutation({ mutationFn: async () => { if (!txPayload) { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to missing tx payload')); return; @@ -155,6 +156,7 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac if (!wallet) { // Biometrics auth failure (retry possible) + haptics.notificationError(); setClaimStatus('error'); return; } @@ -165,17 +167,20 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac }); if (errorMessage) { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to rap error'), { message: errorMessage, }); } else { + haptics.notificationSuccess(); setClaimStatus('success'); // Clear and refresh claimables data queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency })); } }, onError: e => { + haptics.notificationError(); setClaimStatus('error'); logger.error(new RainbowError('[ClaimingTransactionClaimable]: Failed to claim claimable due to unhandled error'), { message: (e as Error)?.message, @@ -183,10 +188,11 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac }, onSuccess: () => { if (claimStatus === 'claiming') { + haptics.notificationError(); + setClaimStatus('error'); logger.error( new RainbowError('[ClaimingTransactionClaimable]: claim function completed but never resolved status to success or error state') ); - setClaimStatus('error'); } }, }); From 9b94c9ea89db836dccb5d67f7a3a92df568bf1d1 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 2 Oct 2024 13:06:53 -0400 Subject: [PATCH 39/64] fix (#6159) --- .../asset-list/RecyclerAssetList2/Claimable.tsx | 11 +++-------- src/screens/claimables/ClaimingClaimableSharedUI.tsx | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx index 3e422b53bfb..9fc42c0acec 100644 --- a/src/components/asset-list/RecyclerAssetList2/Claimable.tsx +++ b/src/components/asset-list/RecyclerAssetList2/Claimable.tsx @@ -40,14 +40,9 @@ export const Claimable = React.memo(function Claimable({ uniqueId, extendedState flexDirection="row" > - + + + - + + + {panelTitle} From b0c287ea8f8dab2faba4e38be1d855452d9e93ee Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 2 Oct 2024 16:32:23 -0400 Subject: [PATCH 40/64] convert sends to typescript (#6120) * convert sends to typescript * more conversions * fix lint * fix optimism security fee * fix file renaming weirdness * fix rotation from max asset to nft not working * fix some typescript bs * fix lint * fix nft sends --- src/components/AddFundsInterstitial.js | 6 +- src/components/{Divider.js => Divider.tsx} | 53 +++- src/components/L2Disclaimer.js | 2 +- src/components/change-wallet/WalletList.tsx | 2 +- .../coin-row/CollectiblesSendRow.tsx | 3 +- .../exchange/CurrencySelectModalHeader.tsx | 1 - src/components/exchange/NetworkSwitcher.js | 6 +- .../expanded-state/AvailableNetworks.js | 2 +- .../expanded-state/AvailableNetworksv2.tsx | 4 +- .../expanded-state/CustomGasState.js | 2 +- .../expanded-state/profile/ProfileModal.tsx | 2 +- .../SwapDetailsSlippageMessage.js | 2 +- src/components/gas/GasSpeedButton.js | 8 +- src/components/layout/LayoutWithDividers.js | 2 +- src/components/list/ListHeader.js | 4 +- src/components/list/ListItemDivider.js | 2 +- src/components/modal/ModalFooterButtonsRow.js | 2 +- src/components/send/SendAssetList.js | 2 +- .../send/{SendHeader.js => SendHeader.tsx} | 87 ++++-- src/components/sheet/SheetDivider.js | 2 +- ...ixedToTop.js => SheetHandleFixedToTop.tsx} | 2 +- src/entities/transactions/transaction.ts | 5 +- src/handlers/web3.ts | 6 +- src/helpers/validators.ts | 5 +- src/hooks/useMaxInputBalance.ts | 8 +- src/hooks/useWalletBalances.ts | 4 +- src/model/wallet.ts | 2 + src/navigation/types.ts | 7 + src/screens/ChangeWalletSheet.tsx | 11 +- .../Diagnostics/DiagnosticsItemRow.tsx | 1 - src/screens/ExchangeModal.tsx | 2 +- src/screens/SendConfirmationSheet.tsx | 8 +- src/screens/{SendSheet.js => SendSheet.tsx} | 257 +++++++++++------- src/screens/SpeedUpAndCancelSheet.js | 2 +- src/screens/WalletConnectApprovalSheet.js | 8 +- .../ClaimingTransactionClaimable.tsx | 1 - src/utils/ethereumUtils.ts | 9 +- src/utils/neverRerender.ts | 3 +- 38 files changed, 338 insertions(+), 197 deletions(-) rename src/components/{Divider.js => Divider.tsx} (55%) rename src/components/send/{SendHeader.js => SendHeader.tsx} (76%) rename src/components/sheet/{SheetHandleFixedToTop.js => SheetHandleFixedToTop.tsx} (89%) rename src/screens/{SendSheet.js => SendSheet.tsx} (79%) diff --git a/src/components/AddFundsInterstitial.js b/src/components/AddFundsInterstitial.js index ac238273c51..01128c72803 100644 --- a/src/components/AddFundsInterstitial.js +++ b/src/components/AddFundsInterstitial.js @@ -7,9 +7,9 @@ import showWalletErrorAlert from '../helpers/support'; import { useNavigation } from '../navigation/Navigation'; import { useTheme } from '../theme/ThemeContext'; import { deviceUtils, magicMemo } from '../utils'; -import Divider from './Divider'; -import { ButtonPressAnimation, ScaleButtonZoomableAndroid } from './animations'; -import { Icon } from './icons'; +import Divider from '@/components/Divider'; +import { ButtonPressAnimation, ScaleButtonZoomableAndroid } from '@/components/animations'; +import { Icon } from '@/components/icons'; import { Centered, Row, RowWithMargins } from './layout'; import { Text } from './text'; import { analyticsV2 } from '@/analytics'; diff --git a/src/components/Divider.js b/src/components/Divider.tsx similarity index 55% rename from src/components/Divider.js rename to src/components/Divider.tsx index 577230a8a91..08a22f7c437 100644 --- a/src/components/Divider.js +++ b/src/components/Divider.tsx @@ -3,10 +3,12 @@ import React from 'react'; import { magicMemo } from '../utils'; import styled from '@/styled-thing'; import { borders, position } from '@/styles'; +import { StyleProp, View, ViewProps, ViewStyle } from 'react-native'; +import { ThemeContextProps, useTheme } from '@/theme'; export const DividerSize = 2; -const buildInsetFromProps = inset => { +const buildInsetFromProps = (inset: number | number[]) => { if (!inset) return [0, 0, 0, 0]; if (isNumber(inset)) return [inset, inset, inset, inset]; @@ -15,35 +17,41 @@ const buildInsetFromProps = inset => { return [inset[0], rightInset, inset[2] || inset[0], !isNil(inset[3]) ? inset[3] : rightInset]; }; -const horizontalBorderLineStyles = inset => ` +const horizontalBorderLineStyles = (inset: number[]) => ` ${inset[3] ? borders.buildRadius('left', 2) : ''} ${inset[1] ? borders.buildRadius('right', 2) : ''} left: ${inset[3]}; right: ${inset[1]}; `; -horizontalBorderLineStyles.object = inset => ({ +horizontalBorderLineStyles.object = (inset: number[]) => ({ ...(inset[3] ? borders.buildRadiusAsObject('left', 2) : {}), ...(inset[1] ? borders.buildRadiusAsObject('right', 2) : {}), left: inset[3], right: inset[1], }); -const verticalBorderLineStyles = inset => ` +const verticalBorderLineStyles = (inset: number[]) => ` ${inset[2] ? borders.buildRadius('bottom', 2) : ''} ${inset[0] ? borders.buildRadius('top', 2) : ''} bottom: ${inset[2]}; top: ${inset[0]}; `; -verticalBorderLineStyles.object = inset => ({ +verticalBorderLineStyles.object = (inset: number[]) => ({ ...(inset[2] ? borders.buildRadiusAsObject('bottom', 2) : {}), ...(inset[0] ? borders.buildRadiusAsObject('top', 2) : {}), bottom: inset[2], top: inset[0], }); -const BorderLine = styled.View(({ color, horizontal, inset }) => { +type BorderLineProps = { + color: string; + horizontal: boolean; + inset: number | number[]; +}; + +const BorderLine = styled(View)(({ color, horizontal, inset }: BorderLineProps) => { const insetFromProps = buildInsetFromProps(inset); return { ...position.coverAsObject, @@ -52,14 +60,37 @@ const BorderLine = styled.View(({ color, horizontal, inset }) => { }; }); -const Container = styled.View({ - backgroundColor: ({ backgroundColor, theme: { colors } }) => backgroundColor || colors.white, +type ContainerProps = { + backgroundColor: string; + horizontal: boolean; + size: number; + theme: ThemeContextProps; +}; + +const Container = styled(View)({ + backgroundColor: ({ backgroundColor, theme: { colors } }: ContainerProps) => backgroundColor || colors.white, flexShrink: 0, - height: ({ horizontal, size }) => (horizontal ? size : '100%'), - width: ({ horizontal, size }) => (horizontal ? '100%' : size), + height: ({ horizontal, size }: ContainerProps) => (horizontal ? size : '100%'), + width: ({ horizontal, size }: ContainerProps) => (horizontal ? '100%' : size), }); -const Divider = ({ backgroundColor, color, horizontal = true, inset = [0, 0, 0, 19], size = DividerSize, ...props }) => { +type DividerProps = { + backgroundColor?: string; + color?: string; + horizontal?: boolean; + inset?: number | number[]; + size?: number; + flex?: number; +}; + +const Divider = ({ + backgroundColor = undefined, + color = undefined, + horizontal = true, + inset = [0, 0, 0, 19], + size = DividerSize, + ...props +}: DividerProps) => { const { colors } = useTheme(); return ( diff --git a/src/components/L2Disclaimer.js b/src/components/L2Disclaimer.js index 04fe6024fd3..3e44506a096 100644 --- a/src/components/L2Disclaimer.js +++ b/src/components/L2Disclaimer.js @@ -1,6 +1,6 @@ import React from 'react'; import RadialGradient from 'react-native-radial-gradient'; -import Divider from './Divider'; +import Divider from '@/components/Divider'; import ButtonPressAnimation from './animations/ButtonPressAnimation'; import ChainBadge from './coin-icon/ChainBadge'; import { Column, Row } from './layout'; diff --git a/src/components/change-wallet/WalletList.tsx b/src/components/change-wallet/WalletList.tsx index ee7679fe455..5d08e917f60 100644 --- a/src/components/change-wallet/WalletList.tsx +++ b/src/components/change-wallet/WalletList.tsx @@ -6,7 +6,7 @@ import { FlatList } from 'react-native-gesture-handler'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import WalletTypes from '../../helpers/walletTypes'; import { address } from '../../utils/abbreviations'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { EmptyAssetList } from '../asset-list'; import { Column } from '../layout'; import AddressRow from './AddressRow'; diff --git a/src/components/coin-row/CollectiblesSendRow.tsx b/src/components/coin-row/CollectiblesSendRow.tsx index 45d3e37cbea..4f87045430d 100644 --- a/src/components/coin-row/CollectiblesSendRow.tsx +++ b/src/components/coin-row/CollectiblesSendRow.tsx @@ -3,7 +3,7 @@ import { PressableProps, TouchableWithoutFeedback } from 'react-native'; import { buildAssetUniqueIdentifier } from '../../helpers/assets'; import { useTheme } from '../../theme/ThemeContext'; import { deviceUtils, getUniqueTokenType, magicMemo } from '../../utils'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { ButtonPressAnimation } from '../animations'; import { RequestVendorLogoIcon } from '../coin-icon'; import { Centered } from '../layout'; @@ -114,7 +114,6 @@ const CollectiblesSendRow = React.memo( {isFirstRow && ( - {/* @ts-expect-error JavaScript component */} )} diff --git a/src/components/exchange/CurrencySelectModalHeader.tsx b/src/components/exchange/CurrencySelectModalHeader.tsx index ed4cffbd286..204946d9c4e 100644 --- a/src/components/exchange/CurrencySelectModalHeader.tsx +++ b/src/components/exchange/CurrencySelectModalHeader.tsx @@ -39,7 +39,6 @@ export default function CurrencySelectModalHeader({ return ( - {/** @ts-expect-error JavaScript component */} {showHandle && } {showBackButton && ( diff --git a/src/components/exchange/NetworkSwitcher.js b/src/components/exchange/NetworkSwitcher.js index efdb540ad31..821af7a514d 100644 --- a/src/components/exchange/NetworkSwitcher.js +++ b/src/components/exchange/NetworkSwitcher.js @@ -1,9 +1,9 @@ import lang from 'i18n-js'; import React from 'react'; import RadialGradient from 'react-native-radial-gradient'; -import Divider from '../Divider'; -import ChainBadge from '../coin-icon/ChainBadge'; -import { ContextMenuButton } from '../context-menu'; +import Divider from '@/components/Divider'; +import ChainBadge from '@/components/coin-icon/ChainBadge'; +import { ContextMenuButton } from '@/components/context-menu'; import { Column, Row } from '../layout'; import { Text } from '../text'; import { padding, position } from '@/styles'; diff --git a/src/components/expanded-state/AvailableNetworks.js b/src/components/expanded-state/AvailableNetworks.js index 4c32fe9c2be..29ac98354ca 100644 --- a/src/components/expanded-state/AvailableNetworks.js +++ b/src/components/expanded-state/AvailableNetworks.js @@ -10,7 +10,7 @@ import { useTheme } from '@/theme'; import { ButtonPressAnimation } from '../animations'; import { Column, Row } from '../layout'; import { ChainBadge } from '../coin-icon'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { Text } from '../text'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { ChainId } from '@/chains/types'; diff --git a/src/components/expanded-state/AvailableNetworksv2.tsx b/src/components/expanded-state/AvailableNetworksv2.tsx index 52917d1d693..9a53a2c605f 100644 --- a/src/components/expanded-state/AvailableNetworksv2.tsx +++ b/src/components/expanded-state/AvailableNetworksv2.tsx @@ -1,8 +1,8 @@ import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; import RadialGradient from 'react-native-radial-gradient'; -import Divider from '../Divider'; -import ChainBadge from '../coin-icon/ChainBadge'; +import Divider from '@/components/Divider'; +import ChainBadge from '@/components/coin-icon/ChainBadge'; import { Box, Inline, Text } from '@/design-system'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; diff --git a/src/components/expanded-state/CustomGasState.js b/src/components/expanded-state/CustomGasState.js index d19af128e10..425befacda5 100644 --- a/src/components/expanded-state/CustomGasState.js +++ b/src/components/expanded-state/CustomGasState.js @@ -1,7 +1,7 @@ import { useIsFocused, useRoute } from '@react-navigation/native'; import React, { useEffect, useState } from 'react'; import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { ExchangeHeader } from '../exchange'; import { FloatingPanel } from '../floating-panels'; import { GasSpeedButton } from '../gas'; diff --git a/src/components/expanded-state/profile/ProfileModal.tsx b/src/components/expanded-state/profile/ProfileModal.tsx index 84063391422..deeaf60d11f 100644 --- a/src/components/expanded-state/profile/ProfileModal.tsx +++ b/src/components/expanded-state/profile/ProfileModal.tsx @@ -1,7 +1,7 @@ import lang from 'i18n-js'; import React, { useCallback, useRef } from 'react'; import { View } from 'react-native'; -import Divider from '../../Divider'; +import Divider from '@/components/Divider'; import { ButtonPressAnimation } from '../../animations'; import { BiometricButtonContent } from '../../buttons'; import CopyTooltip from '../../copy-tooltip'; diff --git a/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js b/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js index 69ddc5bc69e..135dd07202a 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js +++ b/src/components/expanded-state/swap-details/SwapDetailsSlippageMessage.js @@ -1,6 +1,6 @@ import lang from 'i18n-js'; import React from 'react'; -import Divider from '../../Divider'; +import Divider from '@/components/Divider'; import { Centered, Column, ColumnWithMargins, Row } from '../../layout'; import { Emoji, Text } from '../../text'; import styled from '@/styled-thing'; diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 209d2dd2a39..470415b39de 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -139,13 +139,13 @@ const GasSpeedButton = ({ marginBottom = 20, marginTop = 18, speeds = null, - testID, + testID = 'gas-speed-button', theme = 'dark', canGoBack = true, - showGasOptions, - validateGasParams, + showGasOptions = false, + validateGasParams = undefined, flashbotTransaction = false, - crossChainServiceTime, + crossChainServiceTime = undefined, loading = false, }) => { const { colors } = useTheme(); diff --git a/src/components/layout/LayoutWithDividers.js b/src/components/layout/LayoutWithDividers.js index f9dd16fd1db..beac4e45665 100644 --- a/src/components/layout/LayoutWithDividers.js +++ b/src/components/layout/LayoutWithDividers.js @@ -1,5 +1,5 @@ import React, { Children, createElement, Fragment, useMemo } from 'react'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import Flex from './Flex'; const LayoutWithDividers = ({ children, dividerHorizontal, dividerRenderer = Divider, ...props }, ref) => { diff --git a/src/components/list/ListHeader.js b/src/components/list/ListHeader.js index 16f94b6d21e..349f2e4a5d4 100644 --- a/src/components/list/ListHeader.js +++ b/src/components/list/ListHeader.js @@ -1,7 +1,7 @@ import lang from 'i18n-js'; -import React, { createElement, Fragment } from 'react'; +import React, { Fragment } from 'react'; import { Share } from 'react-native'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { ButtonPressAnimation } from '../animations'; import CoinDividerButtonLabel from '../coin-divider/CoinDividerButtonLabel'; import { ContextMenu } from '../context-menu'; diff --git a/src/components/list/ListItemDivider.js b/src/components/list/ListItemDivider.js index 307e5a8de24..e4a45450e40 100644 --- a/src/components/list/ListItemDivider.js +++ b/src/components/list/ListItemDivider.js @@ -1,5 +1,5 @@ import { PropTypes } from 'prop-types'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import styled from '@/styled-thing'; import { neverRerender } from '@/utils'; diff --git a/src/components/modal/ModalFooterButtonsRow.js b/src/components/modal/ModalFooterButtonsRow.js index d5ef24d7d2e..294ebb86741 100644 --- a/src/components/modal/ModalFooterButtonsRow.js +++ b/src/components/modal/ModalFooterButtonsRow.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React, { Children, Fragment } from 'react'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import { Row } from '../layout'; import styled from '@/styled-thing'; diff --git a/src/components/send/SendAssetList.js b/src/components/send/SendAssetList.js index 5c849b9f4fd..5ad4919144b 100644 --- a/src/components/send/SendAssetList.js +++ b/src/components/send/SendAssetList.js @@ -4,7 +4,7 @@ import { View } from 'react-primitives'; import { DataProvider, LayoutProvider, RecyclerListView } from 'recyclerlistview'; import { buildCoinsList } from '../../helpers/assets'; import { deviceUtils } from '../../utils'; -import Divider, { DividerSize } from '../Divider'; +import Divider, { DividerSize } from '@/components/Divider'; import { FlyInAnimation } from '../animations'; import { CoinDividerOpenButton } from '../coin-divider'; import { CollectiblesSendRow, SendCoinRow } from '../coin-row'; diff --git a/src/components/send/SendHeader.js b/src/components/send/SendHeader.tsx similarity index 76% rename from src/components/send/SendHeader.js rename to src/components/send/SendHeader.tsx index fe4ef3c7047..57d65e7281d 100644 --- a/src/components/send/SendHeader.js +++ b/src/components/send/SendHeader.tsx @@ -1,13 +1,13 @@ import { isHexString } from '@ethersproject/bytes'; import lang from 'i18n-js'; import isEmpty from 'lodash/isEmpty'; -import React, { Fragment, useCallback, useEffect, useMemo } from 'react'; -import { ActivityIndicator, Keyboard } from 'react-native'; +import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'; +import { ActivityIndicator, Keyboard, TextInput } from 'react-native'; import { useNavigation } from '../../navigation/Navigation'; -import { useTheme } from '../../theme/ThemeContext'; -import Divider from '../Divider'; -import Spinner from '../Spinner'; -import { ButtonPressAnimation } from '../animations'; +import { ThemeContextProps, useTheme } from '../../theme/ThemeContext'; +import Divider from '@/components/Divider'; +import Spinner from '@/components/Spinner'; +import { ButtonPressAnimation } from '@/components/animations'; import { PasteAddressButton } from '../buttons'; import showDeleteContactActionSheet from '../contacts/showDeleteContactActionSheet'; import { AddressField } from '../fields'; @@ -17,35 +17,46 @@ import { Label, Text } from '../text'; import useExperimentalFlag, { PROFILES } from '@/config/experimentalHooks'; import { resolveNameOrAddress } from '@/handlers/web3'; import { removeFirstEmojiFromString } from '@/helpers/emojiHandler'; -import { useClipboard, useDimensions } from '@/hooks'; +import { useClipboard, useDimensions, useContacts } from '@/hooks'; import Routes from '@/navigation/routesNames'; import styled from '@/styled-thing'; import { padding } from '@/styles'; import { profileUtils, showActionSheetWithOptions } from '@/utils'; +import { IS_ANDROID } from '@/env'; +import { RainbowAccount } from '@/model/wallet'; +import { Contact } from '@/redux/contacts'; -const AddressInputContainer = styled(Row).attrs({ align: 'center' })(({ isSmallPhone, theme: { colors }, isTinyPhone }) => ({ - ...(android - ? padding.object(0, 19) - : isTinyPhone - ? padding.object(23, 15, 10) - : isSmallPhone - ? padding.object(11, 19, 15) - : padding.object(18, 19, 19)), - backgroundColor: colors.white, - overflow: 'hidden', - width: '100%', -})); +type ComponentPropsWithTheme = { + theme: ThemeContextProps; + isSmallPhone: boolean; + isTinyPhone: boolean; +}; + +const AddressInputContainer = styled(Row).attrs({ align: 'center' })( + ({ isSmallPhone, theme: { colors }, isTinyPhone }: ComponentPropsWithTheme) => ({ + ...(IS_ANDROID + ? padding.object(0, 19) + : isTinyPhone + ? padding.object(23, 15, 10) + : isSmallPhone + ? padding.object(11, 19, 15) + : padding.object(18, 19, 19)), + backgroundColor: colors.white, + overflow: 'hidden', + width: '100%', + }) +); const AddressFieldLabel = styled(Label).attrs({ size: 'large', weight: 'bold', })({ - color: ({ theme: { colors } }) => colors.alpha(colors.blueGreyDark, 0.6), + color: ({ theme: { colors } }: ComponentPropsWithTheme) => colors.alpha(colors.blueGreyDark, 0.6), marginRight: 4, opacity: 1, }); -const LoadingSpinner = styled(android ? Spinner : ActivityIndicator).attrs(({ theme: { colors } }) => ({ +const LoadingSpinner = styled(android ? Spinner : ActivityIndicator).attrs(({ theme: { colors } }: ComponentPropsWithTheme) => ({ color: colors.alpha(colors.blueGreyDark, 0.3), }))({ marginRight: 2, @@ -64,11 +75,31 @@ const defaultContactItem = { nickname: '', }; +type AccountWithContact = RainbowAccount & Contact; + +type SendHeaderProps = { + contacts: ReturnType['contacts']; + hideDivider: boolean; + isValidAddress: boolean; + fromProfile?: boolean; + nickname: string; + onChangeAddressInput: (text: string) => void; + onFocus?: () => void; + onPressPaste: (recipient: string) => void; + onRefocusInput?: () => void; + recipient: string; + recipientFieldRef: React.RefObject; + removeContact: (address: string) => void; + showAssetList: boolean; + userAccounts: RainbowAccount[]; + watchedAccounts: RainbowAccount[]; +}; + export default function SendHeader({ contacts, hideDivider, isValidAddress, - fromProfile, + fromProfile = false, nickname, onChangeAddressInput, onFocus, @@ -80,7 +111,7 @@ export default function SendHeader({ showAssetList, userAccounts, watchedAccounts, -}) { +}: SendHeaderProps) { const profilesEnabled = useExperimentalFlag(PROFILES); const { setClipboard } = useClipboard(); const { isSmallPhone, isTinyPhone } = useDimensions(); @@ -120,7 +151,7 @@ export default function SendHeader({ removeFirstEmojiFromString(contact?.nickname || userWallet?.label || nickname) || userWallet?.ens || contact?.ens || recipient; const handleNavigateToContact = useCallback(() => { - let color = ''; + let color = 0; const nickname = !isHexString(name) ? name : ''; if (!profilesEnabled) { color = contact?.color; @@ -154,7 +185,7 @@ export default function SendHeader({ lang.t('contacts.options.cancel'), // <-- cancelButtonIndex ], }, - async buttonIndex => { + async (buttonIndex: number) => { if (buttonIndex === 0) { showDeleteContactActionSheet({ address: hexAddress, @@ -166,10 +197,10 @@ export default function SendHeader({ }); } else if (buttonIndex === 1) { handleNavigateToContact(); - onRefocusInput(); + onRefocusInput?.(); } else if (buttonIndex === 2) { setClipboard(hexAddress); - onRefocusInput(); + onRefocusInput?.(); } } ); @@ -186,7 +217,7 @@ export default function SendHeader({ ]); const onChange = useCallback( - text => { + (text: string) => { onChangeAddressInput(text); setHexAddress(''); }, diff --git a/src/components/sheet/SheetDivider.js b/src/components/sheet/SheetDivider.js index d5239c94080..b01cb634de8 100644 --- a/src/components/sheet/SheetDivider.js +++ b/src/components/sheet/SheetDivider.js @@ -1,5 +1,5 @@ import { neverRerender } from '../../utils'; -import Divider from '../Divider'; +import Divider from '@/components/Divider'; import styled from '@/styled-thing'; const SheetDivider = styled(Divider).attrs(({ theme: { colors } }) => ({ diff --git a/src/components/sheet/SheetHandleFixedToTop.js b/src/components/sheet/SheetHandleFixedToTop.tsx similarity index 89% rename from src/components/sheet/SheetHandleFixedToTop.js rename to src/components/sheet/SheetHandleFixedToTop.tsx index 36aca808439..b1583a710c8 100644 --- a/src/components/sheet/SheetHandleFixedToTop.js +++ b/src/components/sheet/SheetHandleFixedToTop.tsx @@ -18,7 +18,7 @@ const Container = styled(Centered)({ zIndex: 9, }); -export default function SheetHandleFixedToTop({ showBlur }) { +export default function SheetHandleFixedToTop({ showBlur = false }) { return ( diff --git a/src/entities/transactions/transaction.ts b/src/entities/transactions/transaction.ts index 83024af9234..e611e8d007e 100644 --- a/src/entities/transactions/transaction.ts +++ b/src/entities/transactions/transaction.ts @@ -8,7 +8,8 @@ import { SwapMetadata } from '@/raps/references'; import { UniqueAsset } from '../uniqueAssets'; import { ParsedAsset } from '@/resources/assets/types'; import { TransactionStatus, TransactionType } from '@/resources/transactions/types'; -import { ChainId, Network } from '@/chains/types'; +import { ChainId } from '@/chains/types'; +import { BytesLike } from '@ethersproject/bytes'; export type TransactionDirection = 'in' | 'out' | 'self'; @@ -43,7 +44,7 @@ export interface RainbowTransaction { }; direction?: TransactionDirection; description?: string; - data?: string; // for pending tx + data?: string | BytesLike; // for pending tx from: EthereumAddress | null; gasLimit?: BigNumberish; gasPrice?: BigNumberish; diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts index 1a36621d926..8955ee41ddb 100644 --- a/src/handlers/web3.ts +++ b/src/handlers/web3.ts @@ -91,7 +91,7 @@ type TransactionDetailsReturned = { * This is useful for functions that assume that certain fields are not set * to null on a `NewTransaction`. */ -type NewTransactionNonNullable = { +export type NewTransactionNonNullable = { [key in keyof NewTransaction]-?: NonNullable; }; @@ -657,7 +657,7 @@ export const buildTransaction = async ( amount: number; gasLimit?: string; }, - provider: StaticJsonRpcProvider | null, + provider: StaticJsonRpcProvider | undefined, chainId: ChainId ): Promise => { const _amount = amount && Number(amount) ? convertAmountToRawAmount(amount, asset.decimals) : estimateAssetBalancePortion(asset); @@ -713,7 +713,7 @@ export const estimateGasLimit = async ( amount: number; }, addPadding = false, - provider: StaticJsonRpcProvider | null = null, + provider: StaticJsonRpcProvider | undefined = undefined, chainId: ChainId = ChainId.mainnet ): Promise => { const estimateGasData = await buildTransaction({ address, amount, asset, recipient }, provider, chainId); diff --git a/src/helpers/validators.ts b/src/helpers/validators.ts index 39189567be8..c2c5fce0942 100644 --- a/src/helpers/validators.ts +++ b/src/helpers/validators.ts @@ -33,8 +33,7 @@ export const isENSAddressFormat = memoFn(address => { return true; }); -export const isUnstoppableAddressFormat = memoFn(address => { - // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. +export const isUnstoppableAddressFormat = memoFn((address: string) => { const parts = !!address && address.split('.'); if ( !parts || @@ -89,7 +88,7 @@ export const checkIsValidAddressOrDomain = async (address: any) => { * @param {String} ENS, or Unstoppable * @return {Boolean} */ -export const isValidDomainFormat = memoFn(domain => { +export const isValidDomainFormat = memoFn((domain: string) => { return isUnstoppableAddressFormat(domain) || isENSAddressFormat(domain); }); /** diff --git a/src/hooks/useMaxInputBalance.ts b/src/hooks/useMaxInputBalance.ts index 4ad3f124dd3..791a5974de5 100644 --- a/src/hooks/useMaxInputBalance.ts +++ b/src/hooks/useMaxInputBalance.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react'; import useGas from './useGas'; import { ethereumUtils } from '@/utils'; -import { ParsedAddressAsset } from '@/entities'; +import { ParsedAddressAsset, UniqueAsset } from '@/entities'; export default function useMaxInputBalance() { const [maxInputBalance, setMaxInputBalance] = useState('0'); @@ -9,7 +9,11 @@ export default function useMaxInputBalance() { const { selectedGasFee, l1GasFeeOptimism } = useGas(); const updateMaxInputBalance = useCallback( - (inputCurrency: ParsedAddressAsset) => { + (inputCurrency: ParsedAddressAsset | UniqueAsset | undefined) => { + const isUniqueAssetOrUndefined = typeof inputCurrency === 'undefined' || 'collection' in inputCurrency; + if (isUniqueAssetOrUndefined) { + return '0'; + } // Update current balance const newInputBalance = ethereumUtils.getBalanceAmount(selectedGasFee, inputCurrency, l1GasFeeOptimism); diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 5e52dbf1aa2..695c4faaa92 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -14,7 +14,7 @@ const QUERY_CONFIG = { cacheTime: 1000 * 60 * 60 * 24, // 24 hours }; -type WalletBalance = { +export type WalletBalance = { assetBalanceAmount: string; assetBalanceDisplay: string; positionsBalanceAmount: string; @@ -23,7 +23,7 @@ type WalletBalance = { totalBalanceDisplay: string; }; -type WalletBalanceResult = { +export type WalletBalanceResult = { balances: Record; isLoading: boolean; }; diff --git a/src/model/wallet.ts b/src/model/wallet.ts index 3c3b61200ce..c1d12fd303c 100644 --- a/src/model/wallet.ts +++ b/src/model/wallet.ts @@ -58,6 +58,7 @@ import { Signer } from '@ethersproject/abstract-signer'; import { sanitizeTypedData } from '@/utils/signingUtils'; import { ExecuteFnParamsWithoutFn, performanceTracking, Screen } from '@/state/performance/performance'; import { Network } from '@/chains/types'; +import { WalletBalanceResult } from '@/hooks/useWalletBalances'; export type EthereumPrivateKey = string; type EthereumMnemonic = string; @@ -122,6 +123,7 @@ export interface RainbowAccount { avatar: null | string; color: number; visible: boolean; + ens?: string; image?: string | null; } diff --git a/src/navigation/types.ts b/src/navigation/types.ts index f8eae26f27d..fbb848d8972 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -5,6 +5,7 @@ import Routes from '@/navigation/routesNames'; import { PortalSheetProps } from '@/screens/Portal'; import { REGISTRATION_MODES } from '@/helpers/ens'; import { CampaignCheckResult } from '@/components/remote-promo-sheet/checkForRemotePromoSheet'; +import { ParsedAddressAsset, UniqueAsset } from '@/entities'; import { Claimable } from '@/resources/addys/claimables/types'; export type PartialNavigatorConfigOptions = Pick['Screen']>[0]>, 'options'>; @@ -18,6 +19,12 @@ declare global { } export type RootStackParamList = { + [Routes.SEND_SHEET]: { + asset?: ParsedAddressAsset | UniqueAsset; + address?: string; + nativeAmount?: string; + fromProfile?: boolean; + }; [Routes.CHANGE_WALLET_SHEET]: { watchOnly: boolean; currentAccountAddress: string; diff --git a/src/screens/ChangeWalletSheet.tsx b/src/screens/ChangeWalletSheet.tsx index bf5b6bc7817..2c287f32d1f 100644 --- a/src/screens/ChangeWalletSheet.tsx +++ b/src/screens/ChangeWalletSheet.tsx @@ -4,9 +4,9 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Alert, InteractionManager, View } from 'react-native'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { useDispatch } from 'react-redux'; -import Divider from '../components/Divider'; -import { ButtonPressAnimation } from '../components/animations'; -import WalletList from '../components/change-wallet/WalletList'; +import Divider from '@/components/Divider'; +import { ButtonPressAnimation } from '@/components/animations'; +import WalletList from '@/components/change-wallet/WalletList'; import { Centered, Column, Row } from '../components/layout'; import { Sheet, SheetTitle } from '../components/sheet'; import { Text } from '../components/text'; @@ -386,10 +386,7 @@ export default function ChangeWalletSheet() { )} - {showDividers && ( - // @ts-expect-error JS component - - )} + {showDividers && } { - {/* @ts-expect-error JS component */} ); diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 7c46f4519b5..5d6a1f2ea64 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -911,7 +911,6 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty )} - {/* @ts-expect-error - Javascript Component */} diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 579946fb0b7..2c63775d21e 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -8,7 +8,7 @@ import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'reac import { Keyboard } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import ContactRowInfoButton from '../components/ContactRowInfoButton'; -import Divider from '../components/Divider'; +import Divider from '@/components/Divider'; import L2Disclaimer from '../components/L2Disclaimer'; import Pill from '../components/Pill'; import TouchableBackdrop from '../components/TouchableBackdrop'; @@ -57,7 +57,7 @@ import { position } from '@/styles'; import { useTheme } from '@/theme'; import { getUniqueTokenType, promiseUtils } from '@/utils'; import { logger, RainbowError } from '@/logger'; -import { IS_ANDROID } from '@/env'; +import { IS_ANDROID, IS_IOS } from '@/env'; import { useConsolidatedTransactions } from '@/resources/transactions/consolidatedTransactions'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { performanceTracking, TimeToSignOperation, Screens } from '@/state/performance/performance'; @@ -467,8 +467,7 @@ export const SendConfirmationSheet = () => { return ( - {/* @ts-expect-error JavaScript component */} - {ios && } + {IS_IOS && } {lang.t('wallet.transaction.sending_title')} @@ -583,7 +582,6 @@ export const SendConfirmationSheet = () => { )} - {/* @ts-expect-error JavaScript component */} {(isL2 || isENS || shouldShowChecks) && ( diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.tsx similarity index 79% rename from src/screens/SendSheet.js rename to src/screens/SendSheet.tsx index 330776eb0f4..5d824eb8dc7 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.tsx @@ -1,8 +1,8 @@ -import { useRoute } from '@react-navigation/native'; +import { RouteProp, useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import { isEmpty, isEqual, isString } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { InteractionManager, Keyboard, StatusBar, View } from 'react-native'; +import { InteractionManager, Keyboard, StatusBar, TextInput, View } from 'react-native'; import { useDebounce } from 'use-debounce'; import { GasSpeedButton } from '../components/gas'; import { Column } from '../components/layout'; @@ -12,7 +12,7 @@ import { getDefaultCheckboxes } from './SendConfirmationSheet'; import { WrappedAlert as Alert } from '@/helpers/alert'; import { analytics } from '@/analytics'; import { PROFILES, useExperimentalFlag } from '@/config'; -import { AssetTypes } from '@/entities'; +import { AssetTypes, NewTransaction, ParsedAddressAsset, UniqueAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; import { debouncedFetchSuggestions } from '@/handlers/ens'; import { @@ -21,7 +21,9 @@ import { estimateGasLimit, getProvider, isL2Chain, + NewTransactionNonNullable, resolveNameOrAddress, + web3Provider, } from '@/handlers/web3'; import { checkIsValidAddressOrDomain, checkIsValidAddressOrDomainFormat, isENSAddressFormat } from '@/helpers/validators'; import { @@ -64,12 +66,20 @@ import { performanceTracking, Screens, TimeToSignOperation } from '@/state/perfo import { REGISTRATION_STEPS } from '@/helpers/ens'; import { ChainId } from '@/chains/types'; import { chainsName, chainsNativeAsset, needsL1SecurityFeeChains } from '@/chains'; +import { RootStackParamList } from '@/navigation/types'; +import { ThemeContextProps, useTheme } from '@/theme'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { Contact } from '@/redux/contacts'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; -const Container = styled.View({ - backgroundColor: ({ theme: { colors } }) => colors.transparent, +type ComponentPropsWithTheme = { + theme: ThemeContextProps; +}; + +const Container = styled(View)({ + backgroundColor: ({ theme: { colors } }: ComponentPropsWithTheme) => colors.transparent, flex: 1, paddingTop: IS_IOS ? 0 : statusBarHeight, width: '100%', @@ -80,15 +90,16 @@ const SheetContainer = styled(Column).attrs({ flex: 1, })({ ...borders.buildRadiusAsObject('top', IS_IOS ? 0 : 16), - backgroundColor: ({ theme: { colors } }) => colors.white, + backgroundColor: ({ theme: { colors } }: ComponentPropsWithTheme) => colors.white, height: sheetHeight, width: '100%', }); -const validateRecipient = (toAddress, tokenAddress) => { - if (toAddress?.toLowerCase() === tokenAddress?.toLowerCase()) { +const validateRecipient = (toAddress?: string, tokenAddress?: string) => { + if (!toAddress || toAddress?.toLowerCase() === tokenAddress?.toLowerCase()) { return false; } + // Don't allow send to known ERC20 contracts on mainnet if (rainbowTokenList.RAINBOW_TOKEN_LIST[toAddress.toLowerCase()]) { return false; @@ -96,7 +107,15 @@ const validateRecipient = (toAddress, tokenAddress) => { return true; }; -export default function SendSheet(props) { +type OnSubmitProps = { + ens?: { + setAddress: boolean; + transferControl: boolean; + clearRecords: boolean; + }; +}; + +export default function SendSheet() { const { goBack, navigate } = useNavigation(); const { data: sortedAssets } = useSortedUserAssets(); const { @@ -111,7 +130,7 @@ export default function SendSheet(props) { updateTxFee, l1GasFeeOptimism, } = useGas(); - const recipientFieldRef = useRef(); + const recipientFieldRef = useRef(null); const profilesEnabled = useExperimentalFlag(PROFILES); const { contacts, onRemoveContact, filteredContacts } = useContacts(); @@ -125,17 +144,17 @@ export default function SendSheet(props) { }); const { hiddenCoinsObj, pinnedCoinsObj } = useCoinListEditOptions(); - const [toAddress, setToAddress] = useState(); + const [toAddress, setToAddress] = useState(''); const [amountDetails, setAmountDetails] = useState({ assetAmount: '', isSufficientBalance: false, nativeAmount: '', }); - const [currentChainId, setCurrentChainId] = useState(); + const [currentChainId, setCurrentChainId] = useState(ChainId.mainnet); const prevChainId = usePrevious(currentChainId); const [currentInput, setCurrentInput] = useState(''); - const { params } = useRoute(); + const { params } = useRoute>(); const assetOverride = params?.asset; const prevAssetOverride = usePrevious(assetOverride); @@ -143,7 +162,7 @@ export default function SendSheet(props) { const nativeAmountOverride = params?.nativeAmount; const [recipient, setRecipient] = useState(''); const [nickname, setNickname] = useState(''); - const [selected, setSelected] = useState({}); + const [selected, setSelected] = useState(); const [maxEnabled, setMaxEnabled] = useState(false); const { maxInputBalance, updateMaxInputBalance } = useMaxInputBalance(); @@ -151,7 +170,7 @@ export default function SendSheet(props) { const [debouncedRecipient] = useDebounce(recipient, 500); const [isValidAddress, setIsValidAddress] = useState(!!recipientOverride); - const [currentProvider, setCurrentProvider] = useState(); + const [currentProvider, setCurrentProvider] = useState(); const theme = useTheme(); const { colors, isDarkMode } = theme; @@ -162,18 +181,18 @@ export default function SendSheet(props) { const showAssetForm = isValidAddress && !isEmpty(selected); const isNft = selected?.type === AssetTypes.nft; + const isUniqueAsset = selected && 'collection' in selected; - let colorForAsset = useColorForAsset(selected, null, false, true); - const nftColor = usePersistentDominantColorFromImage(selected?.lowResUrl) ?? colors.appleBlue; - + let colorForAsset = useColorForAsset(selected, undefined, false, true); + const nftColor = usePersistentDominantColorFromImage(isUniqueAsset && isNft ? selected?.lowResUrl : null) ?? colors.appleBlue; if (isNft) { colorForAsset = nftColor; } - const uniqueTokenType = isNft ? getUniqueTokenType(selected) : undefined; + const uniqueTokenType = isUniqueAsset && isNft ? getUniqueTokenType(selected) : undefined; const isENS = uniqueTokenType === 'ENS'; - const ensName = selected.uniqueId ? selected.uniqueId?.split(' ')?.[0] : selected.uniqueId; + const ensName = selected?.uniqueId ? selected.uniqueId?.split(' ')?.[0] : selected?.uniqueId ?? ''; const ensProfile = useENSProfile(ensName, { enabled: isENS, supportedRecordsOnly: false, @@ -184,11 +203,11 @@ export default function SendSheet(props) { }, [currentChainId]); const sendUpdateAssetAmount = useCallback( - newAssetAmount => { + (newAssetAmount: string) => { const _assetAmount = newAssetAmount.replace(/[^0-9.]/g, ''); let _nativeAmount = ''; if (_assetAmount.length) { - const priceUnit = selected?.price?.value ?? 0; + const priceUnit = !isUniqueAsset ? selected?.price?.value ?? 0 : selected?.currentPrice ?? 0; const { amount: convertedNativeAmount } = convertAmountAndPriceToNativeDisplay(_assetAmount, priceUnit, nativeCurrency); _nativeAmount = formatInputDecimals(convertedNativeAmount, _assetAmount); } @@ -201,11 +220,11 @@ export default function SendSheet(props) { nativeAmount: _nativeAmount, }); }, - [maxInputBalance, nativeCurrency, selected] + [isUniqueAsset, maxInputBalance, nativeCurrency, selected] ); const sendUpdateSelected = useCallback( - newSelected => { + (newSelected: ParsedAddressAsset | UniqueAsset | undefined) => { if (isEqual(newSelected, selected)) return; updateMaxInputBalance(newSelected); if (newSelected?.type === AssetTypes.nft) { @@ -215,8 +234,10 @@ export default function SendSheet(props) { nativeAmount: '0', }); + const isUniqueAsset = 'collection' in newSelected; + // Prevent a state update loop - if (selected?.uniqueId !== newSelected?.uniqueId) { + if (selected?.uniqueId !== newSelected?.uniqueId && isUniqueAsset) { setSelected({ ...newSelected, symbol: newSelected?.collection?.name, @@ -242,7 +263,7 @@ export default function SendSheet(props) { updateMaxInputBalance(assetOverride); } - if (nativeAmountOverride && !amountDetails.assetAmount && maxInputBalance) { + if (nativeAmountOverride && maxInputBalance) { sendUpdateAssetAmount(nativeAmountOverride); } }, [ @@ -267,7 +288,7 @@ export default function SendSheet(props) { startPollingGasFees(currentChainId); }); } - }, [startPollingGasFees, selected.chainId, prevChainId, currentChainId]); + }, [startPollingGasFees, selected?.chainId, prevChainId, currentChainId]); // Stop polling when the sheet is unmounted useEffect(() => { @@ -279,7 +300,7 @@ export default function SendSheet(props) { }, [stopPollingGasFees]); useEffect(() => { - const assetChainId = selected.chainId; + const assetChainId = selected?.chainId; if (assetChainId && (assetChainId !== currentChainId || !currentChainId || prevChainId !== currentChainId)) { if (chainId === ChainId.goerli) { setCurrentChainId(ChainId.goerli); @@ -291,10 +312,10 @@ export default function SendSheet(props) { setCurrentProvider(provider); } } - }, [currentChainId, isNft, chainId, prevChainId, selected?.chainId, sendUpdateSelected]); + }, [currentChainId, isNft, chainId, prevChainId, selected?.chainId]); const onChangeNativeAmount = useCallback( - newNativeAmount => { + (newNativeAmount: string) => { if (!isString(newNativeAmount)) return; if (maxEnabled) { setMaxEnabled(false); @@ -302,8 +323,9 @@ export default function SendSheet(props) { const _nativeAmount = newNativeAmount.replace(/[^0-9.]/g, ''); let _assetAmount = ''; if (_nativeAmount.length) { - const priceUnit = selected?.price?.value ?? 0; - const convertedAssetAmount = convertAmountFromNativeValue(_nativeAmount, priceUnit, selected.decimals); + const priceUnit = !isUniqueAsset ? selected?.price?.value ?? 0 : 0; + const decimals = !isUniqueAsset ? selected?.decimals ?? 18 : 0; + const convertedAssetAmount = convertAmountFromNativeValue(_nativeAmount, priceUnit, decimals); _assetAmount = formatInputDecimals(convertedAssetAmount, _nativeAmount); } @@ -315,7 +337,7 @@ export default function SendSheet(props) { }); analytics.track('Changed native currency input in Send flow'); }, - [maxEnabled, maxInputBalance, selected.decimals, selected?.price?.value] + [maxEnabled, maxInputBalance, isUniqueAsset, selected] ); useEffect(() => { @@ -328,7 +350,7 @@ export default function SendSheet(props) { }, [selected, sendUpdateAssetAmount, updateMaxInputBalance, selectedGasFee, maxEnabled]); const onChangeAssetAmount = useCallback( - newAssetAmount => { + (newAssetAmount: string) => { if (isString(newAssetAmount)) { if (maxEnabled) { setMaxEnabled(false); @@ -358,26 +380,30 @@ export default function SendSheet(props) { }, [debouncedRecipient]); const updateTxFeeForOptimism = useCallback( - async updatedGasLimit => { + async (updatedGasLimit: string) => { + if (!selected) return; + const txData = await buildTransaction( { address: accountAddress, - amount: amountDetails.assetAmount, - asset: selected, + amount: Number(amountDetails.assetAmount), + asset: selected as ParsedAddressAsset, gasLimit: updatedGasLimit, recipient: toAddress, }, currentProvider, currentChainId ); - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txData, currentProvider); + const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txData, currentProvider || web3Provider); updateTxFee(updatedGasLimit, null, l1GasFeeOptimism); }, [accountAddress, amountDetails.assetAmount, currentChainId, currentProvider, selected, toAddress, updateTxFee] ); const onSubmit = useCallback( - async ({ ens: { setAddress, transferControl, clearRecords } = {} } = {}) => { + async ({ ens }: OnSubmitProps = {}) => { + if (!selected) return; + const wallet = await performanceTracking.getState().executeFn({ fn: loadWallet, operation: TimeToSignOperation.KeychainRead, @@ -391,7 +417,7 @@ export default function SendSheet(props) { }); if (!wallet) return; - const currentChainIdNetwork = chainsName[currentChainId]; + const currentChainIdNetwork = chainsName[currentChainId ?? ChainId.mainnet]; const validTransaction = isValidAddress && amountDetails.isSufficientBalance && isSufficientGas && isValidGas; if (!selectedGasFee?.gasFee?.estimatedFee || !validTransaction) { @@ -407,13 +433,13 @@ export default function SendSheet(props) { let updatedGasLimit = null; // Attempt to update gas limit before sending ERC20 / ERC721 - if (!isNativeAsset(selected.address, currentChainId)) { + if (!isUniqueAsset && selected && !isNativeAsset(selected.address, currentChainId)) { try { // Estimate the tx with gas limit padding before sending updatedGasLimit = await estimateGasLimit( { address: accountAddress, - amount: amountDetails.assetAmount, + amount: Number(amountDetails.assetAmount), asset: selected, recipient: toAddress, }, @@ -422,7 +448,7 @@ export default function SendSheet(props) { currentChainId ); - if (!lessThan(updatedGasLimit, gasLimit)) { + if (updatedGasLimit && !lessThan(updatedGasLimit, gasLimit)) { if (needsL1SecurityFeeChains.includes(currentChainId)) { updateTxFeeForOptimism(updatedGasLimit); } else { @@ -435,29 +461,39 @@ export default function SendSheet(props) { let nextNonce; - if (isENS && toAddress && (clearRecords || setAddress || transferControl)) { - const { nonce } = await transferENS({ - clearRecords, + if (isENS && toAddress && (ens?.clearRecords || ens?.setAddress || ens?.transferControl)) { + const transferENSResult = await transferENS({ + clearRecords: ens.clearRecords, name: ensName, records: { ...(ensProfile?.data?.contenthash ? { contenthash: ensProfile?.data?.contenthash } : {}), ...(ensProfile?.data?.records || {}), ...(ensProfile?.data?.coinAddresses || {}), }, - setAddress, + setAddress: ens.setAddress, toAddress, - transferControl, + transferControl: ens.transferControl, wallet, }); - nextNonce = nonce + 1; + + if (!transferENSResult) { + logger.error(new RainbowError(`[SendSheet]: transferENS failed`), { + transferENSResult, + }); + return; + } + + if (typeof transferENSResult.nonce === 'number') { + nextNonce = transferENSResult.nonce + 1; + } } const gasLimitToUse = updatedGasLimit && !lessThan(updatedGasLimit, gasLimit) ? updatedGasLimit : gasLimit; const gasParams = parseGasParamsForTransaction(selectedGasFee); - const txDetails = { + const txDetails: Partial = { amount: amountDetails.assetAmount, - asset: selected, + asset: selected as ParsedAddressAsset, from: accountAddress, gasLimit: gasLimitToUse, network: currentChainIdNetwork, @@ -472,7 +508,7 @@ export default function SendSheet(props) { fn: createSignableTransaction, operation: TimeToSignOperation.CreateSignableTransaction, screen: isENS ? Screens.SEND_ENS : Screens.SEND, - })(txDetails); + })(txDetails as NewTransactionNonNullable); if (!signableTransaction.to) { logger.error(new RainbowError(`[SendSheet]: txDetails is missing the "to" field`), { txDetails, @@ -481,38 +517,68 @@ export default function SendSheet(props) { Alert.alert(lang.t('wallet.transaction.alert.invalid_transaction')); submitSuccess = false; } else { - const { result: txResult, error } = await performanceTracking.getState().executeFn({ + const sendTransactionResult = await performanceTracking.getState().executeFn({ fn: sendTransaction, screen: isENS ? Screens.SEND_ENS : Screens.SEND, operation: TimeToSignOperation.BroadcastTransaction, })({ existingWallet: wallet, provider: currentProvider, - transaction: signableTransaction, + transaction: { + ...signableTransaction, + to: signableTransaction.to, + data: signableTransaction.data, + from: signableTransaction.from, + gasLimit: signableTransaction.gasLimit, + chainId: signableTransaction.chainId as ChainId, + value: signableTransaction.value, + nonce: signableTransaction.nonce, + }, }); - if (error) { - throw new Error(`SendSheet sendTransaction failed`); + if (!sendTransactionResult || !sendTransactionResult.result) { + logger.error(new RainbowError(`[SendSheet]: No result from sendTransaction`), { + sendTransactionResult, + signableTransaction, + }); + return; } - const { hash, nonce } = txResult; + if (sendTransactionResult?.error) { + logger.error(new RainbowError(`[SendSheet]: Error from sendTransaction`), { + sendTransactionResult, + signableTransaction, + }); + return; + } + + const { hash, nonce } = sendTransactionResult.result; const { data, value } = signableTransaction; if (!isEmpty(hash)) { submitSuccess = true; - txDetails.hash = hash; + + if (hash) { + txDetails.hash = hash; + } + + if (data) { + txDetails.data = data; + } + + if (value) { + txDetails.value = value; + } + txDetails.nonce = nonce; txDetails.network = currentChainIdNetwork; txDetails.chainId = currentChainId; - txDetails.data = data; - txDetails.value = value; txDetails.txTo = signableTransaction.to; - txDetails.pending = true; txDetails.type = 'send'; txDetails.status = 'pending'; addNewTransaction({ address: accountAddress, chainId: currentChainId, - transaction: txDetails, + transaction: txDetails as NewTransaction, }); } } @@ -544,6 +610,7 @@ export default function SendSheet(props) { gasLimit, isENS, isSufficientGas, + isUniqueAsset, isValidAddress, isValidGas, selected, @@ -556,14 +623,14 @@ export default function SendSheet(props) { ); const submitTransaction = useCallback( - async (...args) => { + async (args: OnSubmitProps) => { if (Number(amountDetails.assetAmount) <= 0) { logger.error(new RainbowError(`[SendSheet]: preventing tx submit because amountDetails.assetAmount is <= 0`), { amountDetails, }); return false; } - const submitSuccessful = await onSubmit(...args); + const submitSuccessful = await onSubmit(args); analytics.track('Sent transaction', { assetName: selected?.name || '', network: selected?.network || '', @@ -644,9 +711,12 @@ export default function SendSheet(props) { let toAddress = recipient; const isValid = await checkIsValidAddressOrDomain(recipient); if (isValid) { - toAddress = await resolveNameOrAddress(recipient); + const resolvedAddress = await resolveNameOrAddress(recipient); + if (resolvedAddress && typeof resolvedAddress === 'string') { + toAddress = resolvedAddress; + } } - const tokenAddress = selected?.address; + const tokenAddress = !isUniqueAsset ? selected?.address : undefined; const validRecipient = validateRecipient(toAddress, tokenAddress); assetInputRef?.current?.blur(); nativeCurrencyInputRef?.current?.blur(); @@ -664,7 +734,7 @@ export default function SendSheet(props) { }); return; } - const uniqueTokenType = getUniqueTokenType(selected); + const uniqueTokenType = isUniqueAsset ? getUniqueTokenType(selected) : undefined; const isENS = uniqueTokenType === 'ENS'; const checkboxes = getDefaultCheckboxes({ ensProfile, @@ -687,33 +757,35 @@ export default function SendSheet(props) { toAddress, }); }, [ - amountDetails, - assetInputRef, buttonDisabled, - currentChainId, + recipient, + isUniqueAsset, + selected, + assetInputRef, + nativeCurrencyInputRef, ensProfile, + chainId, + navigate, + amountDetails, + submitTransaction, isL2, isNft, - nativeCurrencyInputRef, - navigate, - chainId, + currentChainId, profilesEnabled, - recipient, - selected, - submitTransaction, ]); const onResetAssetSelection = useCallback(() => { analytics.track('Reset asset selection in Send flow'); - sendUpdateSelected({}); + sendUpdateSelected(undefined); + setMaxEnabled(false); }, [sendUpdateSelected]); const onChangeInput = useCallback( - text => { + (text: string) => { const isValid = checkIsValidAddressOrDomainFormat(text); if (!isValid) { - setIsValidAddress(); - setToAddress(); + setIsValidAddress(false); + setToAddress(''); } setCurrentInput(text); setRecipient(text); @@ -736,14 +808,14 @@ export default function SendSheet(props) { } }, [isValidAddress, selected, showAssetForm, showAssetList]); - const checkAddress = useCallback(recipient => { + const checkAddress = useCallback((recipient: string) => { if (recipient) { const isValidFormat = checkIsValidAddressOrDomainFormat(recipient); setIsValidAddress(isValidFormat); } }, []); - const [ensSuggestions, setEnsSuggestions] = useState([]); + const [ensSuggestions, setEnsSuggestions] = useState([]); const [loadingEnsSuggestions, setLoadingEnsSuggestions] = useState(false); useEffect(() => { if (chainId === ChainId.mainnet && !recipientOverride && recipient?.length) { @@ -759,22 +831,24 @@ export default function SendSheet(props) { useEffect(() => { if (!currentProvider?._network?.chainId) return; - const assetChainId = selected.chainId; + const assetChainId = selected?.chainId; const currentProviderChainId = currentProvider._network.chainId; if ( + selected && !!accountAddress && - Object.entries(selected).length && assetChainId === currentChainId && currentProviderChainId === currentChainId && + toAddress && isValidAddress && - !isEmpty(selected) + !isEmpty(selected) && + (isUniqueAsset || Number(amountDetails.assetAmount) >= 0) ) { estimateGasLimit( { address: accountAddress, - amount: amountDetails.assetAmount, - asset: selected, + amount: Number(amountDetails.assetAmount), + asset: selected as ParsedAddressAsset, recipient: toAddress, }, false, @@ -782,7 +856,7 @@ export default function SendSheet(props) { currentChainId ) .then(async gasLimit => { - if (needsL1SecurityFeeChains.includes(currentChainId)) { + if (gasLimit && needsL1SecurityFeeChains.includes(currentChainId)) { updateTxFeeForOptimism(gasLimit); } else { updateTxFee(gasLimit, null); @@ -806,6 +880,7 @@ export default function SendSheet(props) { chainId, isNft, currentChainId, + isUniqueAsset, ]); const sendContactListDataKey = useMemo(() => `${ensSuggestions?.[0]?.address || '_'}`, [ensSuggestions]); @@ -822,14 +897,13 @@ export default function SendSheet(props) { { + onPressPaste={(recipient: string) => { checkAddress(recipient); setRecipient(recipient); }} @@ -847,7 +921,7 @@ export default function SendSheet(props) { ensSuggestions={ensSuggestions} key={sendContactListDataKey} loadingEnsSuggestions={loadingEnsSuggestions} - onPressContact={(recipient, nickname) => { + onPressContact={(recipient: string, nickname: string) => { setIsValidAddress(true); setRecipient(recipient); setNickname(nickname); @@ -885,7 +959,6 @@ export default function SendSheet(props) { ))} {showAssetForm && ( => { +const calculateL1FeeOptimism = async ( + tx: RainbowTransaction | TransactionRequest, + provider: StaticJsonRpcProvider +): Promise => { const newTx = cloneDeep(tx); try { if (newTx.value) { @@ -470,9 +473,7 @@ const calculateL1FeeOptimism = async (tx: RainbowTransaction, provider: Provider newTx.nonce = Number(await provider.getTransactionCount(newTx.from)); } - // @ts-expect-error operand should be optional delete newTx?.chainId; - // @ts-expect-error operand should be optional delete newTx?.from; // @ts-expect-error gas is not in type RainbowTransaction delete newTx?.gas; diff --git a/src/utils/neverRerender.ts b/src/utils/neverRerender.ts index bc01f4f50ab..10b5f739bdd 100644 --- a/src/utils/neverRerender.ts +++ b/src/utils/neverRerender.ts @@ -1,6 +1,7 @@ import React from 'react'; const alwaysTrue = () => true; -export default function neverRerender(Component: any) { + +export default function neverRerender(Component: React.ComponentType) { return React.memo(Component, alwaysTrue); } From 18314eba169d6829942bde1df0c859dbe258c462 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:32:42 -0400 Subject: [PATCH 41/64] comments watchdog action (#6153) --- .github/workflows/comments-watchdog.yml | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/comments-watchdog.yml diff --git a/.github/workflows/comments-watchdog.yml b/.github/workflows/comments-watchdog.yml new file mode 100644 index 00000000000..0c5d71ee911 --- /dev/null +++ b/.github/workflows/comments-watchdog.yml @@ -0,0 +1,71 @@ +name: Comments watchdog + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + discussion_comment: + types: [created] + +jobs: + remove_spam_comments: + runs-on: ubuntu-latest + steps: + - name: Set up environment + run: | + # Create an empty file to log deleted comments + touch deleted_comments_log.txt + + - name: Get list of organization members + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Fetch the list of users in the organization + ORG_MEMBERS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/orgs/${{ github.repository_owner }}/members" | jq -r '.[].login') + + # Save the allowlisted users in a file + echo "$ORG_MEMBERS" > allowlisted_users.txt + + - name: Check comment for spam + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMENT_ID=$(jq -r '.comment.id' < "$GITHUB_EVENT_PATH") + COMMENT_BODY=$(jq -r '.comment.body' < "$GITHUB_EVENT_PATH") + COMMENT_USER=$(jq -r '.comment.user.login' < "$GITHUB_EVENT_PATH") + + # Check if the comment user is allowlisted + if grep -q "$COMMENT_USER" allowlisted_users.txt; then + echo "Comment by $COMMENT_USER is from an allowlisted user. No action taken." + exit 0 + fi + + # Define enhanced spam keywords/phrases regex + SPAM_KEYWORDS="(message (support|help)|contact support|contact (agent|live agent)|telegram|live chat|https?://t\.me/|resolve your (issue|request))" + + # Check if the comment contains any spam patterns + if echo "$COMMENT_BODY" | grep -iE "$SPAM_KEYWORDS"; then + echo "Spam comment detected: $COMMENT_BODY" + + # Delete the comment using the GitHub API + curl -X DELETE \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/comments/$COMMENT_ID" || \ + "https://api.github.com/repos/${{ github.repository }}/pulls/comments/$COMMENT_ID" || \ + "https://api.github.com/repos/${{ github.repository }}/discussions/comments/$COMMENT_ID" + + echo "Spam comment deleted" + # Log the deleted comment + echo "Deleted comment from user $COMMENT_USER: $COMMENT_BODY" >> deleted_comments_log.txt + else + echo "No spam detected" + fi + + - name: Upload deleted comments log as artifact + uses: actions/upload-artifact@v3 + with: + name: deleted-comments-log + path: deleted_comments_log.txt \ No newline at end of file From f688f32322eb3bdf35cfb60dd1c7ffc7f919b2bd Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:01:18 -0400 Subject: [PATCH 42/64] pod unlock (#6168) --- .github/workflows/macstadium-builds.yml | 4 +++- .github/workflows/macstadium-tests.yml | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/macstadium-builds.yml b/.github/workflows/macstadium-builds.yml index 4de26e67e9b..2b313c9c09c 100644 --- a/.github/workflows/macstadium-builds.yml +++ b/.github/workflows/macstadium-builds.yml @@ -59,7 +59,9 @@ jobs: npx react-native info - name: Install pods - run: yarn install-bundle && yarn install-pods + run: | + rm -rf /Users/administrator/.cocoapods/repos/cocoapods/.git/index.lock + yarn install-bundle && yarn install-pods - uses: irgaly/xcode-cache@v1 with: diff --git a/.github/workflows/macstadium-tests.yml b/.github/workflows/macstadium-tests.yml index 237d1614507..41877da3908 100644 --- a/.github/workflows/macstadium-tests.yml +++ b/.github/workflows/macstadium-tests.yml @@ -103,8 +103,10 @@ jobs: npx react-native info - name: Install pods - run: yarn install-bundle && yarn install-pods - + run: | + rm -rf /Users/administrator/.cocoapods/repos/cocoapods/.git/index.lock + yarn install-bundle && yarn install-pods + - uses: irgaly/xcode-cache@v1 with: key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} From f0f3bf82df40875f59586293c4d9db3b0e2825ab Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 3 Oct 2024 16:56:00 -0400 Subject: [PATCH 43/64] Fix WalletconnectV2 changeAccount issues (#6160) * fix wc v2 change account issues * Update src/walletConnect/index.tsx * Update src/components/walletconnect-list/WalletConnectV2ListItem.tsx * Update src/components/walletconnect-list/WalletConnectV2ListItem.tsx * fix deps array --- .../WalletConnectV2ListItem.tsx | 85 ++++++++----------- src/walletConnect/index.tsx | 8 +- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx index e7b90d6435d..01fbc9dbdc4 100644 --- a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx +++ b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SessionTypes } from '@walletconnect/types'; import RadialGradient from 'react-native-radial-gradient'; @@ -20,7 +20,6 @@ import { padding, position } from '@/styles'; import { showActionSheetWithOptions } from '@/utils'; import * as lang from '@/languages'; import { useTheme } from '@/theme'; -import { logger, RainbowError } from '@/logger'; import { changeAccount, disconnectSession } from '@/walletConnect'; import { Box, Inline } from '@/design-system'; import ChainBadge from '@/components/coin-icon/ChainBadge'; @@ -48,6 +47,9 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT const { colors } = useTheme(); const { wallets, walletNames } = useWallets(); + const [address, setAddress] = useState(undefined); + const [accountInfo, setAccountInfo] = useState | undefined>(undefined); + const radialGradientProps = { center: [0, 1], colors: colors.gradients.lightGreyWhite, @@ -58,64 +60,49 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT }, }; - const { dappName, dappUrl, dappLogo, address, chainIds } = React.useMemo(() => { - const { namespaces, requiredNamespaces, peer } = session; - const { metadata } = peer; - const chains = requiredNamespaces?.eip155?.chains || []; - - const eip155Account = namespaces.eip155?.accounts?.[0] || undefined; - - if (!eip155Account) { - const e = new RainbowError(`[WalletConnectV2ListItem]: unsupported namespace`); - logger.error(e); - - // defensive, just for types, should never happen - throw e; - } + const { namespaces, peer } = session; + const { metadata } = peer; + useEffect(() => { + const eip155Account = session.namespaces.eip155?.accounts?.[0] || undefined; const address = eip155Account?.split(':')?.[2]; - const chainIds = (chains - ?.map(chain => parseInt(chain.split(':')[1])) - ?.filter(chainId => supportedWalletConnectChainIds.includes(chainId)) ?? []) as ChainId[]; - - if (!address) { - const e = new RainbowError(`[WalletConnectV2ListItem]: could not parse address`); - logger.error(e); + setAddress(address); + }, [session]); - // defensive, just for types, should never happen - throw e; + useEffect(() => { + if (address) { + setAccountInfo(getAccountProfileInfo(findWalletWithAccount(wallets || {}, address), walletNames, address)); } + }, [address, walletNames, wallets]); + + const chains = useMemo(() => namespaces?.eip155?.chains || [], [namespaces]); + const chainIds = useMemo( + () => + (chains?.map(chain => parseInt(chain.split(':')[1]))?.filter(chainId => supportedWalletConnectChainIds.includes(chainId)) ?? + []) as ChainId[], + [chains] + ); - return { - dappName: metadata.name || 'Unknown Dapp', - dappUrl: metadata.url || 'Unknown URL', - dappLogo: metadata && metadata.icons ? metadata.icons[0] : undefined, - address, - chainIds, - }; - }, [session]); + const dappName = metadata.name || 'Unknown Dapp'; + const dappUrl = metadata.url || 'Unknown URL'; + const dappLogo = metadata && metadata.icons ? metadata.icons[0] : undefined; const availableNetworksChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); - const approvalAccountInfo = useMemo(() => { - const selectedWallet = findWalletWithAccount(wallets!, address); - const approvalAccountInfo = getAccountProfileInfo(selectedWallet, walletNames, address); - return { - ...approvalAccountInfo, - }; - }, [wallets, walletNames, address]); - const handlePressChangeWallet = useCallback(() => { Navigation.handleAction(Routes.CHANGE_WALLET_SHEET, { currentAccountAddress: address, onChangeWallet: async (address: string) => { - await changeAccount(session, { address }); - reload(); + const success = await changeAccount(session, { address }); + if (success) { + setAddress(address); + reload(); + } goBack(); }, watchOnly: true, }); - }, [address, session, reload, goBack]); + }, [address, session, goBack, reload]); const onPressAndroid = useCallback(() => { showActionSheetWithOptions( @@ -181,13 +168,13 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT paddingLeft: 10, }} > - {approvalAccountInfo.accountImage ? ( - + {accountInfo?.accountImage ? ( + ) : ( )} - {approvalAccountInfo.accountName} + {accountInfo?.accountName} diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index a34dcf2e1b0..e3e06d57040 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -970,8 +970,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres const client = await getWeb3WalletClient(); const namespaces: Parameters[0]['namespaces'] = {}; - - for (const [key, value] of Object.entries(session.requiredNamespaces)) { + for (const [key, value] of Object.entries(session.namespaces)) { /** * The `namespace` that corresponds to the `requiredNamespace` that was * requested when connecting the session. The `requiredNamespace` does @@ -982,6 +981,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres const ns = session.namespaces[key]; namespaces[key] = { + ...ns, accounts: ns.accounts || [], methods: value.methods, events: value.events, @@ -1036,7 +1036,7 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { */ await addAccountToSession(session, { address }); - for (const value of Object.values(session.requiredNamespaces)) { + for (const value of Object.values(session.namespaces)) { if (!value.chains) { logger.debug(`[walletConnect]: changeAccount, no chains found for namespace`); continue; @@ -1064,11 +1064,13 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { } logger.debug(`[walletConnect]: changeAccount complete`); + return true; } catch (e: any) { logger.error(new RainbowError(`[walletConnect]: error changing account`), { message: e.message, }); } + return false; } /** From a6f135798ba753104569cdce215bc0d7366f355f Mon Sep 17 00:00:00 2001 From: Ibrahim Taveras Date: Thu, 3 Oct 2024 19:25:18 -0400 Subject: [PATCH 44/64] bump android and iOS to version v1.9.42 (#6170) --- CHANGELOG.md | 23 +++++++++++++++++++++++ android/app/build.gradle | 4 ++-- ios/Rainbow.xcodeproj/project.pbxproj | 8 ++++---- package.json | 2 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f5b25a2b3..7f5a3ac6add 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.41] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.41) + +### Added + +- Added support for typing into native inputs in swap (#6100) +- Implementation of claimables (#6140, #6141, #6138, #6146, #6155, #6158, #6159) + +### Changed + +- Updated arbitrum default back to mainnet for WC message signing (#6122) +- Bumped dependencies for xcode 16 compatibility (#6110) +- Bumped CI to work with xcode 16/ iOS 18 (#6129) +- Now using backend to omit certain defi positions from users wallet balance (#6103) +- We are now filtering out backend driven networks that are internal and not in production (#6148) + +### Fixed + +- Fixed Spindl featured image resolution on dapp browser (#6114) +- Fixed a bug where an error would occur during personal signing using MWP (#6142) +- Updated url navigation to fix a bug in dapp browser (#6150) +- Fixed a bug with improper gas fee calculation on mainnet (#6125) +- Fixed a crash on token search for newly added chains (#6147) + ## [1.9.40] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.40) ### Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index a6572fd6ec5..925f9fab6af 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -130,8 +130,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 233 - versionName "1.9.41" + versionCode 234 + versionName "1.9.42" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index abcaf71efa3..18aa0ee4bdf 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1836,7 +1836,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.41; + MARKETING_VERSION = 1.9.42; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1900,7 +1900,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.41; + MARKETING_VERSION = 1.9.42; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2017,7 +2017,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.41; + MARKETING_VERSION = 1.9.42; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", @@ -2133,7 +2133,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.41; + MARKETING_VERSION = 1.9.42; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index 8c11df2b871..387cb409138 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.41-1", + "version": "1.9.42-1", "private": true, "scripts": { "setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen && yarn fetch:networks", From 2e900e7309a55b81de457a8cd70eaceffb843730 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 3 Oct 2024 19:26:12 -0400 Subject: [PATCH 45/64] fix approval sheet not using verifiedData incoming from walletconnect (#6162) --- src/screens/WalletConnectApprovalSheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/WalletConnectApprovalSheet.js b/src/screens/WalletConnectApprovalSheet.js index 1e4eed9438c..e400908cef8 100644 --- a/src/screens/WalletConnectApprovalSheet.js +++ b/src/screens/WalletConnectApprovalSheet.js @@ -196,7 +196,7 @@ export default function WalletConnectApprovalSheet() { url: verifiedData?.origin || dappUrl, }); - const isScam = metadata?.status === DAppStatus.Scam; + const isScam = metadata?.status === DAppStatus.Scam || verifiedData?.isScam; // we can only safely mark a dapp as verified if the source is the browser const isVerified = metadata?.status === DAppStatus.Verified && source === 'browser'; From cb2b7a6670e6e94c9157406db833599409fe4960 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 3 Oct 2024 20:57:26 -0400 Subject: [PATCH 46/64] network expansion (#6149) * init apechain support * fix wrong images * Apply suggestions from code review * Update chain badge * Update chain colors * Add all badge assets * add backend networks runtime * added back internal flag * remove unused NATIVE_ASSETS_PER_CHAIN * fix some discrepancies --------- Co-authored-by: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> --- .../badges/apechain.imageset/Contents.json | 23 +++++ .../badges/apechain.imageset/apechain.png | Bin 0 -> 1388 bytes .../badges/apechain.imageset/apechain@2x.png | Bin 0 -> 2895 bytes .../badges/apechain.imageset/apechain@3x.png | Bin 0 -> 4552 bytes .../apechainBadge.imageset/Contents.json | 23 +++++ .../apechainBadge.imageset/apechainBadge.png | Bin 0 -> 2815 bytes .../apechainBadge@2x.png | Bin 0 -> 6991 bytes .../apechainBadge@3x.png | Bin 0 -> 12810 bytes .../apechainBadgeDark.imageset/Contents.json | 23 +++++ .../apechainBadgeDark.png | Bin 0 -> 2486 bytes .../apechainBadgeDark@2x.png | Bin 0 -> 5830 bytes .../apechainBadgeDark@3x.png | Bin 0 -> 10522 bytes .../apechainBadgeLarge.imageset/Contents.json | 23 +++++ .../apechainBadgeLarge.png | Bin 0 -> 6991 bytes .../apechainBadgeLarge@2x.png | Bin 0 -> 19899 bytes .../apechainBadgeLarge@3x.png | Bin 0 -> 39315 bytes .../Contents.json | 23 +++++ .../apechainBadgeLargeDark.png | Bin 0 -> 5830 bytes .../apechainBadgeLargeDark@2x.png | Bin 0 -> 16438 bytes .../apechainBadgeLargeDark@3x.png | Bin 0 -> 32491 bytes .../Contents.json | 23 +++++ .../apechainBadgeNoShadow.png | Bin 0 -> 1002 bytes .../apechainBadgeNoShadow@2x.png | Bin 0 -> 1691 bytes .../apechainBadgeNoShadow@3x.png | Bin 0 -> 2485 bytes package.json | 2 +- scripts/networks.js | 86 +----------------- src/App.tsx | 2 + .../components/AnimatedChainImage.android.tsx | 28 +++--- .../components/AnimatedChainImage.ios.tsx | 28 +++--- .../components/TokenList/ChainSelection.tsx | 3 +- src/__swaps__/utils/chains.ts | 4 +- src/__swaps__/utils/swaps.ts | 16 ++-- src/assets/badges/apechain.png | Bin 0 -> 1388 bytes src/assets/badges/apechain@2x.png | Bin 0 -> 2895 bytes src/assets/badges/apechain@3x.png | Bin 0 -> 4552 bytes src/assets/badges/apechainBadge.png | Bin 0 -> 2815 bytes src/assets/badges/apechainBadge@2x.png | Bin 0 -> 6991 bytes src/assets/badges/apechainBadge@3x.png | Bin 0 -> 12810 bytes src/assets/badges/apechainBadgeDark.png | Bin 0 -> 2486 bytes src/assets/badges/apechainBadgeDark@2x.png | Bin 0 -> 5830 bytes src/assets/badges/apechainBadgeDark@3x.png | Bin 0 -> 10522 bytes src/assets/badges/apechainBadgeLarge.png | Bin 0 -> 6991 bytes src/assets/badges/apechainBadgeLarge@2x.png | Bin 0 -> 19899 bytes src/assets/badges/apechainBadgeLarge@3x.png | Bin 0 -> 39315 bytes src/assets/badges/apechainBadgeLargeDark.png | Bin 0 -> 5830 bytes .../badges/apechainBadgeLargeDark@2x.png | Bin 0 -> 16438 bytes .../badges/apechainBadgeLargeDark@3x.png | Bin 0 -> 32491 bytes src/assets/badges/apechainBadgeNoShadow.png | Bin 0 -> 1002 bytes .../badges/apechainBadgeNoShadow@2x.png | Bin 0 -> 1691 bytes .../badges/apechainBadgeNoShadow@3x.png | Bin 0 -> 2485 bytes src/chains/index.ts | 12 ++- src/chains/types.ts | 3 + src/components/AppStateChangeHandler.tsx | 4 +- src/components/BackendNetworks.tsx | 7 ++ .../FastComponents/FastCoinBadge.tsx | 6 ++ src/components/coin-icon/ChainImage.tsx | 3 + src/handlers/tokenSearch.ts | 1 - src/hooks/useSearchCurrencyList.ts | 5 + src/languages/en_US.json | 6 +- src/model/remoteConfig.ts | 11 ++- src/references/index.ts | 35 +------ src/resources/metadata/backendNetworks.ts | 68 ++++++++++++++ src/resources/metadata/sharedQueries.js | 86 ++++++++++++++++++ src/state/appSessions/index.ts | 1 + src/styles/colors.ts | 2 + yarn.lock | 10 +- 66 files changed, 398 insertions(+), 169 deletions(-) create mode 100644 ios/Images.xcassets/badges/apechain.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechain.imageset/apechain.png create mode 100644 ios/Images.xcassets/badges/apechain.imageset/apechain@2x.png create mode 100644 ios/Images.xcassets/badges/apechain.imageset/apechain@3x.png create mode 100644 ios/Images.xcassets/badges/apechainBadge.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechainBadge.imageset/apechainBadge.png create mode 100644 ios/Images.xcassets/badges/apechainBadge.imageset/apechainBadge@2x.png create mode 100644 ios/Images.xcassets/badges/apechainBadge.imageset/apechainBadge@3x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeDark.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@2x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@3x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLarge.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge@2x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge@3x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark@2x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark@3x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/Contents.json create mode 100644 ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/apechainBadgeNoShadow.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/apechainBadgeNoShadow@2x.png create mode 100644 ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/apechainBadgeNoShadow@3x.png create mode 100644 src/assets/badges/apechain.png create mode 100644 src/assets/badges/apechain@2x.png create mode 100644 src/assets/badges/apechain@3x.png create mode 100644 src/assets/badges/apechainBadge.png create mode 100644 src/assets/badges/apechainBadge@2x.png create mode 100644 src/assets/badges/apechainBadge@3x.png create mode 100644 src/assets/badges/apechainBadgeDark.png create mode 100644 src/assets/badges/apechainBadgeDark@2x.png create mode 100644 src/assets/badges/apechainBadgeDark@3x.png create mode 100644 src/assets/badges/apechainBadgeLarge.png create mode 100644 src/assets/badges/apechainBadgeLarge@2x.png create mode 100644 src/assets/badges/apechainBadgeLarge@3x.png create mode 100644 src/assets/badges/apechainBadgeLargeDark.png create mode 100644 src/assets/badges/apechainBadgeLargeDark@2x.png create mode 100644 src/assets/badges/apechainBadgeLargeDark@3x.png create mode 100644 src/assets/badges/apechainBadgeNoShadow.png create mode 100644 src/assets/badges/apechainBadgeNoShadow@2x.png create mode 100644 src/assets/badges/apechainBadgeNoShadow@3x.png create mode 100644 src/components/BackendNetworks.tsx create mode 100644 src/resources/metadata/backendNetworks.ts create mode 100644 src/resources/metadata/sharedQueries.js diff --git a/ios/Images.xcassets/badges/apechain.imageset/Contents.json b/ios/Images.xcassets/badges/apechain.imageset/Contents.json new file mode 100644 index 00000000000..da07f759e91 --- /dev/null +++ b/ios/Images.xcassets/badges/apechain.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "apechain.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "apechain@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "apechain@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/apechain.imageset/apechain.png b/ios/Images.xcassets/badges/apechain.imageset/apechain.png new file mode 100644 index 0000000000000000000000000000000000000000..f74845164afca3abb36bbfbca8202e8d4b06472b GIT binary patch literal 1388 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 literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechain.imageset/apechain@3x.png b/ios/Images.xcassets/badges/apechain.imageset/apechain@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb69328296cb412ac3245bfa03a1bcbef0d35ca GIT binary patch literal 4552 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 literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadge.imageset/apechainBadge@3x.png b/ios/Images.xcassets/badges/apechainBadge.imageset/apechainBadge@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..06a7eeb07a8eb6d1794fd11b12da644bdfa0958b GIT binary patch literal 12810 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 z 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 QpDfDB!NtDpknhd^0+-^dMF0Q* literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@2x.png b/ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ced4dc053fd8f74390ba7a8cef432e7c66eede22 GIT binary patch literal 5830 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 Q;{X5v07*qoM6N<$f_HUEGynhq literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@3x.png b/ios/Images.xcassets/badges/apechainBadgeDark.imageset/apechainBadgeDark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..302ccc1d6e9ca2876708e82e99c8c222c358f324 GIT binary patch literal 10522 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 literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/Contents.json new file mode 100644 index 00000000000..75d4f16a879 --- /dev/null +++ b/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "apechainBadgeLarge.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "apechainBadgeLarge@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "apechainBadgeLarge@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge.png b/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge.png new file mode 100644 index 0000000000000000000000000000000000000000..bd647873044e2bbcf99d9ae0ee4013e69c050c0c GIT binary patch literal 6991 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 literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge@2x.png b/ios/Images.xcassets/badges/apechainBadgeLarge.imageset/apechainBadgeLarge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3e39ee0e49f078a76046683ee2ecd5feb6e75675 GIT binary patch literal 19899 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%0D$;2uzHTL=+>M`9`wTP(8-ej!Hlx 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

;uGw1o_wAlhHkNpPnTcPWS(Q@pM+-LL!eX3+p0a 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 XQ8ut7@2rWw00000NkvXXu0mjf;~?hp literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/Contents.json new file mode 100644 index 00000000000..411ee3f78cd --- /dev/null +++ b/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "apechainBadgeLargeDark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "apechainBadgeLargeDark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "apechainBadgeLargeDark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark.png b/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark.png new file mode 100644 index 0000000000000000000000000000000000000000..ced4dc053fd8f74390ba7a8cef432e7c66eede22 GIT binary patch literal 5830 zcmV;%7CGsOP)U>h*?d$b`aT5T$j!+mv){4Q-A$k)t||l z8vWnYL5&#};E{=yZHo!sB_1(oDdMW&yvxUe(=MMoo|R;r_?B4G3TlS9Ni3&3^n3@G zie;^}gV^4m)mN9r9O_ZbVG|}8icnF31O?%L{L_vm;#3HD6?Va!XbBie+=k*;rwF^@ErnnmGiG7&g~k7pMfj=2Nx-7yfG6D#+Qn^fM*vu5a~$ZSyKI(?{$`M~xg*rIlq z<-)oMFb^|Ku0xhGX0~-OqyTAQkg|%UpP!$lZ0K9ccO2JI$aDf{jlSwjA_dG%2r~o9 zvH+k`4^zIO#;mlJ1X2>f;I9-%`|r~a8gHG5K5TI*j`U0vy}`eyg8y{r4&Fi@_C z0kgtUgXL?jXn?`OvS4k10k8}WFz3vfGe$LY=FG9E%lQFOTiloa0I}#PfK{_K$70vT zVV#kzcsv5x;9zW(4*#CHbF2e5++JNDZ#|*Q`&tjqCM;#yl2GA7du3fvnm`tYs zSu7T>@9XQ^-QVAz?C-C{|7l&?o-7m!H)ONfgS4;v1B};e05)4R1kgGr4{(j!V3)@h z=`#o>9xo*A^;1+WeV5?F1z3KR6#}3toFk7sa=ro+cu9d+QJ`8XR~3%A4afRN$mjDH zu359@Eo|lNS}f4MB@<+n_0s<5X2pSk+05ku7lVNna9j$jWmf<mIy&15nsDL{f~ z)lK1dsAscP(0Xk^)+fZbXpS~Gjs{#dpin$K>@h0!1jtra2FTW8#*qQs0fU`9dGf?W zBJr=jK6&JY3`;@PwDnP4&jtFI%YD0BTU&pl_n9(f$^{aNv_3Eco3 zv|KIOa!s%*vRc3$&2Z&7p0L8rqoEQKd>C5}mDs-sWY=xa%z480d1V_|erNNmmU z$tqZ~%1TVE+@Nm45!~?bWLYlYGGM^PBF1o!J@(iIg+l4>GQmULZo_d{R^MVNdP?sp z_m3g7Z)V7THi(TQEPUVs(7}*4xqQrKe0dWt6J$ zIlm5Zf2g;&aJSxD#t~aHAafEEa2MWAoSo@1$M6DYKLRzeF8f?cXKNNSosdpUG%1zJ zoureGR0IQcv_pV~W2+y;{Vb(YnUfT<8;>fsX5W%w#nz4;r`2^_L^xdIA6wRoBm#gr z##OkK48)6>!fj}7{VgW@YN@TQ<39=+e%W#t*waw)Q_2iNuhAe-Hurux?zO@)hO6Sl z0d7GpSN@xP*=3i#qfjjGj>>(NQshK+ZTDof_j5byo{avumnteJ_t;~P#efW-tgZ}@ z;VO7r)QBw1lsOzl7`tT*7OsK)E+Pi_Tv)DXg7bS*lBw)P0Py0~|NQ!wYx^gXsgoy6 zm@q}3D))5NK!&S;>+^jvyND2Eb3X#dxDYPNsW9)|a{9|LuEMQa@73?ni4|WYuwwWe z%3%M0|NGxdWc)E?xC*vnhD_U?X7T~mjf4I8mt6w{3-*04FKP(v6|ln9NhFe)i;Jb| zgi)f7a?;OBmDhZysx2+$xDDseSYM9YSwB5KscC%XrGVo6~*TuzGi!S*P-Lb zkDn^zn@kHI`(tc_j_^}y3$SO=sM{_g6gkiy121f2b%1~?^%$;9Vp_hp*nr6uxFj){ z%kLw=A_lzZH_JaWfrlS{{Obx~SQYy!4{ogmeERwb=i>5o0=!m0eoVk=ec^9PxK7-TK8If$ zRW>)5E1Ts>u)Ju`;`(Quz<}`Lm0{YmV5ucBN0p1!9F#1Tl2e=$mHeYNQ zJ9g|$_QeR>MC^-WD`v=?KG*%C)1r?K3|HlE=6D-9@#5wNI!!G@20tsCE2Z*oLiqwz zd;Ajzj30gOd)it8{CEVw+%IltAH1{>vo5BRHxRFB{OHybeYzl);a zy>*P96;GnAO;~V^n^5^*3WZW-lpG7!q{fzefd6%H5t_yB!CA(fL2Dt)@s1Ug0%4FF z8YAfwF<@V@<>NZHn1J;>`gr$u^)X|{%#m@2wTyXf9f>trnM_rY6OIXtr$ets-$-FIVB$gzJQWr!^%(Fo8#nKp z%}15tbjdwcFcW`Ps{HrDsIu_aRY<=(y`<+Aga9km0M1m~<$O7=s~GSpYa%`8e$V|Q zC~n)jz2T_QqestE3lP)spQ56qA5fA(Ta&cFVdqQ%I}4sA z1J_4y{+VS^apq+0({e>_H}NS_5tg^rc`;hA*wWW;=HZv zZ(8$x8RvX82DfHRhabj52Mfk4C0MZWGSqetRt$`iwkXXQ7#SQV7|sf2ITta1)TmJl zQ<-8ald~uJ+taI1KDtK?R2DA+o*N!8&Y{kLAzTweUsX@Kw^7EnNS`tSj7Wmh=CPIq z^S2YvIf^jeVEB6wY{C5If&n8$o2%BLli09j=P_xSlq#bo=_>~fTC5(cYk;vVi$Ge)^uW*3pBa(mx12BEGGGyb&w_>mhzK=sE-lp{5UU=V> z)c{s2z#``!zeV5u?vLJ)&go=*MbE2*{Nt2sOYwQ`!~LB0+2??d>DY9PI#&2#KNjYl zwR&(-0<0B9SRa@$V3ldXa1qW2&+uHtBGHm9+ZxWPPBL;T4bi~)Eb6KV?FC@H`|h4} zA*GWf0jLP2buQF58hS2AZKSTAT+<$V?DGi#CLcT#E>}#=VAQS8 zqx>+9KPaI%m;o;oEgm^?4cjrA1{9DGdOvb%f z#4qM7yjd>5Y1C0vs=J zBDG+o%DkhdfMe?gVC)I|FF%dXTWt&WKT##X=Bpw<*`PNWGdS6BZX&i~o#dCk^riQ= zbfh){#LKP5mhOkX^rf%A-vM5(V*y}JGDJ0RnfbFSD?|gM;5#`cZzUR5s3sZ!EMhk? z)pHYSB?|ypElB#TmdU>P=?&Y@R{)8$g3z)!P$q_el5^Tl=g?Qhzh~(^^j@OXuwHdp zyucwu;|Ky4taIdVi2>O9$|oF2gkKred_f5}0l>WBqgJeso?5X3yx95THM_j-%yO|V|6FV(T=nA{UK+u!K%BM1)B z02puq*n;eVwJjKsjnAb69~ndEY}%oVsO4)9sBdJ)0FEz;;Mo z{|I{S*4yttLi@@!-l@O!;I4*i5DmBn-r#Yr0nr0qcn)z7yugubKxrfy0Bk$vA$!7B zG$C#LJ+QfT$ySWS!-DJ{<+rFc3#hjPv>d-n%6E(2<4V=Lq#b1pR(}Jy?y1m4?f;yd zg>Ee|5JMk>SdX#H^#7mN>(!jd@+L~uO4bE!>D2X3Xs#6vz-4HhwQsuDcUj84tY!>O zAtgT^rFkb4AoE25>|3W;pUW(ciUP>YPn=p+v4bpFfIht5VCQqd9^P};-YMVpHS4Cf z8H63_=R>y+D_q zkaF5yKbBj$&Ld%i%S}x;SRYg{_v>qLtYA1?94x?OZ*K&bW$xOVTttNgU@6ZXEx#t$r7>jd8) zjC;FBY=>}XEemmR9+mZxT^@YNjer6ir5lySG1Y~j)W5adhohxiR{_=IO**!+y}{K; zl9G(&d0!gxvMkRxTW5e>Jj@OuqUA(} z&dR@9qG6=>&p@9PXor2SM^tfgE&;Z#KdC>|QGLz79t$-Q_0vhBbz-s$Z1!(6rCsvN{rAN*o+0i#ktHkWHjV{ z;E0A?qF?KJGHckDeRw2XoRFCaawiiPezc(WumSnEpa>4Im7h4fry4Z;FX7lN4g}Zc z7`_Rv{=mRhgynogp1@D{Cg9L`J#MuTCW@Hop0M}U1)j-l()OF5K;vklL!biqU!L>Y zoUdztvw;M#R0?b+sqeiz8$r96Qo0htnx zGLF~)cKtg`0LR^%fUig7F(3ifvzC*m)VjXcK?1JVyRJO8HOpYsBR)M8o_mM&m>o~t z$$-W=4w}f&N4i>~@^ArE!l{Y@2Mb&#Z$^>eGi9UP&CFftW1Re^o$PWs-yMv4%{fN> zBP3#j=pZ9;HNb(F(lrv-;#M3B*xn$bb3_Zc84bBCl#J4tOQX42UmAy;)x|r+500C? zHyWV*RK91lKqDN! zqOj1>x*S{_R*!|uXLR#3Xp#G63P zD^{F2WZ{iKu%q1s>FD6@SMk`#SAsK4^P^*V^x`#9;~=Q!Y{o{43p(2bD;FJ?#M8e!o0S{Zw_VGO4Y7~~+riadtI)X)7+n1T zxk$&n-^AyUD&@OxEBajPzn5`5?l-Pk+yaJg;eKk0U(P&=Vc{vJZ!%FZ?@zB5ti(f^ zF{eGt+B+!RyHDq$=oFq8ZwEfk-rQ$FUF}Km5Q%qdW#z_PD$n{T@WpVc<$ksW>U+z3 z_d<^Xvr3oc=P^#WuemK{<>}S$MZrtG&B=e7h}a`Ud$OqQsX@8rdNuF!-=`$@##h}E z1#jP3`$D;{V=l*-`_x(-vG45-r^EfbkV|v^n8;0huDz*rr>%SH_usUB03O|QOQiIr Q;{X5v07*qoM6N<$f_HUEGynhq literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/apechainBadgeLargeDark.imageset/apechainBadgeLargeDark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3e8ecae55695023b70021afbdd108d6d2411b9 GIT binary patch literal 16438 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

Jd|-A- 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

1W_rgPGaD^$f-x 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 Y0BT0QvS%$3@Bjb+07*qoM6N<$f(K01C;$Ke literal 0 HcmV?d00001 diff --git a/ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/apechainBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/apechainBadgeNoShadow.imageset/apechainBadgeNoShadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..85fc3696ae6150d24cf4b8443d4d5c6404a3f13c GIT binary patch literal 1691 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 387cb409138..4dcc4bcb705 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "@notifee/react-native": "7.8.2", "@rainbow-me/provider": "0.1.1", "@rainbow-me/react-native-animated-number": "0.0.2", - "@rainbow-me/swaps": "0.26.0", + "@rainbow-me/swaps": "0.27.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-camera-roll/camera-roll": "7.7.0", "@react-native-clipboard/clipboard": "1.13.2", diff --git a/scripts/networks.js b/scripts/networks.js index d4fb99379e0..5c64de4aefa 100644 --- a/scripts/networks.js +++ b/scripts/networks.js @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path'); +const { BACKEND_NETWORKS_QUERY } = require('../src/resources/metadata/sharedQueries'); const fs = require('fs-extra'); @@ -7,94 +8,11 @@ const fs = require('fs-extra'); * Fetches data from the GraphQL API and saves it to a JSON file. */ async function fetchData() { - const graphqlQuery = ` - query getNetworks($device: Device!, $includeTestnets: Boolean!) { - networks(device: $device, includeTestnets: $includeTestnets) { - id - name - label - icons { - badgeURL - } - testnet - internal - opStack - defaultExplorer { - url - label - transactionURL - tokenURL - } - defaultRPC { - enabledDevices - url - } - gasUnits { - basic { - approval - swap - swapPermit - eoaTransfer - tokenTransfer - } - wrapped { - wrap - unwrap - } - } - nativeAsset { - address - name - symbol - decimals - iconURL - colors { - primary - fallback - shadow - } - } - nativeWrappedAsset { - address - name - symbol - decimals - iconURL - colors { - primary - fallback - shadow - } - } - enabledServices { - meteorology { - enabled - } - swap { - enabled - } - addys { - approvals - transactions - assets - positions - } - tokenSearch { - enabled - } - nftProxy { - enabled - } - } - } - } -`; - const response = await fetch('https://metadata.p.rainbow.me/v1/graph', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - query: graphqlQuery, + query: BACKEND_NETWORKS_QUERY, variables: { device: 'APP', includeTestnets: true }, }), }); diff --git a/src/App.tsx b/src/App.tsx index c2ea134d17b..8efd36871b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,6 +42,7 @@ import { Address } from 'viem'; import { IS_DEV } from '@/env'; import { prefetchDefaultFavorites } from '@/resources/favorites'; import Routes from '@/navigation/Routes'; +import { BackendNetworks } from '@/components/BackendNetworks'; if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -81,6 +82,7 @@ function App({ walletReady }: AppProps) { + ); } diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx index d9eab63e94f..f4d77a39815 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx @@ -12,6 +12,7 @@ const ZoraBadge = require('@/assets/badges/zora.png'); const AvalancheBadge = require('@/assets/badges/avalanche.png'); const BlastBadge = require('@/assets/badges/blast.png'); const DegenBadge = require('@/assets/badges/degen.png'); +const ApechainBadge = require('@/assets/badges/apechainBadge.png'); import { ChainId } from '@/chains/types'; import { globalColors } from '@/design-system'; @@ -19,26 +20,27 @@ import { PIXEL_RATIO } from '@/utils/deviceUtils'; import { useSwapsStore } from '@/state/swaps/swapsStore'; const networkBadges = { - [ChainId.mainnet]: EthereumBadge, - [ChainId.polygon]: PolygonBadge, - [ChainId.optimism]: OptimismBadge, + [ChainId.apechain]: ApechainBadge, [ChainId.arbitrum]: ArbitrumBadge, + [ChainId.arbitrumSepolia]: ArbitrumBadge, + [ChainId.avalanche]: AvalancheBadge, + [ChainId.avalancheFuji]: AvalancheBadge, [ChainId.base]: BaseBadge, - [ChainId.zora]: ZoraBadge, + [ChainId.baseSepolia]: BaseBadge, + [ChainId.blast]: BlastBadge, + [ChainId.blastSepolia]: BlastBadge, [ChainId.bsc]: BscBadge, - [ChainId.avalanche]: AvalancheBadge, - [ChainId.sepolia]: EthereumBadge, + [ChainId.bscTestnet]: BscBadge, + [ChainId.degen]: DegenBadge, [ChainId.holesky]: EthereumBadge, + [ChainId.mainnet]: EthereumBadge, + [ChainId.optimism]: OptimismBadge, [ChainId.optimismSepolia]: OptimismBadge, - [ChainId.bscTestnet]: BscBadge, + [ChainId.polygon]: PolygonBadge, [ChainId.polygonAmoy]: PolygonBadge, - [ChainId.arbitrumSepolia]: ArbitrumBadge, - [ChainId.baseSepolia]: BaseBadge, + [ChainId.sepolia]: EthereumBadge, + [ChainId.zora]: ZoraBadge, [ChainId.zoraSepolia]: ZoraBadge, - [ChainId.avalancheFuji]: AvalancheBadge, - [ChainId.blast]: BlastBadge, - [ChainId.blastSepolia]: BlastBadge, - [ChainId.degen]: DegenBadge, }; export function AnimatedChainImage({ diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index c00fe9099f5..305806ab0ba 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -11,6 +11,7 @@ import ZoraBadge from '@/assets/badges/zora.png'; import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; +import ApechainBadge from '@/assets/badges/apechainBadge.png'; import { ChainId } from '@/chains/types'; import { useAnimatedProps, useDerivedValue } from 'react-native-reanimated'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; @@ -22,26 +23,27 @@ import { useSwapContext } from '../providers/swap-provider'; import { BLANK_BASE64_PIXEL } from '@/components/DappBrowser/constants'; const networkBadges = { - [ChainId.mainnet]: Image.resolveAssetSource(EthereumBadge).uri, - [ChainId.polygon]: Image.resolveAssetSource(PolygonBadge).uri, - [ChainId.optimism]: Image.resolveAssetSource(OptimismBadge).uri, + [ChainId.apechain]: Image.resolveAssetSource(ApechainBadge).uri, [ChainId.arbitrum]: Image.resolveAssetSource(ArbitrumBadge).uri, + [ChainId.arbitrumSepolia]: Image.resolveAssetSource(ArbitrumBadge).uri, + [ChainId.avalanche]: Image.resolveAssetSource(AvalancheBadge).uri, + [ChainId.avalancheFuji]: Image.resolveAssetSource(AvalancheBadge).uri, [ChainId.base]: Image.resolveAssetSource(BaseBadge).uri, - [ChainId.zora]: Image.resolveAssetSource(ZoraBadge).uri, + [ChainId.baseSepolia]: Image.resolveAssetSource(BaseBadge).uri, + [ChainId.blast]: Image.resolveAssetSource(BlastBadge).uri, + [ChainId.blastSepolia]: Image.resolveAssetSource(BlastBadge).uri, [ChainId.bsc]: Image.resolveAssetSource(BscBadge).uri, - [ChainId.avalanche]: Image.resolveAssetSource(AvalancheBadge).uri, - [ChainId.sepolia]: Image.resolveAssetSource(EthereumBadge).uri, + [ChainId.bscTestnet]: Image.resolveAssetSource(BscBadge).uri, + [ChainId.degen]: Image.resolveAssetSource(DegenBadge).uri, [ChainId.holesky]: Image.resolveAssetSource(EthereumBadge).uri, + [ChainId.mainnet]: Image.resolveAssetSource(EthereumBadge).uri, + [ChainId.optimism]: Image.resolveAssetSource(OptimismBadge).uri, [ChainId.optimismSepolia]: Image.resolveAssetSource(OptimismBadge).uri, - [ChainId.bscTestnet]: Image.resolveAssetSource(BscBadge).uri, + [ChainId.polygon]: Image.resolveAssetSource(PolygonBadge).uri, [ChainId.polygonAmoy]: Image.resolveAssetSource(PolygonBadge).uri, - [ChainId.arbitrumSepolia]: Image.resolveAssetSource(ArbitrumBadge).uri, - [ChainId.baseSepolia]: Image.resolveAssetSource(BaseBadge).uri, + [ChainId.sepolia]: Image.resolveAssetSource(EthereumBadge).uri, + [ChainId.zora]: Image.resolveAssetSource(ZoraBadge).uri, [ChainId.zoraSepolia]: Image.resolveAssetSource(ZoraBadge).uri, - [ChainId.avalancheFuji]: Image.resolveAssetSource(AvalancheBadge).uri, - [ChainId.blast]: Image.resolveAssetSource(BlastBadge).uri, - [ChainId.blastSepolia]: Image.resolveAssetSource(BlastBadge).uri, - [ChainId.degen]: Image.resolveAssetSource(DegenBadge).uri, }; export function AnimatedChainImage({ diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index a96762deb47..2fb3f5a389d 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -81,7 +81,8 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: actionTitle: chainsLabel[chainId], icon: { iconType: 'ASSET', - iconValue: `${chainsName[chainId]}Badge${chainId === ChainId.mainnet ? '' : 'NoShadow'}`, + // NOTE: chainsName[chainId] for mainnet is 'mainnet' and we need it to be 'ethereum' + iconValue: chainId === ChainId.mainnet ? 'ethereumBadge' : `${chainsName[chainId]}BadgeNoShadow`, }, }; }); diff --git a/src/__swaps__/utils/chains.ts b/src/__swaps__/utils/chains.ts index 0c1b7afda35..63b1748d97a 100644 --- a/src/__swaps__/utils/chains.ts +++ b/src/__swaps__/utils/chains.ts @@ -1,8 +1,8 @@ import { celo, fantom, harmonyOne, moonbeam } from 'viem/chains'; -import { NATIVE_ASSETS_PER_CHAIN } from '@/references'; import { AddressOrEth } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; +import { chainsNativeAsset } from '@/chains'; // @ts-expect-error Property '[ChainId.hardhat]' is missing export const customChainIdsToAssetNames: Record = { @@ -44,7 +44,7 @@ export const customChainIdsToAssetNames: Record = { }; export function isNativeAsset(address: AddressOrEth, chainId: ChainId) { - return isLowerCaseMatch(NATIVE_ASSETS_PER_CHAIN[chainId], address); + return isLowerCaseMatch(chainsNativeAsset[chainId].address, address); } export const chainIdToUse = (connectedToHardhat: boolean, connectedToHardhatOp: boolean, activeSessionChainId: number) => { diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index fc81b10cb35..7a2ce11ca15 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -9,7 +9,7 @@ import { SLIDER_WIDTH, STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; -import { ChainId, ChainName } from '@/chains/types'; +import { ChainId } from '@/chains/types'; import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; import { globalColors } from '@/design-system'; import { ForegroundColor, palettes } from '@/design-system/color/palettes'; @@ -341,15 +341,17 @@ export const opacityWorklet = (color: string, opacity: number) => { // /---- END worklet utils ----/ // export const DEFAULT_SLIPPAGE_BIPS = { - [ChainId.mainnet]: 100, - [ChainId.polygon]: 200, - [ChainId.bsc]: 200, - [ChainId.optimism]: 200, - [ChainId.base]: 200, - [ChainId.zora]: 200, + [ChainId.apechain]: 200, [ChainId.arbitrum]: 200, [ChainId.avalanche]: 200, + [ChainId.base]: 200, [ChainId.blast]: 200, + [ChainId.bsc]: 200, + [ChainId.degen]: 200, + [ChainId.mainnet]: 100, + [ChainId.optimism]: 200, + [ChainId.polygon]: 200, + [ChainId.zora]: 200, }; export const slippageInBipsToString = (slippageInBips: number) => (slippageInBips / 100).toFixed(1); diff --git a/src/assets/badges/apechain.png b/src/assets/badges/apechain.png new file mode 100644 index 0000000000000000000000000000000000000000..f74845164afca3abb36bbfbca8202e8d4b06472b GIT binary patch literal 1388 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 literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechain@3x.png b/src/assets/badges/apechain@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb69328296cb412ac3245bfa03a1bcbef0d35ca GIT binary patch literal 4552 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 literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadge@3x.png b/src/assets/badges/apechainBadge@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..06a7eeb07a8eb6d1794fd11b12da644bdfa0958b GIT binary patch literal 12810 zcmV+lGWE@gP)U>h*?d$b`aT5T$j!+mv){4Q-A$k)t||l z8vWnYL5&#};E{=yZHo!sB_1(oDdMW&yvxUe(=MMoo|R;r_?B4G3TlS9Ni3&3^n3@G zie;^}gV^4m)mN9r9O_ZbVG|}8icnF31O?%L{L_vm;#3HD6?Va!XbBie+=k*;rwF^@ErnnmGiG7&g~k7pMfj=2Nx-7yfG+b@9RhSoC7+4d^G6WGyK&=Y zUwDu%>BOFLzv3&JBj%uu_a?nsXWY;C8j1*RiVm&GrT1~lQN11?Fq9atL^z~Y*WSuN zBKFIPK;0KV3()753%_sBu?|`LnZXC$_80l`j9{dFCuPeUa?_{;KoPlQ1Gla_#3;5~ zLM?DRWd8cE1^Z${?B-Jd-L9?YI+$;`*~6@)=31{*0e-L5&6uBBCal_Rh(kI+pHG6E zGy#)Bg4MWWW4RV)fO0R?^f!8}x0K^gC52yaHAyU{14j4F2fzBMk?aa+ju~V8&T(Pi zku7u_3z=g736wcn&j+wdQ@;egH!<8a*6a4`gUCbdhngwqLr4N`{^mS@3;U39%FMp& z94QXCW*HJ;ArWvxCLj|=A)v8_V25O| z0mfAs&wAhYKlE>$o9juuOZiA-xM`egS)t$hYU!5bcgqwNT2S`Gd8oUbMz*+(_ zAoK-LAt$iZGYE3nk03w%4gXI6c3ne4MQmQnm`n<=Je7<>60m%w3{bziRzj3d;`Wa~ zjah%VsmQH|vUBG#)$k=vz~eXp%WpJ64WJ2TXly3|LjMc>5_Zi3Bmsp0hRz^zP8lH&|EW0# zSm}~HiodZLXBlIqm@ili!?o4fSsT%Iu(lL!u(qYKZQJ^+*W7!$-*5Z$nR-*H9e>Hm zzpDDa+qdg~>aVK%O=nW*{osQA5{yd%?&o9lu^>)RWG!YXr^&dPwu}wgva|ZAS%{YF zN_jhDxjNh$2)^pv4XB?MVbzqawv>wO^=L1TAz#_Qa{tBHbiaPr=Q_dL+#l%{1uvw|9Hk9z2h)~Q0OsIu$0~P)u!)wOaJ5p1^ToB1W~(tChiR>0`^=Hz^27Wc&!C1p|)wLuINW= zhqNV|sOP_h9Y2e>i4SlK$P5$}7tFJQ={}ffT!AKCBoJ8XsciN8KfL)sVXpMKeC1cq zI_Q}9N%4#$4fZ#APpk<9gpeX41cexx!FXJOWNw%r&$O0ZuJCAV`Pbb?!`{D8u_mSK z%{35Qxm$1|xY?Ctk~P7abP>W$dam?T{)c9`{h(Kx1Rrtpp#HtW;!z?E==p;VT6~I1xNP5D_dED3SnWN=k_* z^e;GAeaT({Z~W4whxC51=zp~9YfRUl(Er21B9PkLSAE@A--i^i93F3!(pr$Nghi3* zDOfDFOifd^Z!9*PeyTC5zB}hZQmKxR7Qxd*!L0x>06rcPI1x}RN=CVoj(7J@KJ<+r z{re#=>!5ai-hI}|zaNqW0$2{dTaxEGrh{8B4?;Z-qDYp-FWhKuw6T%4QgPE2%TRF{ z)e}uEerX-n_gzHx1}s=yxP=~tp-2-Is06FT?=^xtcS>k#gWC(GvJDdJv=bQT0jhbCv7jMmkV1E0!u;KS+@ku zg{#WN6*E*OMt>Tql=(?Fl}QM93F#WJ2AXuFkVHU{uIQ65eemg9w~t=4W7qH#Yir{l zPo~pL$CJseot*tcPP(ztXma{+H2vf5wc%&)+qCBx{q4uUaLr+mWKNhlIrwh57Lf#Z zg1ZtnawP=GtAz^aifyX7>%i>_EhX)?Qg;35*#eb=CwUej;I~Qi_{@t!j~A(rI*#sh~C}_k}6rAq~UMg4})I&SOus@08ULNgxJf^-Kn0%q-qI zIZDMf;)x`LW9eLou(7pnKi*$YNjMHhsWePfv^kGJ%s|}Wl}1m=`c_Vc3>G7VTbRcL znC-bju7L>0iU>{s+Vra=sk9yA@#NM`Ti0Iu`cI#8xS;3Ia|t5t2}M#U8<~?Gcj(0| zVR_dzs+*c;D_(&G8CK#HTEjVtnl-4Iosv6Ai+~g`hgb%IRl?+uK>&9P1c(HMQh*e3 z!PT3dCnBJ{F$x1QULwWV$6UrefRFE98~ymQ?inBkamJjuCm-6gl$C76gG~Vo@q^js z8focbs~)I!lc^~1Era$KikuL&3pcbFG46x_1d}H(0w#VueiZX50;r-$#4dTlx>b00 zxQ6$A^PIzbpTFRUZJl2qjVD`B_@jeq5~+$&XDznu9KTD?BvN=li+dxk5U>_lMCQVS zP2VtcSF5%YqO@yT3oJ|R=Jzj41*ppYMW0G0XKnrzyvpu zVdS!Y-J^faoD{NVJU`hG0VmcXH_fn|{S69O)N=^dnfv{=hRp8>kr;(IV3~u;4XuJCri?PdI>UQ z00|jNmf@t&oqp8zouls!U71M&Ytzp*(h>KoAik$UM6Tc71)3tX@Jf+_n#DRPNJ|B* z1#{(P?hG)o5KfBROl*1cPN<%w7P2}C{Gf}Ud*r|?KLnP0^$|BgsTa$j)dLy2>yA9> zbC(_Uz}CULnf#|JHf>w`)SMJ2&q&eD!{N9PEdq}zvZ^WMF+G9#*8KaciS7S)MRTTM zwFe^tZMJEtMlQ_TbYE>&c?D7+h=d4K9&r=6h}U(N;oK`WzhG@Jxiu$vFdTOhOi~K@ zIndu$6*wr~&oK`8Sn>O}oO|u&7l_~@g$J9nw7P==YCuduJOLq8xLS=g)L&9cQdBKP z^lLAPR$o%}RFlktV{fEQewM-Ags>Ddd~;zLUiZn+9pV3V)fFh}VP3JlNvf*JKoF8HBfdZSHH|-aqT;+wa}|1}3?ViD3P}Xj1vx zhVRLC18%x=+ljC2ew#?4-%0i%ygNvdK_+on#uJ)YN&x2(#oSpvvPbFV_ zd@|pPXTvjsICRh@4?uh)b#tpXlV&JE@m7;NVO`pu8=HMueqya##6s#XfMj+xhz=+X zWrH-ig}I584rBuLhK~j@3dsPmy6@=Nho=GqL1ZSkz4d9Yt#=H!mC8TfIY+#$((XgU z9|0%|`b6O+01v*H>Jw^tP|2i%sWA^K?|dP>i9fcVIaEee1E%o?A_w+0C@10o~2ZP&Sc$v38F{^OHH`ta&J{&SD};U#&t%g{sX zre|t?m7m#lOpcS|-~1#0I8f~`^RfHnHsT$LlmO|_!Pp0Q!<-6chiS+hWc?D}+|q2n zmgcnby_0_0eFhXZTl%fW??BQR1&CIKN(Kk)WnfTBXI4gjK<^=&e*=-L+F z1WOfw9z;r%?1zvY(JCg+Y-|~~oEcRNUTJDP9Z=TaP#8})Cwjx;T<;@K4NiUEGI7#u zoBz;xbKmybX+4hX1cZ?cd64cyoALp{wsC@AVok%!`yYj81}HFGLQ+6#(l_B*9gw)z z>VV#6hYbwrI`v`x@`%13G4I41C%kREl9$_|Lcl;zWY*t6B@)u1VP*fLM#l1O=scI$ zxrVxS3m}9}ocGW10|SUZ-I<@+c{F>D53kB+ujv3{=dlYUUW;v`gkb~EiWeXmI#T~c z#kcR6jgT^AZY>TQD)%P-XnZ3(UoedlUsf~*sYeupH&i zh(iSd@vmDCKXY>GLWX%}+x_qMO&2^sgicP6onre32L$%pVT0{?tOZ2$9`#s<@T>p@ zo*h1y&x+MST>|`452j{Xk=J}7spQ*y2@OQf=b4Twlth$Qz{AU&)}aD7twY5(B=wEb z0SurLN$Ifj(AY=G%n34iO6fw_pX~r_ufNV0&_jdQ38YGWMKBW%9Jy!9i>ncSfh zZU_ig$(ypG^D)B{`J2Ho5QX#2K+$4!2}%j-5{ZOGGK~NN2}a($Q5`lwHr!Bt zqOJZZZcu_j;})9y;?1Xk#|;t!q7UOD6-mS@FkFBUctgOH=D?hh@q8N@Rt~YFsO`(T z*S<_ZkX%6gw<##kTk*^Q1$7BX3Gl`a8<{Y^E!x*t0_kv}{>85jndDlV~)gnf4ZQ$ADdq;;ZCpab<7u7BSE z;*UNc^g13OW>jhN zjIHSvFE$(ZR%XDGopfUXae3CV_-ZO0(wWsoFcj=<`W-61d;g;XXcFmAy`uMVWR?<{ z=>12^bnfkW?NmJvm#&1c=i#y1(H0QcCUx#)y9k+Qs9tgZx6B^`Q@ z4hjf5sR#(2MXo6de5tJfuoUxaXZBJ2c+!D@;8cA|2i$xDh~F4xQv427Ch?{;=2{<5 zMwLSYM~WT!kl>8Ebwjq(yki^*5zaHWv)SGP!lTwjn^bJKDA$?* zg39IM6c8Zr3Os}N(t0kvTnWvQ@ElZ>_*^GYN*gY8pX5LQd9H*IS2ZL9)9I7p0yC}X zdq@IUG&Kvu1sDQA;B6t(5}E+zL9)&N^0VugwG}(*O`*fKA6C&1fLt)g6A zZp(J|+G}`R@pyE)wz;K$E@&)y62b?prR=#$gTvDuc;@O95a0!rYz(DLwF4;V$_78x z{KRap%&^hoo1RR-b-y`M(y)Q0GZ9CNec1V$3J5x+w19`|4;86|03c{?5)fww^Ouu( z!2}J&rCApO2x8*D`WJG11Q2>1wBeHL-nGVT8`-w!;yHK^XNU5);+dgG02JT{h>9h6 zML@7Pi5KkR6hH!keWQ`-Qo`S*l@qdn;JgbBbxV;C>JXYh%Q*jMCCrWC0;ZNKl+9~8 zpLp=(M2^fOc_7eEdhyrefcW65xq$j!91!+;ycY`yw)OAVxqoec~fUuSZbO;TJ+nHWEcSDs*{d9u`1Eqf4 zA!)%NV#T_wY2#8tC?F^yaKmbH!j2{&C?Twc0c{(As9fIml(RwELMN0$?K|?>j_phK z+YS%nS&#Dp5xHr-+0ER@T_=dl@K5}>^`3k82|*VGdieXwGk8= z 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 QpDfDB!NtDpknhd^0+-^dMF0Q* literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeDark@2x.png b/src/assets/badges/apechainBadgeDark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ced4dc053fd8f74390ba7a8cef432e7c66eede22 GIT binary patch literal 5830 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 Q;{X5v07*qoM6N<$f_HUEGynhq literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeDark@3x.png b/src/assets/badges/apechainBadgeDark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..302ccc1d6e9ca2876708e82e99c8c222c358f324 GIT binary patch literal 10522 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 literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeLarge.png b/src/assets/badges/apechainBadgeLarge.png new file mode 100644 index 0000000000000000000000000000000000000000..bd647873044e2bbcf99d9ae0ee4013e69c050c0c GIT binary patch literal 6991 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 literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeLarge@2x.png b/src/assets/badges/apechainBadgeLarge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3e39ee0e49f078a76046683ee2ecd5feb6e75675 GIT binary patch literal 19899 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%0D$;2uzHTL=+>M`9`wTP(8-ej!Hlx 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

;uGw1o_wAlhHkNpPnTcPWS(Q@pM+-LL!eX3+p0a zJjWrxldK3b8jeE@Jm`0#!oR%;r-&lM?)(uOi)jB!R!D-t)lu>JUS$a^$8O8~cA^@X zB)%;z^a;XsdN>;hl9;(mJNu?Jts<_=U37wem6LtUt_D%?5slU);kc9o87I0M#%C*X_(U|jL zHCXxEg58YPXYhv^*v%mI=~H4& zR=FZ`K)Lq7?c%m!yL{FV1UiT5E&4iQDA4xxd)xtHP*CzC>mCk4)K<=2>%r(iURw~r z*^A1nO%Qc8+&A`WS9+hHc9iN0OacakP>JVDE$P}LVyn-4x5A;`7{`fiYqix z!s1r6h1{%Ii!UiA2M`0U>pR`U#2pr5;JaCJFnlwN-a#!A2FhM^Z{_@q5LmaMW!FnO zh_fR6zYZxJfD9r(&Rn$SqD^V}&JQ;C+vHGe%I3Re6t~1uedFTl52Mp=WuXuRrqW-D zIjyK<#T;|@osz_FCtx{%-SHK%AW*+K?P-#zK4OZw2xigC5|VhUk@yuf5`X=!b$;h- zbSSV%qLu`AQg^5#CCUb)&6@Ad-kh^py@3aihb@f_45Qhu4IFF)jW4t%9N_-}G1y{g zF9!gr4k0|NRXsWfQqCm?$f0h9tp}HI+j=0a*;JwJ&%Rzk6?#^7@>byw%x=bugLdL(7!ZXU%h-yr6x%a3Xlq`g z7%476OZrDJTLYiB?ekwJcu8CWcLU&Sd&AH|Z8p@v0X_V9Z$ysYsA+D=gN<$iVKw^P zM`YlikCp*IXv7z06rKm#v>Fqu0kIkJh5?1!!L)$0(&z@jQL9YpKvb}&AU z%|%-~V9SDO<7f-!*o46`*GV}p1OQ|eD4wub3I2T!KxmKxJtKFkp^aW}C#rFv;R{xS z$)T2m;|`8FfFTEN3>|>uOjyNuwHm-)bCGII-3SLyPe(wqaR=a}ft%Id8oYh6jKbKM z<7N((=U@9JDBfZnLP z;hnYc7aylDf9-EShs6XShOL(apVzn)gK^`mkgQulI+K=y6GK`{xT|;g`81&Y=*U=< zB%HONm0VnlUHcGy53aRW+zjG%Z3ttyU<;fnCa-jx;{4V?jb3kXvd^t<0+{()kTOi# z85#q-9CkAXj4yK^%eh9QQ1`EFt%l*=5goqM9uf{(T5(D3hO!$KRIT!C+zv+kjWxlh zA=PJ*6|M&rqO-bTuCpLiLY62X5H|d*b8f4kM1YmRIwrS6ea2>kk_Tv^NGcOjU~Rw2 zDkW=0G*h@rspCT2F#?K{Mzz=aMXYVDmNW`z(a9EvXzIsA@!OSOH?twQn2Y8z7P*ul z0D;S2{Dd5AP0+*?mjk7xhrQ1FC=F_&IIWU6+v}p=Vw$GF{>lP9D5Kdf5g2-9r1_^-HDKDKy;f%GqxnqNeswvm>_^j z;y^5l#3rZ=B~WLgC}C7bG2rfPE5owxcDkBl&|o4Y!0Su~98+!i;qU+L=PZqG{RHH| z|80#m5R4pcL#i=xCkA7wK@PO5|3;es>;Xxpad#rtU=2zRus+)WW+MkcP?BTUTQsHF ziCF`0_^yvy$elE#y_luU>#n}2D2wgaSmjz|JYQSm8*OSkQtZYdws{)VX@HVL-Hm|< zu9_rpHA)hWN3gbT!|@5?5}3xMLWxb_cC-=AU^Wp$XT8xpmtz2F(9Nn~GiOs10M#`e z%R0eTAeJ!Aa3+>u*MSxAJI+eFlIqKuQ$cw)u?8wI0kMVw*4P9i2lLqua487_-RKxU zf?A|M<8r796r$K`IEYJ>C;(&Oym2tUoAsDulZ9Q7S{Lq3F~`j&u<`36@VesL6dS32 zfjA1s084>&06BJErr|ygbt^o+pi{7%si@AuMOdr@r!p(0cKbNTz+87x7kJj{dY&SN z0&3AX#|cJ?4BB#G4G&z;LUskWg-{z4d5sIQM_`q%slWB)YsqeGZ zHK^e%q8hl#%+IT^YYSQe_mTj_C2%=#JLre_I6i?~dvCQrcV5RVAPX0SwStr}bR`hZ z4cvl9&n3&kdt${v20vUKL*feq7g%}xK4^wwFrV3vK@Oixzv6T51a1hzLw2zqsI!>A z?SK+)TMdpqpvr|HAO|c6#T}%WBrQMd@m+nAJ27j;flrnKR0y@qx;QDydR&0X@j0## zM9vc&Ibo#(OBSQ5ut6+|e3(+52@nH}jhW|^(^k|4K>+s@w9C=%*dFI+Yr>6Ak@DJ_ zXz-G>9KxzcBmwN7Lly>d$xtUS6B=~4mesfs z08+q>psLmlW;bGhyc>{gDl-8|xe|cBrw8~xflV>lxC0gh=rEjF5f3^b*?m%3!POi2 zt$p3+orxyyTNe(Ryy)BbKkiJ%D!;DM%<+h}r*l@rDbFe`-8SE1D#s-1ZX^wgyOA{L zo(d(?(W=ma+pxPqE^!K56sQU7hH#gzJA#G59lc$z<}oH;Ljqi1$Y6b+#ul&?nw4-l zc6&ualmiokCv5{)*|wJhs{@iH26{RS_w#Jd=2SS&z&bz?*(?Rb;1=~*4(cf>a@gh5ZT@2M z9jF+p6`GJkO9vW+?|0m8_o{zrW~2Y~oU7HGr}2j`bUtS~OD*r0>-@5nhjI>NQP7ib zK49ABHxg`9?p1q8=y=mfxL(6#fve$mb*ycGYm)FCtks~vL}7x!^?)p9bue>4M8CmJ zhWd=!(3`Vak$H@GMoEL~#0>nLIj+lKf@(~`uY}T!OE+q60M$7_3frju5`zl{-2tu2U)F&Gl0FV8sEs}Q=IRK4AD;&-_kEsgcPDDy z5lofsn!l~TB@=5Mx>GX^I2KW&5J`j}Vo^9~=@cj{RiJ?dH%_TBS7BDCLLmp%0cJFc zKuqjf6A;GhqyQP{17R56ye^aOu{(6*#_$}7fagO<=s4(u!Oy4s+P^1O!oaRX=Ti(U zlV^%QvkC+udf7@3>Q&Lk0t9_;N!& zx$9q>X>%5l~#K2_mtj~xz!`6}>kjBKFC^-;QaypxaLUt?2R^VC?;Py1`!Fz?IYAceh z7_9RxmOy+Z0CvUK%tWQr%g8-&cHOIwPm8cFF-{q4&t|N8h&~TS4QsCL>6FdD$6I3( zz^Nz`g#%A6-)*|fO=s+OfS*sHS0{LZHlle>pNXHY0k;@P=YB%fiTLJ3_aO_`5(aJw z{{MkCwb=;(@EYPE5P%8EKi8HH#L#+)S>0LM*jx#YiR}=4#H|2r^x{ku#DK+M5tM5y zJO!&AsT-Jl6^oVccGqgJscFpGl${(V`;X z#leqV+~6A+dRq|_KeNHcSs}LmAF!TE4s|6gdTN%!Kr+qW!=zBO6O<*??(N`Ixmz9@ zwq)HUYR_2Y`^+)>;8d5y^)KA|H%|BenGbU3dT}z4XMJ8L2W4n!B-vy%(v1o@vaDlV z4Qe)ACQH3e_q92z!7##WF|OS9^B2+86Dp!DK~dyEDUEtx?wT( zAq|cJ&-XcGGAl&G<_EYy)-6C{11ts4z$_V*7~1uTrY;RcHhtOjGz8uhCVyL7jO&Oz z9Z$Srj%%d?$HY9-Vo=3!)BY7hZ7%q!mCt%1V{yO5!+rDQvfTQG;0o}6vO=oW)c_4z zwj(yI&oPyDSQ3u{mmFh7%<3;$ff^5qw&lFUBn<9^wAyc46zpVyI`g;-|A-o-(34_7 zW`IpxQ=emlLMUoS?8>+Xq9icqkX2C^!Y~oc>dem&X#$XIiFOI%GhL_SQc#mKH-iDy zzZCbCD5}z}5UQ=?>Q1m8=zv|?Ae=Q?;@YJVdOANx_y6rI#N>#)=yY))8HjBiXjSO6a%-mbG?AU#%aLLVDq_y zlHC(4H(4PNHa9K+t6u}&=-7{cqka}JAt)OW4a2~M@L5u5`#&r)S72J7($u9zP)%W0 z_{8@BH^XUhj_tl4ptgeaI|Q*;C3D`nFfWbJYXhcS-}mSjSzEPQ4!29avvRzg3r{78 zo%z%2x?()3}!PATtLQS-q_Y=@H&od6=20cRPFy*Yp57v7`Solz{eIkK@MLhO94fs zQ_)xqCiS`!0GF~oC11VTr@A%H)m|kp^>zSQ#w(i*Nj9qil4L<67+SM>x$itV>cP@kGr&VDRb*~x|e;cB( zMG?rVfHZdB9`x?cUEglYBiF#qSg;I?jxB7864#+x1~-na3qg#bfE3&oZqA7lRW3b% z3~(u0u3f7EG?h8nn$EVA+eBd*bz^Rq;|!qBzo=ij)01+2@EkW7=XzVK{hSZA=}@W8 z8lPb8AIZ^WN1b1#3e>TQ5yuSpGR^fCe9g)i()}fDNpx6`VUnP>1B;@vVS;tuT(vB` zQL!*qD>Px_v)hqH|yB}M66gPyss5epj8@PJ0F3Fb-5CO;0D72yy*o~Og zmV(uftd8ICSyd)YjKQ!HvMvRdA*2s6xWJFKX>tpoOcW)Fk^>fQVK=%P{>FIz7YSoK zzBSqVs;O}$;(2z=>IO2!S`1j&^gN~4bht&8pXxg%Y;K5df z*&YsLDLPOWa9)xi^Hr?_Hyx`b8*t>fVkoYi%K*;F;_mgU?{z=Lc^_*ecK@M!b!TG{ zJl<<1hAJ%m2V`w=SBLUUuDj{bl7-2kXz;RtDBPH)?ni+|fg5699m2rUXf^;j@?6`> z(K$CLoh|`zj=SLZbk`;@ziU;GkOD-2Ze-kvSz9|%f*@@l+bdV7-Im4s34XXIE9p z5XyCR^ZxuVI>mXxN8$W({l?vDvNLpgIK)vaqB&?H%Ra}C1pPt)o0+vBY)K6Kr4ZM*X_TWl4r%^HyGe3#Aoy&4oex61Ezgt~jv zzimZ)k!|QM`#nC7s%)@!qC!x`If0>EgESn#wCT78E`igb6;|=(!`C%IbRLH-iDo`P zt%_N@v5ca@KGLYZRwnCrgi>MH4^PJVsOfjw|^-gsv29979#U@f?Q|A^SZvau0ja`G?oBL3a{!$ zBqIU%%xX2b+H#J$_K~_wgLa(eTIXeAEpWVHn`hDQCPmY?kZnFvl6H_f3 zt{Kfh;8N9fC~R#3S6Q-lqLq(pDUhM4h3NEmzXL6>>Un2X_?+XGcJCwc&aLrt%l!Ui ztuLM&ZsO`MRGq`M!NBi>ldC2@uWxznLJArboXK5IYgs)vYtx~Mh~s`Vm|35ga}V`!T~e6y|EKP}l4EOb7~C879{z3IC%H5ciF3QbNwSC(=DO7)vWBooagPiXECh+1_L0r9-`U} z$7xZFRNED_2Po$5^dAf30l^z(7$iKwxZlNWOoDRBbxv%8%@C*L3ugVV zzhfDQMJ`P+a@(KxFDz+s*?r@7iaez{ATP8YsDTM>_&2q#Z&VgWd7LzVF0!CokyFb@ zds~g*rH*T#NB%+y9zsy{F-+aW!#e6*Zhb)$d8zFrvI>z86Pql1M~;H-h?lfB z;w051wzYxl;_&sf4$L1&rywCy9^@5R)yv$DGcKQnD%q!E0D#@CsIVmm^&;7HA|ccM z2n^sBZc`YZUcY2Id$ignU-QKrM8QM!D7FZu3Qb0eZiuwxpTkxJhXq#dJZ@jts!Z|} z9==-+Pz*Ca077>}gr3|IPSBfW2rZ&$uYrgb01L4sE=ATs6qOA)Bv42IoAPHUdU9nk zfqWD|*|JPG5Ep!TK2f*s_=nqTPDZC_-p~CqV7M`TJh~zj=}me&i>`d^_<9Lc5;t*h z9*0|@%^!;E0135q&sn05eo$n^;fbI-D<+X97Ym^q@Q}~1{anIAcsd;7C5M3H(K?9w1p$TJH&|`a z?OSsxHd2xrhtVm_zMOrTZbs)eoVZNp&-K_WT2y@gaRU%-2y_FK26c_(dQP|%J4Ye$ zisL6Ux?xBP5%-Zjj;KoigQrJ&#=)TPs|*MKjk}-I(8t z)2%6Jf?H*8=*P-%vTIp#At_lJ=(HJ7Dn0z5o!>bbLqZ-?--rI z4+)W^-@vu90#;z+`}b!vAQ%|PF&GHQ0c}DjlaRC)-in!07Ugz_CTGyQ;O!jhM+Tp7 z1qUpzGC`YRvd7`jWa@e1XFa>GwRyM|qw(@VQu8T4p)ecnd}<3HRDX6&Tx=Ih6u7a) zK;X!xz1KZRta_aaMhe7|KEa>K6F)V;oo#6rTns3z%lacAL?K-OiuW`*N815p+RoaA z$goyT`kF6)w0=}R?k_qk$GS>6J~waM9Aw$?q>g)Vn|UARD;}?jOqbuE*gO5=q^F=O z!jUEv0V5H7p6o2jJI7|5=ll>L@Hw4_LV%j{hB)MYg93$SsqUk<2^d@+gSLlU2fGkC zXNOH}j+BpQ@3&5}Tdo(t$^ z_aoQClus-YO<~eA*V_L^h2`yT{7gs`CVK1?C50knzLz;6eK(uKH)$=>o6>v_wE@1)|d zNU4c-5HM!{!%b4S?&2a&2qL@wz`7=ini9|O#O9=9=`^zb`PxhrLPm}y_H$o4KUfBK zvG`eijzN}yunkA9=9&SCImVXzv>MDs1p*Q^NzT=TEF0Y{D&?MV{AOaZPX%wnmz_)D zq3bRhkV1g6hfwDa6dL!;z8pW#4TjC2o2NJsVsR|{!i39Z{VnEl#pcN&8BTYgBFrYYx&pjg_oiq~@XR02nE&6yZ}5o+LS0t=nM1 zNp3$s?`J}q0DwTHZN0~r>>|v0{QQaNW$|IU4c4l7!W+B~I0gTvF*|rEo#;Z!V^O4< zCM`#BJEwf?_S)}%^h~SP`wxC!7M$5~zW&T{TQBVhzyQ-#f65VB{c9Y7%!QU*D^J`L_K{Cm!kQ;r2(jpj z!kh1N5Pb>9CgEzIN5A0FR?lrRJTFycHww>pfF{0A4=_Q82P7A8;Z5a#1P z#}O1>%Ywa5h2}LTPSg8}QH5eq*6e-H+_$JV2>%6~i?IN|tkG<-E*8Ue(b-NC-OHfIUdb2Ot6e?@@#-=FRr{{d}b_DNQS^FJ2gSjoGP!mdgEmLybi znt(#<>WjJ#PkeM{=Q&3pJWGN^n%o598Cl4LaXiVA&?!Y0F1l-LpWNP_x9e-e{(5k@ z27-c9XqnkQgU#1yzs@i7{ntB}OQD|w#U4VZ011G`PJ+(y^Q7GW99x>d#BHCCbsU3~ zd0qqWch(I8+?OmX>r%2_1ru)hjW(;GsIBm+h=cd_u6JM2`uBJg-GbT=UVM24m(ka- zFo;-;b*x-g!;b@^z>}_4`1yD7y{> zgHR6ymRV8vakCy|RldVr^e*-eD!Vn@xgftaK(qu-Yjy0d!R-&kb8A*Pid#5od5qkJ z&-ZZWXA8H;VI&2i^9NE&GhG{AOYMM@m;#}Sp^od1*YC+z6G6c5Cj$8^@I3?xgeG^A zF||{;Ij_m2>~D(uXxwNX`y&9N&C%E<|F&JQ$4iuD=N^uqYXHV3XY`5nI$@bVn$NxmhU{=UZk{kbsB`)XxZMO&)S;!R|)P(v0C zd;h>k6S-))jC1-He#hJIvA(zb$%5SXy%lj+JF{Pco)cV^<4JsxJ0h}_!*_e0<84p) zgYihf6?6!%EQ$aa86+Ha*e1~V|M{{UT7lo)ox88*biJ>5Uw@A6MU!2Ta z4S=2Wy6Jj!vN^2dH!jEt=j7D+aE>M=&3;=0xF#!3HFQll_dLDkq~Cm%$nW(`+qCOO z^K)$t7q8I}oPWL*(Tq>Ten0=3H>eFzDMT@w<)dOY7{`QKD2jU7>lB^P!(Cqc(ggQ* z?o7SaWHj&uD@&l~@6`Kry$Z!Hjhc4r;OV|hi2kF!`$~)>hG77z`(HZwjizAvj5O~K z-J7~tFfb{}TH4~*PKJ6DZz5|-uk*=|ThpQ559TayCTA7C0z+@CZ16}Xhb>#vt*uv2 zegVpw(xGr;f=i+!0f)agiFOPo1@D;{*lNnqs`tg*eR^I zO13)zY%e{GTiW=F>$E8(P_A?9x%Tf(tmF{czYABw`P-E^@C!m63%c-aYPt1J0vDGK zrMP3%e|8N)vkzHrMQz2a+91>C>>Chz{`t-Kwsx!BAG#ljz_eCh(Dtj~B`6pAS!nY; zMYSV>Ky+A>#S!e|F78drDuqp9&fy^CF%RcVLZ5THhH(2g{zjJ#| zY5rnmE8VO0N#bBmoyar%zPs~n_F*rO9Oe@!#Ca}(BM=mrg<%1d4U*T^o?@lGCSz4E zH?M~qwa*gRoXH01_drPzA#2)3m?zXTuxeyL-pT|VNQSBflTw?KotXjc(2wthpM?Pi z_Q({pQEks{hcG}a797Sgm?$j3hR45Ny5#0EPpVK&E~o9Q1KWB1TZ7_%^T;M13V?!v z_!F=PAjQZ9jdMk%l)N$o+QKFB%wN(kGZ?{wlgCN@^bE^Kf~14NE-YA#^wGH;J^5x_ z1lio!O_au-nUcEzfIG{@&{w(6edMfA%(Y>qVe-gyg9H#V457dH;O?`BBD@iCmn&~B z&!3or0`jRf7Y|SHk0+e?W_c#$cO5WwmZ7%M9r6HzLcfZKNIry#C!@-mE-nDP!n=9E z$NxMJrZ5RB?gbn+xs+z5`ll>~QX>zp&4V!ALu#)8o5jOjkNjP6Z^A8=f=mX0y4CUI zrcU`44?vU>QSS?7)U84PX!7wCzL_}6-7w?&>PI?Oy~v7rxToPEoLc6({{n>kBn9vj XQ8ut7@2rWw00000NkvXXu0mjf;~?hp literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeLargeDark.png b/src/assets/badges/apechainBadgeLargeDark.png new file mode 100644 index 0000000000000000000000000000000000000000..ced4dc053fd8f74390ba7a8cef432e7c66eede22 GIT binary patch literal 5830 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 Q;{X5v07*qoM6N<$f_HUEGynhq literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeLargeDark@2x.png b/src/assets/badges/apechainBadgeLargeDark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f3e8ecae55695023b70021afbdd108d6d2411b9 GIT binary patch literal 16438 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

Jd|-A- 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

1W_rgPGaD^$f-x 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 Y0BT0QvS%$3@Bjb+07*qoM6N<$f(K01C;$Ke literal 0 HcmV?d00001 diff --git a/src/assets/badges/apechainBadgeNoShadow@2x.png b/src/assets/badges/apechainBadgeNoShadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..85fc3696ae6150d24cf4b8443d4d5c6404a3f13c GIT binary patch literal 1691 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 literal 0 HcmV?d00001 diff --git a/src/chains/index.ts b/src/chains/index.ts index 446d0cb5122..0d627b470ae 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -1,12 +1,15 @@ import { Chain } from 'viem/chains'; - -import backendNetworks from '../references/networks.json'; - +import { queryClient } from '@/react-query'; +import { backendNetworksQueryKey, BackendNetworksResponse } from '@/resources/metadata/backendNetworks'; import { ChainId, BackendNetwork, BackendNetworkServices, chainHardhat, chainHardhatOptimism } from './types'; import { transformBackendNetworksToChains } from './utils/backendNetworks'; import { gasUtils } from '@/utils'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; import { IS_TEST } from '@/env'; +import buildTimeNetworks from '@/references/networks.json'; + +// NOTE: Prefer runtime data from backendNetworksQueryKey, but fallback to buildTimeNetworks if needed +const backendNetworks = queryClient.getQueryData(backendNetworksQueryKey()) ?? buildTimeNetworks; const BACKEND_CHAINS = transformBackendNetworksToChains(backendNetworks.networks); @@ -105,6 +108,8 @@ export const chainsSwapPollingInterval: Record = backendNetwork const defaultSimplehashNetworks = (chainId: ChainId) => { switch (chainId) { + case ChainId.apechain: + return 'apechain'; case ChainId.arbitrum: return 'arbitrum'; case ChainId.avalanche: @@ -168,6 +173,7 @@ export const supportedTokenSearchChainIds = filterChainIdsByService(services => export const supportedNftChainIds = filterChainIdsByService(services => services.nftProxy.enabled); export const supportedWalletConnectChainIds = [ + ChainId.apechain, ChainId.arbitrum, ChainId.avalanche, ChainId.base, diff --git a/src/chains/types.ts b/src/chains/types.ts index 92425c2c14a..e7f318821a2 100644 --- a/src/chains/types.ts +++ b/src/chains/types.ts @@ -4,6 +4,7 @@ const HARDHAT_CHAIN_ID = 1337; const HARDHAT_OP_CHAIN_ID = 1338; export enum Network { + apechain = 'apechain', arbitrum = 'arbitrum', goerli = 'goerli', mainnet = 'mainnet', @@ -19,6 +20,7 @@ export enum Network { } export enum ChainId { + apechain = 33139, arbitrum = chain.arbitrum.id, arbitrumNova = chain.arbitrumNova.id, arbitrumSepolia = chain.arbitrumSepolia.id, @@ -54,6 +56,7 @@ export enum ChainId { } export enum ChainName { + apechain = 'apechain', arbitrum = 'arbitrum', arbitrumNova = 'arbitrum-nova', arbitrumSepolia = 'arbitrum-sepolia', diff --git a/src/components/AppStateChangeHandler.tsx b/src/components/AppStateChangeHandler.tsx index ee8b29d2ebb..d43dceff17d 100644 --- a/src/components/AppStateChangeHandler.tsx +++ b/src/components/AppStateChangeHandler.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { AppState, AppStateStatus, Linking } from 'react-native'; +import { AppState, AppStateStatus } from 'react-native'; import { analyticsV2 } from '@/analytics'; import store from '@/redux/store'; import { walletConnectLoadState } from '@/redux/walletconnect'; @@ -32,7 +32,7 @@ export function AppStateChangeHandler({ walletReady }: AppStateChangeHandlerProp eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); return () => eventSubscription.current?.remove(); - }, [handleAppStateChange]); + }, [handleAppStateChange, walletReady]); return null; } diff --git a/src/components/BackendNetworks.tsx b/src/components/BackendNetworks.tsx new file mode 100644 index 00000000000..9ff6747ddd2 --- /dev/null +++ b/src/components/BackendNetworks.tsx @@ -0,0 +1,7 @@ +import { useBackendNetworks } from '@/resources/metadata/backendNetworks'; + +export const BackendNetworks = () => { + useBackendNetworks(); + + return null; +}; diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx index 2ebf3a15295..5eba92ba6fa 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx @@ -18,6 +18,8 @@ import BlastBadge from '@/assets/badges/blastBadge.png'; import BlastBadgeDark from '@/assets/badges/blastBadgeDark.png'; import DegenBadge from '@/assets/badges/degenBadge.png'; import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png'; +import ApechainBadge from '@/assets/badges/apechainBadge.png'; +import ApechainBadgeDark from '@/assets/badges/apechainBadgeDark.png'; import { ChainId } from '@/chains/types'; interface FastChainBadgeProps { @@ -31,6 +33,10 @@ const AssetIconsByTheme: { light: ImageSourcePropType; }; } = { + [ChainId.apechain]: { + dark: ApechainBadgeDark, + light: ApechainBadge, + }, [ChainId.arbitrum]: { dark: ArbitrumBadgeDark, light: ArbitrumBadge, diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx index 7eddcedc647..4db59d9c61f 100644 --- a/src/components/coin-icon/ChainImage.tsx +++ b/src/components/coin-icon/ChainImage.tsx @@ -11,11 +11,14 @@ import ZoraBadge from '@/assets/badges/zora.png'; import AvalancheBadge from '@/assets/badges/avalanche.png'; import BlastBadge from '@/assets/badges/blast.png'; import DegenBadge from '@/assets/badges/degen.png'; +import ApechainBadge from '@/assets/badges/apechain.png'; import FastImage, { Source } from 'react-native-fast-image'; export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) { const source = useMemo(() => { switch (chainId) { + case ChainId.apechain: + return ApechainBadge; case ChainId.arbitrum: return ArbitrumBadge; case ChainId.base: diff --git a/src/handlers/tokenSearch.ts b/src/handlers/tokenSearch.ts index f5b6f85fa98..be7293fa56b 100644 --- a/src/handlers/tokenSearch.ts +++ b/src/handlers/tokenSearch.ts @@ -5,7 +5,6 @@ import { TokenSearchThreshold, TokenSearchTokenListId, TokenSearchUniswapAssetKe import { logger, RainbowError } from '@/logger'; import { EthereumAddress } from '@rainbow-me/swaps'; import { RainbowToken, TokenSearchToken } from '@/entities/tokens'; -import ethereumUtils from '@/utils/ethereumUtils'; import { chainsName } from '@/chains'; type TokenSearchApiResponse = { diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index a902eae1a34..092ba8d0bc9 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -94,6 +94,11 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main [ChainId.polygon]: [], [ChainId.bsc]: [], [ChainId.arbitrum]: [], + [ChainId.base]: [], + [ChainId.avalanche]: [], + [ChainId.optimism]: [], + [ChainId.blast]: [], + [ChainId.apechain]: [], }); const crosschainSwapsEnabled = useExperimentalFlag(CROSSCHAIN_SWAPS); diff --git a/src/languages/en_US.json b/src/languages/en_US.json index afefe1ab151..6101c6ed0ed 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -936,13 +936,17 @@ "title": "What's Avalanche" }, "degen": { - "text": "Degen Chain is a Layer 3 network that runs on top of Arbitrum and Ethereum, offering incredibly fast and cheap transactions denominated in the Farcaster community token $degen.", + "text": "Degen Chain is a Layer 3 network that runs on top of Arbitrum Orbit and Ethereum.", "title": "What's Degen Chain?" }, "blast": { "text": "Blast is a Layer 2 network that runs on top of Ethereum, enabling cheaper and faster transactions while still benefiting from the underlying security of Ethereum.\n\nIt bundles lots of transactions together in a \"roll up\" before sending them down to live permanently on Ethereum.", "title": "What's Blast?" }, + "apechain": { + "text": "ApeChain is a Layer 3 network that runs on top of Arbitrum Orbit and Ethereum. It significantly enhances ApeCoin's utility, fostering a robust and dynamic economy driven by $APE.", + "title": "What's ApeChain?" + }, "zora": { "text": "Zora is a Layer 2 network that runs on top of Ethereum, enabling cheaper and faster transactions while still benefiting from the underlying security of Ethereum.\n\nIt bundles lots of transactions together in a \"roll up\" before sending them down to live permanently on Ethereum.", "title": "What's Zora?" diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index 208fd748aa6..c380f3545a4 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -102,16 +102,17 @@ export const DEFAULT_CONFIG: RainbowConfig = { data_endpoint: DATA_ENDPOINT || 'wss://api-v4.zerion.io', data_origin: DATA_ORIGIN, default_slippage_bips: JSON.stringify({ + apechain: 200, arbitrum: 200, + avalanche: 200, + base: 200, + blast: 200, + bsc: 200, + degen: 200, mainnet: 100, optimism: 200, polygon: 200, - bsc: 200, - base: 200, zora: 200, - avalanche: 200, - blast: 200, - degen: 200, }), ethereum_goerli_rpc: __DEV__ ? ETHEREUM_GOERLI_RPC_DEV : ETHEREUM_GOERLI_RPC, ethereum_mainnet_rpc: __DEV__ ? ETHEREUM_MAINNET_RPC_DEV : ETHEREUM_MAINNET_RPC, diff --git a/src/references/index.ts b/src/references/index.ts index cb4dc3e38f5..121c6f5dd17 100644 --- a/src/references/index.ts +++ b/src/references/index.ts @@ -1,10 +1,7 @@ -import { AddressOrEth } from '@/__swaps__/types/assets'; -import { ChainId, Network } from '@/chains/types'; +import { Network } from '@/chains/types'; import { Asset } from '@/entities'; import { AddressZero } from '@ethersproject/constants'; -import type { Address } from 'viem'; - export { default as balanceCheckerContractAbi } from './balances-checker-abi.json'; export { default as chainAssets } from './chain-assets.json'; export { signatureRegistryABI, SIGNATURE_REGISTRY_ADDRESS } from './signatureRegistry'; @@ -71,6 +68,9 @@ export const BLAST_ETH_ADDRESS = AddressZero; export const DAI_AVALANCHE_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f'; export const USDC_AVALANCHE_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; export const WBTC_AVALANCHE_ADDRESS = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'; +export const APECOIN_MAINNET_ADDRESS = '0x4d224452801aced8b2f0aebe155379bb5d594381'; +export const APECOIN_ARBITRUM_ADDRESS = '0x7f9FBf9bDd3F4105C478b996B648FE6e828a1e98'; +export const APECOIN_APECHAIN_ADDRESS = AddressZero; export const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; export const WETH_POLYGON_ADDRESS = '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619'; @@ -136,33 +136,6 @@ export const AddCashCurrencyInfo: { }, }; -export const NATIVE_ASSETS_PER_CHAIN: Record = { - [ChainId.mainnet]: ETH_ADDRESS, - [ChainId.hardhat]: AddressZero, - [ChainId.sepolia]: AddressZero, - [ChainId.holesky]: AddressZero, - [ChainId.arbitrum]: AddressZero, - [ChainId.arbitrumSepolia]: AddressZero, - [ChainId.bsc]: AddressZero, - [ChainId.bscTestnet]: AddressZero, - [ChainId.optimism]: AddressZero, - [ChainId.hardhatOptimism]: AddressZero, - [ChainId.optimismSepolia]: AddressZero, - [ChainId.rari]: AddressZero, - [ChainId.base]: AddressZero, - [ChainId.baseSepolia]: AddressZero, - [ChainId.zora]: AddressZero, - [ChainId.zoraSepolia]: AddressZero, - [ChainId.polygon]: MATIC_POLYGON_ADDRESS, - [ChainId.polygonMumbai]: AddressZero, - [ChainId.avalanche]: AVAX_AVALANCHE_ADDRESS, - [ChainId.avalancheFuji]: AddressZero, - [ChainId.blast]: AddressZero, - [ChainId.blastSepolia]: AddressZero, - [ChainId.polygonAmoy]: AddressZero, - [ChainId.degen]: AddressZero, -}; - export type ReferrerType = 'native-app' | 'app-claim'; export const REFERRER: ReferrerType = 'native-app'; export const REFERRER_CLAIM: ReferrerType = 'app-claim'; diff --git a/src/resources/metadata/backendNetworks.ts b/src/resources/metadata/backendNetworks.ts new file mode 100644 index 00000000000..71a1f75b695 --- /dev/null +++ b/src/resources/metadata/backendNetworks.ts @@ -0,0 +1,68 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryConfigWithSelect, QueryFunctionArgs, createQueryKey, queryClient } from '@/react-query'; +import { BackendNetwork } from '@/chains/types'; +import { BACKEND_NETWORKS_QUERY } from './sharedQueries'; + +// /////////////////////////////////////////////// +// Query Types + +export interface BackendNetworksResponse { + networks: BackendNetwork[]; +} + +// /////////////////////////////////////////////// +// Query Key + +/** + * GraphQL query to fetch backend networks + * @see scripts/networks.js - for the build time version of this query + */ +export const GRAPHQL_QUERY = BACKEND_NETWORKS_QUERY; + +export const backendNetworksQueryKey = () => createQueryKey('backendNetworks', {}, { persisterVersion: 1 }); + +export type BackendNetworksQueryKey = ReturnType; + +// /////////////////////////////////////////////// +// Query Function + +export async function fetchBackendNetworks(): Promise { + const response = await fetch('https://metadata.p.rainbow.me/v1/graph', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query: BACKEND_NETWORKS_QUERY, + variables: { device: 'APP', includeTestnets: true }, + }), + }); + + const { data } = await response.json(); + + return data as BackendNetworksResponse; +} + +export async function backendNetworksQueryFunction({ + queryKey: [_key], +}: QueryFunctionArgs): Promise { + return await fetchBackendNetworks(); +} + +// /////////////////////////////////////////////// +// Query Hook + +export function useBackendNetworks( + config: QueryConfigWithSelect = {} +) { + return useQuery(backendNetworksQueryKey(), backendNetworksQueryFunction, { + ...config, + cacheTime: 1000 * 60 * 60 * 24, // 24 hours + staleTime: 1000 * 60 * 15, // 15 minutes + }); +} + +// /////////////////////////////////////////////// +// Prefetch Function + +export async function prefetchBackendNetworks() { + await queryClient.prefetchQuery(backendNetworksQueryKey(), backendNetworksQueryFunction); +} diff --git a/src/resources/metadata/sharedQueries.js b/src/resources/metadata/sharedQueries.js new file mode 100644 index 00000000000..65f543ed7c2 --- /dev/null +++ b/src/resources/metadata/sharedQueries.js @@ -0,0 +1,86 @@ +const BACKEND_NETWORKS_QUERY = ` + query getNetworks($device: Device!, $includeTestnets: Boolean!) { + networks(device: $device, includeTestnets: $includeTestnets) { + id + name + label + icons { + badgeURL + } + internal + testnet + opStack + defaultExplorer { + url + label + transactionURL + tokenURL + } + defaultRPC { + enabledDevices + url + } + gasUnits { + basic { + approval + swap + swapPermit + eoaTransfer + tokenTransfer + } + wrapped { + wrap + unwrap + } + } + nativeAsset { + address + name + symbol + decimals + iconURL + colors { + primary + fallback + shadow + } + } + nativeWrappedAsset { + address + name + symbol + decimals + iconURL + colors { + primary + fallback + shadow + } + } + enabledServices { + meteorology { + enabled + } + swap { + enabled + } + addys { + approvals + transactions + assets + positions + } + tokenSearch { + enabled + } + nftProxy { + enabled + } + } + } + } +`; + +module.exports = { + BACKEND_NETWORKS_QUERY, +}; diff --git a/src/state/appSessions/index.ts b/src/state/appSessions/index.ts index d8d28f5c397..02128c94c47 100644 --- a/src/state/appSessions/index.ts +++ b/src/state/appSessions/index.ts @@ -4,6 +4,7 @@ import { Network, ChainId } from '@/chains/types'; import { createRainbowStore } from '../internal/createRainbowStore'; const chainsIdByNetwork: Record = { + [Network.apechain]: ChainId.apechain, [Network.mainnet]: ChainId.mainnet, [Network.polygon]: ChainId.polygon, [Network.avalanche]: ChainId.avalanche, diff --git a/src/styles/colors.ts b/src/styles/colors.ts index b36d5f31409..b1bfe39c376 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -199,6 +199,7 @@ const getColorsByTheme = (darkMode?: boolean) => { [ChainId.avalanche]: '#E84142', [ChainId.degen]: '#A36EFD', [ChainId.blast]: '#25292E', + [ChainId.apechain]: '#0054FA', }; let gradients = { @@ -341,6 +342,7 @@ const getColorsByTheme = (darkMode?: boolean) => { [ChainId.avalanche]: '#FF5D5E', [ChainId.degen]: '#A36EFD', [ChainId.blast]: '#FCFC03', + [ChainId.apechain]: '#397BFF', }; } diff --git a/yarn.lock b/yarn.lock index 34a83377e27..36aba833c7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4427,9 +4427,9 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/swaps@npm:0.26.0": - version: 0.26.0 - resolution: "@rainbow-me/swaps@npm:0.26.0" +"@rainbow-me/swaps@npm:0.27.0": + version: 0.27.0 + resolution: "@rainbow-me/swaps@npm:0.27.0" dependencies: "@ethereumjs/util": "npm:9.0.0" "@ethersproject/abi": "npm:5.7.0" @@ -4444,7 +4444,7 @@ __metadata: "@ethersproject/transactions": "npm:5.7.0" "@ethersproject/wallet": "npm:5.7.0" "@metamask/eth-sig-util": "npm:7.0.0" - checksum: 10c0/f98672819af669e7dc0c9cc89a439e24e6ac2944ab661ba1c28d691cd2793a0b08550032d4b2f50a67aee0e52907d73efb203798f54264bc40519540eef9ff6a + checksum: 10c0/aba215c73c65bd8281f61a5658e41767f356d0d14c8bcf88defb3ec37ecf3d20f48ea8ceb545e93bdca903115334679b37aa735514432064f633da6ba542c001 languageName: node linkType: hard @@ -7886,7 +7886,7 @@ __metadata: "@notifee/react-native": "npm:7.8.2" "@rainbow-me/provider": "npm:0.1.1" "@rainbow-me/react-native-animated-number": "npm:0.0.2" - "@rainbow-me/swaps": "npm:0.26.0" + "@rainbow-me/swaps": "npm:0.27.0" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-camera-roll/camera-roll": "npm:7.7.0" "@react-native-clipboard/clipboard": "npm:1.13.2" From 9712448ae228ef54359a173ee0ada5fc9574e3ef Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 4 Oct 2024 11:55:06 -0400 Subject: [PATCH 47/64] Analytics changes (#6169) * analytics changes * idk * watched wallets cohort + properly track watched wallets --- .../screens/Swap/providers/swap-provider.tsx | 52 +++++++++----- src/analytics/event.ts | 48 +++++++++---- src/helpers/runWatchedWalletCohort.ts | 29 ++++++++ src/hooks/useApplicationSetup.ts | 3 + src/hooks/useImportingWallet.ts | 69 ++++++++++++++++--- src/resources/favorites.ts | 8 +++ src/screens/AddWalletSheet.tsx | 2 +- src/screens/ImportOrWatchWalletSheet.tsx | 3 +- src/storage/index.ts | 4 +- src/storage/schema.ts | 4 ++ 10 files changed, 176 insertions(+), 46 deletions(-) create mode 100644 src/helpers/runWatchedWalletCohort.ts diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index abefed50f50..9575fb0e54e 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -207,7 +207,6 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress; const isDegenModeEnabled = swapsStore.getState().degenMode; - const slippage = swapsStore.getState().slippage; const isSwappingToPopularAsset = swapsStore.getState().outputAsset?.sectionId === 'popular'; const selectedGas = getSelectedGas(parameters.chainId); @@ -244,7 +243,6 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } const gasFeeParamsBySpeed = getGasSettingsBySpeed(parameters.chainId); - const selectedGasSpeed = getSelectedGasSpeed(parameters.chainId); let gasParams: TransactionGasParamAmounts | LegacyTransactionGasParamAmounts = {} as | TransactionGasParamAmounts @@ -282,18 +280,26 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { SwapInputController.quoteFetchingInterval.start(); analyticsV2.track(analyticsV2.event.swapsFailed, { - createdAt: Date.now(), type, - parameters, - selectedGas, - selectedGasSpeed, - slippage, - bridge: isBridge, - errorMessage, - inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, - outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + isBridge: isBridge, + inputAssetSymbol: internalSelectedInputAsset.value?.symbol || '', + inputAssetName: internalSelectedInputAsset.value?.name || '', + inputAssetAddress: internalSelectedInputAsset.value?.address as AddressOrEth, + inputAssetChainId: internalSelectedInputAsset.value?.chainId || ChainId.mainnet, + inputAssetAmount: parameters.quote.sellAmount as number, + outputAssetSymbol: internalSelectedOutputAsset.value?.symbol || '', + outputAssetName: internalSelectedOutputAsset.value?.name || '', + outputAssetAddress: internalSelectedOutputAsset.value?.address as AddressOrEth, + outputAssetChainId: internalSelectedOutputAsset.value?.chainId || ChainId.mainnet, + outputAssetAmount: parameters.quote.buyAmount as number, + mainnetAddress: (parameters.assetToBuy.chainId === ChainId.mainnet + ? parameters.assetToBuy.address + : parameters.assetToSell.mainnetAddress) as AddressOrEth, + flashbots: parameters.flashbots ?? false, + tradeAmountUSD: parameters.quote.tradeAmountUSD, degenMode: isDegenModeEnabled, isSwappingToPopularAsset, + errorMessage, }); if (errorMessage !== 'handled') { @@ -333,15 +339,23 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { })(Routes.PROFILE_SCREEN, {}); analyticsV2.track(analyticsV2.event.swapsSubmitted, { - createdAt: Date.now(), type, - parameters, - selectedGas, - selectedGasSpeed, - slippage, - bridge: isBridge, - inputNativeValue: SwapInputController.inputValues.value.inputNativeValue, - outputNativeValue: SwapInputController.inputValues.value.outputNativeValue, + isBridge: isBridge, + inputAssetSymbol: internalSelectedInputAsset.value?.symbol || '', + inputAssetName: internalSelectedInputAsset.value?.name || '', + inputAssetAddress: internalSelectedInputAsset.value?.address as AddressOrEth, + inputAssetChainId: internalSelectedInputAsset.value?.chainId || ChainId.mainnet, + inputAssetAmount: parameters.quote.sellAmount as number, + outputAssetSymbol: internalSelectedOutputAsset.value?.symbol || '', + outputAssetName: internalSelectedOutputAsset.value?.name || '', + outputAssetAddress: internalSelectedOutputAsset.value?.address as AddressOrEth, + outputAssetChainId: internalSelectedOutputAsset.value?.chainId || ChainId.mainnet, + outputAssetAmount: parameters.quote.buyAmount as number, + mainnetAddress: (parameters.assetToBuy.chainId === ChainId.mainnet + ? parameters.assetToBuy.address + : parameters.assetToSell.mainnetAddress) as AddressOrEth, + flashbots: parameters.flashbots ?? false, + tradeAmountUSD: parameters.quote.tradeAmountUSD, degenMode: isDegenModeEnabled, isSwappingToPopularAsset, }); diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 5b67f2730ee..0352f86656e 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -1,13 +1,10 @@ -import { GasSettings } from '@/__swaps__/screens/Swap/hooks/useCustomGas'; -import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; +import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; import { ChainId, Network } from '@/chains/types'; -import { GasSpeed } from '@/__swaps__/types/gas'; import { SwapAssetType } from '@/__swaps__/types/swap'; import { UnlockableAppIconKey } from '@/appIcons/appIcons'; import { CardType } from '@/components/cards/GenericCard'; import { LearnCategory } from '@/components/cards/utils/types'; import { FiatProviderName } from '@/entities/f2c'; -import { RapSwapActionParameters } from '@/raps/references'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; import { AnyPerformanceLog, Screen } from '../state/performance/operations'; @@ -149,18 +146,28 @@ export const event = { performanceTimeToSign: 'performance.time_to_sign', performanceTimeToSignOperation: 'performance.time_to_sign.operation', + + addFavoriteToken: 'add_favorite_token', + watchWallet: 'watch_wallet', + watchedWalletCohort: 'watched_wallet_cohort', } as const; type SwapEventParameters = { - createdAt: number; type: T; - bridge: boolean; - inputNativeValue: string | number; - outputNativeValue: string | number; - parameters: Omit, 'gasParams' | 'gasFeeParamsBySpeed' | 'selectedGasFee'>; - selectedGas: GasSettings; - selectedGasSpeed: GasSpeed; - slippage: string; + isBridge: boolean; + inputAssetSymbol: string; + inputAssetName: string; + inputAssetAddress: AddressOrEth; + inputAssetChainId: ChainId; + inputAssetAmount: number; + outputAssetSymbol: string; + outputAssetName: string; + outputAssetAddress: AddressOrEth; + outputAssetChainId: ChainId; + outputAssetAmount: number; + mainnetAddress: string; + flashbots: boolean; + tradeAmountUSD: number; degenMode: boolean; isSwappingToPopularAsset: boolean; }; @@ -571,4 +578,21 @@ export type EventProperties = { }; [event.performanceTimeToSignOperation]: AnyPerformanceLog; + + [event.addFavoriteToken]: { + address: AddressOrEth; + chainId: ChainId; + name: string; + symbol: string; + }; + + [event.watchWallet]: { + addressOrEnsName: string; + address: string; + }; + + [event.watchedWalletCohort]: { + numWatchedWallets: number; + watchedWalletsAddresses: string[]; + }; }; diff --git a/src/helpers/runWatchedWalletCohort.ts b/src/helpers/runWatchedWalletCohort.ts new file mode 100644 index 00000000000..bea778eb997 --- /dev/null +++ b/src/helpers/runWatchedWalletCohort.ts @@ -0,0 +1,29 @@ +import { useWallets } from '@/hooks'; +import walletTypes from './walletTypes'; +import { useEffect } from 'react'; +import { analyticsV2 } from '@/analytics'; +import * as ls from '@/storage'; + +const WATCHED_WALLET_COHORT_INTERVAL = 1000 * 60 * 60 * 24; // 1 day between cohort reports + +export function useRunWatchedWalletCohort() { + const { wallets } = useWallets(); + + useEffect(() => { + const watchedWallets = Object.values(wallets || {}).filter(wallet => wallet.type === walletTypes.readOnly); + if (!watchedWallets.length) { + return; + } + + const lastReported = ls.watchedWalletCohort.get(['lastReported']); + if (lastReported && Date.now() - lastReported < WATCHED_WALLET_COHORT_INTERVAL) { + return; + } + + ls.watchedWalletCohort.set(['lastReported'], Date.now()); + analyticsV2.track(analyticsV2.event.watchedWalletCohort, { + numWatchedWallets: watchedWallets.length, + watchedWalletsAddresses: watchedWallets.flatMap(wallet => wallet.addresses.map(acc => acc.address)), + }); + }, [wallets]); +} diff --git a/src/hooks/useApplicationSetup.ts b/src/hooks/useApplicationSetup.ts index 0ce196b74b8..7a07034cdc1 100644 --- a/src/hooks/useApplicationSetup.ts +++ b/src/hooks/useApplicationSetup.ts @@ -14,10 +14,13 @@ import { initListeners as initWalletConnectListeners, initWalletConnectPushNotif import isTestFlight from '@/helpers/isTestFlight'; import { PerformanceTracking } from '@/performance/tracking'; import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; +import { useRunWatchedWalletCohort } from '@/helpers/runWatchedWalletCohort'; export function useApplicationSetup() { const [initialRoute, setInitialRoute] = useState(null); + useRunWatchedWalletCohort(); + const identifyFlow = useCallback(async () => { const address = await loadAddress(); if (address) { diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index bd48b4bab07..e78fa43a2e8 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -14,7 +14,7 @@ import usePrevious from './usePrevious'; import useWalletENSAvatar from './useWalletENSAvatar'; import useWallets from './useWallets'; import { WrappedAlert as Alert } from '@/helpers/alert'; -import { analytics } from '@/analytics'; +import { analytics, analyticsV2 } from '@/analytics'; import { PROFILES, useExperimentalFlag } from '@/config'; import { fetchReverseRecord } from '@/handlers/ens'; import { getProvider, isValidBluetoothDeviceId, resolveUnstoppableDomain } from '@/handlers/web3'; @@ -110,7 +110,19 @@ export default function useImportingWallet({ showImportModal = true } = {}) { ); const handlePressImportButton = useCallback( - async (forceColor: any, forceAddress: any, forceEmoji: any = null, avatarUrl: any) => { + async ({ + forceColor, + forceAddress = '', + forceEmoji, + avatarUrl, + type = 'import', + }: { + forceColor?: string | number; + forceAddress?: string; + forceEmoji?: string; + avatarUrl?: string; + type?: 'import' | 'watch'; + } = {}) => { setBusy(true); analytics.track('Tapped "Import" button'); // guard against pressEvent coming in as forceColor if @@ -135,9 +147,17 @@ export default function useImportingWallet({ showImportModal = true } = {}) { } setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; - avatarUrl = avatarUrl || (avatar && avatar?.imageUrl); + const finalAvatarUrl = avatarUrl || (avatar && avatar?.imageUrl); setBusy(false); - startImportProfile(name, guardedForceColor, address, avatarUrl); + + if (type === 'watch') { + analyticsV2.track(analyticsV2.event.watchWallet, { + addressOrEnsName: input, // ENS name + address, + }); + } + + startImportProfile(name, guardedForceColor, address, finalAvatarUrl); analytics.track('Show wallet profile modal for ENS address', { address, input, @@ -159,6 +179,14 @@ export default function useImportingWallet({ showImportModal = true } = {}) { setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; setBusy(false); + + if (type === 'watch') { + analyticsV2.track(analyticsV2.event.watchWallet, { + addressOrEnsName: input, // unstoppable domain name + address, + }); + } + // @ts-expect-error ts-migrate(2554) FIXME: Expected 4 arguments, but got 3. startImportProfile(name, guardedForceColor, address); analytics.track('Show wallet profile modal for Unstoppable address', { @@ -171,15 +199,18 @@ export default function useImportingWallet({ showImportModal = true } = {}) { return; } } else if (isValidAddress(input)) { + let finalAvatarUrl: string | null | undefined = avatarUrl; + let ens = input; try { - const ens = await fetchReverseRecord(input); + ens = await fetchReverseRecord(input); if (ens && ens !== input) { name = forceEmoji ? `${forceEmoji} ${ens}` : ens; if (!avatarUrl && profilesEnabled) { const avatar = await fetchENSAvatar(name, { swallowError: true }); - avatarUrl = avatar?.imageUrl; + finalAvatarUrl = avatar?.imageUrl; } } + analytics.track('Show wallet profile modal for read only wallet', { ens, input, @@ -188,8 +219,15 @@ export default function useImportingWallet({ showImportModal = true } = {}) { logger.error(new RainbowError(`[useImportingWallet]: Error resolving ENS during wallet import: ${e}`)); } setBusy(false); - // @ts-expect-error ts-migrate(2554) FIXME: Expected 4 arguments, but got 3. - startImportProfile(name, guardedForceColor, input); + + if (type === 'watch') { + analyticsV2.track(analyticsV2.event.watchWallet, { + addressOrEnsName: ens, + address: input, + }); + } + + startImportProfile(name, guardedForceColor, input, finalAvatarUrl); } else { try { setTimeout(async () => { @@ -200,17 +238,26 @@ export default function useImportingWallet({ showImportModal = true } = {}) { return null; } const ens = await fetchReverseRecord(walletResult.address); + let finalAvatarUrl: string | null | undefined = avatarUrl; if (ens && ens !== input) { name = forceEmoji ? `${forceEmoji} ${ens}` : ens; - if (!avatarUrl && profilesEnabled) { + if (!finalAvatarUrl && profilesEnabled) { const avatar = await fetchENSAvatar(name, { swallowError: true, }); - avatarUrl = avatar?.imageUrl; + finalAvatarUrl = avatar?.imageUrl; } } setBusy(false); - startImportProfile(name, guardedForceColor, walletResult.address, avatarUrl); + + if (type === 'watch') { + analyticsV2.track(analyticsV2.event.watchWallet, { + addressOrEnsName: ens, + address: input, + }); + } + + startImportProfile(name, guardedForceColor, walletResult.address, finalAvatarUrl); analytics.track('Show wallet profile modal for imported wallet', { address: walletResult.address, type: walletResult.type, diff --git a/src/resources/favorites.ts b/src/resources/favorites.ts index e64e758be4b..82413ed4ac7 100644 --- a/src/resources/favorites.ts +++ b/src/resources/favorites.ts @@ -9,6 +9,7 @@ import { useQuery } from '@tanstack/react-query'; import { omit } from 'lodash'; import { externalTokenQueryKey, fetchExternalToken } from './assets/externalAssetsQuery'; import { chainsIdByName, chainsName } from '@/chains'; +import { analyticsV2 } from '@/analytics'; export const favoritesQueryKey = createQueryKey('favorites', {}, { persisterVersion: 4 }); @@ -123,6 +124,13 @@ export async function toggleFavorite(address: string, chainId = ChainId.mainnet) queryClient.setQueryData(favoritesQueryKey, omit(favorites, uniqueId)); } else { const metadata = await fetchMetadata([lowercasedAddress], chainId); + analyticsV2.track(analyticsV2.event.addFavoriteToken, { + address: lowercasedAddress, + chainId, + name: metadata[uniqueId].name, + symbol: metadata[uniqueId].symbol, + }); + queryClient.setQueryData(favoritesQueryKey, { ...favorites, ...metadata }); } } diff --git a/src/screens/AddWalletSheet.tsx b/src/screens/AddWalletSheet.tsx index 67c13b27d42..6021f7ad295 100644 --- a/src/screens/AddWalletSheet.tsx +++ b/src/screens/AddWalletSheet.tsx @@ -182,7 +182,7 @@ export const AddWalletSheet = () => { }; const onPressWatch = () => { - analytics.track('Tapped "Add an existing wallet"'); + analytics.track('Tapped "Watch an Ethereum Address"'); analyticsV2.track(analyticsV2.event.addWalletFlowStarted, { isFirstWallet, type: 'watch', diff --git a/src/screens/ImportOrWatchWalletSheet.tsx b/src/screens/ImportOrWatchWalletSheet.tsx index bcdccc4067e..c027b7d888d 100644 --- a/src/screens/ImportOrWatchWalletSheet.tsx +++ b/src/screens/ImportOrWatchWalletSheet.tsx @@ -74,8 +74,7 @@ export const ImportOrWatchWalletSheet = () => { multiline numberOfLines={3} onSubmitEditing={() => { - // @ts-expect-error callback needs refactor - if (isSecretValid) handlePressImportButton(); + if (isSecretValid) handlePressImportButton({ type }); }} placeholder={i18n.t(TRANSLATIONS[type].placeholder)} placeholderTextColor={labelTertiary} diff --git a/src/storage/index.ts b/src/storage/index.ts index e65fefd1396..1014ce03dbf 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -1,6 +1,6 @@ import { MMKV } from 'react-native-mmkv'; -import { Account, Cards, Campaigns, Device, Review } from '@/storage/schema'; +import { Account, Cards, Campaigns, Device, Review, WatchedWalletCohort } from '@/storage/schema'; import { EthereumAddress, RainbowTransaction } from '@/entities'; import { SecureStorage } from '@coinbase/mobile-wallet-protocol-host'; import { ChainId } from '@/chains/types'; @@ -135,3 +135,5 @@ export const mwp: SecureStorage = { mwpStorage.remove([key]); }, }; + +export const watchedWalletCohort = new Storage<[], WatchedWalletCohort>({ id: 'watchedWalletCohort' }); diff --git a/src/storage/schema.ts b/src/storage/schema.ts index 2983aa51317..0d1c898fd84 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -81,3 +81,7 @@ export type Campaigns = CampaignKeys & CampaignMetadata; export type Cards = { [cardKey: string]: boolean; }; + +export type WatchedWalletCohort = { + lastReported: ReturnType; +}; From 92513f12b2de738b1c27db7f3c8675804ffb0e5e Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 4 Oct 2024 13:12:37 -0400 Subject: [PATCH 48/64] [CHORE]: Walletconnect Approval / Redirect sheet typescript conversion (#6167) * wc ts conversion * remove memo and fix lint --- ...nButtonRow.js => SheetActionButtonRow.tsx} | 23 +++- .../walletConnectApprovalSheetTypes.ts | 9 +- src/helpers/walletConnectNetworks.ts | 3 +- src/hooks/useSearchCurrencyList.ts | 1 - src/navigation/types.ts | 13 ++- src/redux/walletconnect.ts | 27 +++-- src/screens/ENSAssignRecordsSheet.tsx | 1 - src/screens/ENSConfirmRegisterSheet.tsx | 1 - ...heet.js => WalletConnectApprovalSheet.tsx} | 105 +++++++++++------- ...heet.js => WalletConnectRedirectSheet.tsx} | 22 ++-- src/screens/WalletScreen/index.tsx | 79 ++++++------- 11 files changed, 164 insertions(+), 120 deletions(-) rename src/components/sheet/sheet-action-buttons/{SheetActionButtonRow.js => SheetActionButtonRow.tsx} (65%) rename src/screens/{WalletConnectApprovalSheet.js => WalletConnectApprovalSheet.tsx} (83%) rename src/screens/{WalletConnectRedirectSheet.js => WalletConnectRedirectSheet.tsx} (71%) diff --git a/src/components/sheet/sheet-action-buttons/SheetActionButtonRow.js b/src/components/sheet/sheet-action-buttons/SheetActionButtonRow.tsx similarity index 65% rename from src/components/sheet/sheet-action-buttons/SheetActionButtonRow.js rename to src/components/sheet/sheet-action-buttons/SheetActionButtonRow.tsx index 7b9e87f84c2..82ad6524d6b 100644 --- a/src/components/sheet/sheet-action-buttons/SheetActionButtonRow.js +++ b/src/components/sheet/sheet-action-buttons/SheetActionButtonRow.tsx @@ -1,11 +1,18 @@ -import React, { Children } from 'react'; +import React, { Children, ReactNode } from 'react'; import { FlexItem, Row } from '../../layout'; import styled from '@/styled-thing'; import { padding } from '@/styles'; +interface ContainerProps { + ignorePaddingBottom?: boolean; + paddingBottom?: number | null; + ignorePaddingTop?: boolean; + paddingHorizontal?: number | null; +} + const Container = styled(Row).attrs({ justify: 'space-around', -})(({ ignorePaddingBottom, paddingBottom, ignorePaddingTop, paddingHorizontal }) => ({ +})(({ ignorePaddingBottom, paddingBottom, ignorePaddingTop, paddingHorizontal }: ContainerProps) => ({ ...padding.object(ignorePaddingTop ? 0 : 19, paddingHorizontal || 11.5, ignorePaddingBottom ? 0 : 24), ...(paddingBottom && { paddingBottom }), @@ -14,18 +21,26 @@ const Container = styled(Row).attrs({ zIndex: 2, })); -function renderButton(child) { +function renderButton(child: ReactNode) { if (!child) return null; return {child}; } +interface SheetActionButtonRowProps { + children: ReactNode; + ignorePaddingBottom?: boolean; + ignorePaddingTop?: boolean; + paddingBottom?: number | null; + paddingHorizontal?: number | null; +} + export default function SheetActionButtonRow({ children, ignorePaddingBottom = false, ignorePaddingTop = false, paddingBottom = null, paddingHorizontal = null, -}) { +}: SheetActionButtonRowProps) { return ( defaultChains[chainId]); @@ -14,7 +15,7 @@ const androidNetworkActions = () => { export const NETWORK_MENU_ACTION_KEY_FILTER = 'switch-to-network-'; -export const networksMenuItems = () => { +export const networksMenuItems: () => MenuActionConfig[] = () => { const { testnetsEnabled } = store.getState().settings; return walletConnectChains .filter(({ testnet }) => testnetsEnabled || !testnet) diff --git a/src/hooks/useSearchCurrencyList.ts b/src/hooks/useSearchCurrencyList.ts index 092ba8d0bc9..027dd4cd7aa 100644 --- a/src/hooks/useSearchCurrencyList.ts +++ b/src/hooks/useSearchCurrencyList.ts @@ -96,7 +96,6 @@ const useSearchCurrencyList = (searchQuery: string, searchChainId = ChainId.main [ChainId.arbitrum]: [], [ChainId.base]: [], [ChainId.avalanche]: [], - [ChainId.optimism]: [], [ChainId.blast]: [], [ChainId.apechain]: [], }); diff --git a/src/navigation/types.ts b/src/navigation/types.ts index fbb848d8972..59420752554 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -7,6 +7,8 @@ import { REGISTRATION_MODES } from '@/helpers/ens'; import { CampaignCheckResult } from '@/components/remote-promo-sheet/checkForRemotePromoSheet'; import { ParsedAddressAsset, UniqueAsset } from '@/entities'; import { Claimable } from '@/resources/addys/claimables/types'; +import { WalletconnectApprovalSheetRouteParams, WalletconnectResultType } from '@/redux/walletconnect'; +import { WalletConnectApprovalSheetType } from '@/helpers/walletConnectApprovalSheetTypes'; export type PartialNavigatorConfigOptions = Pick['Screen']>[0]>, 'options'>; @@ -61,7 +63,10 @@ export type RootStackParamList = { [key: string]: any; }; [Routes.PORTAL]: PortalSheetProps; - [Routes.WALLET_SCREEN]: any; + [Routes.WALLET_SCREEN]: { + initialized?: boolean; + emptyWallet?: boolean; + }; [Routes.PROFILE_SCREEN]: any; [Routes.SWAP_SETTINGS_SHEET]: any; [Routes.SWAP_DETAILS_SHEET]: any; @@ -86,4 +91,10 @@ export type RootStackParamList = { [Routes.CLAIM_CLAIMABLE_PANEL]: { claimable: Claimable; }; + [Routes.WALLET_CONNECT_APPROVAL_SHEET]: WalletconnectApprovalSheetRouteParams & { + type: WalletConnectApprovalSheetType; + }; + [Routes.WALLET_CONNECT_REDIRECT_SHEET]: { + type: WalletconnectResultType; + }; }; diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index 61e593856ad..88a7ee473df 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -128,6 +128,19 @@ interface WalletconnectAddUriAction { */ export type WalletconnectResultType = 'timedOut' | 'sign' | 'transaction' | 'sign-canceled' | 'transaction-canceled' | 'connect' | 'reject'; +export type WalletconnectMeta = { + /** + * WC v2 introduced multi-chain support, while v1 only supported a single + * chain at a time. To avoid confusion, we now send both as an array + * `chainIds`. WC v1 will always be an array with length `1`, while v2 can + * have more than one. + */ + chainIds: number[]; + isWalletConnectV2?: boolean; + proposedChainId?: number; + proposedAddress?: string; +} & Pick; + /** * Route parameters sent to a WalletConnect approval sheet. */ @@ -142,18 +155,8 @@ export interface WalletconnectApprovalSheetRouteParams { dappUrl: WalletconnectRequestData['dappUrl'] ) => Promise; receivedTimestamp: number; - meta?: { - /** - * WC v2 introduced multi-chain support, while v1 only supported a single - * chain at a time. To avoid confusion, we now send both as an array - * `chainIds`. WC v1 will always be an array with length `1`, while v2 can - * have more than one. - */ - chainIds: number[]; - isWalletConnectV2?: boolean; - proposedChainId?: number; - proposedAddress?: string; - } & Pick; + currentChainId?: ChainId; + meta?: WalletconnectMeta; timeout?: ReturnType | null; timedOut?: boolean; failureExplainSheetVariant?: string; diff --git a/src/screens/ENSAssignRecordsSheet.tsx b/src/screens/ENSAssignRecordsSheet.tsx index 46b7b91a204..8561306ee10 100644 --- a/src/screens/ENSAssignRecordsSheet.tsx +++ b/src/screens/ENSAssignRecordsSheet.tsx @@ -307,7 +307,6 @@ export function ENSAssignRecordsBottomActions({ - {/* @ts-expect-error JavaScript component */} - {/* @ts-expect-error JavaScript component */} {/* @ts-expect-error JavaScript component */} ({ +const LoadingSpinner = styled(android ? Spinner : ActivityIndicator).attrs(({ theme: { colors } }: WithThemeProps) => ({ color: colors.alpha(colors.blueGreyDark, 0.3), size: android ? 40 : 'large', }))({}); -const DappLogo = styled(RequestVendorLogoIcon).attrs(({ theme: { colors } }) => ({ +const DappLogo = styled(RequestVendorLogoIcon).attrs(({ theme: { colors } }: WithThemeProps) => ({ backgroundColor: colors.transparent, borderRadius: 18, showLargeShadow: true, @@ -46,23 +58,23 @@ const DappLogo = styled(RequestVendorLogoIcon).attrs(({ theme: { colors } }) => marginBottom: 24, }); -const LabelText = ({ children, ...props }) => { +const LabelText = ({ children, ...props }: Partial) => { return ( - + {children} ); }; -const SwitchText = ({ children, ...props }) => { +const SwitchText = ({ children, ...props }: Partial) => { return ( - + {children} ); }; -const NetworkPill = ({ chainIds }) => { +const NetworkPill = ({ chainIds }: { chainIds: ChainId[] }) => { const { colors } = useTheme(); const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); @@ -89,20 +101,19 @@ const NetworkPill = ({ chainIds }) => { // @ts-expect-error overloaded props ContextMenuButton menuConfig={{ menuItems: networkMenuItems, menuTitle: '' }} isMenuPrimaryAction - onPressMenuItem={() => {}} + onPressMenuItem={noop} useActionSheetFallback={false} width="100%" > {}} + onPress={noop} testID={'available-networks-v2'} paddingTop="8px" marginRight={{ custom: -2 }} > - + {availableNetworkChainIds.length > 1 ? ( <> {availableNetworkChainIds.map((chainId, index) => { @@ -150,24 +161,30 @@ const NetworkPill = ({ chainIds }) => { ); }; -export default function WalletConnectApprovalSheet() { +export function WalletConnectApprovalSheet() { const { colors, isDarkMode } = useTheme(); const { goBack } = useNavigation(); - const { params } = useRoute(); + const { params } = useRoute>(); const { chainId: settingsChainId, accountAddress } = useAccountSettings(); const { navigate } = useNavigation(); const { selectedWallet, walletNames, wallets } = useWallets(); const handled = useRef(false); - const initialApprovalAccount = useMemo( - () => - params?.meta?.proposedAddress - ? { address: params.meta.proposedAddress, wallet: findWalletWithAccount(wallets, params.meta.proposedAddress) } - : { - address: accountAddress, - wallet: selectedWallet, - }, - [accountAddress, params?.meta?.proposedAddress, selectedWallet, wallets] - ); + const initialApprovalAccount = useMemo<{ address: Address; wallet: RainbowWallet }>(() => { + const accountAddressAsAddress = accountAddress as Address; + + if (!params?.meta?.proposedAddress) { + return { address: accountAddressAsAddress, wallet: selectedWallet }; + } + + const wallet = findWalletWithAccount(wallets || {}, params?.meta?.proposedAddress); + if (!wallet) { + return { address: accountAddressAsAddress, wallet: selectedWallet }; + } + + const proposedAddressAsAddress = params?.meta?.proposedAddress as Address; + + return { address: proposedAddressAsAddress, wallet }; + }, [accountAddress, params?.meta?.proposedAddress, selectedWallet, wallets]); const [approvalAccount, setApprovalAccount] = useState(initialApprovalAccount); const type = params?.type || WalletConnectApprovalSheetType.connect; @@ -177,7 +194,7 @@ export default function WalletConnectApprovalSheet() { * CAN BE UNDEFINED if we navigated here with no data. This is how we show a * loading state. */ - const meta = params?.meta || {}; + const meta = params?.meta || ({} as WalletconnectMeta); const timeout = params?.timeout; const callback = params?.callback; const receivedTimestamp = params?.receivedTimestamp; @@ -186,7 +203,7 @@ export default function WalletConnectApprovalSheet() { const chainIds = meta?.chainIds; // WC v2 supports multi-chain const chainId = meta?.proposedChainId || chainIds?.[0] || 1; // WC v1 only supports 1 const currentChainId = params?.currentChainId; - const [approvalChainId, setApprovalChainId] = useState(currentChainId || settingsChainId); + const [approvalChainId, setApprovalChainId] = useState(currentChainId || settingsChainId); const isWalletConnectV2 = meta.isWalletConnectV2; const { dappName, dappUrl, dappScheme, imageUrl, peerId } = meta; @@ -205,7 +222,9 @@ export default function WalletConnectApprovalSheet() { useEffect(() => { return () => { - clearTimeout(timeout); + if (timeout) { + clearTimeout(timeout); + } }; }, [timeout]); @@ -238,7 +257,8 @@ export default function WalletConnectApprovalSheet() { }, [approvalChainId, isDarkMode]); const handleOnPressNetworksMenuItem = useCallback( - ({ nativeEvent }) => setApprovalChainId(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, '')), + ({ nativeEvent }: OnPressMenuItemEventObject) => + setApprovalChainId(Number(nativeEvent.actionKey?.replace(NETWORK_MENU_ACTION_KEY_FILTER, ''))), [setApprovalChainId] ); @@ -285,15 +305,15 @@ export default function WalletConnectApprovalSheet() { handleSuccess(false); }, [handleSuccess, goBack]); - const onPressAndroid = useCallback(() => { - androidShowNetworksActionSheet(({ chainId }) => setApprovalChainId(chainId)); + const onPressAndroid = useCallback(({ chainId }: { chainId: ChainId }) => { + setApprovalChainId(chainId); }, []); const handlePressChangeWallet = useCallback(() => { type === WalletConnectApprovalSheetType.connect && Navigation.handleAction(Routes.CHANGE_WALLET_SHEET, { currentAccountAddress: approvalAccount.address, - onChangeWallet: (address, wallet) => { + onChangeWallet: (address: Address, wallet: RainbowWallet) => { setApprovalAccount({ address, wallet }); goBack(); }, @@ -341,8 +361,8 @@ export default function WalletConnectApprovalSheet() { } else { return ( ); } + +export default WalletConnectApprovalSheet; diff --git a/src/screens/WalletConnectRedirectSheet.js b/src/screens/WalletConnectRedirectSheet.tsx similarity index 71% rename from src/screens/WalletConnectRedirectSheet.js rename to src/screens/WalletConnectRedirectSheet.tsx index a4e453a378b..00f79028d17 100644 --- a/src/screens/WalletConnectRedirectSheet.js +++ b/src/screens/WalletConnectRedirectSheet.tsx @@ -1,14 +1,16 @@ -import { useRoute } from '@react-navigation/native'; +import { RouteProp, useRoute } from '@react-navigation/native'; import lang from 'i18n-js'; import React, { useEffect } from 'react'; -import { Centered } from '../components/layout'; -import { Sheet } from '../components/sheet'; -import { Text } from '../components/text'; -import { useNavigation } from '../navigation/Navigation'; +import { Centered } from '@/components/layout'; +import { Sheet } from '@/components/sheet'; +import { Text } from '@/components/text'; +import { useNavigation } from '@/navigation/Navigation'; import { useAppState } from '@/hooks'; import styled from '@/styled-thing'; +import { ThemeContextProps, useTheme } from '@/theme'; +import { RootStackParamList } from '@/navigation/types'; -const BodyText = styled(Text).attrs(({ theme: { colors } }) => ({ +const BodyText = styled(Text).attrs(({ theme: { colors } }: { theme: ThemeContextProps }) => ({ align: 'center', color: colors.alpha(colors.blueGreyDark, 0.6), lineHeight: 'loosest', @@ -20,6 +22,7 @@ const BodyText = styled(Text).attrs(({ theme: { colors } }) => ({ const emojisMap = { 'connect': '🥳', + 'timedOut': '👻', 'reject': '👻', 'sign': '🥳', 'sign-canceled': '👻', @@ -29,6 +32,7 @@ const emojisMap = { const titlesMap = { 'connect': lang.t('walletconnect.titles.connect'), + 'timedOut': lang.t('walletconnect.titles.reject'), 'reject': lang.t('walletconnect.titles.reject'), 'sign': lang.t('walletconnect.titles.sign'), 'sign-canceled': lang.t('walletconnect.titles.sign_canceled'), @@ -36,11 +40,11 @@ const titlesMap = { 'transaction-canceled': lang.t('walletconnect.titles.transaction_canceled'), }; -const WalletConnectRedirectSheet = () => { +function WalletConnectRedirectSheet() { const { colors } = useTheme(); const { goBack } = useNavigation(); const { appState } = useAppState(); - const { params } = useRoute(); + const { params } = useRoute>(); const type = params?.type; @@ -65,6 +69,6 @@ const WalletConnectRedirectSheet = () => { ); -}; +} export default React.memo(WalletConnectRedirectSheet); diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index 391dae64608..026acd32b4e 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -1,4 +1,3 @@ -import { View } from 'react-native'; import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { AssetList } from '../../components/asset-list'; @@ -14,31 +13,26 @@ import { useLoadGlobalLateData, useWalletSectionsData, } from '@/hooks'; -import Routes from '@rainbow-me/routes'; -import { position } from '@/styles'; import { Toast, ToastPositionContainer } from '@/components/toasts'; import { useRecoilValue } from 'recoil'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { analyticsV2 } from '@/analytics'; import { AppState } from '@/redux/store'; import { addressCopiedToastAtom } from '@/recoil/addressCopiedToastAtom'; -import styled from '@/styled-thing'; import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; - -const WalletPage = styled(Page)({ - ...position.sizeAsObject('100%'), - flex: 1, -}); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const WalletScreen: React.FC = ({ navigation, route }) => { - const { params } = route; - const { setParams, getState: dangerouslyGetState, getParent: dangerouslyGetParent } = navigation; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { RootStackParamList } from '@/navigation/types'; +import { useNavigation } from '@/navigation'; +import Routes from '@/navigation/Routes'; + +function WalletScreen() { + const { params } = useRoute>(); + const { setParams, getState: dangerouslyGetState, getParent: dangerouslyGetParent } = useNavigation(); const removeFirst = useRemoveFirst(); const [initialized, setInitialized] = useState(!!params?.initialized); const initializeWallet = useInitializeWallet(); @@ -97,38 +91,31 @@ const WalletScreen: React.FC = ({ navigation, route }) => { const { highContrastAccentColor } = useAccountAccentColor(); return ( - - - - {/* @ts-expect-error JavaScript component */} - - - - - - - {/* NOTE: The components below render null and are solely for keeping react-query and Zustand in sync */} - - - - - {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} - - - + + + {/* @ts-expect-error JavaScript component */} + + + + + + + {/* NOTE: The components below render null and are solely for keeping react-query and Zustand in sync */} + + + + + {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} + + ); -}; +} export default WalletScreen; From 6df14df0795a0ae70fd748b5555fd7c25755f151 Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 4 Oct 2024 15:21:48 -0300 Subject: [PATCH 49/64] nft expanded state fix crash (#6115) * fix crash * destructure in same line --- src/components/expanded-state/UniqueTokenExpandedState.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/expanded-state/UniqueTokenExpandedState.tsx b/src/components/expanded-state/UniqueTokenExpandedState.tsx index eec0400d8c2..b267c87f305 100644 --- a/src/components/expanded-state/UniqueTokenExpandedState.tsx +++ b/src/components/expanded-state/UniqueTokenExpandedState.tsx @@ -275,7 +275,7 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE }, [isReportSpamToastActive]); const { - collection: { description: familyDescription, external_url: familyLink, slug }, + collection: { description: familyDescription, external_url: familyLink, slug } = {}, description, familyImage, familyName, From b272a12a95213affa65f05664963d42125f57e32 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 4 Oct 2024 16:35:48 -0400 Subject: [PATCH 50/64] fix lint issues on develop (#6174) --- .../expanded-state/UniqueTokenExpandedState.tsx | 2 +- src/hooks/useWatchWallet.ts | 15 +++++++++------ .../PairHardwareWalletSigningSheet.tsx | 4 +++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/expanded-state/UniqueTokenExpandedState.tsx b/src/components/expanded-state/UniqueTokenExpandedState.tsx index b267c87f305..255706e32c8 100644 --- a/src/components/expanded-state/UniqueTokenExpandedState.tsx +++ b/src/components/expanded-state/UniqueTokenExpandedState.tsx @@ -594,7 +594,7 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE {...asset} color={imageColor} hideNftMarketplaceAction={hideNftMarketplaceAction} - slug={slug} + slug={slug ?? ''} /> ) : null} diff --git a/src/hooks/useWatchWallet.ts b/src/hooks/useWatchWallet.ts index 19703a3b344..1e2151f02ee 100644 --- a/src/hooks/useWatchWallet.ts +++ b/src/hooks/useWatchWallet.ts @@ -36,7 +36,7 @@ export default function useWatchWallet({ const initializeWallet = useInitializeWallet(); const changeAccount = useCallback( async (walletId: string, address: string) => { - const wallet = wallets![walletId]; + const wallet = (wallets || {})[walletId]; try { const p1 = dispatch(walletsSetSelected(wallet)); const p2 = dispatch(addressSetSelected(address)); @@ -60,13 +60,16 @@ export default function useWatchWallet({ const watchWallet = useCallback(async () => { if (!isWatching) { handleSetSeedPhrase(ensName ?? ''); - handlePressImportButton(null, ensName, null, avatarUrl); + handlePressImportButton({ + forceAddress: ensName, + avatarUrl: avatarUrl ?? undefined, + }); } else { // If there's more than 1 account, // it's deletable - const isLastAvailableWallet = Object.keys(wallets!).find(key => { - const someWallet = wallets![key]; - const otherAccount = someWallet.addresses?.find((account: any) => account.visible && account.address !== accountAddress); + const isLastAvailableWallet = Object.keys(wallets || {}).find(key => { + const someWallet = (wallets || {})[key]; + const otherAccount = someWallet.addresses?.find(account => account.visible && account.address !== accountAddress); if (otherAccount) { return true; } @@ -85,7 +88,7 @@ export default function useWatchWallet({ const { wallet: foundWallet, key } = doesWalletsContainAddress({ address: primaryAddress, - wallets: wallets!, + wallets: wallets || {}, }) || {}; if (foundWallet && key) { await changeAccount(key, foundWallet.address); diff --git a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx index fca391ca357..a396542cf2d 100644 --- a/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx +++ b/src/screens/hardware-wallets/PairHardwareWalletSigningSheet.tsx @@ -114,7 +114,9 @@ export function PairHardwareWalletSigningSheet() { } logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId }, DebugContext.ledger); handleSetSeedPhrase(deviceId); - handlePressImportButton(null, deviceId, null, null); + handlePressImportButton({ + forceAddress: deviceId, + }); }, [busy, handlePressImportButton, handleSetSeedPhrase] ); From f1a4544f10e6b7ac0791b72c348a8bee3017fd88 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 4 Oct 2024 20:15:16 -0400 Subject: [PATCH 51/64] Fix MWP from failing to prompt if dapp metadata retrieval fails (#6164) * prevent mwp flow from failing if failed to fetch dapp metadata * prevent logging user rejections * ignore logging some user errors --- .../MobileWalletProtocolListener.tsx | 23 ++++++++++++++++--- src/utils/requestNavigationHandlers.ts | 23 ++++++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/components/MobileWalletProtocolListener.tsx b/src/components/MobileWalletProtocolListener.tsx index 27a678834cb..bf3d58fa204 100644 --- a/src/components/MobileWalletProtocolListener.tsx +++ b/src/components/MobileWalletProtocolListener.tsx @@ -9,6 +9,15 @@ import { handleMobileWalletProtocolRequest } from '@/utils/requestNavigationHand import { logger, RainbowError } from '@/logger'; import { IS_ANDROID, IS_DEV } from '@/env'; +export enum MobileWalletProtocolUserErrors { + USER_REJECTED_HANDSHAKE = 'User rejected the handshake', + USER_REJECTED_REQUEST = 'User rejected request', + ABORTED = 'Aborted', + READ_ONLY_WALLET = 'This wallet is read-only', +} + +const ERROR_MESSAGES_TO_IGNORE = Object.values(MobileWalletProtocolUserErrors); + export const MobileWalletProtocolListener = () => { const { message, handleRequestUrl, sendFailureToClient, session, ...mwpProps } = useMobileWalletProtocolHost(); const lastMessageUuidRef = useRef(null); @@ -26,9 +35,17 @@ export const MobileWalletProtocolListener = () => { try { await handleMobileWalletProtocolRequest({ request: message, session, ...mwpProps }); } catch (error) { - logger.error(new RainbowError('Error handling Mobile Wallet Protocol request'), { - error, - }); + if (error instanceof Error && ERROR_MESSAGES_TO_IGNORE.includes(error.message as MobileWalletProtocolUserErrors)) { + logger.debug(`[MobileWalletProtocolListener]: Error handling Mobile Wallet Protocol request`, { + error, + message, + }); + } else { + logger.error(new RainbowError(`[MobileWalletProtocolListener]: Error handling Mobile Wallet Protocol request`), { + error, + message, + }); + } } } else { // Store the message to process once we have a valid session diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index e9eececaa38..e1716ed5422 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -21,6 +21,7 @@ import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; import { + AppMetadata, EthereumAction, isEthereumAction, isHandshakeAction, @@ -35,6 +36,7 @@ import { BigNumber } from '@ethersproject/bignumber'; import { Address } from 'viem'; import { ChainId } from '@/chains/types'; import { chainsName, SUPPORTED_MAINNET_CHAIN_IDS } from '@/chains'; +import { MobileWalletProtocolUserErrors } from '@/components/MobileWalletProtocolListener'; export enum RequestSource { WALLETCONNECT = 'walletconnect', @@ -94,7 +96,7 @@ export const handleMobileWalletProtocolRequest = async ({ if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { logger.debug('Rejecting request due to read-only wallet'); watchingAlert(); - return Promise.reject(new Error('This wallet is read-only.')); + return Promise.reject(new Error(MobileWalletProtocolUserErrors.READ_ONLY_WALLET)); } const handleAction = async (currentIndex: number): Promise => { @@ -106,7 +108,16 @@ export const handleMobileWalletProtocolRequest = async ({ const receivedTimestamp = Date.now(); - const dappMetadata = await fetchClientAppMetadata(); + let dappMetadata: AppMetadata | null = null; + try { + dappMetadata = await fetchClientAppMetadata(); + } catch (error) { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Failed to fetch client app metadata`), { + error, + action, + }); + } + return new Promise((resolve, reject) => { const routeParams: WalletconnectApprovalSheetRouteParams = { receivedTimestamp, @@ -133,8 +144,8 @@ export const handleMobileWalletProtocolRequest = async ({ resolve(success); } else { logger.debug(`Handshake rejected for ${action.appId}`); - await rejectHandshake('User rejected the handshake'); - reject('User rejected the handshake'); + await rejectHandshake(MobileWalletProtocolUserErrors.USER_REJECTED_HANDSHAKE); + reject(MobileWalletProtocolUserErrors.USER_REJECTED_HANDSHAKE); } }, }; @@ -228,10 +239,10 @@ export const handleMobileWalletProtocolRequest = async ({ } else { logger.debug(`Ethereum action rejected: [${action.method}]: User rejected request`); await rejectAction(action, { - message: 'User rejected request', + message: MobileWalletProtocolUserErrors.USER_REJECTED_REQUEST, code: 4001, }); - reject('User rejected request'); + reject(MobileWalletProtocolUserErrors.USER_REJECTED_REQUEST); } }; From 78d9d96cb088ba20e654e4eb9065f96bfc36e32b Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 4 Oct 2024 20:15:25 -0400 Subject: [PATCH 52/64] lel wtf (#6166) --- src/screens/points/content/PointsContent.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/screens/points/content/PointsContent.tsx b/src/screens/points/content/PointsContent.tsx index 3b2078a9147..220ce06a933 100644 --- a/src/screens/points/content/PointsContent.tsx +++ b/src/screens/points/content/PointsContent.tsx @@ -747,16 +747,16 @@ export function PointsContent() { {!shouldDisplayError ? ( - - + + {i18n.t(i18n.l.points.points.my_points)} {canDisplayTotalPoints ? : } - - + + {!!cardIds.length && !isReadOnlyWallet && } @@ -877,7 +877,7 @@ export function PointsContent() { )} - + {i18n.t(i18n.l.points.points.leaderboard)} @@ -944,8 +944,8 @@ export function PointsContent() { ) : ( )} - - + + ) : ( From 2fe99334d73033515ef4eb7ad29c704492d767de Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:57:10 -0400 Subject: [PATCH 53/64] WC migration to WalletKit (#6163) --- ios/Podfile.lock | 4 +- package.json | 10 +- src/walletConnect/index.tsx | 115 ++++++------ src/walletConnect/sheets/AuthRequest.tsx | 4 +- yarn.lock | 226 +++++++---------------- 5 files changed, 136 insertions(+), 223 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32d0852c36a..6771cbd3530 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1177,7 +1177,7 @@ PODS: - React-Core - react-native-cloud-fs (2.6.2): - React - - react-native-compat (2.15.1): + - react-native-compat (2.17.0): - DoubleConversion - glog - hermes-engine @@ -2352,7 +2352,7 @@ SPEC CHECKSUMS: react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22 react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 react-native-cloud-fs: c90379f513b8d7ad5cfed610ccf50f27d837016e - react-native-compat: 408a4b320fa4426f2c25ab74ed5764be6b3eb5aa + react-native-compat: 4e82fe33d1af54feba2f25d3d88f2b4d8e5c2b78 react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 diff --git a/package.json b/package.json index 4dcc4bcb705..fbdb7bc9008 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "@react-navigation/material-top-tabs": "6.6.2", "@react-navigation/native": "6.1.17", "@react-navigation/stack": "6.3.29", + "@reown/walletkit": "^1.0.0", "@reservoir0x/reservoir-sdk": "2.3.0", "@rudderstack/rudder-sdk-react-native": "1.12.1", "@sentry/react-native": "5.31.1", @@ -140,12 +141,11 @@ "@unstoppabledomains/resolution": "7.1.4", "@wagmi/chains": "1.8.0", "@walletconnect/client": "1.8.0", - "@walletconnect/core": "2.15.1", + "@walletconnect/core": "2.17.0", "@walletconnect/legacy-utils": "2.0.0", - "@walletconnect/react-native-compat": "2.15.1", - "@walletconnect/types": "2.15.1", - "@walletconnect/utils": "2.15.1", - "@walletconnect/web3wallet": "1.14.1", + "@walletconnect/react-native-compat": "2.17.0", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", "assert": "1.5.0", "async-mutex": "0.3.2", "asyncstorage-down": "4.2.0", diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index e3e06d57040..04c5361551b 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -9,7 +9,7 @@ import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; import WalletConnectCore, { Core } from '@walletconnect/core'; -import Client, { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; +import { WalletKit, WalletKitTypes, IWalletKit } from '@reown/walletkit'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -96,20 +96,20 @@ let lastConnector: string | undefined = undefined; let walletConnectCore: WalletConnectCore | undefined; -let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; +let walletKitClient: ReturnType<(typeof WalletKit)['init']> | undefined; -let initPromise: Promise | undefined = undefined; +let initPromise: Promise | undefined = undefined; -let syncWeb3WalletClient: Client | undefined = undefined; +let syncWalletKitClient: IWalletKit | undefined = undefined; export const initializeWCv2 = async () => { if (!walletConnectCore) { walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); } - if (!web3WalletClient) { + if (!walletKitClient) { // eslint-disable-next-line require-atomic-updates - web3WalletClient = Web3Wallet.init({ + walletKitClient = WalletKit.init({ core: walletConnectCore, metadata: { name: '🌈 Rainbow', @@ -124,10 +124,10 @@ export const initializeWCv2 = async () => { }); } - return web3WalletClient; + return walletKitClient; }; -export async function getWeb3WalletClient() { +export async function getWalletKitClient() { if (!initPromise) { initPromise = initializeWCv2(); } @@ -196,7 +196,7 @@ export function parseRPCParams({ method, params }: RPCPayload): { /** * Better signature for this type of function * - * @see https://docs.walletconnect.com/2.0/web/web3wallet/wallet-usage#-namespaces-builder-util + * @see https://docs.walletconnect.com/2.0/web/walletKit/wallet-usage#-namespaces-builder-util */ export function getApprovedNamespaces(props: Parameters[0]): | { @@ -303,7 +303,7 @@ async function rejectProposal({ proposal, reason, }: { - proposal: Web3WalletTypes.SessionProposal; + proposal: WalletKitTypes.SessionProposal; reason: Parameters[0]; }) { logger.warn(`[walletConnect]: session approval denied`, { @@ -311,7 +311,7 @@ async function rejectProposal({ proposal, }); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); const { id, proposer } = proposal.params; await client.rejectSession({ id, reason: getSdkError(reason) }); @@ -323,11 +323,10 @@ async function rejectProposal({ } // listen for THIS topic pairing, and clear timeout if received -function trackTopicHandler(proposal: Web3WalletTypes.SessionProposal | Web3WalletTypes.AuthRequest) { +function trackTopicHandler(proposal: WalletKitTypes.SessionProposal | WalletKitTypes.SessionAuthenticate) { logger.debug(`[walletConnect]: pair: handler`, { proposal }); - const { metadata } = - (proposal as Web3WalletTypes.SessionProposal).params.proposer || (proposal as Web3WalletTypes.AuthRequest).params.requester; + const { metadata } = 'proposer' in proposal.params ? proposal.params.proposer : proposal.params.requester; analytics.track(analytics.event.wcNewPairing, { dappName: metadata.name, @@ -344,7 +343,7 @@ export async function pair({ uri, connector }: { uri: string; connector?: string */ const { topic, ...rest } = parseUri(uri); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); logger.debug(`[walletConnect]: pair: parsed uri`, { topic, rest }); @@ -355,16 +354,16 @@ export async function pair({ uri, connector }: { uri: string; connector?: string export async function initListeners() { PerformanceTracking.startMeasuring(PerformanceMetrics.initializeWalletconnect); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); PerformanceTracking.finishMeasuring(PerformanceMetrics.initializeWalletconnect); - syncWeb3WalletClient = client; + syncWalletKitClient = client; - logger.debug(`[walletConnect]: web3WalletClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); + logger.debug(`[walletConnect]: walletKitClient initialized, initListeners`, {}, logger.DebugContext.walletconnect); client.on('session_proposal', onSessionProposal); client.on('session_request', onSessionRequest); - client.on('auth_request', onAuthRequest); + client.on('session_authenticate', onSessionAuthenticate); client.on('session_delete', () => { logger.debug(`[walletConnect]: session_delete`, {}, logger.DebugContext.walletconnect); @@ -379,7 +378,7 @@ export async function initWalletConnectPushNotifications() { const token = await getFCMToken(); if (token) { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); const client_id = await client.core.crypto.getClientId(); // initial subscription @@ -429,7 +428,7 @@ async function subscribeToEchoServer({ client_id, token }: { client_id: string; } } -export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposal) { +export async function onSessionProposal(proposal: WalletKitTypes.SessionProposal) { try { trackTopicHandler(proposal); @@ -468,7 +467,8 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa verifiedData, timedOut: false, callback: async (approved, approvedChainId, accountAddress) => { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); + const { id, proposer, requiredNamespaces } = proposal.params; if (approved) { @@ -592,12 +592,14 @@ export async function onSessionProposal(proposal: Web3WalletTypes.SessionProposa // For WC v2 export async function onSessionRequest(event: SignClientTypes.EventArguments['session_request']) { setHasPendingDeeplinkPendingRedirect(true); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); logger.debug(`[walletConnect]: session_request`, {}, logger.DebugContext.walletconnect); const { id, topic } = event; - const { method, params } = event.params.request; + const { method: _method, params } = event.params.request; + + const method = _method as RPCMethod; logger.debug(`[walletConnect]: session_request method`, { method, params }, logger.DebugContext.walletconnect); @@ -615,10 +617,10 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se }); return; } - if (isSupportedMethod(method as RPCMethod)) { - const isSigningMethod = isSupportedSigningMethod(method as RPCMethod); + if (isSupportedMethod(method)) { + const isSigningMethod = isSupportedSigningMethod(method); const { address, message } = parseRPCParams({ - method: method as RPCMethod, + method, params, }); if (!address) { @@ -808,7 +810,7 @@ export async function handleSessionRequestResponse( success: Boolean(result), }); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); const { topic, id } = sessionRequestEvent; if (result) { const payload = { @@ -829,32 +831,33 @@ export async function handleSessionRequestResponse( store.dispatch(removeRequest(sessionRequestEvent.id)); } -export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { +export async function onSessionAuthenticate(event: WalletKitTypes.SessionAuthenticate) { trackTopicHandler(event); - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); logger.debug(`[walletConnect]: auth_request`, { event }, logger.DebugContext.walletconnect); const authenticate: AuthRequestAuthenticateSignature = async ({ address }) => { try { const { wallets } = store.getState().wallets; - const selectedWallet = findWalletWithAccount(wallets!, address); + const selectedWallet = findWalletWithAccount(wallets || {}, address); const isHardwareWallet = selectedWallet?.type === WalletTypes.bluetooth; const iss = `did:pkh:eip155:1:${address}`; // exit early if possible if (selectedWallet?.type === WalletTypes.readOnly) { - await client.respondAuthRequest( - { + await client.respondSessionRequest({ + topic: event.topic, + response: { id: event.id, error: { code: 0, message: `Wallet is read-only`, }, + jsonrpc: '2.0', }, - iss - ); + }); return { success: false, @@ -875,8 +878,10 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { return undefined; } - - const message = client.formatMessage(event.params.cacaoPayload, iss); + const message = client.formatAuthMessage({ + iss, + request: event.params.authPayload, + }); // prompt the user to sign the message return wallet.signMessage(message); }; @@ -904,16 +909,19 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { } // respond to WC - await client.respondAuthRequest( - { + await client.respondSessionRequest({ + topic: event.topic, + response: { id: event.id, - signature: { - s: signature, - t: 'eip191', - }, + result: JSON.stringify({ + signature: { + s: signature, + t: 'eip191', + }, + }), + jsonrpc: '2.0', }, - iss - ); + }); // only handled on success maybeGoBackAndClearHasPendingRedirect({ delay: 300 }); @@ -928,9 +936,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { }; // need to prefetch dapp metadata since portal is static - const url = - // @ts-ignore Web3WalletTypes.AuthRequest type is missing VerifyContext - event?.verifyContext?.origin || event.params.requester.metadata.url; + const url = event?.verifyContext?.verified?.origin || event.params.requester.metadata.url; const metadata = await fetchDappMetadata({ url, status: true }); const isScam = metadata.status === DAppStatus.Scam; @@ -939,8 +945,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { AuthRequest({ authenticate, requesterMeta: event.params.requester.metadata, - // @ts-ignore Web3WalletTypes.AuthRequest type is missing VerifyContext - verifiedData: event?.verifyContext, + verifiedData: event?.verifyContext.verified, }), { sheetHeight: IS_ANDROID ? 560 : 520 + (isScam ? 40 : 0) } ); @@ -950,7 +955,7 @@ export async function onAuthRequest(event: Web3WalletTypes.AuthRequest) { * Returns all active settings in a type-safe manner. */ export async function getAllActiveSessions() { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); return Object.values(client?.getActiveSessions() || {}) || []; } @@ -959,7 +964,7 @@ export async function getAllActiveSessions() { * in a type-safe manner. */ export function getAllActiveSessionsSync() { - return Object.values(syncWeb3WalletClient?.getActiveSessions() || {}) || []; + return Object.values(syncWalletKitClient?.getActiveSessions() || {}) || []; } /** @@ -967,7 +972,7 @@ export function getAllActiveSessionsSync() { */ export async function addAccountToSession(session: SessionTypes.Struct, { address }: { address?: string }) { try { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); const namespaces: Parameters[0]['namespaces'] = {}; for (const [key, value] of Object.entries(session.namespaces)) { @@ -1028,7 +1033,7 @@ export async function addAccountToSession(session: SessionTypes.Struct, { addres export async function changeAccount(session: SessionTypes.Struct, { address }: { address?: string }) { try { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); /* * Before we can effectively switch accounts, we need to add the account to @@ -1078,7 +1083,7 @@ export async function changeAccount(session: SessionTypes.Struct, { address }: { * within a dapp is handled internally by WC v2. */ export async function disconnectSession(session: SessionTypes.Struct) { - const client = await getWeb3WalletClient(); + const client = await getWalletKitClient(); await client.disconnectSession({ topic: session.topic, diff --git a/src/walletConnect/sheets/AuthRequest.tsx b/src/walletConnect/sheets/AuthRequest.tsx index 7c7eefb5936..338acad39b4 100644 --- a/src/walletConnect/sheets/AuthRequest.tsx +++ b/src/walletConnect/sheets/AuthRequest.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useNavigation } from '@react-navigation/native'; -import { Web3WalletTypes } from '@walletconnect/web3wallet'; import { Box, Text, Separator, BackgroundProvider, AccentColorProvider } from '@/design-system'; import ButtonPressAnimation from '@/components/animations/ButtonPressAnimation'; @@ -19,13 +18,14 @@ import { Verify } from '@walletconnect/types'; import { useDappMetadata } from '@/resources/metadata/dapp'; import { DAppStatus } from '@/graphql/__generated__/metadata'; import { InfoAlert } from '@/components/info-alert/info-alert'; +import { WalletKitTypes } from '@reown/walletkit'; export function AuthRequest({ requesterMeta, authenticate, verifiedData, }: { - requesterMeta: Web3WalletTypes.AuthRequest['params']['requester']['metadata']; + requesterMeta: WalletKitTypes.SessionProposal['params']['proposer']['metadata']; authenticate: AuthRequestAuthenticateSignature; verifiedData?: Verify.Context['verified']; }) { diff --git a/yarn.lock b/yarn.lock index 36aba833c7a..b3a51ad90e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5164,6 +5164,21 @@ __metadata: languageName: node linkType: hard +"@reown/walletkit@npm:^1.0.0": + version: 1.1.0 + resolution: "@reown/walletkit@npm:1.1.0" + dependencies: + "@walletconnect/core": "npm:2.17.0" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + checksum: 10c0/1079b626bb978d086054ca2e2a53ada84209616726281c3f1ae06c7a5fb3bf4e1001fc5e6c651cbd2138720ec6c1c74e42aff371d79500a35bee4aa1eed1c3db + languageName: node + linkType: hard + "@reservoir0x/reservoir-sdk@npm:2.3.0": version: 2.3.0 resolution: "@reservoir0x/reservoir-sdk@npm:2.3.0" @@ -5838,7 +5853,7 @@ __metadata: languageName: node linkType: hard -"@stablelib/sha256@npm:1.0.1, @stablelib/sha256@npm:^1.0.1": +"@stablelib/sha256@npm:1.0.1": version: 1.0.1 resolution: "@stablelib/sha256@npm:1.0.1" dependencies: @@ -7051,27 +7066,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/auth-client@npm:2.1.2": - version: 2.1.2 - resolution: "@walletconnect/auth-client@npm:2.1.2" - dependencies: - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@stablelib/random": "npm:^1.0.2" - "@stablelib/sha256": "npm:^1.0.1" - "@walletconnect/core": "npm:^2.10.1" - "@walletconnect/events": "npm:^1.0.1" - "@walletconnect/heartbeat": "npm:^1.2.1" - "@walletconnect/jsonrpc-utils": "npm:^1.0.8" - "@walletconnect/logger": "npm:^2.0.1" - "@walletconnect/time": "npm:^1.0.2" - "@walletconnect/utils": "npm:^2.10.1" - events: "npm:^3.3.0" - isomorphic-unfetch: "npm:^3.1.0" - checksum: 10c0/7c57ab68672cb7f1e89e9ac1607d3aa716f7d728d9c66937bee6f94df0f25c67f76db13cd7ac0e56ace1c4d872c5d126b4ab25f4251424aa6c481317ef37103f - languageName: node - linkType: hard - "@walletconnect/browser-utils@npm:^1.8.0": version: 1.8.0 resolution: "@walletconnect/browser-utils@npm:1.8.0" @@ -7097,9 +7091,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/core@npm:2.15.1" +"@walletconnect/core@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/core@npm:2.17.0" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -7112,12 +7106,12 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: 10c0/3c831303bffcc360bb7d2f6e71b9928e73039e34e582e8da3f03dbc67e7876cf3ff89491ec9bad7ae31c3c3706ea6e44d2cd1be501349e882739a5184e917797 + checksum: 10c0/34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d languageName: node linkType: hard @@ -7132,31 +7126,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:^2.10.1": - version: 2.13.3 - resolution: "@walletconnect/core@npm:2.13.3" - dependencies: - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.10" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.3" - "@walletconnect/utils": "npm:2.13.3" - events: "npm:3.3.0" - isomorphic-unfetch: "npm:3.1.0" - lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:3.1.0" - checksum: 10c0/1f2ce950997330e89f2a773291426cb3cfe994227300ee74c683f6af282ac5069689406bf05ad752d443024802d570a2ae2f93b65e45439849453734122936c9 - languageName: node - linkType: hard - "@walletconnect/crypto@npm:^1.0.2": version: 1.0.3 resolution: "@walletconnect/crypto@npm:1.0.3" @@ -7201,7 +7170,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/heartbeat@npm:1.2.2, @walletconnect/heartbeat@npm:^1.2.1": +"@walletconnect/heartbeat@npm:1.2.2": version: 1.2.2 resolution: "@walletconnect/heartbeat@npm:1.2.2" dependencies: @@ -7308,7 +7277,7 @@ __metadata: languageName: node linkType: hard -"@walletconnect/logger@npm:2.1.2, @walletconnect/logger@npm:^2.0.1": +"@walletconnect/logger@npm:2.1.2": version: 2.1.2 resolution: "@walletconnect/logger@npm:2.1.2" dependencies: @@ -7330,9 +7299,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/react-native-compat@npm:2.15.1" +"@walletconnect/react-native-compat@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/react-native-compat@npm:2.17.0" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -7345,16 +7314,7 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: 10c0/996452a58797b811b4417c649943602decadb5cbe4dd2717f1aaaed42df0bebcbaeda81e43754d62c319d335702d9e1cb8161e9102ce1bd25354e41588b26b97 - languageName: node - linkType: hard - -"@walletconnect/relay-api@npm:1.0.10": - version: 1.0.10 - resolution: "@walletconnect/relay-api@npm:1.0.10" - dependencies: - "@walletconnect/jsonrpc-types": "npm:^1.0.2" - checksum: 10c0/2709bbe45f60579cd2e1c74b0fd03c36ea409cd8a9117e00a7485428d0c9ba7eb02e525c21e5286db2b6ce563b1d29053b0249c2ed95f8adcf02b11e54f61fcd + checksum: 10c0/936f93b2f96fcd4f0eb4df4de19c20f76b0daec5568c49fe7e346157b9486e2c4a8f538d9094e5902a9eae736eb64d42e6be77009fbed1364e63ff22fca67743 languageName: node linkType: hard @@ -7397,20 +7357,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/sign-client@npm:2.15.1" +"@walletconnect/sign-client@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/sign-client@npm:2.17.0" dependencies: - "@walletconnect/core": "npm:2.15.1" + "@walletconnect/core": "npm:2.17.0" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 10c0/4ff66239c2994cb501cd1830b2cbc2ecf854799cfc4d100cc6af3523f8524daead7c08daa7dba944def79640515ccabe2250f700e0be1a07dfee5c3668aa2dee + checksum: 10c0/48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 languageName: node linkType: hard @@ -7434,23 +7394,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.13.3": - version: 2.13.3 - resolution: "@walletconnect/types@npm:2.13.3" - dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - events: "npm:3.3.0" - checksum: 10c0/b12e92e39fa2fd9dbdfbef62265c7f5e5aa1d824b6db68ea5ce4fa9ed56c9fcdf6d93fbc6ffeb57169cff4082d475899739377b91f2b13f5209c8ccb66c47d6e - languageName: node - linkType: hard - -"@walletconnect/types@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/types@npm:2.15.1" +"@walletconnect/types@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/types@npm:2.17.0" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -7458,7 +7404,7 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 10c0/0666874a4acd9326f2554542936a88f6df50ca958440254ab605dca81d57cdaf839b05cac983ad194f4ed44734d07d564f4277156556cadac07302e8dd86aa4d + checksum: 10c0/bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade languageName: node linkType: hard @@ -7469,31 +7415,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.3, @walletconnect/utils@npm:^2.10.1": - version: 2.13.3 - resolution: "@walletconnect/utils@npm:2.13.3" - dependencies: - "@stablelib/chacha20poly1305": "npm:1.0.1" - "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:1.0.2" - "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.10" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.3" - "@walletconnect/window-getters": "npm:1.0.1" - "@walletconnect/window-metadata": "npm:1.0.1" - detect-browser: "npm:5.3.0" - query-string: "npm:7.1.3" - uint8arrays: "npm:3.1.0" - checksum: 10c0/d33d66f306612637ed29f113c3cf6fd28f2a0c1062f88eafde2e9d2689859418725be0591c14d8a38ba24f56b70874117d47a6aa7ce0c1efa16e6eb6e3b79aad - languageName: node - linkType: hard - -"@walletconnect/utils@npm:2.15.1": - version: 2.15.1 - resolution: "@walletconnect/utils@npm:2.15.1" +"@walletconnect/utils@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/utils@npm:2.17.0" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" @@ -7501,15 +7425,17 @@ __metadata: "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.15.1" + "@walletconnect/types": "npm:2.17.0" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" + elliptic: "npm:^6.5.7" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 10c0/bde087f530f91502ba26c9553abd464234adb5738c7e10cfbdd275699d630a81601b5928abeeb77abe29e88238a4067a1bd1c5159f04b49178452066c82d99ae + checksum: 10c0/d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c languageName: node linkType: hard @@ -7528,22 +7454,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/web3wallet@npm:1.14.1": - version: 1.14.1 - resolution: "@walletconnect/web3wallet@npm:1.14.1" - dependencies: - "@walletconnect/auth-client": "npm:2.1.2" - "@walletconnect/core": "npm:2.15.1" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.15.1" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - checksum: 10c0/1918cb3319036fef3ed5565a607b230e083ea37b12a067df22f1bc81ab34fcf9a48d7ff2f4eb9dbd16066432f0e53460db727466b976e009bab378db5b4da1f6 - languageName: node - linkType: hard - "@walletconnect/window-getters@npm:1.0.0": version: 1.0.0 resolution: "@walletconnect/window-getters@npm:1.0.0" @@ -7904,6 +7814,7 @@ __metadata: "@react-navigation/material-top-tabs": "npm:6.6.2" "@react-navigation/native": "npm:6.1.17" "@react-navigation/stack": "npm:6.3.29" + "@reown/walletkit": "npm:^1.0.0" "@reservoir0x/reservoir-sdk": "npm:2.3.0" "@rnx-kit/align-deps": "npm:2.2.4" "@rudderstack/rudder-sdk-react-native": "npm:1.12.1" @@ -7935,12 +7846,11 @@ __metadata: "@unstoppabledomains/resolution": "npm:7.1.4" "@wagmi/chains": "npm:1.8.0" "@walletconnect/client": "npm:1.8.0" - "@walletconnect/core": "npm:2.15.1" + "@walletconnect/core": "npm:2.17.0" "@walletconnect/legacy-utils": "npm:2.0.0" - "@walletconnect/react-native-compat": "npm:2.15.1" - "@walletconnect/types": "npm:2.15.1" - "@walletconnect/utils": "npm:2.15.1" - "@walletconnect/web3wallet": "npm:1.14.1" + "@walletconnect/react-native-compat": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" assert: "npm:1.5.0" ast-parser: "npm:0.0.5" async-mutex: "npm:0.3.2" @@ -12159,6 +12069,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.7": + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/799959b6c54ea3564e8961f35abdf8c77e37617f3051614b05ab1fb6a04ddb65bd1caa75ed1bae375b15dda312a0f79fed26ebe76ecf05c5a7af244152a601b8 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -15914,16 +15839,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-unfetch@npm:3.1.0, isomorphic-unfetch@npm:^3.1.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: "npm:^2.6.1" - unfetch: "npm:^4.2.0" - checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 - languageName: node - linkType: hard - "isows@npm:1.0.3": version: 1.0.3 resolution: "isows@npm:1.0.3" @@ -18902,7 +18817,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -24859,13 +24774,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" From f4ca59ae39f9bc6d209d67995d2d6ce9f406a0cd Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Mon, 7 Oct 2024 15:01:56 -0400 Subject: [PATCH 54/64] Fix networks crash (#6176) * fix networks crash * remove duplicated walletconnect chain ids * use all supported chains for walletconnect * fix weird space on approval sheet * Update src/screens/WalletConnectApprovalSheet.tsx --- src/chains/index.ts | 14 ----------- .../control-panel/ControlPanel.tsx | 5 ++-- .../swap-details/SwapDetailsExchangeRow.js | 4 ++-- .../WalletConnectV2ListItem.tsx | 5 ++-- src/helpers/walletConnectNetworks.ts | 17 ++++++------- src/redux/walletconnect.ts | 5 ++-- src/screens/WalletConnectApprovalSheet.tsx | 24 +++---------------- src/walletConnect/index.tsx | 6 ++--- 8 files changed, 23 insertions(+), 57 deletions(-) diff --git a/src/chains/index.ts b/src/chains/index.ts index 0d627b470ae..f8a27da21b5 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -172,20 +172,6 @@ export const supportedTokenSearchChainIds = filterChainIdsByService(services => export const supportedNftChainIds = filterChainIdsByService(services => services.nftProxy.enabled); -export const supportedWalletConnectChainIds = [ - ChainId.apechain, - ChainId.arbitrum, - ChainId.avalanche, - ChainId.base, - ChainId.blast, - ChainId.bsc, - ChainId.degen, - ChainId.mainnet, - ChainId.optimism, - ChainId.polygon, - ChainId.zora, -]; - export const supportedFlashbotsChainIds = [ChainId.mainnet]; export const shouldDefaultToFastGasChainIds = [ChainId.mainnet, ChainId.polygon, ChainId.goerli]; diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index 4c635fd4a9a..a7867ee6f68 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -62,7 +62,7 @@ import { swapsStore } from '@/state/swaps/swapsStore'; import { userAssetsStore } from '@/state/assets/userAssets'; import { greaterThan } from '@/helpers/utilities'; import { ChainId } from '@/chains/types'; -import { chainsLabel, defaultChains, supportedWalletConnectChainIds } from '@/chains'; +import { chainsLabel, defaultChains } from '@/chains'; const PAGES = { HOME: 'home', @@ -183,8 +183,7 @@ export const ControlPanel = () => { const { testnetsEnabled } = store.getState().settings; const allNetworkItems = useMemo(() => { - const chains = supportedWalletConnectChainIds.map(chainId => defaultChains[chainId]); - return chains + return Object.values(defaultChains) .filter(({ testnet }) => testnetsEnabled || !testnet) .map(chain => { return { diff --git a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js index ca5cadcf4cc..7e0cb82c02c 100644 --- a/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js +++ b/src/components/expanded-state/swap-details/SwapDetailsExchangeRow.js @@ -10,10 +10,10 @@ import { usePrevious, useStepper } from '@/hooks'; import { ImgixImage } from '@/components/images'; import { getExchangeIconUrl, magicMemo } from '@/utils'; import { SocketBridges } from '@/references/swap/bridges'; -import { SUPPORTED_CHAINS } from '@/chains'; +import { defaultChains } from '@/chains'; const parseExchangeName = name => { - const networks = SUPPORTED_CHAINS.map(network => network.name.toLowerCase()); + const networks = Object.values(defaultChains).map(network => network.name.toLowerCase()); const removeNetworks = name => networks.some(network => name.toLowerCase().includes(network)) ? name.slice(name.indexOf('_') + 1, name.length) : name; diff --git a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx index 01fbc9dbdc4..56a9d22b4cf 100644 --- a/src/components/walletconnect-list/WalletConnectV2ListItem.tsx +++ b/src/components/walletconnect-list/WalletConnectV2ListItem.tsx @@ -25,7 +25,7 @@ import { Box, Inline } from '@/design-system'; import ChainBadge from '@/components/coin-icon/ChainBadge'; import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { ChainId } from '@/chains/types'; -import { supportedWalletConnectChainIds } from '@/chains'; +import { SUPPORTED_CHAIN_IDS } from '@/chains'; const CONTAINER_PADDING = 15; const VENDOR_LOGO_ICON_SIZE = 50; @@ -78,8 +78,7 @@ export function WalletConnectV2ListItem({ session, reload }: { session: SessionT const chains = useMemo(() => namespaces?.eip155?.chains || [], [namespaces]); const chainIds = useMemo( () => - (chains?.map(chain => parseInt(chain.split(':')[1]))?.filter(chainId => supportedWalletConnectChainIds.includes(chainId)) ?? - []) as ChainId[], + (chains?.map(chain => parseInt(chain.split(':')[1]))?.filter(chainId => SUPPORTED_CHAIN_IDS.includes(chainId)) ?? []) as ChainId[], [chains] ); diff --git a/src/helpers/walletConnectNetworks.ts b/src/helpers/walletConnectNetworks.ts index 207876c343e..69fb67ee2c1 100644 --- a/src/helpers/walletConnectNetworks.ts +++ b/src/helpers/walletConnectNetworks.ts @@ -2,23 +2,24 @@ import store from '@/redux/store'; import { showActionSheetWithOptions } from '@/utils'; import * as i18n from '@/languages'; import { ChainId } from '@/chains/types'; -import { chainsLabel, defaultChains, supportedWalletConnectChainIds } from '@/chains'; +import { chainsLabel, defaultChains } from '@/chains'; import { isL2Chain } from '@/handlers/web3'; import { MenuActionConfig } from 'react-native-ios-context-menu'; -const walletConnectChains = supportedWalletConnectChainIds.map(chainId => defaultChains[chainId]); - const androidNetworkActions = () => { const { testnetsEnabled } = store.getState().settings; - return walletConnectChains.filter(({ testnet }) => testnetsEnabled || !testnet).map(chain => chain.id); + return Object.values(defaultChains) + .filter(chain => testnetsEnabled || !chain.testnet) + .map(chain => chain.id); }; export const NETWORK_MENU_ACTION_KEY_FILTER = 'switch-to-network-'; export const networksMenuItems: () => MenuActionConfig[] = () => { const { testnetsEnabled } = store.getState().settings; - return walletConnectChains - .filter(({ testnet }) => testnetsEnabled || !testnet) + + return Object.values(defaultChains) + .filter(chain => testnetsEnabled || !chain.testnet) .map(chain => ({ actionKey: `${NETWORK_MENU_ACTION_KEY_FILTER}${chain.id}`, actionTitle: chainsLabel[chain.id], @@ -75,10 +76,10 @@ export const androidShowNetworksActionSheet = (callback: any) => { showSeparators: true, title: i18n.t(i18n.l.walletconnect.menu_options.available_networks), }, - (idx: any) => { + (idx: number) => { if (idx !== undefined) { const networkActions = androidNetworkActions(); - const chain = walletConnectChains.find(chain => chain.id === networkActions[idx]) || defaultChains[ChainId.mainnet]; + const chain = defaultChains[networkActions[idx]] || defaultChains[ChainId.mainnet]; callback({ chainId: chain.id }); } } diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index 88a7ee473df..cf18a5b301a 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -33,7 +33,7 @@ import { Verify } from '@walletconnect/types'; import { RequestSource, handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; import { ChainId } from '@/chains/types'; import { Address } from 'viem'; -import { supportedWalletConnectChainIds } from '@/chains'; +import { SUPPORTED_CHAIN_IDS } from '@/chains'; // -- Variables --------------------------------------- // let showRedirectSheetThreshold = 300; @@ -458,7 +458,6 @@ const listenOnNewMessages = if (error) { analytics.track('Error on wc call_request', { - // @ts-ignore error, payload, }); @@ -478,7 +477,7 @@ const listenOnNewMessages = // @ts-expect-error "_chainId" is private. const currentChainId = Number(walletConnector._chainId); const numericChainId = Number(convertHexToString(chainId)); - if (supportedWalletConnectChainIds.includes(numericChainId)) { + if (SUPPORTED_CHAIN_IDS.includes(numericChainId)) { dispatch(walletConnectSetPendingRedirect()); Navigation.handleAction(Routes.WALLET_CONNECT_APPROVAL_SHEET, { callback: async (approved: boolean) => { diff --git a/src/screens/WalletConnectApprovalSheet.tsx b/src/screens/WalletConnectApprovalSheet.tsx index 80cf7be50b4..09e8f31305c 100644 --- a/src/screens/WalletConnectApprovalSheet.tsx +++ b/src/screens/WalletConnectApprovalSheet.tsx @@ -30,8 +30,7 @@ import { InfoAlert } from '@/components/info-alert/info-alert'; import { EthCoinIcon } from '@/components/coin-icon/EthCoinIcon'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { ChainId } from '@/chains/types'; -import { chainsLabel, chainsNativeAsset, defaultChains, supportedWalletConnectChainIds } from '@/chains'; -import { isL2Chain } from '@/handlers/web3'; +import { chainsLabel, chainsNativeAsset, defaultChains } from '@/chains'; import { ThemeContextProps, useTheme } from '@/theme'; import { noop } from 'lodash'; import { RootStackParamList } from '@/navigation/types'; @@ -79,31 +78,14 @@ const NetworkPill = ({ chainIds }: { chainIds: ChainId[] }) => { const availableNetworkChainIds = useMemo(() => chainIds.sort(chainId => (chainId === ChainId.mainnet ? -1 : 1)), [chainIds]); - const walletConnectSupportedChains = supportedWalletConnectChainIds.map(chainId => defaultChains[chainId]); - - const networkMenuItems = useMemo(() => { - walletConnectSupportedChains - .filter(({ id }) => chainIds.includes(id)) - .map(chain => ({ - actionKey: chain.id, - actionTitle: chainsLabel[chain.id], - icon: { - iconType: 'ASSET', - iconValue: `${isL2Chain({ chainId: chain.id }) ? `${chain.name}BadgeNoShadow` : 'ethereumBadge'}`, - }, - })); - }, [chainIds, walletConnectSupportedChains]); - if (availableNetworkChainIds.length === 0) return null; return ( { paddingTop="8px" marginRight={{ custom: -2 }} > - + {availableNetworkChainIds.length > 1 ? ( <> {availableNetworkChainIds.map((chainId, index) => { diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 04c5361551b..9cc1cfc8c64 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -44,7 +44,7 @@ import { handleWalletConnectRequest } from '@/utils/requestNavigationHandlers'; import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; import { PerformanceTracking } from '@/performance/tracking'; import { ChainId } from '@/chains/types'; -import { supportedWalletConnectChainIds } from '@/chains'; +import { SUPPORTED_CHAIN_IDS } from '@/chains'; const SUPPORTED_SESSION_EVENTS = ['chainChanged', 'accountsChanged']; @@ -445,7 +445,7 @@ export async function onSessionProposal(proposal: WalletKitTypes.SessionProposal // we already checked for eip155 namespace above const chainIds = chains?.map(chain => parseInt(chain.split('eip155:')[1])); - const supportedChainIds = chainIds.filter(chainId => supportedWalletConnectChainIds.includes(chainId)); + const supportedChainIds = chainIds.filter(chainId => SUPPORTED_CHAIN_IDS.includes(chainId)); const peerMeta = proposer.metadata; const metadata = await fetchDappMetadata({ url: peerMeta.url, status: true }); @@ -486,7 +486,7 @@ export async function onSessionProposal(proposal: WalletKitTypes.SessionProposal const supportedEvents = requiredNamespaces?.eip155?.events || SUPPORTED_SESSION_EVENTS; /** @see https://chainagnostic.org/CAIPs/caip-2 */ - const caip2ChainIds = supportedWalletConnectChainIds.map(id => `eip155:${id}`); + const caip2ChainIds = SUPPORTED_CHAIN_IDS.map(id => `eip155:${id}`); const namespaces = getApprovedNamespaces({ proposal: proposal.params, supportedNamespaces: { From caed4a7911e38b2c4c63f71b88d7fb92bcbb35c2 Mon Sep 17 00:00:00 2001 From: gregs Date: Mon, 7 Oct 2024 23:03:36 -0300 Subject: [PATCH 55/64] hide send button when not transferable (#6123) --- src/components/expanded-state/asset/ChartExpandedState.js | 5 ++++- src/graphql/queries/metadata.graphql | 1 + src/resources/assets/assets.ts | 1 + src/resources/assets/types.ts | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/expanded-state/asset/ChartExpandedState.js b/src/components/expanded-state/asset/ChartExpandedState.js index 1e35b208080..d54bce38627 100644 --- a/src/components/expanded-state/asset/ChartExpandedState.js +++ b/src/components/expanded-state/asset/ChartExpandedState.js @@ -173,6 +173,7 @@ export default function ChartExpandedState({ asset }) { const isL2 = useMemo(() => isL2Chain({ chainId: asset?.chainId }), [asset?.chainId]); const isTestnet = isTestnetChain({ chainId: currentChainId }); + const isTransferable = asset?.transferable ?? genericAsset?.transferable ?? true; const { data, isLoading: additionalAssetDataLoading } = useAdditionalAssetData({ address: asset?.address, @@ -293,7 +294,9 @@ export default function ChartExpandedState({ asset }) { )} {hasBalance ? ( - + isTransferable ? ( + + ) : null ) : swapEnabled ? ( Date: Tue, 8 Oct 2024 11:14:55 -0400 Subject: [PATCH 56/64] Claim button fixes (#6165) * hold -> tap * debounce --- .../claimables/ClaimingClaimableSharedUI.tsx | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx index 0190188708f..a800405e838 100644 --- a/src/screens/claimables/ClaimingClaimableSharedUI.tsx +++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { AccentColorProvider, Bleed, Box, Inline, Text, TextShadow, globalColors, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeader, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; @@ -17,6 +17,7 @@ import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-na import { convertAmountToNativeDisplayWorklet, handleSignificantDecimalsWithThreshold } from '@/__swaps__/utils/numbers'; import { useAccountSettings, useWallets } from '@/hooks'; import { enableActionsOnReadOnlyWallet } from '@/config'; +import { debounce } from 'lodash'; const BUTTON_WIDTH = deviceUtils.dimensions.width - 52; @@ -144,6 +145,22 @@ export const ClaimingClaimableSharedUI = ({ }; }); + const onPress = useCallback( + debounce(() => { + if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { + if (claimStatus === 'idle' || claimStatus === 'error') { + setClaimStatus('claiming'); + claim(); + } else if (claimStatus === 'success' || claimStatus === 'pending') { + goBack(); + } + } else { + watchingAlert(); + } + }, 300), + [claimStatus, claim, goBack, isReadOnlyWallet, setClaimStatus] + ); + return ( <> { - if (claimStatus === 'success' || claimStatus === 'pending') { - goBack(); - } - }} - onLongPress={() => { - haptics.impactHeavy(); - if (!isReadOnlyWallet || enableActionsOnReadOnlyWallet) { - if (claimStatus === 'idle' || claimStatus === 'error') { - setClaimStatus('claiming'); - claim(); - } - } else { - watchingAlert(); - } - }} + onPress={onPress} > Date: Tue, 8 Oct 2024 14:39:34 -0400 Subject: [PATCH 57/64] fix lint (#6180) --- src/resources/addys/claimables/utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/resources/addys/claimables/utils.ts b/src/resources/addys/claimables/utils.ts index 90889a2668a..b4eb06a6bbd 100644 --- a/src/resources/addys/claimables/utils.ts +++ b/src/resources/addys/claimables/utils.ts @@ -18,7 +18,11 @@ export const parseClaimables = (claimables: AddysClaimable[], currency: NativeCu const baseClaimable = { asset: parseAsset({ address: claimable.asset.asset_code, - asset: { ...claimable.asset, network: chainsName[claimable.network] as Network }, + asset: { + ...claimable.asset, + network: chainsName[claimable.network] as Network, + transferable: claimable.asset.transferable ?? false, + }, }), chainId: claimable.network, name: claimable.name, From 94fe488e36bb16caf409392bcae67bdbe78b3155 Mon Sep 17 00:00:00 2001 From: Wayne Cheng <677680+welps@users.noreply.github.com> Date: Wed, 9 Oct 2024 00:44:32 -0400 Subject: [PATCH 58/64] chore: use new fields from swap sdk and remove extraneous code (#6171) * chore: update swaps * chore: replace extraneous crosschain logic with needsAllowance field * chore: get rid of all WRAPPED_ASSET references and use quote swap type * chore: remove swapType as a param to get quotes * chore: replace swap unlock logic with needsAllowance field * chore: code review changes --- package.json | 2 +- .../Swap/hooks/useSwapInputsController.ts | 6 ++- .../screens/Swap/providers/swap-provider.tsx | 26 +++------- src/__swaps__/utils/swaps.ts | 44 +---------------- src/handlers/swap.ts | 48 ++----------------- src/hooks/usePriceImpactDetails.ts | 18 ++----- src/hooks/useSwapDerivedOutputs.ts | 2 - src/raps/actions/claimBridge.ts | 2 - src/raps/actions/swap.ts | 16 ++----- src/raps/unlockAndCrosschainSwap.ts | 42 ++++------------ src/raps/unlockAndSwap.ts | 28 +++-------- src/screens/ExchangeModal.tsx | 19 ++------ yarn.lock | 10 ++-- 13 files changed, 48 insertions(+), 215 deletions(-) diff --git a/package.json b/package.json index fbdb7bc9008..4763673b771 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "@notifee/react-native": "7.8.2", "@rainbow-me/provider": "0.1.1", "@rainbow-me/react-native-animated-number": "0.0.2", - "@rainbow-me/swaps": "0.27.0", + "@rainbow-me/swaps": "0.28.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-camera-roll/camera-roll": "7.7.0", "@react-native-clipboard/clipboard": "1.13.2", diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 030f7297458..877ef1eb5b1 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -41,7 +41,7 @@ import { } from '@/resources/assets/externalAssetsQuery'; import { triggerHapticFeedback } from '@/screens/points/constants'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { CrosschainQuote, Quote, QuoteError, SwapType, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; +import { CrosschainQuote, Quote, QuoteError, getCrosschainQuote, getQuote } from '@rainbow-me/swaps'; import { useCallback } from 'react'; import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; import { useDebouncedCallback } from 'use-debounce'; @@ -445,6 +445,8 @@ export function useSwapInputsController({ outputAsset: internalSelectedOutputAsset.value, }); + const isCrosschainSwap = internalSelectedInputAsset.value?.chainId !== internalSelectedOutputAsset.value?.chainId; + logger.debug(`[useSwapInputsController]: quote params`, { data: params, }); @@ -464,7 +466,7 @@ export function useSwapInputsController({ try { const [quoteResponse, fetchedPrices] = await Promise.all([ - params.swapType === SwapType.crossChain ? getCrosschainQuote(params) : getQuote(params), + isCrosschainSwap ? getCrosschainQuote(params) : getQuote(params), fetchAssetPrices({ inputAsset: internalSelectedInputAsset.value, outputAsset: internalSelectedOutputAsset.value, diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 9575fb0e54e..bbc534d55cc 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -1,12 +1,12 @@ // @refresh -import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef } from 'react'; +import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef } from 'react'; import { InteractionManager, NativeModules, StyleProp, TextInput, TextStyle } from 'react-native'; import { AnimatedRef, DerivedValue, - SharedValue, runOnJS, runOnUI, + SharedValue, useAnimatedReaction, useAnimatedRef, useAnimatedStyle, @@ -25,8 +25,8 @@ import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/ import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets'; import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets'; import { ChainId } from '@/chains/types'; -import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap'; -import { getDefaultSlippageWorklet, isUnwrapEthWorklet, isWrapEthWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; +import { inputKeys, SwapAssetType } from '@/__swaps__/types/swap'; +import { getDefaultSlippageWorklet, parseAssetAndExtend } from '@/__swaps__/utils/swaps'; import { analyticsV2 } from '@/analytics'; import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities'; import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; @@ -34,7 +34,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { useAccountSettings } from '@/hooks'; import { useAnimatedInterval } from '@/hooks/reanimated/useAnimatedInterval'; import * as i18n from '@/languages'; -import { RainbowError, logger } from '@/logger'; +import { logger, RainbowError } from '@/logger'; import { loadWallet } from '@/model/wallet'; import { Navigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; @@ -45,7 +45,7 @@ import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; import { haptics } from '@/utils'; -import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; +import { CrosschainQuote, Quote, QuoteError, SwapType } from '@rainbow-me/swaps'; import { IS_IOS } from '@/env'; import { Address } from 'viem'; @@ -397,17 +397,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const quoteData = q as QuoteTypeMap[typeof type]; const flashbots = (SwapSettings.flashbots.value && !!supportedFlashbotsChainIds.includes(inputAsset.chainId)) ?? false; - const isNativeWrapOrUnwrap = - isWrapEthWorklet({ - buyTokenAddress: quoteData.buyTokenAddress, - sellTokenAddress: quoteData.sellTokenAddress, - chainId: inputAsset.chainId, - }) || - isUnwrapEthWorklet({ - buyTokenAddress: quoteData.buyTokenAddress, - sellTokenAddress: quoteData.sellTokenAddress, - chainId: inputAsset.chainId, - }); + const isNativeWrapOrUnwrap = quoteData.swapType === SwapType.wrap || quoteData.swapType === SwapType.unwrap; // Do not deleeeet the comment below 😤 // About to get quote @@ -422,8 +412,6 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { buyAmountDisplay: isNativeWrapOrUnwrap ? quoteData.buyAmount : quoteData.buyAmountDisplay, sellAmountDisplay: isNativeWrapOrUnwrap ? quoteData.sellAmount : quoteData.sellAmountDisplay, feeInEth: isNativeWrapOrUnwrap ? '0' : quoteData.feeInEth, - fromChainId: inputAsset.chainId, - toChainId: outputAsset.chainId, }, flashbots, }; diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 7a2ce11ca15..d61abbfd6db 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -10,18 +10,17 @@ import { STABLECOIN_MINIMUM_SIGNIFICANT_DECIMALS, } from '@/__swaps__/screens/Swap/constants'; import { ChainId } from '@/chains/types'; -import { isLowerCaseMatchWorklet } from '@/__swaps__/utils/strings'; import { globalColors } from '@/design-system'; import { ForegroundColor, palettes } from '@/design-system/color/palettes'; import { TokenColors } from '@/graphql/__generated__/metadata'; import * as i18n from '@/languages'; import { RainbowConfig } from '@/model/remoteConfig'; import store from '@/redux/store'; -import { ETH_ADDRESS, supportedNativeCurrencies } from '@/references'; +import { supportedNativeCurrencies } from '@/references'; import { userAssetsStore } from '@/state/assets/userAssets'; import { colors } from '@/styles'; import { BigNumberish } from '@ethersproject/bignumber'; -import { CrosschainQuote, ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR, Quote, QuoteParams, SwapType, WRAPPED_ASSET } from '@rainbow-me/swaps'; +import { CrosschainQuote, ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR, Quote, QuoteParams } from '@rainbow-me/swaps'; import { swapsStore } from '../../state/swaps/swapsStore'; import { divWorklet, @@ -497,44 +496,6 @@ export const getCrossChainTimeEstimateWorklet = ({ }; }; -export const isUnwrapEthWorklet = ({ - buyTokenAddress, - chainId, - sellTokenAddress, -}: { - chainId: ChainId; - sellTokenAddress: string; - buyTokenAddress: string; -}) => { - 'worklet'; - if (chainId === ChainId.mainnet) { - return isLowerCaseMatchWorklet(sellTokenAddress, WRAPPED_ASSET[chainId]) && isLowerCaseMatchWorklet(buyTokenAddress, ETH_ADDRESS); - } else { - return ( - isLowerCaseMatchWorklet(sellTokenAddress, WRAPPED_ASSET[chainId]) && isLowerCaseMatchWorklet(buyTokenAddress, ETH_ADDRESS_AGGREGATOR) - ); - } -}; - -export const isWrapEthWorklet = ({ - buyTokenAddress, - chainId, - sellTokenAddress, -}: { - chainId: ChainId; - sellTokenAddress: string; - buyTokenAddress: string; -}) => { - 'worklet'; - if (chainId === ChainId.mainnet) { - return isLowerCaseMatchWorklet(sellTokenAddress, ETH_ADDRESS) && isLowerCaseMatchWorklet(buyTokenAddress, WRAPPED_ASSET[chainId]); - } else { - return ( - isLowerCaseMatchWorklet(sellTokenAddress, ETH_ADDRESS_AGGREGATOR) && isLowerCaseMatchWorklet(buyTokenAddress, WRAPPED_ASSET[chainId]) - ); - } -}; - export const priceForAsset = ({ asset, assetType, @@ -653,7 +614,6 @@ export const buildQuoteParams = ({ : undefined, slippage: Number(slippage), refuel: false, - swapType: isCrosschainSwap ? SwapType.crossChain : SwapType.normal, toChainId: isCrosschainSwap ? outputAsset.chainId : inputAsset.chainId, currency: store.getState().settings.nativeCurrency, }; diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts index eeebdc43547..4b5dcc394e8 100644 --- a/src/handlers/swap.ts +++ b/src/handlers/swap.ts @@ -3,13 +3,12 @@ import { Block, StaticJsonRpcProvider } from '@ethersproject/providers'; import { ALLOWS_PERMIT, CrosschainQuote, - ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS, getQuoteExecutionDetails, + getRainbowRouterContractAddress, getWrappedAssetMethod, PermitSupportedTokenList, Quote, - getRainbowRouterContractAddress, - WRAPPED_ASSET, + SwapType, } from '@rainbow-me/swaps'; import { Contract } from '@ethersproject/contracts'; import { MaxUint256 } from '@ethersproject/constants'; @@ -195,36 +194,6 @@ export const getSwapGasLimitWithFakeApproval = async ( return getDefaultGasLimitForTrade(tradeDetails, chainId); }; -export const isUnwrapNative = ({ - buyTokenAddress, - chainId, - sellTokenAddress, -}: { - chainId: ChainId; - sellTokenAddress: string; - buyTokenAddress: string; -}) => { - return ( - sellTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() && - buyTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() - ); -}; - -export const isWrapNative = ({ - buyTokenAddress, - chainId, - sellTokenAddress, -}: { - chainId: ChainId; - sellTokenAddress: string; - buyTokenAddress: string; -}) => { - return ( - sellTokenAddress.toLowerCase() === ETH_ADDRESS_AGGREGATORS.toLowerCase() && - buyTokenAddress.toLowerCase() === WRAPPED_ASSET[chainId]?.toLowerCase() - ); -}; - export const estimateSwapGasLimit = async ({ chainId, requiresApprove, @@ -238,17 +207,8 @@ export const estimateSwapGasLimit = async ({ if (!provider || !tradeDetails) { return ethereumUtils.getBasicSwapGasLimit(Number(chainId)); } - const { sellTokenAddress, buyTokenAddress } = tradeDetails; - const isWrapNativeAsset = isWrapNative({ - buyTokenAddress, - sellTokenAddress, - chainId, - }); - const isUnwrapNativeAsset = isUnwrapNative({ - buyTokenAddress, - sellTokenAddress, - chainId, - }); + const isWrapNativeAsset = tradeDetails.swapType === SwapType.wrap; + const isUnwrapNativeAsset = tradeDetails.swapType === SwapType.unwrap; // Wrap / Unwrap Eth if (isWrapNativeAsset || isUnwrapNativeAsset) { diff --git a/src/hooks/usePriceImpactDetails.ts b/src/hooks/usePriceImpactDetails.ts index e3c1a86f8cb..85a68a0238e 100644 --- a/src/hooks/usePriceImpactDetails.ts +++ b/src/hooks/usePriceImpactDetails.ts @@ -12,9 +12,8 @@ import { subtract, } from '@/helpers/utilities'; -import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; +import { CrosschainQuote, Quote, SwapType } from '@rainbow-me/swaps'; import { useNativeAsset } from '@/utils/ethereumUtils'; -import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; import { ChainId } from '@/chains/types'; export enum SwapPriceImpactType { @@ -37,7 +36,7 @@ export default function usePriceImpactDetails( const { colors } = useTheme(); const sellChainId = ( - (tradeDetails as CrosschainQuote)?.fromChainId ? (tradeDetails as CrosschainQuote)?.fromChainId : chainId + (tradeDetails as CrosschainQuote)?.sellTokenAsset?.chainId ? (tradeDetails as CrosschainQuote)?.sellTokenAsset?.chainId : chainId ) as ChainId; const buyChainId = (outputCurrency?.chainId || chainId) as ChainId; const sellNativeAsset = useNativeAsset({ chainId: sellChainId }); @@ -45,18 +44,7 @@ export default function usePriceImpactDetails( const isWrapOrUnwrap = useMemo(() => { if (!tradeDetails) return false; - return ( - isWrapNative({ - buyTokenAddress: tradeDetails?.buyTokenAddress, - sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId: buyChainId, - }) || - isUnwrapNative({ - buyTokenAddress: tradeDetails?.buyTokenAddress, - sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId: buyChainId, - }) - ); + return tradeDetails.swapType === SwapType.wrap || tradeDetails.swapType === SwapType.unwrap; }, [buyChainId, tradeDetails]); const inputNativeAmount = useMemo(() => { diff --git a/src/hooks/useSwapDerivedOutputs.ts b/src/hooks/useSwapDerivedOutputs.ts index 4444bc523d4..338ffd40c9c 100644 --- a/src/hooks/useSwapDerivedOutputs.ts +++ b/src/hooks/useSwapDerivedOutputs.ts @@ -101,7 +101,6 @@ const getInputAmount = async ( // Add 5% slippage for testing to prevent flaky tests slippage: IS_TESTING !== 'true' ? slippage : 5, ...(quoteSource ? { source } : {}), - swapType: SwapType.normal, currency: store.getState().settings.nativeCurrency, }; @@ -192,7 +191,6 @@ const getOutputAmount = async ( // Add 5% slippage for testing to prevent flaky tests slippage: IS_TESTING !== 'true' ? slippage : 5, ...(quoteSource ? { source } : {}), - swapType: isCrosschainSwap ? SwapType.crossChain : SwapType.normal, toChainId: Number(outputChainId), refuel, currency: store.getState().settings.nativeCurrency, diff --git a/src/raps/actions/claimBridge.ts b/src/raps/actions/claimBridge.ts index d4f9addd1e5..b4ed75e4635 100644 --- a/src/raps/actions/claimBridge.ts +++ b/src/raps/actions/claimBridge.ts @@ -38,7 +38,6 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps buyTokenAddress: AddressZero, sellAmount: sellAmount, slippage: 2, - swapType: SwapType.crossChain, currency, }); @@ -99,7 +98,6 @@ export async function claimBridge({ parameters, wallet, baseNonce }: ActionProps buyTokenAddress: AddressZero, sellAmount: maxBridgeableAmount, slippage: 2, - swapType: SwapType.crossChain, currency, }); diff --git a/src/raps/actions/swap.ts b/src/raps/actions/swap.ts index 8f84b5f1441..696bae0d8a7 100644 --- a/src/raps/actions/swap.ts +++ b/src/raps/actions/swap.ts @@ -3,11 +3,9 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { Transaction } from '@ethersproject/transactions'; import { CrosschainQuote, - ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS, Quote, ChainId as SwapChainId, SwapType, - WRAPPED_ASSET, fillQuote, getQuoteExecutionDetails, getRainbowRouterContractAddress, @@ -23,8 +21,6 @@ import { ChainId } from '@/chains/types'; import { NewTransaction } from '@/entities/transactions'; import { TxHash } from '@/resources/transactions/types'; import { add } from '@/helpers/utilities'; -import { isLowerCaseMatch } from '@/__swaps__/utils/strings'; -import { isUnwrapNative, isWrapNative } from '@/handlers/swap'; import { addNewTransaction } from '@/state/pendingTransactions'; import { RainbowError, logger } from '@/logger'; @@ -67,12 +63,8 @@ export const estimateSwapGasLimit = async ({ return gasUnits.basic_swap[chainId]; } - const { sellTokenAddress, buyTokenAddress } = quote; - const isWrapNativeAsset = - isLowerCaseMatch(sellTokenAddress, ETH_ADDRESS_AGGREGATORS) && isLowerCaseMatch(buyTokenAddress, WRAPPED_ASSET[chainId]); - - const isUnwrapNativeAsset = - isLowerCaseMatch(sellTokenAddress, WRAPPED_ASSET[chainId]) && isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS_AGGREGATORS); + const isWrapNativeAsset = quote.swapType === SwapType.wrap; + const isUnwrapNativeAsset = quote.swapType === SwapType.unwrap; // Wrap / Unwrap Eth if (isWrapNativeAsset || isUnwrapNativeAsset) { @@ -231,10 +223,10 @@ export const executeSwap = async ({ }; // Wrap Eth - if (isWrapNative({ buyTokenAddress, sellTokenAddress, chainId })) { + if (quote.swapType === SwapType.wrap) { return wrapNativeAsset(quote.buyAmount, wallet, chainId as unknown as SwapChainId, transactionParams); // Unwrap Weth - } else if (isUnwrapNative({ buyTokenAddress, sellTokenAddress, chainId })) { + } else if (quote.swapType === SwapType.unwrap) { return unwrapNativeAsset(quote.sellAmount, wallet, chainId as unknown as SwapChainId, transactionParams); // Swap } else { diff --git a/src/raps/unlockAndCrosschainSwap.ts b/src/raps/unlockAndCrosschainSwap.ts index a5d3b20ed21..f5d9ab1371d 100644 --- a/src/raps/unlockAndCrosschainSwap.ts +++ b/src/raps/unlockAndCrosschainSwap.ts @@ -1,10 +1,6 @@ -import { ALLOWS_PERMIT, ChainId, ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR, PermitSupportedTokenList, WRAPPED_ASSET } from '@rainbow-me/swaps'; import { Address } from 'viem'; -import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; -import { isLowerCaseMatch } from '@/utils'; -import { ETH_ADDRESS } from '../references'; import { assetNeedsUnlocking, estimateApprove } from './actions'; import { estimateCrosschainSwapGasLimit } from './actions/crosschainSwap'; @@ -20,30 +16,19 @@ export const estimateUnlockAndCrosschainSwap = async ({ const { from: accountAddress, sellTokenAddress, - buyTokenAddress, allowanceTarget, - no_approval, + allowanceNeeded, } = quote as { from: Address; sellTokenAddress: Address; - buyTokenAddress: Address; allowanceTarget: Address; - no_approval: boolean; + allowanceNeeded: boolean; }; - const isNativeAssetUnwrapping = - (isLowerCaseMatch(sellTokenAddress, WRAPPED_ASSET?.[chainId]) && isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS)) || - isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS_AGGREGATOR); - let gasLimits: (string | number)[] = []; let swapAssetNeedsUnlocking = false; - // Aggregators represent native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(assetToSell.address, chainId); - - const shouldNotHaveApproval = no_approval !== undefined && no_approval; - - if (!isNativeAssetUnwrapping && !nativeAsset && allowanceTarget && !shouldNotHaveApproval) { + if (allowanceNeeded) { swapAssetNeedsUnlocking = await assetNeedsUnlocking({ owner: accountAddress, amount: sellAmount, @@ -90,26 +75,18 @@ export const createUnlockAndCrosschainSwapRap = async (swapParameters: RapSwapAc sellTokenAddress, buyTokenAddress, allowanceTarget, - no_approval, + allowanceNeeded, } = quote as { from: Address; sellTokenAddress: Address; buyTokenAddress: Address; allowanceTarget: Address; - no_approval: boolean; + allowanceNeeded: boolean; }; - const isNativeAssetUnwrapping = - isLowerCaseMatch(sellTokenAddress, WRAPPED_ASSET[`${chainId}`]) && isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS); - - // Aggregators represent native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || assetToSell?.isNativeAsset; - - const shouldNotHaveApproval = no_approval !== undefined && no_approval; - let swapAssetNeedsUnlocking = false; - if (!isNativeAssetUnwrapping && !nativeAsset && allowanceTarget && !shouldNotHaveApproval) { + if (allowanceNeeded) { swapAssetNeedsUnlocking = await assetNeedsUnlocking({ owner: accountAddress, amount: sellAmount, @@ -118,10 +95,8 @@ export const createUnlockAndCrosschainSwapRap = async (swapParameters: RapSwapAc chainId, }); } - const allowsPermit = - !nativeAsset && chainId === ChainId.mainnet && ALLOWS_PERMIT[assetToSell.address?.toLowerCase() as keyof PermitSupportedTokenList]; - if (swapAssetNeedsUnlocking && !allowsPermit) { + if (swapAssetNeedsUnlocking) { const unlock = createNewAction('unlock', { fromAddress: accountAddress, amount: sellAmount, @@ -135,8 +110,7 @@ export const createUnlockAndCrosschainSwapRap = async (swapParameters: RapSwapAc // create a swap rap const swap = createNewAction('crosschainSwap', { chainId, - permit: swapAssetNeedsUnlocking && allowsPermit, - requiresApprove: swapAssetNeedsUnlocking && !allowsPermit, + requiresApprove: swapAssetNeedsUnlocking, quote, meta: swapParameters.meta, assetToSell, diff --git a/src/raps/unlockAndSwap.ts b/src/raps/unlockAndSwap.ts index feee5b53053..01d226f2703 100644 --- a/src/raps/unlockAndSwap.ts +++ b/src/raps/unlockAndSwap.ts @@ -1,9 +1,8 @@ import { ALLOWS_PERMIT, ETH_ADDRESS as ETH_ADDRESS_AGGREGATOR, - PermitSupportedTokenList, - WRAPPED_ASSET, getRainbowRouterContractAddress, + PermitSupportedTokenList, } from '@rainbow-me/swaps'; import { Address } from 'viem'; @@ -11,9 +10,7 @@ import { ChainId } from '@/chains/types'; import { isNativeAsset } from '@/handlers/assets'; import { add } from '@/helpers/utilities'; import { isLowerCaseMatch } from '@/utils'; -import { ETH_ADDRESS } from '../references'; -import { isUnwrapNative } from '@/handlers/swap'; import { assetNeedsUnlocking, estimateApprove, estimateSwapGasLimit } from './actions'; import { estimateUnlockAndSwapFromMetadata } from './actions/swap'; import { createNewAction, createNewRap } from './common'; @@ -28,22 +25,17 @@ export const estimateUnlockAndSwap = async ({ const { from: accountAddress, sellTokenAddress, - buyTokenAddress, + allowanceNeeded, } = quote as { from: Address; sellTokenAddress: Address; - buyTokenAddress: Address; + allowanceNeeded: boolean; }; - const isNativeAssetUnwrapping = - isLowerCaseMatch(sellTokenAddress, WRAPPED_ASSET?.[chainId]) && - (isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS?.[chainId]) || isLowerCaseMatch(buyTokenAddress, ETH_ADDRESS_AGGREGATOR?.[chainId])); - let gasLimits: (string | number)[] = []; let swapAssetNeedsUnlocking = false; - const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, chainId); - if (!isNativeAssetUnwrapping && !nativeAsset) { + if (allowanceNeeded) { swapAssetNeedsUnlocking = await assetNeedsUnlocking({ owner: accountAddress, amount: sellAmount, @@ -100,25 +92,19 @@ export const createUnlockAndSwapRap = async (swapParameters: RapSwapActionParame const { from: accountAddress, sellTokenAddress, - buyTokenAddress, + allowanceNeeded, } = quote as { from: Address; sellTokenAddress: Address; - buyTokenAddress: Address; + allowanceNeeded: boolean; }; - const isNativeAssetUnwrapping = isUnwrapNative({ - chainId, - sellTokenAddress, - buyTokenAddress, - }); - // Aggregators represent native asset as 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE const nativeAsset = isLowerCaseMatch(ETH_ADDRESS_AGGREGATOR, sellTokenAddress) || isNativeAsset(sellTokenAddress, chainId); let swapAssetNeedsUnlocking = false; - if (!isNativeAssetUnwrapping && !nativeAsset) { + if (allowanceNeeded) { swapAssetNeedsUnlocking = await assetNeedsUnlocking({ owner: accountAddress, amount: sellAmount as string, diff --git a/src/screens/ExchangeModal.tsx b/src/screens/ExchangeModal.tsx index 5d6a1f2ea64..5925bd822d7 100644 --- a/src/screens/ExchangeModal.tsx +++ b/src/screens/ExchangeModal.tsx @@ -56,9 +56,9 @@ import { ethereumUtils, gasUtils } from '@/utils'; import { IS_ANDROID, IS_IOS, IS_TEST } from '@/env'; import { logger, RainbowError } from '@/logger'; import { CROSSCHAIN_SWAPS, useExperimentalFlag } from '@/config'; -import { CrosschainQuote, Quote } from '@rainbow-me/swaps'; +import { CrosschainQuote, Quote, SwapType } from '@rainbow-me/swaps'; import store from '@/redux/store'; -import { getCrosschainSwapServiceTime, isUnwrapNative, isWrapNative } from '@/handlers/swap'; +import { getCrosschainSwapServiceTime } from '@/handlers/swap'; import useParamsForExchangeModal from '@/hooks/useParamsForExchangeModal'; import { Wallet } from '@ethersproject/wallet'; import { setHardwareTXError } from '@/navigation/HardwareWalletTxNavigator'; @@ -447,18 +447,7 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty } as ParsedAsset; const isWrapOrUnwrapEth = () => { - return ( - isWrapNative({ - buyTokenAddress: tradeDetails?.buyTokenAddress, - sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId: inputCurrency?.chainId || ChainId.mainnet, - }) || - isUnwrapNative({ - buyTokenAddress: tradeDetails?.buyTokenAddress, - sellTokenAddress: tradeDetails?.sellTokenAddress, - chainId: inputCurrency?.chainId || ChainId.mainnet, - }) - ); + return tradeDetails.swapType === SwapType.wrap || tradeDetails.swapType === SwapType.unwrap; }; const { errorMessage } = await walletExecuteRap(wallet, isCrosschainSwap ? 'crosschainSwap' : 'swap', { @@ -471,8 +460,6 @@ export function ExchangeModal({ fromDiscover, ignoreInitialTypeCheck, testID, ty quote: { ...tradeDetails, feeInEth: isWrapOrUnwrapEth() ? '0' : tradeDetails.feeInEth, - fromChainId: inputCurrency.chainId, - toChainId: outputCurrency.chainId, }, amount: inputAmount, meta: { diff --git a/yarn.lock b/yarn.lock index b3a51ad90e1..40015e2f503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4427,9 +4427,9 @@ __metadata: languageName: node linkType: hard -"@rainbow-me/swaps@npm:0.27.0": - version: 0.27.0 - resolution: "@rainbow-me/swaps@npm:0.27.0" +"@rainbow-me/swaps@npm:0.28.0": + version: 0.28.0 + resolution: "@rainbow-me/swaps@npm:0.28.0" dependencies: "@ethereumjs/util": "npm:9.0.0" "@ethersproject/abi": "npm:5.7.0" @@ -4444,7 +4444,7 @@ __metadata: "@ethersproject/transactions": "npm:5.7.0" "@ethersproject/wallet": "npm:5.7.0" "@metamask/eth-sig-util": "npm:7.0.0" - checksum: 10c0/aba215c73c65bd8281f61a5658e41767f356d0d14c8bcf88defb3ec37ecf3d20f48ea8ceb545e93bdca903115334679b37aa735514432064f633da6ba542c001 + checksum: 10c0/a5c8cd8325ceb7552ad7442a815b1a5afa9ddc9f7487a732704b414fcd94e718286951c02a46440d6252020bdd191bacc38f59d61deec6933d78fb787d1602c9 languageName: node linkType: hard @@ -7796,7 +7796,7 @@ __metadata: "@notifee/react-native": "npm:7.8.2" "@rainbow-me/provider": "npm:0.1.1" "@rainbow-me/react-native-animated-number": "npm:0.0.2" - "@rainbow-me/swaps": "npm:0.27.0" + "@rainbow-me/swaps": "npm:0.28.0" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-camera-roll/camera-roll": "npm:7.7.0" "@react-native-clipboard/clipboard": "npm:1.13.2" From bc291cfdbaf82d3c2da275107d7e1ef974746be5 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 9 Oct 2024 10:42:38 -0400 Subject: [PATCH 59/64] Zeego Dropdown Menus [MintFilters] (#6143) * zeego setup * bump react-native-menu/menu * fix android settings menu * update deps * fix type inference * mvp to fix android context menus * fix some inconsistencies on Android * remove debug logs * Update src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx * Update src/components/DropdownMenu.tsx * remove unused deps * fix build maybe * update lock --- .gitignore | 6 +- android/app/build.gradle | 7 +- .../main/java/me/rainbow/MainApplication.kt | 17 +- .../SystemNavigationBarModule.java | 172 +- android/settings.gradle | 3 + babel.config.js | 1 + ios/Podfile | 14 + ios/Podfile.lock | 73 +- ios/Rainbow-Bridging-Header.h | 1 + ios/Rainbow.xcodeproj/project.pbxproj | 72 +- ios/Rainbow/AppDelegate.h | 3 +- ios/Rainbow/AppDelegate.mm | 7 +- metro.config.js | 3 +- package.json | 7 +- src/App.tsx | 13 +- src/__swaps__/screens/Swap/Swap.tsx | 5 +- src/components/DappBrowser/Dimensions.ts | 3 +- src/components/DropdownMenu.tsx | 118 + src/components/cards/MintsCard/Menu.tsx | 60 +- .../cards/remote-cards/RemoteCard.tsx | 2 +- src/handlers/localstorage/theme.ts | 11 +- src/navigation/SwipeNavigator.tsx | 12 +- src/screens/WelcomeScreen/index.tsx | 3 +- src/screens/discover/DiscoverScreen.tsx | 5 +- yarn.lock | 2584 ++++++++++++++++- 25 files changed, 2912 insertions(+), 290 deletions(-) create mode 100644 src/components/DropdownMenu.tsx diff --git a/.gitignore b/.gitignore index 700a6534b6d..649afb7ba98 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,8 @@ InjectedJSBundle.js.LICENSE.txt !.yarn/sdks !.yarn/versions -src/references/networks.json \ No newline at end of file +src/references/networks.json +# Expo +.expo +dist/ +web-build/ \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 925f9fab6af..7da165adf01 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -77,7 +77,12 @@ react { // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] - } + // + // Added by install-expo-modules + entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", rootDir.getAbsoluteFile().getParentFile().getAbsolutePath(), "android", "absolute"].execute(null, rootDir).text.trim()) + cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim()) + bundleCommand = "export:embed" +} /** * Set this to true to create two separate APKs instead of one: diff --git a/android/app/src/main/java/me/rainbow/MainApplication.kt b/android/app/src/main/java/me/rainbow/MainApplication.kt index 08d0640db6a..3ae4700549d 100644 --- a/android/app/src/main/java/me/rainbow/MainApplication.kt +++ b/android/app/src/main/java/me/rainbow/MainApplication.kt @@ -1,4 +1,7 @@ package me.rainbow +import android.content.res.Configuration +import expo.modules.ApplicationLifecycleDispatcher +import expo.modules.ReactNativeHostWrapper import android.app.Application import android.content.Context @@ -24,7 +27,7 @@ import me.rainbow.NativeModules.SystemNavigationBar.SystemNavigationBarPackage import me.rainbow.NativeModules.NavbarHeight.NavbarHeightPackage class MainApplication : Application(), ReactApplication { - override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { + override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) { override fun getUseDeveloperSupport(): Boolean { return BuildConfig.DEBUG } @@ -47,14 +50,14 @@ class MainApplication : Application(), ReactApplication { } override fun getJSMainModuleName(): String { - return "index" + return ".expo/.virtual-metro-entry" } override val isNewArchEnabled: Boolean get() = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED override val isHermesEnabled: Boolean get() = BuildConfig.IS_HERMES_ENABLED - } + }) override fun onCreate() { super.onCreate() @@ -67,7 +70,8 @@ class MainApplication : Application(), ReactApplication { // Branch logging for debugging RNBranchModule.enableLogging() RNBranchModule.getAutoInstance(this) - } + ApplicationLifecycleDispatcher.onApplicationCreate(this) + } companion object { private val START_MARK = System.currentTimeMillis() @@ -75,4 +79,9 @@ class MainApplication : Application(), ReactApplication { fun getAppContext(): Context = appContext } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) + } } \ No newline at end of file diff --git a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java index c72641cf994..a2c4d5d2b3c 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java @@ -28,37 +28,13 @@ public class SystemNavigationBarModule extends ReactContextBaseJavaModule { public static final String REACT_CLASS = "NavigationBar"; - private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY"; - private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity"; - private static final String ERROR_API_LEVEL = "API_LEVEl"; - private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported"; - private static ReactApplicationContext reactContext = null; - private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; public SystemNavigationBarModule(ReactApplicationContext context) { // Pass in the context to the constructor and save it so you can emit events // https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module super(context); - reactContext = context; } - public void setNavigationBarTheme(Activity activity, Boolean light) { - if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Window window = activity.getWindow(); - int flags = window.getDecorView().getSystemUiVisibility(); - if (light) { - flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } else { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } - window.getDecorView().setSystemUiVisibility(flags); - } - } - - @Override public String getName() { // Tell React the name of the module @@ -77,97 +53,77 @@ public Map getConstants() { } @ReactMethod - public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) { - final WritableMap map = Arguments.createMap(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (getCurrentActivity() != null) { - try { - final Window window = getCurrentActivity().getWindow(); - runOnUiThread(new Runnable() { - @Override - public void run() { - WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(window, window.getDecorView()); - insetsController.setAppearanceLightNavigationBars(!light); - - if (color.equals("transparent") || color.equals("translucent")) { - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - if (color.equals("transparent")) { - window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - } else { - window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - if (animated) { - Integer colorFrom = window.getNavigationBarColor(); - Integer colorTo = Color.parseColor(String.valueOf(color)); - ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); - colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - window.setNavigationBarColor((Integer) animator.getAnimatedValue()); - } - }); - colorAnimation.start(); - } else { - window.setNavigationBarColor(Color.parseColor(String.valueOf(color))); - } - } - setNavigationBarTheme(getCurrentActivity(), light); - map.putBoolean("success", true); - promise.resolve(map); - } - }); - } catch (IllegalViewOperationException e) { - map.putBoolean("success", false); - promise.reject("error", e); - } - } else { - promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE)); - } - } else { - promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE)); - } - } - - @ReactMethod - public void hideNavigationBar(Promise promise) { - try { - runOnUiThread(new Runnable() { + public void changeBarColors(final Boolean isDarkMode, final String translucentLightStr, final String translucentDarkStr) { + Activity activity = getCurrentActivity(); + if (activity != null) { + final Window window = activity.getWindow(); + activity.runOnUiThread(new Runnable() { @Override public void run() { - if (getCurrentActivity() != null) { - View decorView = getCurrentActivity().getWindow().getDecorView(); - decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR); + /** + * Handle the color setting + */ + int translucentLightColor; + if (translucentLightStr.isEmpty()) { + translucentLightColor = Color.parseColor("#50000000"); + } else if (translucentLightStr.equals("transparent")) { + translucentLightColor = Color.TRANSPARENT; + } else { + translucentLightColor = Color.parseColor(translucentLightStr); } - } - }); - } catch (IllegalViewOperationException e) { - WritableMap map = Arguments.createMap(); - map.putBoolean("success", false); - promise.reject("error", e); - } - } - @ReactMethod - public void showNavigationBar(Promise promise) { - try { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (getCurrentActivity() != null) { - View decorView = getCurrentActivity().getWindow().getDecorView(); - int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; - decorView.setSystemUiVisibility(uiOptions); + int translucentDarkColor; + if (translucentDarkStr.isEmpty() || translucentDarkStr.equals("transparent")) { + translucentDarkColor = Color.TRANSPARENT; + } else { + translucentDarkColor = Color.parseColor(translucentDarkStr); } + + // Set the navbar to be drawn over + // Both flags were added in Level 16 + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + boolean isDarkModeTransparent = isDarkMode && translucentDarkStr.equals("transparent"); + boolean isLightModeTransparent = !isDarkMode && translucentLightStr.equals("transparent"); + + // M was the first version that supported light mode status bar + boolean shouldUseTransparentStatusBar = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + // O was the first version that supported light mode nav bar + boolean shouldUseTransparentNavBar = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (isDarkModeTransparent || isLightModeTransparent); + + if (shouldUseTransparentStatusBar) { + window.setStatusBarColor(Color.TRANSPARENT); + if (!isDarkMode) { + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + } else { + int statusBarColor = isDarkMode ? translucentDarkColor : translucentLightColor; + window.setStatusBarColor(statusBarColor); + if (!isDarkMode) { + flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + } + + if (shouldUseTransparentNavBar) { + window.setNavigationBarColor(Color.TRANSPARENT); + if (!isDarkMode) { + flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + } else { + int navBarColor = isDarkMode ? translucentDarkColor : translucentLightColor; + window.setNavigationBarColor(navBarColor); + if (!isDarkMode) { + flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + } + + window.getDecorView().setSystemUiVisibility(flags); + WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(window, window.getDecorView()); + insetsController.setAppearanceLightNavigationBars(!isDarkMode); } }); - } catch (IllegalViewOperationException e) { - WritableMap map = Arguments.createMap(); - map.putBoolean("success", false); - promise.reject("error", e); + } else { + android.util.Log.e("NavigationBar", "Activity is null, cannot change bar colors"); } } } \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 9bc55e25785..e9ae7fde07f 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -10,3 +10,6 @@ include ':detox' project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') includeBuild('../node_modules/@react-native/gradle-plugin') + +apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") +useExpoModules() \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index a0ba7917a2d..4d91b3e4fef 100644 --- a/babel.config.js +++ b/babel.config.js @@ -30,6 +30,7 @@ module.exports = function (api) { root: ['./src'], }, ], + '@babel/plugin-transform-export-namespace-from', 'babel-plugin-styled-components', '@babel/plugin-proposal-numeric-separator', 'date-fns', diff --git a/ios/Podfile b/ios/Podfile index bf1cf540459..07efcb01d80 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,3 +1,4 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") platform :ios, '15.0' source 'https://github.com/CocoaPods/Specs.git' #use_modular_headers! @@ -36,6 +37,19 @@ end target 'Rainbow' do + use_expo_modules! + post_integrate do |installer| + begin + expo_patch_react_imports!(installer) + rescue => e + Pod::UI.warn e + end + begin + expo_patch_react_imports!(installer) + rescue => e + Pod::UI.warn e + end + end # Pods for Rainbow config = use_native_modules! diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6771cbd3530..04ff508dafa 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -8,6 +8,41 @@ PODS: - CoinbaseWalletSDK/Host (1.1.0): - CoinbaseWalletSDK/Client - DoubleConversion (1.1.6) + - EXConstants (16.0.2): + - ExpoModulesCore + - Expo (51.0.33): + - ExpoModulesCore + - ExpoAsset (10.0.10): + - ExpoModulesCore + - ExpoFileSystem (17.0.1): + - ExpoModulesCore + - ExpoFont (12.0.10): + - ExpoModulesCore + - ExpoKeepAwake (13.0.2): + - ExpoModulesCore + - ExpoModulesCore (1.12.24): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - FasterImage (1.6.2): - FasterImage/Nuke (= 1.6.2) - FasterImage/NukeUI (= 1.6.2) @@ -1204,7 +1239,7 @@ PODS: - React-Core - react-native-mail (4.1.0): - React - - react-native-menu (1.0.3): + - react-native-menu (1.1.3): - React - react-native-minimizer (1.3.5): - React @@ -1243,7 +1278,7 @@ PODS: - React - react-native-restart (0.0.22): - React-Core - - react-native-safe-area-context (4.10.1): + - react-native-safe-area-context (4.11.0): - React-Core - react-native-screen-corner-radius (0.2.2): - React @@ -1834,6 +1869,13 @@ DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - BVLinearGradient (from `../node_modules/react-native-linear-gradient`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXConstants (from `../node_modules/expo-constants/ios`) + - Expo (from `../node_modules/expo`) + - ExpoAsset (from `../node_modules/expo-asset/ios`) + - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) + - ExpoFont (from `../node_modules/expo-font/ios`) + - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) + - ExpoModulesCore (from `../node_modules/expo-modules-core`) - "FasterImage (from `../node_modules/@candlefinance/faster-image`)" - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - Firebase @@ -2022,6 +2064,20 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-linear-gradient" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXConstants: + :path: "../node_modules/expo-constants/ios" + Expo: + :path: "../node_modules/expo" + ExpoAsset: + :path: "../node_modules/expo-asset/ios" + ExpoFileSystem: + :path: "../node_modules/expo-file-system/ios" + ExpoFont: + :path: "../node_modules/expo-font/ios" + ExpoKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" + ExpoModulesCore: + :path: "../node_modules/expo-modules-core" FasterImage: :path: "../node_modules/@candlefinance/faster-image" FBLazyVector: @@ -2287,6 +2343,13 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + EXConstants: 409690fbfd5afea964e5e9d6c4eb2c2b59222c59 + Expo: 0b623d99ade6b23201ad615e289d32b669035ea3 + ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875 + ExpoFileSystem: 80bfe850b1f9922c16905822ecbf97acd711dc51 + ExpoFont: 00756e6c796d8f7ee8d211e29c8b619e75cbf238 + ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08 + ExpoModulesCore: f30a203ff1863bab3dd9f4421e7fc1564797f18a FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 @@ -2356,7 +2419,7 @@ SPEC CHECKSUMS: react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 - react-native-menu: 2e368669c7375dd1049ef3c7b2cab190ebe45d9e + react-native-menu: c30eb7a85d7b04d51945f61ea8a8986ed366ac5c react-native-minimizer: b94809a769ac3825b46fd081d4f0ae2560791536 react-native-mmkv: 8c9a677e64a1ac89b0c6cf240feea528318b3074 react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c @@ -2365,7 +2428,7 @@ SPEC CHECKSUMS: react-native-quick-md5: 9a4cb8201cd0b1866e374ac19d530da0964c0aed react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23 react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 - react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d + react-native-safe-area-context: 851c62c48dce80ccaa5637b6aa5991a1bc36eca9 react-native-screen-corner-radius: 67064efbb78f2d48f01626713ae8142f6a20f925 react-native-skia: 8da84ea9410504bf27f0db229539a43f6caabb6a react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 @@ -2445,6 +2508,6 @@ SPEC CHECKSUMS: VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c -PODFILE CHECKSUM: 40f081f682acbe2d1d2129eb3335352ea01aab70 +PODFILE CHECKSUM: 98c3fc206d7041ac7388693bb0753109d1884b57 COCOAPODS: 1.14.3 diff --git a/ios/Rainbow-Bridging-Header.h b/ios/Rainbow-Bridging-Header.h index db1ee815338..9b6ebab6664 100644 --- a/ios/Rainbow-Bridging-Header.h +++ b/ios/Rainbow-Bridging-Header.h @@ -3,6 +3,7 @@ // #import "React/RCTBridgeModule.h" +#import #import "React/RCTView.h" #import diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 18aa0ee4bdf..86aa5eea7f9 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -139,6 +139,7 @@ C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; CC651D36962A724E81524D7F /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6742E1A099DF0612378089CD /* libPods-SelectTokenIntent.a */; }; + E8D2945956C9619768A8D361 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C92E589571C27FFE89AB10 /* ExpoModulesProvider.swift */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; EDE3164FD32FFCE390544F9B /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */; }; /* End PBXBuildFile section */ @@ -252,6 +253,7 @@ 380324A440FD8D773D7F1074 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; + 43C92E589571C27FFE89AB10 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Rainbow/ExpoModulesProvider.swift"; sourceTree = ""; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; 62CAF1459EC3D2C1F76CAAA6 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; @@ -583,6 +585,14 @@ name = Frameworks; sourceTree = ""; }; + 48289AE63A63A1DE86A8E2F0 /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + CE32D29CCB97D084C2BFF555 /* Rainbow */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; 4D098C2C2811A95F006A801A /* RNStartTime */ = { isa = PBXGroup; children = ( @@ -643,6 +653,7 @@ DCAC1D8CC45E468FBB7E1395 /* Resources */, 24979D1220F83E3D007EB0DA /* Recovered References */, C640359C0E6575CE0A7ECD73 /* Pods */, + 48289AE63A63A1DE86A8E2F0 /* ExpoModulesProviders */, ); indentWidth = 2; sourceTree = ""; @@ -774,6 +785,14 @@ path = ShareWithRainbow; sourceTree = ""; }; + CE32D29CCB97D084C2BFF555 /* Rainbow */ = { + isa = PBXGroup; + children = ( + 43C92E589571C27FFE89AB10 /* ExpoModulesProvider.swift */, + ); + name = Rainbow; + sourceTree = ""; + }; DCAC1D8CC45E468FBB7E1395 /* Resources */ = { isa = PBXGroup; children = ( @@ -816,6 +835,7 @@ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( 58D46DFCC897D560DF7238EC /* [CP] Check Pods Manifest.lock */, + AEAD56A917FF4051986EE3E6 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, @@ -1095,7 +1115,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; showEnvVarsInLog = 0; }; 09A7DC74FE15490E6EC30E26 /* [CP-User] [RNFB] Core Configuration */ = { @@ -1304,6 +1324,25 @@ shellPath = /bin/sh; shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; }; + AEAD56A917FF4051986EE3E6 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Rainbow/expo-configure-project.sh\"\n"; + }; B645329271EC2E2C170CA75B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1322,6 +1361,9 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", @@ -1345,6 +1387,9 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", @@ -1446,6 +1491,7 @@ A4D04BA923D12F99008C1DEC /* Button.swift in Sources */, 66A1FEBC24ACBBE600C3F539 /* RNCMPortal.m in Sources */, C18FCD43273C64E40079CE28 /* PriceDataProvider.swift in Sources */, + E8D2945956C9619768A8D361 /* ExpoModulesProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1621,6 +1667,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; @@ -1671,6 +1718,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1720,6 +1768,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; @@ -1768,6 +1817,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ImageNotification; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.ImageNotification"; @@ -1843,6 +1893,7 @@ "-ObjC", "-lc++", ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; @@ -1907,6 +1958,7 @@ "-ObjC", "-lc++", ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; @@ -2024,6 +2076,7 @@ "-ObjC", "-lc++", ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; @@ -2140,6 +2193,7 @@ "-ObjC", "-lc++", ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; @@ -2301,6 +2355,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; @@ -2351,6 +2406,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2399,6 +2455,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; @@ -2446,6 +2503,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2492,6 +2550,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; @@ -2540,6 +2599,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2586,6 +2646,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; @@ -2631,6 +2692,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2688,6 +2750,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2743,6 +2806,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2796,6 +2860,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2849,6 +2914,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.OpenInRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2907,6 +2973,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2962,6 +3029,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3015,6 +3083,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3068,6 +3137,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.ShareWithRainbow; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/Rainbow/AppDelegate.h b/ios/Rainbow/AppDelegate.h index 66b7864c222..cd508aba379 100644 --- a/ios/Rainbow/AppDelegate.h +++ b/ios/Rainbow/AppDelegate.h @@ -5,6 +5,7 @@ #import +#import #import #import @@ -14,7 +15,7 @@ @class RCTBridge; -@interface AppDelegate : RCTAppDelegate +@interface AppDelegate : EXAppDelegateWrapper - (void)hideSplashScreenAnimated; diff --git a/ios/Rainbow/AppDelegate.mm b/ios/Rainbow/AppDelegate.mm index ce32a347b21..b3fe7a22767 100644 --- a/ios/Rainbow/AppDelegate.mm +++ b/ios/Rainbow/AppDelegate.mm @@ -5,7 +5,12 @@ #import "Firebase.h" #import "AppDelegate.h" + +// expo modules MUST be imported before Rainbow-Swift.h +#import "ExpoModulesCore-Swift.h" #import "Rainbow-Swift.h" +// end expo modules + #import #import #import @@ -114,7 +119,7 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge - (NSURL *)bundleURL { #if DEBUG - return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; #else return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif diff --git a/metro.config.js b/metro.config.js index 3d444bc6d12..26b2358dc17 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,6 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies const blacklist = require('metro-config/src/defaults/exclusionList'); -const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const { getDefaultConfig } = require('expo/metro-config'); +const { mergeConfig } = require('@react-native/metro-config'); // Deny list is a function that takes an array of regexes and combines // them with the default blacklist to return a single regex. diff --git a/package.json b/package.json index 4763673b771..52fadc07f42 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "@react-native-firebase/messaging": "20.1.0", "@react-native-firebase/remote-config": "20.1.0", "@react-native-masked-view/masked-view": "0.3.1", - "@react-native-menu/menu": "1.0.3", + "@react-native-menu/menu": "1.1.3", "@react-native/metro-config": "0.74.83", "@react-navigation/core": "6.4.16", "@react-navigation/material-top-tabs": "6.6.2", @@ -175,6 +175,7 @@ "ethereumjs-util": "6.2.1", "ethereumjs-wallet": "1.0.1", "events": "3.3.0", + "expo": "^51.0.0", "fast-text-encoding": "1.0.4", "global": "4.4.0", "grapheme-splitter": "1.0.4", @@ -255,7 +256,7 @@ "react-native-reanimated": "3.15.0", "react-native-redash": "16.3.0", "react-native-restart": "0.0.22", - "react-native-safe-area-context": "4.10.1", + "react-native-safe-area-context": "4.11.0", "react-native-safe-area-view": "rainbow-me/react-native-safe-area-view", "react-native-screen-corner-radius": "0.2.2", "react-native-screens": "3.34.0", @@ -308,6 +309,7 @@ "viem": "2.9.16", "vm-browserify": "0.0.4", "w2t": "3.0.2", + "zeego": "1.10.0", "zod": "3.23.8", "zustand": "4.5.4" }, @@ -318,6 +320,7 @@ "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", "@babel/plugin-proposal-numeric-separator": "7.18.6", "@babel/plugin-proposal-optional-chaining": "7.21.0", + "@babel/plugin-transform-export-namespace-from": "7.24.7", "@babel/plugin-transform-react-inline-elements": "7.18.6", "@babel/plugin-transform-runtime": "7.18.10", "@babel/preset-env": "7.22.0", diff --git a/src/App.tsx b/src/App.tsx index 8efd36871b6..34781cee79f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import { DeeplinkHandler } from '@/components/DeeplinkHandler'; import { AppStateChangeHandler } from '@/components/AppStateChangeHandler'; import { useApplicationSetup } from '@/hooks/useApplicationSetup'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; -import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; import { connect, Provider as ReduxProvider } from 'react-redux'; import { RecoilRoot } from 'recoil'; @@ -22,7 +22,7 @@ import * as keychain from '@/model/keychain'; import { Navigation } from '@/navigation'; import { PersistQueryClientProvider, persistOptions, queryClient } from '@/react-query'; import store, { AppDispatch, type AppState } from '@/redux/store'; -import { MainThemeProvider } from '@/theme/ThemeContext'; +import { MainThemeProvider, useTheme } from '@/theme/ThemeContext'; import { addressKey } from '@/utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; import { InitialRouteContext } from '@/navigation/initialRoute'; @@ -39,7 +39,7 @@ import { initializeRemoteConfig } from '@/model/remoteConfig'; import { NavigationContainerRef } from '@react-navigation/native'; import { RootStackParamList } from '@/navigation/types'; import { Address } from 'viem'; -import { IS_DEV } from '@/env'; +import { IS_ANDROID, IS_DEV } from '@/env'; import { prefetchDefaultFavorites } from '@/resources/favorites'; import Routes from '@/navigation/Routes'; import { BackendNetworks } from '@/components/BackendNetworks'; @@ -54,6 +54,7 @@ enableScreens(); const sx = StyleSheet.create({ container: { flex: 1, + overflow: 'hidden', }, }); @@ -63,14 +64,14 @@ interface AppProps { function App({ walletReady }: AppProps) { const { initialRoute } = useApplicationSetup(); - + const { bottom } = useSafeAreaInsets(); const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { Navigation.setTopLevelNavigator(ref); }, []); return ( - + {initialRoute && ( @@ -203,7 +204,7 @@ function Root() { - + diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index da79e3a04f4..b6fd788cc39 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -209,7 +209,10 @@ const ExchangeRateBubbleAndWarning = () => { export const styles = StyleSheet.create({ rootViewBackground: { - borderRadius: IS_ANDROID ? 20 : ScreenCornerRadius, + borderTopLeftRadius: IS_ANDROID ? 20 : ScreenCornerRadius, + borderTopRightRadius: IS_ANDROID ? 20 : ScreenCornerRadius, + borderBottomLeftRadius: IS_ANDROID ? 0 : ScreenCornerRadius, + borderBottomRightRadius: IS_ANDROID ? 0 : ScreenCornerRadius, flex: 1, overflow: 'hidden', marginTop: StatusBar.currentHeight ?? 0, diff --git a/src/components/DappBrowser/Dimensions.ts b/src/components/DappBrowser/Dimensions.ts index 2cddb184f37..e7f1376b68a 100644 --- a/src/components/DappBrowser/Dimensions.ts +++ b/src/components/DappBrowser/Dimensions.ts @@ -2,11 +2,10 @@ import { IS_ANDROID, IS_IOS } from '@/env'; import { TAB_BAR_HEIGHT } from '@/navigation/SwipeNavigator'; import { deviceUtils, safeAreaInsetValues } from '@/utils'; import { StatusBar } from 'react-native'; -import { NAVIGATION_BAR_HEIGHT } from '@/utils/deviceUtils'; export const BOTTOM_BAR_HEIGHT = 88; export const TOP_INSET = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight ?? 40; -export const BOTTOM_INSET = IS_ANDROID ? NAVIGATION_BAR_HEIGHT - 8 : BOTTOM_BAR_HEIGHT; +export const BOTTOM_INSET = IS_ANDROID ? safeAreaInsetValues.bottom + 32 : BOTTOM_BAR_HEIGHT; export const WEBVIEW_HEIGHT = deviceUtils.dimensions.height - TOP_INSET - TAB_BAR_HEIGHT - BOTTOM_INSET; export const COLLAPSED_WEBVIEW_ASPECT_RATIO = 4 / 3; export const COLLAPSED_WEBVIEW_HEIGHT_UNSCALED = Math.min(WEBVIEW_HEIGHT, deviceUtils.dimensions.width * COLLAPSED_WEBVIEW_ASPECT_RATIO); diff --git a/src/components/DropdownMenu.tsx b/src/components/DropdownMenu.tsx new file mode 100644 index 00000000000..8d2c6572e7c --- /dev/null +++ b/src/components/DropdownMenu.tsx @@ -0,0 +1,118 @@ +import React, { useCallback } from 'react'; +import * as DropdownMenuPrimitive from 'zeego/dropdown-menu'; +import styled from 'styled-components'; +import { IconConfig, MenuActionConfig, MenuConfig as _MenuConfig } from 'react-native-ios-context-menu'; +import { ImageSystemSymbolConfiguration } from 'react-native-ios-context-menu/lib/typescript/types/ImageItemConfig'; +import { ImageSourcePropType } from 'react-native'; +import type { SFSymbols5_0 } from 'sf-symbols-typescript'; +import type { DropdownMenuContentProps } from '@radix-ui/react-dropdown-menu'; + +export const DropdownMenuRoot = DropdownMenuPrimitive.Root; +export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +export const DropdownMenuContent = DropdownMenuPrimitive.Content; +export const DropdownMenuItem = DropdownMenuPrimitive.create( + styled(DropdownMenuPrimitive.CheckboxItem)({ + height: 34, + }), + 'CheckboxItem' +); +export const DropdownMenuItemTitle = DropdownMenuPrimitive.ItemTitle; +export const DropdownMenuItemIcon = DropdownMenuPrimitive.ItemIcon; +export const DropdownMenuItemImage = DropdownMenuPrimitive.ItemImage; + +export type MenuItemSystemImage = { + iconType: 'SYSTEM'; + iconValue: SFSymbols5_0; +} & ImageSystemSymbolConfiguration; + +export type MenuItemAssetImage = { + iconType: 'ASSET'; + iconValue: ImageSourcePropType; +}; + +export type MenuItemIcon = Omit & (MenuItemSystemImage | MenuItemAssetImage); + +export type MenuItem = Omit & { + actionKey: T; + actionTitle: string; + icon?: MenuItemIcon | { iconType: string; iconValue: string }; +}; + +export type MenuConfig = Omit<_MenuConfig, 'menuItems' | 'menuTitle'> & { + menuTitle?: string; + menuItems: Array>; +}; + +type DropDownMenuProps = { + children: React.ReactElement; + menuConfig: MenuConfig; + onPressMenuItem: (actionKey: T) => void; +} & DropdownMenuContentProps; + +const buildIconConfig = (icon?: MenuItemIcon) => { + if (!icon) return null; + + if (icon.iconType === 'SYSTEM' && typeof icon.iconValue === 'string') { + const ios = { name: icon.iconValue }; + + return ; + } + + if (icon.iconType === 'ASSET' && typeof icon.iconValue === 'object') { + return ; + } + + return null; +}; + +export function DropdownMenu({ + children, + menuConfig, + onPressMenuItem, + loop = true, + align = 'end', + sideOffset = 8, + side = 'right', + alignOffset = 5, + avoidCollisions = true, +}: DropDownMenuProps) { + const handleSelectItem = useCallback( + (actionKey: T) => { + onPressMenuItem(actionKey); + }, + [onPressMenuItem] + ); + + return ( + + {children} + + {!!menuConfig.menuTitle?.trim() && ( + + + {menuConfig.menuTitle} + + + )} + {menuConfig.menuItems?.map(item => { + const Icon = buildIconConfig(item.icon as MenuItemIcon); + + return ( + handleSelectItem(item.actionKey)}> + {item.actionTitle} + {Icon} + + ); + })} + + + ); +} diff --git a/src/components/cards/MintsCard/Menu.tsx b/src/components/cards/MintsCard/Menu.tsx index 18ed866171d..de1defea2f2 100644 --- a/src/components/cards/MintsCard/Menu.tsx +++ b/src/components/cards/MintsCard/Menu.tsx @@ -1,41 +1,45 @@ -import React from 'react'; -import ContextMenuButton from '@/components/native-context-menu/contextMenu'; +import React, { useCallback, useMemo } from 'react'; import { ButtonPressAnimation } from '@/components/animations'; import { Inline, Inset, Text } from '@/design-system'; import { haptics } from '@/utils'; import { MintsFilter, getMintsFilterLabel, useMintsFilter } from '@/resources/mints'; +import { DropdownMenu, MenuConfig } from '@/components/DropdownMenu'; export function Menu() { const { filter, setFilter } = useMintsFilter(); - const menuConfig = { - menuTitle: '', - menuItems: [ - { - actionKey: MintsFilter.All, - actionTitle: getMintsFilterLabel(MintsFilter.All), - menuState: filter === MintsFilter.All ? 'on' : 'off', - }, - { - actionKey: MintsFilter.Free, - actionTitle: getMintsFilterLabel(MintsFilter.Free), - menuState: filter === MintsFilter.Free ? 'on' : 'off', - }, - { - actionKey: MintsFilter.Paid, - actionTitle: getMintsFilterLabel(MintsFilter.Paid), - menuState: filter === MintsFilter.Paid ? 'on' : 'off', - }, - ], - } as const; + const menuConfig = useMemo>(() => { + return { + menuItems: [ + { + actionKey: MintsFilter.All, + actionTitle: getMintsFilterLabel(MintsFilter.All), + menuState: filter === MintsFilter.All ? 'on' : 'off', + }, + { + actionKey: MintsFilter.Free, + actionTitle: getMintsFilterLabel(MintsFilter.Free), + menuState: filter === MintsFilter.Free ? 'on' : 'off', + }, + { + actionKey: MintsFilter.Paid, + actionTitle: getMintsFilterLabel(MintsFilter.Paid), + menuState: filter === MintsFilter.Paid ? 'on' : 'off', + }, + ], + }; + }, [filter]); - const onPressMenuItem = ({ nativeEvent: { actionKey: filter } }: { nativeEvent: { actionKey: MintsFilter } }) => { - haptics.selection(); - setFilter(filter); - }; + const onPressMenuItem = useCallback( + (selection: MintsFilter) => { + haptics.selection(); + setFilter(selection); + }, + [setFilter] + ); return ( - + menuConfig={menuConfig} onPressMenuItem={onPressMenuItem}> @@ -50,6 +54,6 @@ export function Menu() { - + ); } diff --git a/src/components/cards/remote-cards/RemoteCard.tsx b/src/components/cards/remote-cards/RemoteCard.tsx index 50d35d9084b..8afd63d994a 100644 --- a/src/components/cards/remote-cards/RemoteCard.tsx +++ b/src/components/cards/remote-cards/RemoteCard.tsx @@ -224,7 +224,6 @@ export const RemoteCard: React.FC = ({ id, gutterSize, carousel - {card.dismissable && ( @@ -241,6 +240,7 @@ export const RemoteCard: React.FC = ({ id, gutterSize, carousel )} + ); }; diff --git a/src/handlers/localstorage/theme.ts b/src/handlers/localstorage/theme.ts index a18f805da04..f9dac336f1f 100644 --- a/src/handlers/localstorage/theme.ts +++ b/src/handlers/localstorage/theme.ts @@ -2,7 +2,6 @@ import { IS_ANDROID } from '@/env'; import { getGlobal, saveGlobal } from './common'; import { NativeModules } from 'react-native'; import { colors } from '@/styles'; -import { isUsingButtonNavigation } from '@/utils/deviceUtils'; import { Themes, ThemesType } from '@/theme'; const { NavigationBar } = NativeModules; @@ -10,10 +9,6 @@ const { NavigationBar } = NativeModules; const THEME = 'theme'; export const getColorForThemeAndNavigationStyle = (theme: ThemesType) => { - if (!isUsingButtonNavigation()) { - return 'transparent'; - } - return theme === Themes.DARK ? '#191A1C' : colors.white; }; @@ -29,7 +24,11 @@ export const getTheme = () => getGlobal(THEME, 'system'); export const saveTheme = (theme: ThemesType, isSystemDarkMode: boolean) => { if (IS_ANDROID) { const themeToUse = theme === Themes.SYSTEM ? (isSystemDarkMode ? Themes.DARK : Themes.LIGHT) : theme; - NavigationBar.changeNavigationBarColor(getColorForThemeAndNavigationStyle(themeToUse), themeToUse === Themes.DARK, true); + NavigationBar.changeBarColors( + themeToUse === Themes.DARK, + getColorForThemeAndNavigationStyle('light'), + getColorForThemeAndNavigationStyle('dark') + ); } return saveGlobal(THEME, theme); diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index 361fbf5d462..eae5c76d1da 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -44,17 +44,7 @@ import { ScrollPositionContext } from './ScrollPositionContext'; import SectionListScrollToTopProvider, { useSectionListScrollToTopContext } from './SectionListScrollToTopContext'; import { TextSize } from '@/design-system/components/Text/Text'; -export const TAB_BAR_HEIGHT = getTabBarHeight(); - -function getTabBarHeight() { - if (IS_IOS) { - return 82; - } - if (!isUsingButtonNavigation()) { - return 82; - } - return 48; -} +export const TAB_BAR_HEIGHT = IS_IOS ? 82 : 54; const HORIZONTAL_TAB_BAR_INSET = 6; const HORIZONTAL_TAB_BAR_INSET_5_TABS = 10; diff --git a/src/screens/WelcomeScreen/index.tsx b/src/screens/WelcomeScreen/index.tsx index c175c259d0a..641acb3fc0a 100644 --- a/src/screens/WelcomeScreen/index.tsx +++ b/src/screens/WelcomeScreen/index.tsx @@ -31,8 +31,7 @@ import { logger } from '@/logger'; import { IS_ANDROID, IS_TEST } from '@/env'; import { WelcomeScreenRainbowButton } from '@/screens/WelcomeScreen/WelcomeScreenRainbowButton'; -// @ts-expect-error Our implementation of SC complains -const Container = styled.View({ +const Container = styled(View)({ ...position.coverAsObject, alignItems: 'center', backgroundColor: ({ theme: { colors } }: { theme: ThemeContextProps }) => colors.white, diff --git a/src/screens/discover/DiscoverScreen.tsx b/src/screens/discover/DiscoverScreen.tsx index cd11b6b55f2..dd8ca846a58 100644 --- a/src/screens/discover/DiscoverScreen.tsx +++ b/src/screens/discover/DiscoverScreen.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect } from 'react'; -import { Keyboard, ScrollView } from 'react-native'; +import { Keyboard } from 'react-native'; import { useIsFocused } from '@react-navigation/native'; import { Box } from '@/design-system'; import { Page } from '@/components/layout'; @@ -18,7 +18,7 @@ import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native export let discoverScrollToTopFnRef: () => number | null = () => null; export default function DiscoverScreen() { - const ref = React.useRef(null); + const ref = React.useRef(null); const { navigate } = useNavigation(); const isFocused = useIsFocused(); const { accountSymbol, accountColor, accountImage } = useAccountProfile(); @@ -83,7 +83,6 @@ export default function DiscoverScreen() { title={isSearchModeEnabled ? i18n.t(i18n.l.discover.search.search) : i18n.t(i18n.l.discover.search.discover)} /> =0.8.1" + checksum: 10c0/aca22d101c0662abb7a7a02a05214a340c8b342569df71eb0b08328d4889a90ee495542c8f955538be2f8bc6b07a85dad63c6e6d13c7156b6b300a77ae0da124 + languageName: node + linkType: hard + +"@expo/rudder-sdk-node@npm:1.1.1": + version: 1.1.1 + resolution: "@expo/rudder-sdk-node@npm:1.1.1" + dependencies: + "@expo/bunyan": "npm:^4.0.0" + "@segment/loosely-validate-event": "npm:^2.0.0" + fetch-retry: "npm:^4.1.1" + md5: "npm:^2.2.1" + node-fetch: "npm:^2.6.1" + remove-trailing-slash: "npm:^0.1.0" + uuid: "npm:^8.3.2" + checksum: 10c0/1a13089bc2b8d437c45be64051f6e819966a7b8875bab4587c34c0841374a7b00ade7b76fa09d961a1e31343d5b3423f3a5f65658dcc883fd8b3dbddc53a8f7d + languageName: node + linkType: hard + "@expo/sdk-runtime-versions@npm:^1.0.0": version: 1.0.0 resolution: "@expo/sdk-runtime-versions@npm:1.0.0" @@ -2865,7 +3285,7 @@ __metadata: languageName: node linkType: hard -"@expo/spawn-async@npm:^1.5.0": +"@expo/spawn-async@npm:^1.5.0, @expo/spawn-async@npm:^1.7.2": version: 1.7.2 resolution: "@expo/spawn-async@npm:1.7.2" dependencies: @@ -2874,6 +3294,29 @@ __metadata: languageName: node linkType: hard +"@expo/vector-icons@npm:^14.0.3": + version: 14.0.3 + resolution: "@expo/vector-icons@npm:14.0.3" + dependencies: + prop-types: "npm:^15.8.1" + checksum: 10c0/220b3729eca07f7a5a5dc9d11b3f496e255393e15a18a6f05e54288a28d2f976d76ce5f30b1b62ddc64e5ed1e258b70590d48f10715202bb90257ee3d7be518b + languageName: node + linkType: hard + +"@expo/xcpretty@npm:^4.3.0": + version: 4.3.1 + resolution: "@expo/xcpretty@npm:4.3.1" + dependencies: + "@babel/code-frame": "npm:7.10.4" + chalk: "npm:^4.1.0" + find-up: "npm:^5.0.0" + js-yaml: "npm:^4.1.0" + bin: + excpretty: build/cli.js + checksum: 10c0/f0129afcb693d6a529adc92a546076ee5c65b706b2a27af7182dbe6a40bc3a00824f6c8f8306bf2fa2c8acbc404aa4ab8be82ffe30a5e035140f138717beb4bb + languageName: node + linkType: hard + "@fastify/busboy@npm:^2.0.0": version: 2.1.1 resolution: "@fastify/busboy@npm:2.1.1" @@ -2888,22 +3331,60 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 10c0/0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff +"@floating-ui/core@npm:^1.6.0": + version: 1.6.8 + resolution: "@floating-ui/core@npm:1.6.8" + dependencies: + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10c0/d6985462aeccae7b55a2d3f40571551c8c42bf820ae0a477fc40ef462e33edc4f3f5b7f11b100de77c9b58ecb581670c5c3f46d0af82b5e30aa185c735257eb9 languageName: node linkType: hard -"@gorhom/bottom-sheet@npm:4.6.1": - version: 4.6.1 - resolution: "@gorhom/bottom-sheet@npm:4.6.1" +"@floating-ui/dom@npm:^1.0.0": + version: 1.6.11 + resolution: "@floating-ui/dom@npm:1.6.11" dependencies: - "@gorhom/portal": "npm:1.0.14" - invariant: "npm:^2.2.4" - peerDependencies: - "@types/react": "*" - "@types/react-native": "*" + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.8" + checksum: 10c0/02ef34a75a515543c772880338eea7b66724997bd5ec7cd58d26b50325709d46d480a306b84e7d5509d734434411a4bcf23af5680c2e461e6e6a8bf45d751df8 + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.0.0": + version: 2.1.2 + resolution: "@floating-ui/react-dom@npm:2.1.2" + dependencies: + "@floating-ui/dom": "npm:^1.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/e855131c74e68cab505f7f44f92cd4e2efab1c125796db3116c54c0859323adae4bf697bf292ee83ac77b9335a41ad67852193d7aeace90aa2e1c4a640cafa60 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.8": + version: 0.2.8 + resolution: "@floating-ui/utils@npm:0.2.8" + checksum: 10c0/a8cee5f17406c900e1c3ef63e3ca89b35e7a2ed645418459a73627b93b7377477fc888081011c6cd177cac45ec2b92a6cab018c14ea140519465498dddd2d3f9 + languageName: node + linkType: hard + +"@gar/promisify@npm:^1.1.3": + version: 1.1.3 + resolution: "@gar/promisify@npm:1.1.3" + checksum: 10c0/0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff + languageName: node + linkType: hard + +"@gorhom/bottom-sheet@npm:4.6.1": + version: 4.6.1 + resolution: "@gorhom/bottom-sheet@npm:4.6.1" + dependencies: + "@gorhom/portal": "npm:1.0.14" + invariant: "npm:^2.2.4" + peerDependencies: + "@types/react": "*" + "@types/react-native": "*" react: "*" react-native: "*" react-native-gesture-handler: ">=1.10.1" @@ -2929,7 +3410,7 @@ __metadata: languageName: node linkType: hard -"@graphql-typed-document-node/core@npm:^3.1.1": +"@graphql-typed-document-node/core@npm:^3.1.0, @graphql-typed-document-node/core@npm:^3.1.1": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: @@ -3429,7 +3910,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.5": +"@jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": version: 0.3.5 resolution: "@jridgewell/gen-mapping@npm:0.3.5" dependencies: @@ -4401,6 +4882,472 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/primitive@npm:1.1.0" + checksum: 10c0/1dcc8b5401799416ff8bdb15c7189b4536c193220ad8fd348a48b88f804ee38cec7bd03e2b9641f7da24610e2f61f23a306911ce883af92c4e8c1abac634cb61 + languageName: node + linkType: hard + +"@radix-ui/react-arrow@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-arrow@npm:1.1.0" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/cbe059dfa5a9c1677478d363bb5fd75b0c7a08221d0ac7f8e7b9aec9dbae9754f6a3518218cf63e4ed53df6c36d193c8d2618d03433a37aa0cb7ee77a60a591f + languageName: node + linkType: hard + +"@radix-ui/react-collection@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-collection@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-slot": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/fecb9f0871c827070a8794b39c7379fdc7d0855c4b05804f0b395eef39c37b2c2b6779865d6cb35d3bc74b6b380107bd8b3754d1730a34ea88913e6cd0eb84d4 + languageName: node + linkType: hard + +"@radix-ui/react-compose-refs@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-compose-refs@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/7e18706084397d9458ca3473d8565b10691da06f6499a78edbcc4bd72cde08f62e91120658d17d58c19fc39d6b1dffe0133cc4535c8f5fce470abd478f6107e5 + languageName: node + linkType: hard + +"@radix-ui/react-context-menu@npm:^2.0.1": + version: 2.2.1 + resolution: "@radix-ui/react-context-menu@npm:2.2.1" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-menu": "npm:2.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/722690a42cdf141284788fbaa7e96bbfacb352a7d4a57a886f6cc3649c827db05afa1228fe647425d2c0880b1e542ba78f4771f95ba70617a72e3cb36b334e22 + languageName: node + linkType: hard + +"@radix-ui/react-context@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-context@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/c843980f568cc61b512708863ec84c42a02e0f88359b22ad1c0e290cea3e6d7618eccbd2cd37bd974fadaa7636cbed5bda27553722e61197eb53852eaa34f1bb + languageName: node + linkType: hard + +"@radix-ui/react-direction@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-direction@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/eb07d8cc3ae2388b824e0a11ae0e3b71fb0c49972b506e249cec9f27a5b7ef4305ee668c98b674833c92e842163549a83beb0a197dec1ec65774bdeeb61f932c + languageName: node + linkType: hard + +"@radix-ui/react-dismissable-layer@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.0" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-escape-keydown": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/72967068ab02127b668ecfd0a1863149e2a42d9fd12d3247f51422a41f3d5faa82a147a5b0a8a6ec609eff8fe6baede6fb7d6111f76896656d13567e3ec29ba8 + languageName: node + linkType: hard + +"@radix-ui/react-dropdown-menu@npm:^2.0.1": + version: 2.1.1 + resolution: "@radix-ui/react-dropdown-menu@npm:2.1.1" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-menu": "npm:2.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/b54f1e41ddc8c3709ba2f8a59621138268d0380aca8399450a234997cc2214e4a6acf1a64ab387558ba39c0bd5839995a668bd71781762daac7618a2d71b4082 + languageName: node + linkType: hard + +"@radix-ui/react-focus-guards@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-focus-guards@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/23af9ff17244568db9b2e99ae6e5718747a4b656bf12b1b15b0d3adca407988641a930612eca35a61b7e15d1ce312b3db13ea95999fa31ae641aaaac1e325df8 + languageName: node + linkType: hard + +"@radix-ui/react-focus-scope@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-focus-scope@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/2593d4bbd4a3525624675ec1d5a591a44f015f43f449b99a5a33228159b83f445e8f1c6bc6f9f2011387abaeadd3df406623c08d4e795b7ae509795652a1d069 + languageName: node + linkType: hard + +"@radix-ui/react-id@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-id@npm:1.1.0" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/acf13e29e51ee96336837fc0cfecc306328b20b0e0070f6f0f7aa7a621ded4a1ee5537cfad58456f64bae76caa7f8769231e88dc7dc106197347ee433c275a79 + languageName: node + linkType: hard + +"@radix-ui/react-menu@npm:2.1.1": + version: 2.1.1 + resolution: "@radix-ui/react-menu@npm:2.1.1" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-collection": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-dismissable-layer": "npm:1.1.0" + "@radix-ui/react-focus-guards": "npm:1.1.0" + "@radix-ui/react-focus-scope": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-popper": "npm:1.2.0" + "@radix-ui/react-portal": "npm:1.1.1" + "@radix-ui/react-presence": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-roving-focus": "npm:1.1.0" + "@radix-ui/react-slot": "npm:1.1.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + aria-hidden: "npm:^1.1.1" + react-remove-scroll: "npm:2.5.7" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/2cb11867430276d8db595886ae0e01e67a555676d37e108d5a6c386df23329482115a041b6a4057fad6b855aa423681805c20d1f290fd1502e521e8e55aafb54 + languageName: node + linkType: hard + +"@radix-ui/react-popper@npm:1.2.0": + version: 1.2.0 + resolution: "@radix-ui/react-popper@npm:1.2.0" + dependencies: + "@floating-ui/react-dom": "npm:^2.0.0" + "@radix-ui/react-arrow": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + "@radix-ui/react-use-rect": "npm:1.1.0" + "@radix-ui/react-use-size": "npm:1.1.0" + "@radix-ui/rect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/a78ea534b9822d07153fff0895b6cdf742e7213782b140b3ab94a76df0ca70e6001925aea946e99ca680fc63a7fcca49c1d62e8dc5a2f651692fba3541e180c0 + languageName: node + linkType: hard + +"@radix-ui/react-portal@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-portal@npm:1.1.1" + dependencies: + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/7e7130fcb0d99197322cd97987e1d7279b6c264fb6be3d883cbfcd49267740d83ca17b431e0d98848afd6067a13ee823ca396a8b63ae68f18a728cf70398c830 + languageName: node + linkType: hard + +"@radix-ui/react-presence@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-presence@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/58acb658b15b72991ad7a234ea90995902c470b3a182aa90ad03145cbbeaa40f211700c444bfa14cf47537cbb6b732e1359bc5396182de839bd680843c11bf31 + languageName: node + linkType: hard + +"@radix-ui/react-primitive@npm:2.0.0": + version: 2.0.0 + resolution: "@radix-ui/react-primitive@npm:2.0.0" + dependencies: + "@radix-ui/react-slot": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/00cb6ca499252ca848c299212ba6976171cea7608b10b3f9a9639d6732dea2df1197ba0d97c001a4fdb29313c3e7fc2a490f6245dd3579617a0ffd85ae964fdd + languageName: node + linkType: hard + +"@radix-ui/react-roving-focus@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-roving-focus@npm:1.1.0" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-collection": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.0" + "@radix-ui/react-direction": "npm:1.1.0" + "@radix-ui/react-id": "npm:1.1.0" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/ce367d3033a12d639a8d445d2efa090aa4bc5a78125be568f8c8e4e59f30afd51b585a90031ec18cdba19afbaf1974633dbc0c2c3d2a14d9eb1bfea2ddbe5369 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-slot@npm:1.1.0" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/a2e8bfb70c440506dd84a1a274f9a8bc433cca37ceae275e53552c9122612e3837744d7fc6f113d6ef1a11491aa914f4add71d76de41cb6d4db72547a8e261ae + languageName: node + linkType: hard + +"@radix-ui/react-use-callback-ref@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/e954863f3baa151faf89ac052a5468b42650efca924417470efd1bd254b411a94c69c30de2fdbb90187b38cb984795978e12e30423dc41e4309d93d53b66d819 + languageName: node + linkType: hard + +"@radix-ui/react-use-controllable-state@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-controllable-state@npm:1.1.0" + dependencies: + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2af883b5b25822ac226e60a6bfde647c0123a76345052a90219026059b3f7225844b2c13a9a16fba859c1cda5fb3d057f2a04503f71780e607516492db4eb3a1 + languageName: node + linkType: hard + +"@radix-ui/react-use-escape-keydown@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.0" + dependencies: + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/910fd696e5a0994b0e06b9cb68def8a865f47951a013ec240c77db2a9e1e726105602700ef5e5f01af49f2f18fe0e73164f9a9651021f28538ef8a30d91f3fbb + languageName: node + linkType: hard + +"@radix-ui/react-use-layout-effect@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/9bf87ece1845c038ed95863cfccf9d75f557c2400d606343bab0ab3192b9806b9840e6aa0a0333fdf3e83cf9982632852192f3e68d7d8367bc8c788dfdf8e62b + languageName: node + linkType: hard + +"@radix-ui/react-use-rect@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-rect@npm:1.1.0" + dependencies: + "@radix-ui/rect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/c2e30150ab49e2cec238cda306fd748c3d47fb96dcff69a3b08e1d19108d80bac239d48f1747a25dadca614e3e967267d43b91e60ea59db2befbc7bea913ff84 + languageName: node + linkType: hard + +"@radix-ui/react-use-size@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/react-use-size@npm:1.1.0" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4c8b89037597fdc1824d009e0c941b510c7c6c30f83024cc02c934edd748886786e7d9f36f57323b02ad29833e7fa7e8974d81969b4ab33d8f41661afa4f30a6 + languageName: node + linkType: hard + +"@radix-ui/rect@npm:1.1.0": + version: 1.1.0 + resolution: "@radix-ui/rect@npm:1.1.0" + checksum: 10c0/a26ff7f8708fb5f2f7949baad70a6b2a597d761ee4dd4aadaf1c1a33ea82ea23dfef6ce6366a08310c5d008cdd60b2e626e4ee03fa342bd5f246ddd9d427f6be + languageName: node + linkType: hard + "@rainbow-me/provider@npm:0.1.1": version: 0.1.1 resolution: "@rainbow-me/provider@npm:0.1.1" @@ -4728,13 +5675,13 @@ __metadata: languageName: node linkType: hard -"@react-native-menu/menu@npm:1.0.3": - version: 1.0.3 - resolution: "@react-native-menu/menu@npm:1.0.3" +"@react-native-menu/menu@npm:1.1.3": + version: 1.1.3 + resolution: "@react-native-menu/menu@npm:1.1.3" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/81a6bc6cbc946ff51d1f83b01f39db5a7739448c19272a437fe9149caa5e8a1b61e7a867d0c77767ba3c2aab8a6450e2e218d651a18ad6e9e04d7823413c8732 + checksum: 10c0/870aac440b0a00a6bfd0c0fdd442b359b72bd9761e031b839678e2cdc8360929eb6b63ad744f11516aeff49c43ed30b587ff2ee53c8ff30eda2db530b997fd0e languageName: node linkType: hard @@ -4754,18 +5701,80 @@ __metadata: languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.74.85": +"@react-native/babel-plugin-codegen@npm:0.74.85": + version: 0.74.85 + resolution: "@react-native/babel-plugin-codegen@npm:0.74.85" + dependencies: + "@react-native/codegen": "npm:0.74.85" + checksum: 10c0/dfef5f036368d822652e68011957acba24a3eb86f17d8c44d0eacdbbe9c9d163ae23280677f9f37dea687cf48077c6a93bcdf01456f74665ff91a5cab4737bc9 + languageName: node + linkType: hard + +"@react-native/babel-plugin-codegen@npm:0.74.87": + version: 0.74.87 + resolution: "@react-native/babel-plugin-codegen@npm:0.74.87" + dependencies: + "@react-native/codegen": "npm:0.74.87" + checksum: 10c0/9c299db211c607d6ddfd96577fdda5990909ee833590650f250a6fa01b07c414d240b2637e714a852dab5fba28a4056b42e5e023661eb54743cc4269bbe5e693 + languageName: node + linkType: hard + +"@react-native/babel-preset@npm:0.74.83": + version: 0.74.83 + resolution: "@react-native/babel-preset@npm:0.74.83" + dependencies: + "@babel/core": "npm:^7.20.0" + "@babel/plugin-proposal-async-generator-functions": "npm:^7.0.0" + "@babel/plugin-proposal-class-properties": "npm:^7.18.0" + "@babel/plugin-proposal-export-default-from": "npm:^7.0.0" + "@babel/plugin-proposal-logical-assignment-operators": "npm:^7.18.0" + "@babel/plugin-proposal-nullish-coalescing-operator": "npm:^7.18.0" + "@babel/plugin-proposal-numeric-separator": "npm:^7.0.0" + "@babel/plugin-proposal-object-rest-spread": "npm:^7.20.0" + "@babel/plugin-proposal-optional-catch-binding": "npm:^7.0.0" + "@babel/plugin-proposal-optional-chaining": "npm:^7.20.0" + "@babel/plugin-syntax-dynamic-import": "npm:^7.8.0" + "@babel/plugin-syntax-export-default-from": "npm:^7.0.0" + "@babel/plugin-syntax-flow": "npm:^7.18.0" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.0.0" + "@babel/plugin-syntax-optional-chaining": "npm:^7.0.0" + "@babel/plugin-transform-arrow-functions": "npm:^7.0.0" + "@babel/plugin-transform-async-to-generator": "npm:^7.20.0" + "@babel/plugin-transform-block-scoping": "npm:^7.0.0" + "@babel/plugin-transform-classes": "npm:^7.0.0" + "@babel/plugin-transform-computed-properties": "npm:^7.0.0" + "@babel/plugin-transform-destructuring": "npm:^7.20.0" + "@babel/plugin-transform-flow-strip-types": "npm:^7.20.0" + "@babel/plugin-transform-function-name": "npm:^7.0.0" + "@babel/plugin-transform-literals": "npm:^7.0.0" + "@babel/plugin-transform-modules-commonjs": "npm:^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.0.0" + "@babel/plugin-transform-parameters": "npm:^7.0.0" + "@babel/plugin-transform-private-methods": "npm:^7.22.5" + "@babel/plugin-transform-private-property-in-object": "npm:^7.22.11" + "@babel/plugin-transform-react-display-name": "npm:^7.0.0" + "@babel/plugin-transform-react-jsx": "npm:^7.0.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.0.0" + "@babel/plugin-transform-react-jsx-source": "npm:^7.0.0" + "@babel/plugin-transform-runtime": "npm:^7.0.0" + "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0" + "@babel/plugin-transform-spread": "npm:^7.0.0" + "@babel/plugin-transform-sticky-regex": "npm:^7.0.0" + "@babel/plugin-transform-typescript": "npm:^7.5.0" + "@babel/plugin-transform-unicode-regex": "npm:^7.0.0" + "@babel/template": "npm:^7.0.0" + "@react-native/babel-plugin-codegen": "npm:0.74.83" + babel-plugin-transform-flow-enums: "npm:^0.0.2" + react-refresh: "npm:^0.14.0" + peerDependencies: + "@babel/core": "*" + checksum: 10c0/1bc539fd187f5f6dc564ae6276cd4481b6d261d4409beb9a92c7456e4ffcd22d6a683399ea068350a766ae523f4dc2240cdbcf6ae05a1e3babb4561bd1940fae + languageName: node + linkType: hard + +"@react-native/babel-preset@npm:0.74.85": version: 0.74.85 - resolution: "@react-native/babel-plugin-codegen@npm:0.74.85" - dependencies: - "@react-native/codegen": "npm:0.74.85" - checksum: 10c0/dfef5f036368d822652e68011957acba24a3eb86f17d8c44d0eacdbbe9c9d163ae23280677f9f37dea687cf48077c6a93bcdf01456f74665ff91a5cab4737bc9 - languageName: node - linkType: hard - -"@react-native/babel-preset@npm:0.74.83": - version: 0.74.83 - resolution: "@react-native/babel-preset@npm:0.74.83" + resolution: "@react-native/babel-preset@npm:0.74.85" dependencies: "@babel/core": "npm:^7.20.0" "@babel/plugin-proposal-async-generator-functions": "npm:^7.0.0" @@ -4807,18 +5816,18 @@ __metadata: "@babel/plugin-transform-typescript": "npm:^7.5.0" "@babel/plugin-transform-unicode-regex": "npm:^7.0.0" "@babel/template": "npm:^7.0.0" - "@react-native/babel-plugin-codegen": "npm:0.74.83" + "@react-native/babel-plugin-codegen": "npm:0.74.85" babel-plugin-transform-flow-enums: "npm:^0.0.2" react-refresh: "npm:^0.14.0" peerDependencies: "@babel/core": "*" - checksum: 10c0/1bc539fd187f5f6dc564ae6276cd4481b6d261d4409beb9a92c7456e4ffcd22d6a683399ea068350a766ae523f4dc2240cdbcf6ae05a1e3babb4561bd1940fae + checksum: 10c0/dffab8681ad0ce9f84197e59048005d04f026e47110504870a230d951ea04757e089d1e0cbff3589c945a1748a676a24b6982d4af9b1d36d004c3539a9172a68 languageName: node linkType: hard -"@react-native/babel-preset@npm:0.74.85": - version: 0.74.85 - resolution: "@react-native/babel-preset@npm:0.74.85" +"@react-native/babel-preset@npm:0.74.87": + version: 0.74.87 + resolution: "@react-native/babel-preset@npm:0.74.87" dependencies: "@babel/core": "npm:^7.20.0" "@babel/plugin-proposal-async-generator-functions": "npm:^7.0.0" @@ -4860,12 +5869,12 @@ __metadata: "@babel/plugin-transform-typescript": "npm:^7.5.0" "@babel/plugin-transform-unicode-regex": "npm:^7.0.0" "@babel/template": "npm:^7.0.0" - "@react-native/babel-plugin-codegen": "npm:0.74.85" + "@react-native/babel-plugin-codegen": "npm:0.74.87" babel-plugin-transform-flow-enums: "npm:^0.0.2" react-refresh: "npm:^0.14.0" peerDependencies: "@babel/core": "*" - checksum: 10c0/dffab8681ad0ce9f84197e59048005d04f026e47110504870a230d951ea04757e089d1e0cbff3589c945a1748a676a24b6982d4af9b1d36d004c3539a9172a68 + checksum: 10c0/b8db68da99891eeea3991e74bde06fa07e5668106c6e0dbee03458a58005111004e426fedb750b888a19310037335caace14ef324245d3a7469808b9f7e822f3 languageName: node linkType: hard @@ -4903,6 +5912,23 @@ __metadata: languageName: node linkType: hard +"@react-native/codegen@npm:0.74.87": + version: 0.74.87 + resolution: "@react-native/codegen@npm:0.74.87" + dependencies: + "@babel/parser": "npm:^7.20.0" + glob: "npm:^7.1.1" + hermes-parser: "npm:0.19.1" + invariant: "npm:^2.2.4" + jscodeshift: "npm:^0.14.0" + mkdirp: "npm:^0.5.1" + nullthrows: "npm:^1.1.1" + peerDependencies: + "@babel/preset-env": ^7.1.6 + checksum: 10c0/753f55ab78c05e9eb2cae4e8f01e48f173de2dc75b56edd9003dad31e2bde3eaab592dfce95fa43cb2ab0468d9130d107ce854c1ed2eceb23b94122d897ed4ce + languageName: node + linkType: hard + "@react-native/community-cli-plugin@npm:0.74.85": version: 0.74.85 resolution: "@react-native/community-cli-plugin@npm:0.74.85" @@ -5293,6 +6319,16 @@ __metadata: languageName: node linkType: hard +"@segment/loosely-validate-event@npm:^2.0.0": + version: 2.0.0 + resolution: "@segment/loosely-validate-event@npm:2.0.0" + dependencies: + component-type: "npm:^1.2.1" + join-component: "npm:^1.1.0" + checksum: 10c0/c083c70c5f0a42a2bc5b685f82830b968d01b5b8de2a9a1c362a3952c6bb33ffbdfcf8196c8ce110a5050f78ff9dcf395832eb55687843c80dc77dfe659b0803 + languageName: node + linkType: hard + "@semantic-ui-react/event-stack@npm:^3.1.0": version: 3.1.3 resolution: "@semantic-ui-react/event-stack@npm:3.1.3" @@ -6887,6 +7923,40 @@ __metadata: languageName: node linkType: hard +"@urql/core@npm:2.3.6": + version: 2.3.6 + resolution: "@urql/core@npm:2.3.6" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.0" + wonka: "npm:^4.0.14" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10c0/101ac57a8bd4f6b9747262ed546d236d22aa620585d979832b3d30dccf6a11400e463e72b836d850a7a603404842fca6c39107257f0c456f38605391da8cdab3 + languageName: node + linkType: hard + +"@urql/core@npm:>=2.3.1": + version: 5.0.6 + resolution: "@urql/core@npm:5.0.6" + dependencies: + "@0no-co/graphql.web": "npm:^1.0.5" + wonka: "npm:^6.3.2" + checksum: 10c0/126d008e34d5b1bc5118742d92222002c8c2fd357c905e5a3166ab74220854b9de7eb19d416aba2b24d9ff44ed7f90065868f0ac3ce0dc8d2c3369a4c85f0899 + languageName: node + linkType: hard + +"@urql/exchange-retry@npm:0.3.0": + version: 0.3.0 + resolution: "@urql/exchange-retry@npm:0.3.0" + dependencies: + "@urql/core": "npm:>=2.3.1" + wonka: "npm:^4.0.14" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10c0/6ea0ecbc56de94f228627d06cd084b5d71d605884a68b3f7f03873ac538c290f9500e67938635650edd7f32e53dfa9c4b6e38f5aa8fd48f061b6135c42f3a204 + languageName: node + linkType: hard + "@vibrant/color@npm:^3.2.1-alpha.1": version: 3.2.1-alpha.1 resolution: "@vibrant/color@npm:3.2.1-alpha.1" @@ -7751,6 +8821,7 @@ __metadata: "@babel/plugin-proposal-nullish-coalescing-operator": "npm:7.18.6" "@babel/plugin-proposal-numeric-separator": "npm:7.18.6" "@babel/plugin-proposal-optional-chaining": "npm:7.21.0" + "@babel/plugin-transform-export-namespace-from": "npm:7.24.7" "@babel/plugin-transform-react-inline-elements": "npm:7.18.6" "@babel/plugin-transform-runtime": "npm:7.18.10" "@babel/preset-env": "npm:7.22.0" @@ -7806,7 +8877,7 @@ __metadata: "@react-native-firebase/messaging": "npm:20.1.0" "@react-native-firebase/remote-config": "npm:20.1.0" "@react-native-masked-view/masked-view": "npm:0.3.1" - "@react-native-menu/menu": "npm:1.0.3" + "@react-native-menu/menu": "npm:1.1.3" "@react-native/babel-preset": "npm:0.74.83" "@react-native/metro-config": "npm:0.74.83" "@react-native/typescript-config": "npm:0.74.83" @@ -7896,6 +8967,7 @@ __metadata: ethereumjs-util: "npm:6.2.1" ethereumjs-wallet: "npm:1.0.1" events: "npm:3.3.0" + expo: "npm:^51.0.0" fast-text-encoding: "npm:1.0.4" global: "npm:4.4.0" grapheme-splitter: "npm:1.0.4" @@ -7984,7 +9056,7 @@ __metadata: react-native-reanimated: "npm:3.15.0" react-native-redash: "npm:16.3.0" react-native-restart: "npm:0.0.22" - react-native-safe-area-context: "npm:4.10.1" + react-native-safe-area-context: "npm:4.11.0" react-native-safe-area-view: rainbow-me/react-native-safe-area-view react-native-screen-corner-radius: "npm:0.2.2" react-native-screens: "npm:3.34.0" @@ -8048,6 +9120,7 @@ __metadata: w2t: "npm:3.0.2" webpack: "npm:5.94.0" webpack-cli: "npm:5.1.4" + zeego: "npm:1.10.0" zod: "npm:3.23.8" zustand: "npm:4.5.4" languageName: unknown @@ -8146,7 +9219,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.7, accepts@npm:~1.3.5, accepts@npm:~1.3.7": +"accepts@npm:^1.3.7, accepts@npm:^1.3.8, accepts@npm:~1.3.5, accepts@npm:~1.3.7": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -8324,7 +9397,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -8425,6 +9498,13 @@ __metadata: languageName: node linkType: hard +"any-promise@npm:^1.0.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10c0/60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 + languageName: node + linkType: hard + "anymatch@npm:^3.0.3, anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -8456,6 +9536,13 @@ __metadata: languageName: node linkType: hard +"application-config-path@npm:^0.1.0": + version: 0.1.1 + resolution: "application-config-path@npm:0.1.1" + checksum: 10c0/32afb4d6fd66596119d37bc697353a29e0496ffcceddc60abfc954dbd063d45e4b9875b05ad413d8b0ab05e800bcb0decfb32c8c22fd1e55b5c9fba8936f0e86 + languageName: node + linkType: hard + "aproba@npm:^1.0.3 || ^2.0.0": version: 2.0.0 resolution: "aproba@npm:2.0.0" @@ -8473,6 +9560,13 @@ __metadata: languageName: node linkType: hard +"arg@npm:5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + "argparse@npm:^1.0.7": version: 1.0.10 resolution: "argparse@npm:1.0.10" @@ -8496,6 +9590,15 @@ __metadata: languageName: node linkType: hard +"aria-hidden@npm:^1.1.1": + version: 1.2.4 + resolution: "aria-hidden@npm:1.2.4" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10c0/8abcab2e1432efc4db415e97cb3959649ddf52c8fc815d7384f43f3d3abf56f1c12852575d00df9a8927f421d7e0712652dd5f8db244ea57634344e29ecfc74a + languageName: node + linkType: hard + "arr-diff@npm:^4.0.0": version: 4.0.0 resolution: "arr-diff@npm:4.0.0" @@ -9049,6 +10152,20 @@ __metadata: languageName: node linkType: hard +"babel-plugin-react-compiler@npm:^0.0.0-experimental-592953e-20240517": + version: 0.0.0 + resolution: "babel-plugin-react-compiler@npm:0.0.0" + checksum: 10c0/b7db0bd49dfe28ea8945a72e90a21f1ab8a14e5ed6987a4f8780bbf15e68bb742aa0be45c019084390623a73c39c44dd57964cc71a01093f4f929c09eb5e5e50 + languageName: node + linkType: hard + +"babel-plugin-react-native-web@npm:~0.19.10": + version: 0.19.12 + resolution: "babel-plugin-react-native-web@npm:0.19.12" + checksum: 10c0/5d06fd2e211ee99ac90dd03e99e6bba58a1d967e8de0ff9bd576db5b0e2c723252d35e742635517d5c80ba53dd1e46297894291109278db6bfa322245c247aa5 + languageName: node + linkType: hard + "babel-plugin-rewire@npm:1.2.0": version: 1.2.0 resolution: "babel-plugin-rewire@npm:1.2.0" @@ -9148,6 +10265,24 @@ __metadata: languageName: node linkType: hard +"babel-preset-expo@npm:~11.0.14": + version: 11.0.14 + resolution: "babel-preset-expo@npm:11.0.14" + dependencies: + "@babel/plugin-proposal-decorators": "npm:^7.12.9" + "@babel/plugin-transform-export-namespace-from": "npm:^7.22.11" + "@babel/plugin-transform-object-rest-spread": "npm:^7.12.13" + "@babel/plugin-transform-parameters": "npm:^7.22.15" + "@babel/preset-react": "npm:^7.22.15" + "@babel/preset-typescript": "npm:^7.23.0" + "@react-native/babel-preset": "npm:0.74.87" + babel-plugin-react-compiler: "npm:^0.0.0-experimental-592953e-20240517" + babel-plugin-react-native-web: "npm:~0.19.10" + react-refresh: "npm:^0.14.2" + checksum: 10c0/9d5bb94c21555610c67b7dbe0e592f1ab7f53571dfe72a3ed314768f983a847a9b1dd0efd70e9f172bd68e7dee53d3d012601b8dae27f0593fcdf99c41bcc66f + languageName: node + linkType: hard + "babel-preset-jest@npm:^29.6.3": version: 29.6.3 resolution: "babel-preset-jest@npm:29.6.3" @@ -9286,6 +10421,15 @@ __metadata: languageName: node linkType: hard +"better-opn@npm:~3.0.2": + version: 3.0.2 + resolution: "better-opn@npm:3.0.2" + dependencies: + open: "npm:^8.0.4" + checksum: 10c0/911ef25d44da75aabfd2444ce7a4294a8000ebcac73068c04a60298b0f7c7506b60421aa4cd02ac82502fb42baaff7e4892234b51e6923eded44c5a11185f2f5 + languageName: node + linkType: hard + "big-integer@npm:1.6.36": version: 1.6.36 resolution: "big-integer@npm:1.6.36" @@ -9435,6 +10579,15 @@ __metadata: languageName: node linkType: hard +"bplist-creator@npm:0.0.7": + version: 0.0.7 + resolution: "bplist-creator@npm:0.0.7" + dependencies: + stream-buffers: "npm:~2.2.0" + checksum: 10c0/37044d0070548da6b7c2eeb9c42a5a2b22b3d7eaf4b49e5b4c3ff0cd9f579902b69eb298bda9af2cbe172bc279caf8e4a889771e9e1ee9f412c1ce5afa16d4a9 + languageName: node + linkType: hard + "bplist-creator@npm:0.1.1": version: 0.1.1 resolution: "bplist-creator@npm:0.1.1" @@ -9444,7 +10597,7 @@ __metadata: languageName: node linkType: hard -"bplist-parser@npm:0.3.2": +"bplist-parser@npm:0.3.2, bplist-parser@npm:^0.3.1": version: 0.3.2 resolution: "bplist-parser@npm:0.3.2" dependencies: @@ -9665,7 +10818,7 @@ __metadata: languageName: node linkType: hard -"buffer-alloc@npm:^1.2.0": +"buffer-alloc@npm:^1.1.0, buffer-alloc@npm:^1.2.0": version: 1.2.0 resolution: "buffer-alloc@npm:1.2.0" dependencies: @@ -9734,6 +10887,13 @@ __metadata: languageName: node linkType: hard +"builtins@npm:^1.0.3": + version: 1.0.3 + resolution: "builtins@npm:1.0.3" + checksum: 10c0/493afcc1db0a56d174cc85bebe5ca69144f6fdd0007d6cbe6b2434185314c79d83cb867e492b56aa5cf421b4b8a8135bf96ba4c3ce71994cf3da154d1ea59747 + languageName: node + linkType: hard + "bunyamin@npm:^1.5.2": version: 1.6.3 resolution: "bunyamin@npm:1.6.3" @@ -9852,7 +11012,7 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^18.0.0": +"cacache@npm:^18.0.0, cacache@npm:^18.0.2": version: 18.0.4 resolution: "cacache@npm:18.0.4" dependencies: @@ -10035,7 +11195,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.4.1, chalk@npm:^2.4.2": +"chalk@npm:^2.0.1, chalk@npm:^2.4.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -10070,7 +11230,7 @@ __metadata: languageName: node linkType: hard -"charenc@npm:0.0.2": +"charenc@npm:0.0.2, charenc@npm:~0.0.1": version: 0.0.2 resolution: "charenc@npm:0.0.2" checksum: 10c0/a45ec39363a16799d0f9365c8dd0c78e711415113c6f14787a22462ef451f5013efae8a28f1c058f81fc01f2a6a16955f7a5fd0cd56247ce94a45349c89877d8 @@ -10181,7 +11341,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.2.0, ci-info@npm:^3.7.0": +"ci-info@npm:^3.2.0, ci-info@npm:^3.3.0, ci-info@npm:^3.7.0": version: 3.9.0 resolution: "ci-info@npm:3.9.0" checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a @@ -10284,7 +11444,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -10406,6 +11566,13 @@ __metadata: languageName: node linkType: hard +"clone@npm:^2.1.2": + version: 2.1.2 + resolution: "clone@npm:2.1.2" + checksum: 10c0/ed0601cd0b1606bc7d82ee7175b97e68d1dd9b91fd1250a3617b38d34a095f8ee0431d40a1a611122dcccb4f93295b4fdb94942aa763392b5fe44effa50c2d5e + languageName: node + linkType: hard + "clone@npm:~0.1.9": version: 0.1.19 resolution: "clone@npm:0.1.19" @@ -10567,7 +11734,7 @@ __metadata: languageName: node linkType: hard -"command-exists@npm:^1.2.8": +"command-exists@npm:^1.2.4, command-exists@npm:^1.2.8": version: 1.2.9 resolution: "command-exists@npm:1.2.9" checksum: 10c0/75040240062de46cd6cd43e6b3032a8b0494525c89d3962e280dde665103f8cc304a8b313a5aa541b91da2f5a9af75c5959dc3a77893a2726407a5e9a0234c16 @@ -10595,6 +11762,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + "commander@npm:^5.0.0, commander@npm:^5.1.0": version: 5.1.0 resolution: "commander@npm:5.1.0" @@ -10609,6 +11783,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + "commander@npm:^9.4.1": version: 9.5.0 resolution: "commander@npm:9.5.0" @@ -10630,6 +11811,13 @@ __metadata: languageName: node linkType: hard +"component-type@npm:^1.2.1": + version: 1.2.2 + resolution: "component-type@npm:1.2.2" + checksum: 10c0/02f895362129da1046c8d3939e88ab7a4caa28d3765cc35b43fa3e7bdad5a9ecb9a5782313f61da7cc1a0aca2cc57d3730e59f4faeb06029e235d7784357b235 + languageName: node + linkType: hard + "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -10689,7 +11877,7 @@ __metadata: languageName: node linkType: hard -"connect@npm:^3.6.5": +"connect@npm:^3.6.5, connect@npm:^3.7.0": version: 3.7.0 resolution: "connect@npm:3.7.0" dependencies: @@ -10943,6 +12131,19 @@ __metadata: languageName: node linkType: hard +"cross-spawn@npm:^6.0.0": + version: 6.0.5 + resolution: "cross-spawn@npm:6.0.5" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/e05544722e9d7189b4292c66e42b7abeb21db0d07c91b785f4ae5fefceb1f89e626da2703744657b287e86dcd4af57b54567cef75159957ff7a8a761d9055012 + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -10966,7 +12167,7 @@ __metadata: languageName: node linkType: hard -"crypt@npm:0.0.2": +"crypt@npm:0.0.2, crypt@npm:~0.0.1": version: 0.0.2 resolution: "crypt@npm:0.0.2" checksum: 10c0/adbf263441dd801665d5425f044647533f39f4612544071b1471962209d235042fb703c27eea2795c7c53e1dfc242405173003f83cf4f4761a633d11f9653f18 @@ -10988,6 +12189,20 @@ __metadata: languageName: node linkType: hard +"crypto-random-string@npm:^1.0.0": + version: 1.0.0 + resolution: "crypto-random-string@npm:1.0.0" + checksum: 10c0/0cb4dbbb895656919d1de11ba43829a3527edddb85a9c49c9d4c4eb783d3b03fc9f371cefee62c87082fd8758db2798a52a9cad48a7381826190d3c2cf858e4a + languageName: node + linkType: hard + +"crypto-random-string@npm:^2.0.0": + version: 2.0.0 + resolution: "crypto-random-string@npm:2.0.0" + checksum: 10c0/288589b2484fe787f9e146f56c4be90b940018f17af1b152e4dde12309042ff5a2bf69e949aab8b8ac253948381529cc6f3e5a2427b73643a71ff177fa122b37 + languageName: node + linkType: hard + "css-color-keywords@npm:^1.0.0": version: 1.0.0 resolution: "css-color-keywords@npm:1.0.0" @@ -11190,6 +12405,13 @@ __metadata: languageName: node linkType: hard +"dag-map@npm:~1.0.0": + version: 1.0.2 + resolution: "dag-map@npm:1.0.2" + checksum: 10c0/1b5ee77cbc9caf61178db592ecc8fa8f6905fd4b0571176af74d2fece2332b68c0e9e8275f1c2c76bc1f0c84a9dc973f87233db7a06375bd13254fae9866867f + languageName: node + linkType: hard + "dashify@npm:^2.0.0": version: 2.0.0 resolution: "dashify@npm:2.0.0" @@ -11274,7 +12496,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.2.7": +"debug@npm:^3.1.0, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -11385,6 +12607,16 @@ __metadata: languageName: node linkType: hard +"default-gateway@npm:^4.2.0": + version: 4.2.0 + resolution: "default-gateway@npm:4.2.0" + dependencies: + execa: "npm:^1.0.0" + ip-regex: "npm:^2.1.0" + checksum: 10c0/2f499b3a9a6c995fd2b4c0d2411256b1899c94e7eacdb895be64e25c301fa8bce8fd3f8152e540669bb178c6a355154c2f86ec23d4ff40ff3b8413d2a59cd86d + languageName: node + linkType: hard + "defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" @@ -11414,6 +12646,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 10c0/db6c63864a9d3b7dc9def55d52764968a5af296de87c1b2cc71d8be8142e445208071953649e0386a8cc37cfcf9a2067a47207f1eb9ff250c2a269658fdae422 + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -11460,6 +12699,22 @@ __metadata: languageName: node linkType: hard +"del@npm:^6.0.0": + version: 6.1.1 + resolution: "del@npm:6.1.1" + dependencies: + globby: "npm:^11.0.1" + graceful-fs: "npm:^4.2.4" + is-glob: "npm:^4.0.1" + is-path-cwd: "npm:^2.2.0" + is-path-inside: "npm:^3.0.2" + p-map: "npm:^4.0.0" + rimraf: "npm:^3.0.2" + slash: "npm:^3.0.0" + checksum: 10c0/8a095c5ccade42c867a60252914ae485ec90da243d735d1f63ec1e64c1cfbc2b8810ad69a29ab6326d159d4fddaa2f5bad067808c42072351ec458efff86708f + languageName: node + linkType: hard + "delay@npm:4.4.0": version: 4.4.0 resolution: "delay@npm:4.4.0" @@ -11654,6 +12909,13 @@ __metadata: languageName: node linkType: hard +"detect-node-es@npm:^1.1.0": + version: 1.1.0 + resolution: "detect-node-es@npm:1.1.0" + checksum: 10c0/e562f00de23f10c27d7119e1af0e7388407eb4b06596a25f6d79a360094a109ff285de317f02b090faae093d314cf6e73ac3214f8a5bb3a0def5bece94557fbe + languageName: node + linkType: hard + "detective-amd@npm:^3.0.0": version: 3.1.2 resolution: "detective-amd@npm:3.1.2" @@ -11952,6 +13214,15 @@ __metadata: languageName: node linkType: hard +"dotenv-expand@npm:~11.0.6": + version: 11.0.6 + resolution: "dotenv-expand@npm:11.0.6" + dependencies: + dotenv: "npm:^16.4.4" + checksum: 10c0/e22891ec72cb926d46d9a26290ef77f9cc9ddcba92d2f83d5e6f3a803d1590887be68e25b559415d080053000441b6f63f5b36093a565bb8c5c994b992ae49f2 + languageName: node + linkType: hard + "dotenv@npm:8.2.0": version: 8.2.0 resolution: "dotenv@npm:8.2.0" @@ -11959,6 +13230,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.4, dotenv@npm:~16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + "dotenv@npm:^8.0.0": version: 8.6.0 resolution: "dotenv@npm:8.6.0" @@ -12240,6 +13518,13 @@ __metadata: languageName: node linkType: hard +"env-editor@npm:^0.4.1": + version: 0.4.2 + resolution: "env-editor@npm:0.4.2" + checksum: 10c0/edb33583b0ae5197535905cbcefca424796f6afec799604f7578428ee523245edcd7df48d582fdab67dbcc697ed39070057f512e72f94c91ceefdcb432f5eadb + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -12265,6 +13550,13 @@ __metadata: languageName: node linkType: hard +"eol@npm:^0.9.1": + version: 0.9.1 + resolution: "eol@npm:0.9.1" + checksum: 10c0/5a6654ca1961529429f4eab4473e6d9351969f25baa30de7232e862c6c5f9037fc0ff044a526fe9cdd6ae65bb1b0db7775bf1d4f342f485c10c34b1444bfb7ab + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -13125,6 +14417,28 @@ __metadata: languageName: node linkType: hard +"exec-async@npm:^2.2.0": + version: 2.2.0 + resolution: "exec-async@npm:2.2.0" + checksum: 10c0/9c70693a3d9f53e19cc8ecf26c3b3fc7125bf40051a71cba70d71161d065a6091d3ab1924c56ac1edd68cb98b9fbef29f83e45dcf67ee6b6c4826e0f898ac039 + languageName: node + linkType: hard + +"execa@npm:^1.0.0": + version: 1.0.0 + resolution: "execa@npm:1.0.0" + dependencies: + cross-spawn: "npm:^6.0.0" + get-stream: "npm:^4.0.0" + is-stream: "npm:^1.1.0" + npm-run-path: "npm:^2.0.0" + p-finally: "npm:^1.0.0" + signal-exit: "npm:^3.0.0" + strip-eof: "npm:^1.0.0" + checksum: 10c0/cc71707c9aa4a2552346893ee63198bf70a04b5a1bc4f8a0ef40f1d03c319eae80932c59191f037990d7d102193e83a38ec72115fff814ec2fb3099f3661a590 + languageName: node + linkType: hard + "execa@npm:^4.0.3": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -13248,6 +14562,111 @@ __metadata: languageName: node linkType: hard +"expo-asset@npm:~10.0.10": + version: 10.0.10 + resolution: "expo-asset@npm:10.0.10" + dependencies: + expo-constants: "npm:~16.0.0" + invariant: "npm:^2.2.4" + md5-file: "npm:^3.2.3" + peerDependencies: + expo: "*" + checksum: 10c0/aed3164cee4483e47fa56c8898384769d60ebb3f94553f7ad2a33a8902d73a1379aee3fc51833c8f0a4a59979ed842ba079e52c8e1903104b1ad312ad90fe1d1 + languageName: node + linkType: hard + +"expo-constants@npm:~16.0.0": + version: 16.0.2 + resolution: "expo-constants@npm:16.0.2" + dependencies: + "@expo/config": "npm:~9.0.0" + "@expo/env": "npm:~0.3.0" + peerDependencies: + expo: "*" + checksum: 10c0/3a51ef1d4de7e7a86d8ee7ef7b0e0edb6cbde981e1e09540e5d7a5dfec3327cb805df06f10614ebf87a158b01a274f63eef9f573e87d7cf1040ffd7168c8a5d1 + languageName: node + linkType: hard + +"expo-file-system@npm:~17.0.1": + version: 17.0.1 + resolution: "expo-file-system@npm:17.0.1" + peerDependencies: + expo: "*" + checksum: 10c0/902913301afd11a2d91b1b9bf053924bfb70f868050e6893854052e589afcc3cb09f0d4cb15194313c6d52dbd7d17ec258c86fc9f2303d1d29d1d745aa6c98d5 + languageName: node + linkType: hard + +"expo-font@npm:~12.0.10": + version: 12.0.10 + resolution: "expo-font@npm:12.0.10" + dependencies: + fontfaceobserver: "npm:^2.1.0" + peerDependencies: + expo: "*" + checksum: 10c0/49b7da4c5099f74a3641841e8a684a15b743e0d63bfc60355c7b2cf0a5b33b4321b0657c282126795da5ef53778b4d29a765dc9d08fe395e4bc801662305dee8 + languageName: node + linkType: hard + +"expo-keep-awake@npm:~13.0.2": + version: 13.0.2 + resolution: "expo-keep-awake@npm:13.0.2" + peerDependencies: + expo: "*" + checksum: 10c0/8548e46991739f42456428141b574c9d83ef77f2a79f371b5c6c1b77364759d4a993af8d40c027a904b5870d41165c99e3e4a8fea93316853819ba16fac0d692 + languageName: node + linkType: hard + +"expo-modules-autolinking@npm:1.11.2": + version: 1.11.2 + resolution: "expo-modules-autolinking@npm:1.11.2" + dependencies: + chalk: "npm:^4.1.0" + commander: "npm:^7.2.0" + fast-glob: "npm:^3.2.5" + find-up: "npm:^5.0.0" + fs-extra: "npm:^9.1.0" + require-from-string: "npm:^2.0.2" + resolve-from: "npm:^5.0.0" + bin: + expo-modules-autolinking: bin/expo-modules-autolinking.js + checksum: 10c0/3a0da953bcd6f0db6374056216117c8764c8e8cddd51d9fd30990c09080704ecf1abc05425a352a21ae09255d9c9eae99ce34ca6091d27dcd1eb8f0c510e3578 + languageName: node + linkType: hard + +"expo-modules-core@npm:1.12.24": + version: 1.12.24 + resolution: "expo-modules-core@npm:1.12.24" + dependencies: + invariant: "npm:^2.2.4" + checksum: 10c0/812180ff32f288843592ca38dbebd67878beeb41796a19dd639a2379f0dc728955cff7eabd3e1aedea5ed0c786561334afc41e8fcf206b800c863b8658706d5d + languageName: node + linkType: hard + +"expo@npm:^51.0.0": + version: 51.0.33 + resolution: "expo@npm:51.0.33" + dependencies: + "@babel/runtime": "npm:^7.20.0" + "@expo/cli": "npm:0.18.29" + "@expo/config": "npm:9.0.3" + "@expo/config-plugins": "npm:8.0.8" + "@expo/metro-config": "npm:0.18.11" + "@expo/vector-icons": "npm:^14.0.3" + babel-preset-expo: "npm:~11.0.14" + expo-asset: "npm:~10.0.10" + expo-file-system: "npm:~17.0.1" + expo-font: "npm:~12.0.10" + expo-keep-awake: "npm:~13.0.2" + expo-modules-autolinking: "npm:1.11.2" + expo-modules-core: "npm:1.12.24" + fbemitter: "npm:^3.0.0" + whatwg-url-without-unicode: "npm:8.0.0-3" + bin: + expo: bin/cli + checksum: 10c0/af041b4e52eb02a17e341cbb31b39cf7a829b6bb790557e52b6af62c0ea3e60fa07bd40d386d27351358315ee6ec7d89bb67c2e52756b2e29c79e9cacb165f0f + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -13329,7 +14748,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": +"fast-glob@npm:3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.5, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -13420,6 +14839,22 @@ __metadata: languageName: node linkType: hard +"fbemitter@npm:^3.0.0": + version: 3.0.0 + resolution: "fbemitter@npm:3.0.0" + dependencies: + fbjs: "npm:^3.0.0" + checksum: 10c0/f130dd8e15dc3fc6709a26586b7a589cd994e1d1024b624f2cc8ef1b12401536a94bb30038e68150a24f9ba18863e9a3fe87941ade2c87667bfbd17f4848d5c7 + languageName: node + linkType: hard + +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: 10c0/dfb64116b125a64abecca9e31477b5edb9a2332c5ffe74326fe36e0a72eef7fc8a49b86adf36c2c293078d79f4524f35e80f5e62546395f53fb7c9e69821f54f + languageName: node + linkType: hard + "fbjs@npm:^0.8.9": version: 0.8.18 resolution: "fbjs@npm:0.8.18" @@ -13435,6 +14870,28 @@ __metadata: languageName: node linkType: hard +"fbjs@npm:^3.0.0": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: "npm:^3.1.5" + fbjs-css-vars: "npm:^1.0.0" + loose-envify: "npm:^1.0.0" + object-assign: "npm:^4.1.0" + promise: "npm:^7.1.1" + setimmediate: "npm:^1.0.5" + ua-parser-js: "npm:^1.0.35" + checksum: 10c0/66d0a2fc9a774f9066e35ac2ac4bf1245931d27f3ac287c7d47e6aa1fc152b243c2109743eb8f65341e025621fb51a12038fadb9fd8fda2e3ddae04ebab06f91 + languageName: node + linkType: hard + +"fetch-retry@npm:^4.1.1": + version: 4.1.1 + resolution: "fetch-retry@npm:4.1.1" + checksum: 10c0/f55cdc82d096e8ef92f92218a8379a01d56cc01726a0ac554845eb943758ceca8be2619682678adfbff88ecb4d97269375200af7ca94a726a8195781aa4c2f49 + languageName: node + linkType: hard + "figures@npm:^2.0.0": version: 2.0.0 resolution: "figures@npm:2.0.0" @@ -13605,7 +15062,7 @@ __metadata: languageName: node linkType: hard -"find-yarn-workspace-root@npm:^2.0.0": +"find-yarn-workspace-root@npm:^2.0.0, find-yarn-workspace-root@npm:~2.0.0": version: 2.0.0 resolution: "find-yarn-workspace-root@npm:2.0.0" dependencies: @@ -13700,6 +15157,13 @@ __metadata: languageName: node linkType: hard +"fontfaceobserver@npm:^2.1.0": + version: 2.3.0 + resolution: "fontfaceobserver@npm:2.3.0" + checksum: 10c0/9b539d5021757d3ed73c355bdb839296d6654de473a992aa98993ef46d951f0361545323de68f6d70c5334d7e3e9f409c1ae7a03c168b00cb0f6c5dea6c77bfa + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -13758,7 +15222,7 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^3.0.0": +"form-data@npm:^3.0.0, form-data@npm:^3.0.1": version: 3.0.1 resolution: "form-data@npm:3.0.1" dependencies: @@ -13833,6 +15297,13 @@ __metadata: languageName: node linkType: hard +"freeport-async@npm:2.0.0": + version: 2.0.0 + resolution: "freeport-async@npm:2.0.0" + checksum: 10c0/421828d1a689695b6c8122d310fd8941af99ebe0b5793e3f8d49aa5923ce580b6c4dd6b7470d46983e60839c302f6c793a8541dbab80817396cdde2b04c83c90 + languageName: node + linkType: hard + "fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -13854,6 +15325,18 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:9.0.0": + version: 9.0.0 + resolution: "fs-extra@npm:9.0.0" + dependencies: + at-least-node: "npm:^1.0.0" + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^1.0.0" + checksum: 10c0/c7f8903b5939a585d16c064142929a9ad12d63084009a198da37bd2c49095b938c8f9a88f8378235dafd5312354b6e872c0181f97f820095fb3539c9d5fe6cd0 + languageName: node + linkType: hard + "fs-extra@npm:^0.22.1": version: 0.22.1 resolution: "fs-extra@npm:0.22.1" @@ -13900,7 +15383,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^8.1.0": +"fs-extra@npm:^8.1.0, fs-extra@npm:~8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" dependencies: @@ -13911,7 +15394,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^9.0.0": +"fs-extra@npm:^9.0.0, fs-extra@npm:^9.1.0": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" dependencies: @@ -14076,6 +15559,13 @@ __metadata: languageName: node linkType: hard +"get-nonce@npm:^1.0.0": + version: 1.0.1 + resolution: "get-nonce@npm:1.0.1" + checksum: 10c0/2d7df55279060bf0568549e1ffc9b84bc32a32b7541675ca092dce56317cdd1a59a98dcc4072c9f6a980779440139a3221d7486f52c488e69dc0fd27b1efb162 + languageName: node + linkType: hard + "get-own-enumerable-property-symbols@npm:^3.0.0": version: 3.0.2 resolution: "get-own-enumerable-property-symbols@npm:3.0.2" @@ -14097,6 +15587,22 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^3.2.0": + version: 3.2.0 + resolution: "get-port@npm:3.2.0" + checksum: 10c0/1b6c3fe89074be3753d9ddf3d67126ea351ab9890537fe53fefebc2912d1d66fdc112451bbc76d33ae5ceb6ca70be2a91017944e3ee8fb0814ac9b295bf2a5b8 + languageName: node + linkType: hard + +"get-stream@npm:^4.0.0": + version: 4.1.0 + resolution: "get-stream@npm:4.1.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10c0/294d876f667694a5ca23f0ca2156de67da950433b6fb53024833733975d32582896dbc7f257842d331809979efccf04d5e0b6b75ad4d45744c45f193fd497539 + languageName: node + linkType: hard + "get-stream@npm:^5.0.0": version: 5.2.0 resolution: "get-stream@npm:5.2.0" @@ -14238,7 +15744,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.2": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -14267,7 +15773,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": +"glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -14366,7 +15872,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.1.0": +"globby@npm:^11.0.1, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -14450,6 +15956,17 @@ __metadata: languageName: node linkType: hard +"graphql-tag@npm:^2.10.1": + version: 2.12.6 + resolution: "graphql-tag@npm:2.12.6" + dependencies: + tslib: "npm:^2.1.0" + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10c0/7763a72011bda454ed8ff1a0d82325f43ca6478e4ce4ab8b7910c4c651dd00db553132171c04d80af5d5aebf1ef6a8a9fd53ccfa33b90ddc00aa3d4be6114419 + languageName: node + linkType: hard + "graphql@npm:15.3.0": version: 15.3.0 resolution: "graphql@npm:15.3.0" @@ -14457,6 +15974,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:15.8.0": + version: 15.8.0 + resolution: "graphql@npm:15.8.0" + checksum: 10c0/30cc09b77170a9d1ed68e4c017ec8c5265f69501c96e4f34f8f6613f39a886c96dd9853eac925f212566ed651736334c8fe24ceae6c44e8d7625c95c3009a801 + languageName: node + linkType: hard + "graphviz@npm:0.0.9": version: 0.0.9 resolution: "graphviz@npm:0.0.9" @@ -14819,6 +16343,15 @@ __metadata: languageName: node linkType: hard +"hosted-git-info@npm:^3.0.2": + version: 3.0.8 + resolution: "hosted-git-info@npm:3.0.8" + dependencies: + lru-cache: "npm:^6.0.0" + checksum: 10c0/af1392086ab3ab5576aa81af07be2f93ee1588407af18fd9752eb67502558e6ea0ffdd4be35ac6c8bef12fb9017f6e7705757e21b10b5ce7798da9106c9c0d9d + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -14891,7 +16424,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0": +"https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" dependencies: @@ -15200,6 +16733,16 @@ __metadata: languageName: node linkType: hard +"internal-ip@npm:4.3.0": + version: 4.3.0 + resolution: "internal-ip@npm:4.3.0" + dependencies: + default-gateway: "npm:^4.2.0" + ipaddr.js: "npm:^1.9.0" + checksum: 10c0/c0ad0b95981c8f21a2d4f115212af38c894a6a6d0a2a3cac4d73d1b5beb214fdfce7b5e66f087e8d575977d4df630886914412d1bc9c2678e5870210154ad65b + languageName: node + linkType: hard + "internal-slot@npm:^1.0.7": version: 1.0.7 resolution: "internal-slot@npm:1.0.7" @@ -15260,6 +16803,20 @@ __metadata: languageName: node linkType: hard +"ip-regex@npm:^2.1.0": + version: 2.1.0 + resolution: "ip-regex@npm:2.1.0" + checksum: 10c0/3ce2d8307fa0373ca357eba7504e66e73b8121805fd9eba6a343aeb077c64c30659fa876b11ac7a75635b7529d2ce87723f208a5b9d51571513b5c68c0cc1541 + languageName: node + linkType: hard + +"ipaddr.js@npm:^1.9.0": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + "iron-webcrypto@npm:^1.1.1": version: 1.2.1 resolution: "iron-webcrypto@npm:1.2.1" @@ -15338,7 +16895,7 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^1.0.2, is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.6": +"is-buffer@npm:^1.0.2, is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.1, is-buffer@npm:~1.1.6": version: 1.1.6 resolution: "is-buffer@npm:1.1.6" checksum: 10c0/ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234 @@ -15422,7 +16979,7 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0": +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": version: 2.2.1 resolution: "is-docker@npm:2.2.1" bin: @@ -15456,6 +17013,13 @@ __metadata: languageName: node linkType: hard +"is-extglob@npm:^1.0.0": + version: 1.0.0 + resolution: "is-extglob@npm:1.0.0" + checksum: 10c0/1ce5366d19958f36069a45ca996c1e51ab607f42a01eb0505f0ccffe8f9c91f5bcba6e971605efd8b4d4dfd0111afa3c8df3e1746db5b85b9a8f933f5e7286b7 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -15500,6 +17064,15 @@ __metadata: languageName: node linkType: hard +"is-glob@npm:^2.0.0": + version: 2.0.1 + resolution: "is-glob@npm:2.0.1" + dependencies: + is-extglob: "npm:^1.0.0" + checksum: 10c0/ef156806af0924983325c9218a8b8a838fa50e1a104ed2a11fe94829a5b27c1b05a4c8cf98d96cb3a7fea539c21f14ae2081e1a248f3d5a9eea62f2d4e9f8b0c + languageName: node + linkType: hard + "is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -15534,6 +17107,15 @@ __metadata: languageName: node linkType: hard +"is-invalid-path@npm:^0.1.0": + version: 0.1.0 + resolution: "is-invalid-path@npm:0.1.0" + dependencies: + is-glob: "npm:^2.0.0" + checksum: 10c0/9f7f74825ddcbd70ceb0aca1155d2961f3767a7a0f1351c255d25047cc7dece161b755d0698aaf8f201693d96ea12e04b4afa00ee9b4f8f47ab5ec2adbe96df8 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -15587,6 +17169,20 @@ __metadata: languageName: node linkType: hard +"is-path-cwd@npm:^2.2.0": + version: 2.2.0 + resolution: "is-path-cwd@npm:2.2.0" + checksum: 10c0/afce71533a427a759cd0329301c18950333d7589533c2c90205bd3fdcf7b91eb92d1940493190567a433134d2128ec9325de2fd281e05be1920fbee9edd22e0a + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.2": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 + languageName: node + linkType: hard + "is-plain-obj@npm:^1.1.0": version: 1.1.0 resolution: "is-plain-obj@npm:1.1.0" @@ -15643,7 +17239,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^1.0.1": +"is-stream@npm:^1.0.1, is-stream@npm:^1.1.0": version: 1.1.0 resolution: "is-stream@npm:1.1.0" checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 @@ -15721,6 +17317,15 @@ __metadata: languageName: node linkType: hard +"is-valid-path@npm:^0.1.1": + version: 0.1.1 + resolution: "is-valid-path@npm:0.1.1" + dependencies: + is-invalid-path: "npm:^0.1.0" + checksum: 10c0/05c3533b8d98ac469bec9849e6ee73a07e1f9857e2043c75a9a45d21bae5e11fafb625808d7bd1aaf5cc63e842876c636f9888388a959ee9c33975c7b603c6ba + languageName: node + linkType: hard + "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -16419,6 +18024,13 @@ __metadata: languageName: node linkType: hard +"jimp-compact@npm:0.16.1": + version: 0.16.1 + resolution: "jimp-compact@npm:0.16.1" + checksum: 10c0/2d73bb927d840ce6dc093d089d770eddbb81472635ced7cad1d7c4545d8734aecf5bd3dedf7178a6cfab4d06c9d6cbbf59e5cb274ed99ca11cd4835a6374f16c + languageName: node + linkType: hard + "jiti@npm:^1.21.0": version: 1.21.6 resolution: "jiti@npm:1.21.6" @@ -16448,6 +18060,13 @@ __metadata: languageName: node linkType: hard +"join-component@npm:^1.1.0": + version: 1.1.0 + resolution: "join-component@npm:1.1.0" + checksum: 10c0/7319cb1ca6ffc514d82ac1b965c4e6cd6bf852adec1e7833bd8613e17f4965e78e2653c8de75a1fe51d9a2cae36af3298008df4079cfd903ef3ecbd231fe11c1 + languageName: node + linkType: hard + "jpeg-js@npm:^0.4.2": version: 0.4.4 resolution: "jpeg-js@npm:0.4.4" @@ -16564,7 +18183,7 @@ __metadata: languageName: node linkType: hard -"jsc-safe-url@npm:^0.2.2": +"jsc-safe-url@npm:^0.2.2, jsc-safe-url@npm:^0.2.4": version: 0.2.4 resolution: "jsc-safe-url@npm:0.2.4" checksum: 10c0/429bd645f8a35938f08f5b01c282e5ef55ed8be30a9ca23517b7ca01dcbf84b4b0632042caceab50f8f5c0c1e76816fe3c74de3e59be84da7f89ae1503bd3c68 @@ -16714,6 +18333,22 @@ __metadata: languageName: node linkType: hard +"json-schema-deref-sync@npm:^0.13.0": + version: 0.13.0 + resolution: "json-schema-deref-sync@npm:0.13.0" + dependencies: + clone: "npm:^2.1.2" + dag-map: "npm:~1.0.0" + is-valid-path: "npm:^0.1.1" + lodash: "npm:^4.17.13" + md5: "npm:~2.2.0" + memory-cache: "npm:~0.2.0" + traverse: "npm:~0.6.6" + valid-url: "npm:~1.0.9" + checksum: 10c0/07cc73d85c9ee6f8236444290cfd22ee4199cd6ddc049e329e7ec22103770b34653f95ae87c367aa49ba6551f09e58b649cd588732b67e7a17b3bb9860ecd061 + languageName: node + linkType: hard + "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -17117,6 +18752,96 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-darwin-arm64@npm:1.19.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-darwin-x64@npm:1.19.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.19.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.19.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.19.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.19.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-x64-musl@npm:1.19.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.19.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:~1.19.0": + version: 1.19.0 + resolution: "lightningcss@npm:1.19.0" + dependencies: + detect-libc: "npm:^1.0.3" + lightningcss-darwin-arm64: "npm:1.19.0" + lightningcss-darwin-x64: "npm:1.19.0" + lightningcss-linux-arm-gnueabihf: "npm:1.19.0" + lightningcss-linux-arm64-gnu: "npm:1.19.0" + lightningcss-linux-arm64-musl: "npm:1.19.0" + lightningcss-linux-x64-gnu: "npm:1.19.0" + lightningcss-linux-x64-musl: "npm:1.19.0" + lightningcss-win32-x64-msvc: "npm:1.19.0" + dependenciesMeta: + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/734cb578709d945cf272578fe30c9dec9462dedb24cbfdb80fdf21dd58ca9a7a347e2b11ec80b16c49964c5c7b4180adc2c5db2c93d2360fe27ca707b961b60f + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -17317,13 +19042,22 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.3.0": +"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.15, lodash@npm:^4.17.21, lodash@npm:^4.3.0": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c languageName: node linkType: hard +"log-symbols@npm:^2.2.0": + version: 2.2.0 + resolution: "log-symbols@npm:2.2.0" + dependencies: + chalk: "npm:^2.0.1" + checksum: 10c0/574eb4205f54f0605021aa67ebb372c30ca64e8ddd439efeb8507af83c776dce789e83614e80059014d9e48dcc94c4b60cef2e85f0dc944eea27c799cec62353 + languageName: node + linkType: hard + "log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -17407,6 +19141,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 + languageName: node + linkType: hard + "lru-cache@npm:^7.7.1": version: 7.18.3 resolution: "lru-cache@npm:7.18.3" @@ -17620,6 +19363,17 @@ __metadata: languageName: node linkType: hard +"md5-file@npm:^3.2.3": + version: 3.2.3 + resolution: "md5-file@npm:3.2.3" + dependencies: + buffer-alloc: "npm:^1.1.0" + bin: + md5-file: cli.js + checksum: 10c0/41d2c27534119bea6e7c1b1489290b4a412c256d3f184068753a215fbeb0eeb5d739334e753f997de5d7d104db3118c6ec2f6e50b1ed23d70deacefd098ee560 + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -17642,6 +19396,24 @@ __metadata: languageName: node linkType: hard +"md5@npm:~2.2.0": + version: 2.2.1 + resolution: "md5@npm:2.2.1" + dependencies: + charenc: "npm:~0.0.1" + crypt: "npm:~0.0.1" + is-buffer: "npm:~1.1.1" + checksum: 10c0/e9e7de197a100169f27b956af63ece22348b2d06d40162c8d380d13dcbb7a307c95956857d0cb5ed92059f6448bbdce2d54bc6b922f8e6a36284c303ecc1612d + languageName: node + linkType: hard + +"md5hex@npm:^1.0.0": + version: 1.0.0 + resolution: "md5hex@npm:1.0.0" + checksum: 10c0/cad2569cdbc61c9de1ff2724c7344c695d868579bb21a1ab4cedf3ea5e91fa75d74a861da071ea1ee00a161511104985c30cb08d797bfd7d99f0f8fd14994728 + languageName: node + linkType: hard + "mdn-data@npm:2.0.14": version: 2.0.14 resolution: "mdn-data@npm:2.0.14" @@ -17670,6 +19442,13 @@ __metadata: languageName: node linkType: hard +"memory-cache@npm:~0.2.0": + version: 0.2.0 + resolution: "memory-cache@npm:0.2.0" + checksum: 10c0/d4fe58865dfdc252db18ae152ab6c9d62868cfc42d5e7f6cf30732fcf27f5f1f8d7b179c3b6f26f31a28ab1cc5c3937215c60aa9e8ad7ea8ff35e79f69ef14da + languageName: node + linkType: hard + "memory-fs@npm:^0.5.0": version: 0.5.0 resolution: "memory-fs@npm:0.5.0" @@ -18606,6 +20385,17 @@ __metadata: languageName: node linkType: hard +"mz@npm:^2.7.0": + version: 2.7.0 + resolution: "mz@npm:2.7.0" + dependencies: + any-promise: "npm:^1.0.0" + object-assign: "npm:^4.0.1" + thenify-all: "npm:^1.0.0" + checksum: 10c0/103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 + languageName: node + linkType: hard + "nan@npm:^2.14.0": version: 2.20.0 resolution: "nan@npm:2.20.0" @@ -18717,6 +20507,20 @@ __metadata: languageName: node linkType: hard +"nested-error-stacks@npm:~2.0.1": + version: 2.0.1 + resolution: "nested-error-stacks@npm:2.0.1" + checksum: 10c0/125049632bc3ca2252e994ca07f27d795c0e6decc4077f0f4163348d30d7cb95409ceff6184284c95396aa5ea8ff5010673063db7674058b966b4f0228d4981c + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10c0/95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + "nocache@npm:^3.0.1": version: 3.0.4 resolution: "nocache@npm:3.0.4" @@ -18817,7 +20621,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -18831,7 +20635,7 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1, node-forge@npm:^1.3.1": +"node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" checksum: 10c0/e882819b251a4321f9fc1d67c85d1501d3004b4ee889af822fd07f64de3d1a8e272ff00b689570af0465d65d6bf5074df9c76e900e0aff23e60b847f2a46fbe8 @@ -19015,6 +20819,27 @@ __metadata: languageName: node linkType: hard +"npm-package-arg@npm:^7.0.0": + version: 7.0.0 + resolution: "npm-package-arg@npm:7.0.0" + dependencies: + hosted-git-info: "npm:^3.0.2" + osenv: "npm:^0.1.5" + semver: "npm:^5.6.0" + validate-npm-package-name: "npm:^3.0.0" + checksum: 10c0/2117c3ee2a9449db98c7d2efe92590867fcf68ab143b94a6ff53dee5a0c3343eab8f08a9f73bd6c15acca32f7635ea8b9a97b770ae1631c896a35ca9372a98c8 + languageName: node + linkType: hard + +"npm-run-path@npm:^2.0.0": + version: 2.0.2 + resolution: "npm-run-path@npm:2.0.2" + dependencies: + path-key: "npm:^2.0.0" + checksum: 10c0/95549a477886f48346568c97b08c4fda9cdbf7ce8a4fbc2213f36896d0d19249e32d68d7451bdcbca8041b5fba04a6b2c4a618beaf19849505c05b700740f1de + languageName: node + linkType: hard + "npm-run-path@npm:^4.0.0, npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -19353,6 +21178,17 @@ __metadata: languageName: node linkType: hard +"open@npm:^8.0.4, open@npm:^8.3.0": + version: 8.4.2 + resolution: "open@npm:8.4.2" + dependencies: + define-lazy-prop: "npm:^2.0.0" + is-docker: "npm:^2.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/bb6b3a58401dacdb0aad14360626faf3fb7fba4b77816b373495988b724fb48941cad80c1b65d62bb31a17609b2cd91c41a181602caea597ca80dfbcc27e84c9 + languageName: node + linkType: hard + "opencollective-postinstall@npm:^2.0.2, opencollective-postinstall@npm:^2.0.3": version: 2.0.3 resolution: "opencollective-postinstall@npm:2.0.3" @@ -19403,6 +21239,20 @@ __metadata: languageName: node linkType: hard +"ora@npm:3.4.0, ora@npm:^3.4.0": + version: 3.4.0 + resolution: "ora@npm:3.4.0" + dependencies: + chalk: "npm:^2.4.2" + cli-cursor: "npm:^2.1.0" + cli-spinners: "npm:^2.0.0" + log-symbols: "npm:^2.2.0" + strip-ansi: "npm:^5.2.0" + wcwidth: "npm:^1.0.1" + checksum: 10c0/04cb375f222c36a16a95e6c39c473644a99a42fc34d35c37507cb836ea0a71f4d831fcd53198a460869114b2730891d63cc1047304afe5ddb078974d468edfb1 + languageName: node + linkType: hard + "ora@npm:^5.1.0, ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" @@ -19420,13 +21270,30 @@ __metadata: languageName: node linkType: hard -"os-tmpdir@npm:~1.0.2": +"os-homedir@npm:^1.0.0": + version: 1.0.2 + resolution: "os-homedir@npm:1.0.2" + checksum: 10c0/6be4aa67317ee247b8d46142e243fb4ef1d2d65d3067f54bfc5079257a2f4d4d76b2da78cba7af3cb3f56dbb2e4202e0c47f26171d11ca1ed4008d842c90363f + languageName: node + linkType: hard + +"os-tmpdir@npm:^1.0.0, os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 languageName: node linkType: hard +"osenv@npm:^0.1.5": + version: 0.1.5 + resolution: "osenv@npm:0.1.5" + dependencies: + os-homedir: "npm:^1.0.0" + os-tmpdir: "npm:^1.0.0" + checksum: 10c0/b33ed4b77e662f3ee2a04bf4b56cad2107ab069dee982feb9e39ad44feb9aa0cf1016b9ac6e05d0d84c91fa496798fe48dd05a33175d624e51668068b9805302 + languageName: node + linkType: hard + "output-file-sync@npm:^2.0.1": version: 2.0.1 resolution: "output-file-sync@npm:2.0.1" @@ -19438,6 +21305,13 @@ __metadata: languageName: node linkType: hard +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + "p-limit@npm:^1.1.0": version: 1.3.0 resolution: "p-limit@npm:1.3.0" @@ -19658,6 +21532,15 @@ __metadata: languageName: node linkType: hard +"parse-png@npm:^2.1.0": + version: 2.1.0 + resolution: "parse-png@npm:2.1.0" + dependencies: + pngjs: "npm:^3.3.0" + checksum: 10c0/5157a8bbb976ae1ca990fc53c7014d42aac0967cb30e2daf36c3fef1876c8db0d551e695400c904f33c5c5add76a572c65b5044721d62417d8cc7abe4c4ffa41 + languageName: node + linkType: hard + "parse-svg-path@npm:^0.1.2": version: 0.1.2 resolution: "parse-svg-path@npm:0.1.2" @@ -19679,6 +21562,16 @@ __metadata: languageName: node linkType: hard +"password-prompt@npm:^1.0.4": + version: 1.1.3 + resolution: "password-prompt@npm:1.1.3" + dependencies: + ansi-escapes: "npm:^4.3.2" + cross-spawn: "npm:^7.0.3" + checksum: 10c0/f6c2ec49e8bb91a421ed42809c00f8c1d09ee7ea8454c05a40150ec3c47e67b1f16eea7bceace13451accb7bb85859ee3e8d67e8fa3a85f622ba36ebe681ee51 + languageName: node + linkType: hard + "patch-package@npm:8.0.0": version: 8.0.0 resolution: "patch-package@npm:8.0.0" @@ -19739,6 +21632,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^2.0.0, path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10c0/dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -19753,7 +21653,7 @@ __metadata: languageName: node linkType: hard -"path-parse@npm:^1.0.6, path-parse@npm:^1.0.7": +"path-parse@npm:^1.0.5, path-parse@npm:^1.0.6, path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 @@ -19859,6 +21759,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.0": + version: 1.1.0 + resolution: "picocolors@npm:1.1.0" + checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -19866,6 +21773,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^3.0.1": + version: 3.0.1 + resolution: "picomatch@npm:3.0.1" + checksum: 10c0/70ec738569f1864658378b7abdab8939d15dae0718c1df994eae3346fd33daf6a3c1ff4e0c1a0cd1e2c0319130985b63a2cff34d192f2f2acbb78aca76111736 + languageName: node + linkType: hard + "pify@npm:^4.0.1": version: 4.0.1 resolution: "pify@npm:4.0.1" @@ -19927,7 +21841,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.4, pirates@npm:^4.0.6": +"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": version: 4.0.6 resolution: "pirates@npm:4.0.6" checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 @@ -20096,6 +22010,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:~8.4.32": + version: 8.4.47 + resolution: "postcss@npm:8.4.47" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.1.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/929f68b5081b7202709456532cee2a145c1843d391508c5a09de2517e8c4791638f71dd63b1898dba6712f8839d7a6da046c72a5e44c162e908f5911f57b5f44 + languageName: node + linkType: hard + "prebuild-install@npm:^7.1.1": version: 7.1.2 resolution: "prebuild-install@npm:7.1.2" @@ -20175,6 +22100,13 @@ __metadata: languageName: node linkType: hard +"pretty-bytes@npm:5.6.0": + version: 5.6.0 + resolution: "pretty-bytes@npm:5.6.0" + checksum: 10c0/f69f494dcc1adda98dbe0e4a36d301e8be8ff99bfde7a637b2ee2820e7cb583b0fc0f3a63b0e3752c01501185a5cf38602c7be60da41bdf84ef5b70e89c370f3 + languageName: node + linkType: hard + "pretty-format@npm:^26.5.2, pretty-format@npm:^26.6.2": version: 26.6.2 resolution: "pretty-format@npm:26.6.2" @@ -20242,7 +22174,7 @@ __metadata: languageName: node linkType: hard -"progress@npm:^2.0.0, progress@npm:^2.0.3": +"progress@npm:2.0.3, progress@npm:^2.0.0, progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" checksum: 10c0/1697e07cb1068055dbe9fe858d242368ff5d2073639e652b75a7eb1f2a1a8d4afd404d719de23c7b48481a6aa0040686310e2dac2f53d776daa2176d3f96369c @@ -20291,7 +22223,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1, prompts@npm:^2.4.2": +"prompts@npm:^2.0.1, prompts@npm:^2.3.2, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -20419,6 +22351,15 @@ __metadata: languageName: node linkType: hard +"qrcode-terminal@npm:0.11.0": + version: 0.11.0 + resolution: "qrcode-terminal@npm:0.11.0" + bin: + qrcode-terminal: ./bin/qrcode-terminal.js + checksum: 10c0/7561a649d21d7672d451ada5f2a2b393f586627cea75670c97141dc2b4b4145db547e1fddf512a3552e7fb54de530d513a736cd604c840adb908ed03c32312ad + languageName: node + linkType: hard + "qrcode@npm:1.4.4": version: 1.4.4 resolution: "qrcode@npm:1.4.4" @@ -20590,7 +22531,7 @@ __metadata: languageName: node linkType: hard -"rc@npm:^1.2.7": +"rc@npm:^1.2.7, rc@npm:~1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8" dependencies: @@ -21181,13 +23122,13 @@ __metadata: languageName: node linkType: hard -"react-native-safe-area-context@npm:4.10.1": - version: 4.10.1 - resolution: "react-native-safe-area-context@npm:4.10.1" +"react-native-safe-area-context@npm:4.11.0": + version: 4.11.0 + resolution: "react-native-safe-area-context@npm:4.11.0" peerDependencies: react: "*" react-native: "*" - checksum: 10c0/4d9210f68535d90200a03e18015053f1bd72d6cb562866716d7a206e64956bd0d5fb022788f1882144193c554866e04a0a68c90600ca8c982ab3ab546a68c198 + checksum: 10c0/cf50358c1ff0e50d496ba9a76f4823ac5329f0b804f67f605e5b12d0281d07f16a329a4820e7d567d6afb77cc989dfff953ab48ee63cbcaa35d6fb600cbb4325 languageName: node linkType: hard @@ -21586,13 +23527,48 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"react-refresh@npm:^0.14.0": +"react-refresh@npm:^0.14.0, react-refresh@npm:^0.14.2": version: 0.14.2 resolution: "react-refresh@npm:0.14.2" checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb languageName: node linkType: hard +"react-remove-scroll-bar@npm:^2.3.4": + version: 2.3.6 + resolution: "react-remove-scroll-bar@npm:2.3.6" + dependencies: + react-style-singleton: "npm:^2.2.1" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4e32ee04bf655a8bd3b4aacf6ffc596ae9eb1b9ba27eef83f7002632ee75371f61516ae62250634a9eae4b2c8fc6f6982d9b182de260f6c11841841e6e2e7515 + languageName: node + linkType: hard + +"react-remove-scroll@npm:2.5.7": + version: 2.5.7 + resolution: "react-remove-scroll@npm:2.5.7" + dependencies: + react-remove-scroll-bar: "npm:^2.3.4" + react-style-singleton: "npm:^2.2.1" + tslib: "npm:^2.1.0" + use-callback-ref: "npm:^1.3.0" + use-sidecar: "npm:^1.1.2" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/dcd523ada602bd0a839c2032cadf0b3e4af55ee85acefee3760976a9cceaa4606927801b093bbb8bf3c2989c71e048f5428c2c6eb9e6681762e86356833d039b + languageName: node + linkType: hard + "react-shallow-renderer@npm:^16.15.0": version: 16.15.0 resolution: "react-shallow-renderer@npm:16.15.0" @@ -21614,6 +23590,23 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"react-style-singleton@npm:^2.2.1": + version: 2.2.1 + resolution: "react-style-singleton@npm:2.2.1" + dependencies: + get-nonce: "npm:^1.0.0" + invariant: "npm:^2.2.4" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/6d66f3bdb65e1ec79089f80314da97c9a005087a04ee034255a5de129a4c0d9fd0bf99fa7bf642781ac2dc745ca687aae3de082bd8afdd0d117bc953241e15ad + languageName: node + linkType: hard + "react-test-renderer@npm:18.2.0": version: 18.2.0 resolution: "react-test-renderer@npm:18.2.0" @@ -21998,6 +23991,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"remove-trailing-slash@npm:^0.1.0": + version: 0.1.1 + resolution: "remove-trailing-slash@npm:0.1.1" + checksum: 10c0/6fa91e7b89e0675fdca6ce54af5fad9bd612d51e2251913a2e113b521b157647f1f8c694b55447780b489b30a63ebe949ccda7411ef383d09136bb27121c6c09 + languageName: node + linkType: hard + "repeat-element@npm:^1.1.2": version: 1.1.4 resolution: "repeat-element@npm:1.1.4" @@ -22040,6 +24040,17 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"requireg@npm:^0.2.2": + version: 0.2.2 + resolution: "requireg@npm:0.2.2" + dependencies: + nested-error-stacks: "npm:~2.0.1" + rc: "npm:~1.2.7" + resolve: "npm:~1.7.1" + checksum: 10c0/806cff08d8fa63f2ec9c74fa9602c86b56627a824d0a188bf777c8d82ba012a1b3c01ab6e88ffcf610713b6bc5ec8a9f9e55dc941b7606ce735e72c4d9daa059 + languageName: node + linkType: hard + "requirejs-config-file@npm:^3.1.1": version: 3.1.2 resolution: "requirejs-config-file@npm:3.1.2" @@ -22143,7 +24154,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve.exports@npm:^2.0.0": +"resolve.exports@npm:^2.0.0, resolve.exports@npm:^2.0.2": version: 2.0.2 resolution: "resolve.exports@npm:2.0.2" checksum: 10c0/cc4cffdc25447cf34730f388dca5021156ba9302a3bad3d7f168e790dc74b2827dff603f1bc6ad3d299bac269828dca96dd77e036dc9fba6a2a1807c47ab5c98 @@ -22159,7 +24170,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.2, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -22185,6 +24196,15 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"resolve@npm:~1.7.1": + version: 1.7.1 + resolution: "resolve@npm:1.7.1" + dependencies: + path-parse: "npm:^1.0.5" + checksum: 10c0/6e9e29185ac57801aff013849e9717c769ef0a27eac30b6492405ba3d61db73d8967023b96578f4b2deba4ef5fb11fc4f0a4db47c0f536890ced5c014e94fbde + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A1.17.0#optional!builtin": version: 1.17.0 resolution: "resolve@patch:resolve@npm%3A1.17.0#optional!builtin::version=1.17.0&hash=c3c19d" @@ -22194,7 +24214,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -22220,6 +24240,15 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A~1.7.1#optional!builtin": + version: 1.7.1 + resolution: "resolve@patch:resolve@npm%3A1.7.1#optional!builtin::version=1.7.1&hash=3bafbf" + dependencies: + path-parse: "npm:^1.0.5" + checksum: 10c0/1301dba7c12cd9dab2ab4eee8518089f25bb7480db34b746a923ded472c4c0600ebb1ba9b8028ca843f7c6017ac76524355800c52b82633e53bd601ca288b4de + languageName: node + linkType: hard + "restore-cursor@npm:^2.0.0": version: 2.0.0 resolution: "restore-cursor@npm:2.0.0" @@ -22653,7 +24682,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": +"semver@npm:^7.0.0, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.3 resolution: "semver@npm:7.6.3" bin: @@ -22692,6 +24721,27 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"send@npm:^0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a + languageName: node + linkType: hard + "serialize-error@npm:^2.1.0": version: 2.1.0 resolution: "serialize-error@npm:2.1.0" @@ -22788,6 +24838,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"sf-symbols-typescript@npm:^2.0.0": + version: 2.0.0 + resolution: "sf-symbols-typescript@npm:2.0.0" + checksum: 10c0/c891b59714657300367c9f22b9ec94e12fedb2eb55c266be252700160d9bf6f74a8edf697a42bf0992588f54994f9fd2051fa6c6ce48f04c519b0c3c242a3cc5 + languageName: node + linkType: hard + "sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": version: 2.4.11 resolution: "sha.js@npm:2.4.11" @@ -22865,6 +24922,15 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10c0/7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -22874,6 +24940,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10c0/9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + "shebang-regex@npm:^3.0.0": version: 3.0.0 resolution: "shebang-regex@npm:3.0.0" @@ -22900,7 +24973,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -23020,7 +25093,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"slugify@npm:^1.6.6": +"slugify@npm:^1.3.4, slugify@npm:^1.6.6": version: 1.6.6 resolution: "slugify@npm:1.6.6" checksum: 10c0/e7e63f08f389a371d6228bc19d64ec84360bf0a538333446cc49dbbf3971751a6d180d2f31551188dd007a65ca771e69f574e0283290a7825a818e90b75ef44d @@ -23166,6 +25239,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + "source-map-resolve@npm:^0.5.0": version: 0.5.3 resolution: "source-map-resolve@npm:0.5.3" @@ -23189,7 +25269,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"source-map-support@npm:^0.5.13, source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.13, source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -23371,7 +25451,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"stream-buffers@npm:2.2.x": +"stream-buffers@npm:2.2.x, stream-buffers@npm:~2.2.0": version: 2.2.0 resolution: "stream-buffers@npm:2.2.0" checksum: 10c0/14a351f0a066eaa08c8c64a74f4aedd87dd7a8e59d4be224703da33dca3eb370828ee6c0ae3fff59a9c743e8098728fc95c5f052ae7741672a31e6b1430ba50a @@ -23656,6 +25736,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"strip-eof@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-eof@npm:1.0.0" + checksum: 10c0/f336beed8622f7c1dd02f2cbd8422da9208fae81daf184f73656332899978919d5c0ca84dc6cfc49ad1fc4dd7badcde5412a063cf4e0d7f8ed95a13a63f68f45 + languageName: node + linkType: hard + "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -23710,6 +25797,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"structured-headers@npm:^0.4.1": + version: 0.4.1 + resolution: "structured-headers@npm:0.4.1" + checksum: 10c0/b7d326f6fec7e7f7901d1e0542577293b5d029bf3e1fb84995e33d9aabe47d03259f64ca2d778ef5c427f6f00c78bafa051b6f233131e1556f8bb9102b11ed64 + languageName: node + linkType: hard + "style-value-types@npm:5.0.0": version: 5.0.0 resolution: "style-value-types@npm:5.0.0" @@ -23754,6 +25848,38 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"sucrase@npm:3.34.0": + version: 3.34.0 + resolution: "sucrase@npm:3.34.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + glob: "npm:7.1.6" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 10c0/83e524f2b9386c7029fc9e46b8d608485866d08bea5a0a71e9e3442dc12e1d05a5ab555808d1922f45dd012fc71043479d778aac07391d9740daabe45730a056 + languageName: node + linkType: hard + +"sudo-prompt@npm:9.1.1": + version: 9.1.1 + resolution: "sudo-prompt@npm:9.1.1" + checksum: 10c0/0416b255ce760ad61d828b87da32a15a5a49cfe0f674031e4f0b479e0ac28a43af2bed05a95a9ac2a830f82b3fc803f865ac3ae8b5837d3dd36e22c4aced87e3 + languageName: node + linkType: hard + +"sudo-prompt@npm:^8.2.0": + version: 8.2.5 + resolution: "sudo-prompt@npm:8.2.5" + checksum: 10c0/c17bcc852112c11addcdfb8630fad1a236823887959f133ba47de9fcec22c03aafc49bbb35184ab7f13a78e625fb8324b86d5aa21b73b15d601aee71fb9b976e + languageName: node + linkType: hard + "sudo-prompt@npm:^9.0.0": version: 9.2.1 resolution: "sudo-prompt@npm:9.2.1" @@ -23796,7 +25922,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -23814,6 +25940,16 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"supports-hyperlinks@npm:^2.0.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10c0/4057f0d86afb056cd799602f72d575b8fdd79001c5894bcb691176f14e870a687e7981e50bc1484980e8b688c6d5bcd4931e1609816abb5a7dc1486b7babf6a1 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -23962,7 +26098,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.1": +"tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -24025,6 +26161,40 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"tempy@npm:0.3.0": + version: 0.3.0 + resolution: "tempy@npm:0.3.0" + dependencies: + temp-dir: "npm:^1.0.0" + type-fest: "npm:^0.3.1" + unique-string: "npm:^1.0.0" + checksum: 10c0/9432dc82569ab0f34f23aab19ab277c58c7fcf12f903483436e9e1ee72b6b5be2189da31e351eecc69a0f98f6f2003d524cdbc50e67ee7202edf3675f9b0c2c0 + languageName: node + linkType: hard + +"tempy@npm:^0.7.1": + version: 0.7.1 + resolution: "tempy@npm:0.7.1" + dependencies: + del: "npm:^6.0.0" + is-stream: "npm:^2.0.0" + temp-dir: "npm:^2.0.0" + type-fest: "npm:^0.16.0" + unique-string: "npm:^2.0.0" + checksum: 10c0/f93764c9c236ade74037b5989799930687d8618fb9ce6040d3f2a82b0ae60f655cc07bad883a0ba55dc13dc56af2f92d8e8a534a9eff78f4ac79a19d65f7dadd + languageName: node + linkType: hard + +"terminal-link@npm:^2.1.1": + version: 2.1.1 + resolution: "terminal-link@npm:2.1.1" + dependencies: + ansi-escapes: "npm:^4.2.1" + supports-hyperlinks: "npm:^2.0.0" + checksum: 10c0/947458a5cd5408d2ffcdb14aee50bec8fb5022ae683b896b2f08ed6db7b2e7d42780d5c8b51e930e9c322bd7c7a517f4fa7c76983d0873c83245885ac5ee13e3 + languageName: node + linkType: hard + "terminal-table@npm:^0.0.12": version: 0.0.12 resolution: "terminal-table@npm:0.0.12" @@ -24107,6 +26277,24 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"thenify-all@npm:^1.0.0": + version: 1.6.0 + resolution: "thenify-all@npm:1.6.0" + dependencies: + thenify: "npm:>= 3.1.0 < 4" + checksum: 10c0/9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b + languageName: node + linkType: hard + +"thenify@npm:>= 3.1.0 < 4": + version: 3.3.1 + resolution: "thenify@npm:3.3.1" + dependencies: + any-promise: "npm:^1.0.0" + checksum: 10c0/f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 + languageName: node + linkType: hard + "thread-stream@npm:^0.15.1": version: 0.15.2 resolution: "thread-stream@npm:0.15.2" @@ -24287,6 +26475,17 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"traverse@npm:~0.6.6": + version: 0.6.10 + resolution: "traverse@npm:0.6.10" + dependencies: + gopd: "npm:^1.0.1" + typedarray.prototype.slice: "npm:^1.0.3" + which-typed-array: "npm:^1.1.15" + checksum: 10c0/d37619cd650dda26fc9f8c3c55087098e702abc1a91e57a5701644f3aee67a5c61daf47ca883ebe6777ea810424317bd142b8e90ee0d9dc9171bd19df6cf6fd8 + languageName: node + linkType: hard + "truncate-utf8-bytes@npm:^1.0.0": version: 1.0.2 resolution: "truncate-utf8-bytes@npm:1.0.2" @@ -24303,6 +26502,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + "ts-jest@npm:29.1.1": version: 29.1.1 resolution: "ts-jest@npm:29.1.1" @@ -24432,6 +26638,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"tslib@npm:^2.0.0, tslib@npm:^2.4.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 + languageName: node + linkType: hard + "tsort@npm:0.0.1": version: 0.0.1 resolution: "tsort@npm:0.0.1" @@ -24511,6 +26724,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"type-fest@npm:^0.16.0": + version: 0.16.0 + resolution: "type-fest@npm:0.16.0" + checksum: 10c0/6b4d846534e7bcb49a6160b068ffaed2b62570d989d909ac3f29df5ef1e993859f890a4242eebe023c9e923f96adbcb3b3e88a198c35a1ee9a731e147a6839c3 + languageName: node + linkType: hard + "type-fest@npm:^0.20.2": version: 0.20.2 resolution: "type-fest@npm:0.20.2" @@ -24525,6 +26745,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"type-fest@npm:^0.3.1": + version: 0.3.1 + resolution: "type-fest@npm:0.3.1" + checksum: 10c0/ef632e9549f331024594bbb8b620fe570d90abd8e7f2892d4aff733fd72698774e1a88e277fac02b4267de17d79cbb87860332f64f387145532b13ace6510502 + languageName: node + linkType: hard + "type-fest@npm:^0.7.1": version: 0.7.1 resolution: "type-fest@npm:0.7.1" @@ -24600,6 +26827,20 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"typedarray.prototype.slice@npm:^1.0.3": + version: 1.0.3 + resolution: "typedarray.prototype.slice@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.0" + es-errors: "npm:^1.3.0" + typed-array-buffer: "npm:^1.0.2" + typed-array-byte-offset: "npm:^1.0.2" + checksum: 10c0/6ac110a8b58a1ccb086242f09d1ce9c7ba2885924e816364a7879083b983d4096f19aab6f9aa8c0ce5ddd3d8ae3f3ba5581e10fa6838880f296a0c54c26f424b + languageName: node + linkType: hard + "typedarray@npm:^0.0.6": version: 0.0.6 resolution: "typedarray@npm:0.0.6" @@ -24694,6 +26935,15 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.39 + resolution: "ua-parser-js@npm:1.0.39" + bin: + ua-parser-js: script/cli.js + checksum: 10c0/c6452b0c683000f10975cb0a7e74cb1119ea95d4522ae85f396fa53b0b17884358a24ffdd86a66030c6b2981bdc502109a618c79fdaa217ee9032c9e46fcc78a + languageName: node + linkType: hard + "uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": version: 1.0.6 resolution: "uc.micro@npm:1.0.6" @@ -24860,6 +27110,24 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"unique-string@npm:^1.0.0": + version: 1.0.0 + resolution: "unique-string@npm:1.0.0" + dependencies: + crypto-random-string: "npm:^1.0.0" + checksum: 10c0/79cc2a6515a51e6350c74f65c92246511966c47528f1119318cbe8d68a508842f4e5a2a81857a65f3919629397a525f820505116dd89cac425294598e35ca12c + languageName: node + linkType: hard + +"unique-string@npm:^2.0.0": + version: 2.0.0 + resolution: "unique-string@npm:2.0.0" + dependencies: + crypto-random-string: "npm:^2.0.0" + checksum: 10c0/11820db0a4ba069d174bedfa96c588fc2c96b083066fafa186851e563951d0de78181ac79c744c1ed28b51f9d82ac5b8196ff3e4560d0178046ef455d8c2244b + languageName: node + linkType: hard + "universalify@npm:^0.1.0": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -24867,6 +27135,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"universalify@npm:^1.0.0": + version: 1.0.0 + resolution: "universalify@npm:1.0.0" + checksum: 10c0/735dd9c118f96a13c7810212ef8b45e239e2fe6bf65aceefbc2826334fcfe8c523dbbf1458cef011563c51505e3a367dff7654cfb0cec5b6aa710ef120843396 + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.1 resolution: "universalify@npm:2.0.1" @@ -25018,6 +27293,13 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"url-join@npm:4.0.0": + version: 4.0.0 + resolution: "url-join@npm:4.0.0" + checksum: 10c0/1aa466cfa128adab76dc9e559b38e2171df51e6105b5773382c3726e5a29971da013e4f9f5c36f1414ef1e5f1af535cfaf29611b53b0d2fc4f311f7b41199d13 + languageName: node + linkType: hard + "url-join@npm:4.0.1": version: 4.0.1 resolution: "url-join@npm:4.0.1" @@ -25055,6 +27337,21 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"use-callback-ref@npm:^1.3.0": + version: 1.3.2 + resolution: "use-callback-ref@npm:1.3.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d232c37160fe3970c99255da19b5fb5299fb5926a5d6141d928a87feb47732c323d29be2f8137d3b1e5499c70d284cd1d9cfad703cc58179db8be24d7dd8f1f2 + languageName: node + linkType: hard + "use-debounce@npm:10.0.0": version: 10.0.0 resolution: "use-debounce@npm:10.0.0" @@ -25102,6 +27399,22 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"use-sidecar@npm:^1.1.2": + version: 1.1.2 + resolution: "use-sidecar@npm:1.1.2" + dependencies: + detect-node-es: "npm:^1.1.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/89f0018fd9aee1fc17c85ac18c4bf8944d460d453d0d0e04ddbc8eaddf3fa591e9c74a1f8a438a1bff368a7a2417fab380bdb3df899d2194c4375b0982736de0 + languageName: node + linkType: hard + "use-sync-external-store@npm:1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" @@ -25234,7 +27547,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard -"uuid@npm:^8.3.2": +"uuid@npm:^8.0.0, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" bin: @@ -25270,6 +27583,22 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"valid-url@npm:~1.0.9": + version: 1.0.9 + resolution: "valid-url@npm:1.0.9" + checksum: 10c0/3995e65f9942dbcb1621754c0f9790335cec61e9e9310c0a809e9ae0e2ae91bb7fc6a471fba788e979db0418d9806639f681ecebacc869bc8c3de88efa562ee6 + languageName: node + linkType: hard + +"validate-npm-package-name@npm:^3.0.0": + version: 3.0.0 + resolution: "validate-npm-package-name@npm:3.0.0" + dependencies: + builtins: "npm:^1.0.3" + checksum: 10c0/064f21f59aefae6cc286dd4a50b15d14adb0227e0facab4316197dfb8d06801669e997af5081966c15f7828a5e6ff1957bd20886aeb6b9d0fa430e4cb5db9c4a + languageName: node + linkType: hard + "varint@npm:^5.0.0, varint@npm:^5.0.2": version: 5.0.2 resolution: "varint@npm:5.0.2" @@ -25641,6 +27970,20 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"wonka@npm:^4.0.14": + version: 4.0.15 + resolution: "wonka@npm:4.0.15" + checksum: 10c0/b93f15339c0de08259439d3c5bd3a03ca44196fbd7553cbe13c844e7b3ff2eb31b5dc4a0b2e0c3c2119160e65fc471d8366f4559744b53ab52763eb463b6793b + languageName: node + linkType: hard + +"wonka@npm:^6.3.2": + version: 6.3.4 + resolution: "wonka@npm:6.3.4" + checksum: 10c0/77329eea673da07717476e1b8f1a22f1e1a4f261bb9a58fa446c03d3da13dbd5b254664f8aded5928d953f33ee5b399a17a4f70336e8b236e478209c0e78cda4 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -25806,6 +28149,21 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"ws@npm:^8.12.1": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + "ws@npm:~8.2.3": version: 8.2.3 resolution: "ws@npm:8.2.3" @@ -26116,6 +28474,22 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view: languageName: node linkType: hard +"zeego@npm:1.10.0": + version: 1.10.0 + resolution: "zeego@npm:1.10.0" + dependencies: + "@radix-ui/react-context-menu": "npm:^2.0.1" + "@radix-ui/react-dropdown-menu": "npm:^2.0.1" + sf-symbols-typescript: "npm:^2.0.0" + peerDependencies: + "@react-native-menu/menu": "*" + react: "*" + react-native: "*" + react-native-ios-context-menu: ^2.3.2 + checksum: 10c0/e50dfdf2ef3cd585e1e6c25588622dc9d8c262677dcc4aefe1d4cceac3eb3dd3c9a383e7328ddc8fe9db40c1aa4d84884a11b134148a2d57e380fe216d80eb82 + languageName: node + linkType: hard + "zod@npm:3.23.8": version: 3.23.8 resolution: "zod@npm:3.23.8" From 250cc956b300b67ee9da8f7bca9ae8f408304f8e Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 9 Oct 2024 16:48:30 -0400 Subject: [PATCH 60/64] Claim bug fix (#6182) * fixes * fix experimental config usage * nits * use setTimeout * defaultConfig fix --- .../RecyclerAssetList2/core/RawRecyclerList.tsx | 6 +++--- src/config/experimental.ts | 4 ++++ src/config/experimentalHooks.ts | 12 +++++++++++- src/hooks/useWalletSectionsData.ts | 6 +++--- src/resources/addys/claimables/query.ts | 1 + .../claimables/ClaimingClaimableSharedUI.tsx | 4 ++-- .../claimables/ClaimingSponsoredClaimable.tsx | 17 ++++++++++++----- .../claimables/ClaimingTransactionClaimable.tsx | 13 ++++++++++--- 8 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index c68667a4f87..ffdfcf3cc6a 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -1,4 +1,4 @@ -import React, { LegacyRef, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; +import React, { LegacyRef, useCallback, useEffect, useMemo, useRef } from 'react'; import { LayoutChangeEvent } from 'react-native'; import { SetterOrUpdater } from 'recoil'; import { DataProvider, RecyclerListView } from 'recyclerlistview'; @@ -22,7 +22,7 @@ import { remoteCardsStore } from '@/state/remoteCards/remoteCards'; import { useRoute } from '@react-navigation/native'; import Routes from '@/navigation/routesNames'; import { useRemoteConfig } from '@/model/remoteConfig'; -import { RainbowContext } from '@/helpers/RainbowContext'; +import { useExperimentalConfig } from '@/config/experimentalHooks'; const dataProvider = new DataProvider((r1, r2) => { return r1.uid !== r2.uid; @@ -57,7 +57,7 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ type?: AssetListType; }) { const remoteConfig = useRemoteConfig(); - const experimentalConfig = useContext(RainbowContext).config; + const experimentalConfig = useExperimentalConfig(); const currentDataProvider = useMemoOne(() => dataProvider.cloneWithRows(briefSectionsData), [briefSectionsData]); const { isCoinListEdited, setIsCoinListEdited } = useCoinListEdited(); const y = useRecyclerAssetListPosition()!; diff --git a/src/config/experimental.ts b/src/config/experimental.ts index 6aa5824dbdb..2e563439ed1 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -72,6 +72,10 @@ export const defaultConfig: Record = { [NFTS_ENABLED]: { settings: true, value: !!IS_TEST }, }; +export const defaultConfigValues: Record = Object.fromEntries( + Object.entries(defaultConfig).map(([key, { value }]) => [key, value]) +); + const storageKey = 'config'; const storage = new MMKV({ diff --git a/src/config/experimentalHooks.ts b/src/config/experimentalHooks.ts index f99fa930c37..e8f9bec81d1 100644 --- a/src/config/experimentalHooks.ts +++ b/src/config/experimentalHooks.ts @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import { defaultConfig } from './experimental'; +import { defaultConfig, defaultConfigValues } from './experimental'; import { RainbowContext } from '@/helpers/RainbowContext'; import { IS_DEV } from '@/env'; import isTestFlight from '@/helpers/isTestFlight'; @@ -12,6 +12,16 @@ const useExperimentalFlag = (name: any) => { return defaultConfig[name].value; } }; + +export const useExperimentalConfig = () => { + if (IS_DEV || isTestFlight) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useContext(RainbowContext).config; + } else { + return defaultConfigValues; + } +}; + export default useExperimentalFlag; export * from './experimental'; diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 42a30f464b1..a2b43a2629c 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -1,4 +1,4 @@ -import { useContext, useMemo } from 'react'; +import { useMemo } from 'react'; import useAccountSettings from './useAccountSettings'; import useCoinListEditOptions from './useCoinListEditOptions'; import useCoinListEdited from './useCoinListEdited'; @@ -13,9 +13,9 @@ import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets'; import { useLegacyNFTs } from '@/resources/nfts'; import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames'; import { useRemoteConfig } from '@/model/remoteConfig'; -import { RainbowContext } from '@/helpers/RainbowContext'; import { usePositions } from '@/resources/defi/PositionsQuery'; import { useClaimables } from '@/resources/addys/claimables/query'; +import { useExperimentalConfig } from '@/config/experimentalHooks'; export default function useWalletSectionsData({ type, @@ -51,7 +51,7 @@ export default function useWalletSectionsData({ const { hiddenTokens } = useHiddenTokens(); const remoteConfig = useRemoteConfig(); - const experimentalConfig = useContext(RainbowContext).config; + const experimentalConfig = useExperimentalConfig(); const { hiddenCoinsObj: hiddenCoins, pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); diff --git a/src/resources/addys/claimables/query.ts b/src/resources/addys/claimables/query.ts index c1db04818bd..8ff0a26544b 100644 --- a/src/resources/addys/claimables/query.ts +++ b/src/resources/addys/claimables/query.ts @@ -79,6 +79,7 @@ export function useClaimables( ...config, enabled: !!address && (remoteFlag || localFlag) && !IS_TEST, staleTime: 1000 * 60 * 2, + refetchInterval: 1000 * 60 * 2, cacheTime: 1000 * 60 * 60 * 24, }); } diff --git a/src/screens/claimables/ClaimingClaimableSharedUI.tsx b/src/screens/claimables/ClaimingClaimableSharedUI.tsx index a800405e838..a788f349939 100644 --- a/src/screens/claimables/ClaimingClaimableSharedUI.tsx +++ b/src/screens/claimables/ClaimingClaimableSharedUI.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { AccentColorProvider, Bleed, Box, Inline, Text, TextShadow, globalColors, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeader, Panel, TapToDismiss, controlPanelStyles } from '@/components/SmoothPager/ListPanel'; -import { deviceUtils, haptics, safeAreaInsetValues, watchingAlert } from '@/utils'; +import { deviceUtils, safeAreaInsetValues, watchingAlert } from '@/utils'; import { View } from 'react-native'; import { IS_IOS } from '@/env'; import { ButtonPressAnimation, ShimmerAnimation } from '@/components/animations'; @@ -14,7 +14,7 @@ import { chainsLabel } from '@/chains'; import { useNavigation } from '@/navigation'; import { TextColor } from '@/design-system/color/palettes'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import { convertAmountToNativeDisplayWorklet, handleSignificantDecimalsWithThreshold } from '@/__swaps__/utils/numbers'; +import { convertAmountToNativeDisplayWorklet } from '@/__swaps__/utils/numbers'; import { useAccountSettings, useWallets } from '@/hooks'; import { enableActionsOnReadOnlyWallet } from '@/config'; import { debounce } from 'lodash'; diff --git a/src/screens/claimables/ClaimingSponsoredClaimable.tsx b/src/screens/claimables/ClaimingSponsoredClaimable.tsx index 81aff49c5ce..7ac9e840191 100644 --- a/src/screens/claimables/ClaimingSponsoredClaimable.tsx +++ b/src/screens/claimables/ClaimingSponsoredClaimable.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { ClaimResponse, SponsoredClaimable } from '@/resources/addys/claimables/types'; +import { Claimable, ClaimResponse, SponsoredClaimable } from '@/resources/addys/claimables/types'; import { ClaimingClaimableSharedUI, ClaimStatus } from './ClaimingClaimableSharedUI'; import { logger, RainbowError } from '@/logger'; import { queryClient } from '@/react-query'; @@ -15,6 +15,8 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored const [claimStatus, setClaimStatus] = useState('idle'); + const queryKey = claimablesQueryKey({ address: accountAddress, currency: nativeCurrency }); + const { mutate: claimClaimable } = useMutation({ mutationFn: async () => { const provider = getProvider({ chainId: claimable.chainId }); @@ -59,15 +61,16 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored setClaimStatus('error'); logger.error(new RainbowError('[ClaimSponsoredClaimable]: sponsored claim api call returned unsuccessful response')); } else { + haptics.notificationSuccess(); + if (response.data.payload.claim_transaction_status?.transaction_hash) { - haptics.notificationSuccess(); setClaimStatus('success'); } else { - haptics.notificationSuccess(); setClaimStatus('pending'); } - // Clear and refresh claimables data - queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency })); + + // Immediately remove the claimable from cached data + queryClient.setQueryData(queryKey, (oldData: Claimable[] | undefined) => oldData?.filter(c => c.uniqueId !== claimable.uniqueId)); } }, onError: e => { @@ -86,6 +89,10 @@ export const ClaimingSponsoredClaimable = ({ claimable }: { claimable: Sponsored ); } }, + onSettled: () => { + // Clear and refresh claimables data 20s after claim button is pressed, regardless of success or failure + setTimeout(() => queryClient.invalidateQueries(queryKey), 20_000); + }, }); return ( diff --git a/src/screens/claimables/ClaimingTransactionClaimable.tsx b/src/screens/claimables/ClaimingTransactionClaimable.tsx index 2a838619fd8..1a9c5a14a66 100644 --- a/src/screens/claimables/ClaimingTransactionClaimable.tsx +++ b/src/screens/claimables/ClaimingTransactionClaimable.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useAccountSettings, useGas } from '@/hooks'; import { ethereumUtils, haptics } from '@/utils'; -import { TransactionClaimable } from '@/resources/addys/claimables/types'; +import { Claimable, TransactionClaimable } from '@/resources/addys/claimables/types'; import { estimateGasWithPadding, getProvider } from '@/handlers/web3'; import { parseGasParamsForTransaction } from '@/parsers'; import { getNextNonce } from '@/state/nonces'; @@ -52,6 +52,8 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac const [txPayload, setTxPayload] = useState(); const [claimStatus, setClaimStatus] = useState('idle'); + const queryKey = claimablesQueryKey({ address: accountAddress, currency: nativeCurrency }); + const provider = useMemo(() => getProvider({ chainId: claimable.chainId }), [claimable.chainId]); const buildTxPayload = useCallback(async () => { @@ -174,8 +176,9 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac } else { haptics.notificationSuccess(); setClaimStatus('success'); - // Clear and refresh claimables data - queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency })); + + // Immediately remove the claimable from cached data + queryClient.setQueryData(queryKey, (oldData: Claimable[] | undefined) => oldData?.filter(c => c.uniqueId !== claimable.uniqueId)); } }, onError: e => { @@ -194,6 +197,10 @@ export const ClaimingTransactionClaimable = ({ claimable }: { claimable: Transac ); } }, + onSettled: () => { + // Clear and refresh claimables data 20s after claim button is pressed, regardless of success or failure + setTimeout(() => queryClient.invalidateQueries(queryKey), 20_000); + }, }); return ( From 1eeb5a2c21639e40462872cda7cc4354a4159e67 Mon Sep 17 00:00:00 2001 From: gregs Date: Thu, 10 Oct 2024 14:15:00 -0300 Subject: [PATCH 61/64] fix ledger image blocking text --- src/navigation/PairHardwareWalletNavigator.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/navigation/PairHardwareWalletNavigator.tsx b/src/navigation/PairHardwareWalletNavigator.tsx index e4498eef283..15dda2ba73b 100644 --- a/src/navigation/PairHardwareWalletNavigator.tsx +++ b/src/navigation/PairHardwareWalletNavigator.tsx @@ -57,10 +57,17 @@ export function PairHardwareWalletNavigator() { {({ backgroundColor }) => ( + {(currentRouteName === Routes.PAIR_HARDWARE_WALLET_INTRO_SHEET || + currentRouteName === Routes.PAIR_HARDWARE_WALLET_SEARCH_SHEET) && ( + + )} null} > @@ -92,13 +99,6 @@ export function PairHardwareWalletNavigator() { }} /> - {(currentRouteName === Routes.PAIR_HARDWARE_WALLET_INTRO_SHEET || - currentRouteName === Routes.PAIR_HARDWARE_WALLET_SEARCH_SHEET) && ( - - )} )} From 4b493feeea9ee671a42e91e5b0a94a14ca63fb00 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 16 Oct 2024 01:37:03 -0300 Subject: [PATCH 62/64] =?UTF-8?q?=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ButtonPressAnimation.android.tsx | 138 ++++++++++-------- .../WrappedCollectiblesHeader.tsx | 14 +- src/components/list/ListHeaderMenu.tsx | 6 +- 3 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx index 9bdecba75e5..77bcf901680 100644 --- a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx +++ b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-unused-prop-types */ /* 👆 Had to disable this ESLint rule it was false positive on shared Props interface */ -import React, { PropsWithChildren, useCallback, useContext, useMemo } from 'react'; +import React, { forwardRef, PropsWithChildren, useCallback, useContext, useMemo } from 'react'; import { processColor, requireNativeComponent, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import { createNativeWrapper, NativeViewGestureHandlerGestureEvent, RawButtonProps } from 'react-native-gesture-handler'; import { PureNativeButton } from 'react-native-gesture-handler/src/components/GestureButtons'; @@ -62,19 +62,22 @@ const OVERFLOW_MARGIN = 5; const transparentColor = processColor('transparent'); -const ScaleButton = ({ - children, - contentContainerStyle, - duration, - exclusive, - minLongPressDuration, - onLongPress, - onPress, - overflowMargin, - scaleTo = 0.86, - wrapperStyle, - testID, -}: PropsWithChildren) => { +const ScaleButton = forwardRef(function ScaleButton( + { + children, + contentContainerStyle, + duration, + exclusive, + minLongPressDuration, + onLongPress, + onPress, + overflowMargin, + scaleTo = 0.86, + wrapperStyle, + testID, + }: PropsWithChildren, + ref +) { const parentScale = useContext(ScaleButtonContext); const childScale = useSharedValue(1); const scale = parentScale || childScale; @@ -128,8 +131,9 @@ const ScaleButton = ({ runOnJS(handleCancel)(); }, }); + return ( - + @@ -141,26 +145,29 @@ const ScaleButton = ({ ); -}; +}); -const SimpleScaleButton = ({ - children, - duration, - exclusive, - minLongPressDuration, - onLongPress, - onLongPressEnded, - shouldLongPressHoldPress, - isLongPress, - hapticType, - enableHapticFeedback, - onPress, - scaleTo, - transformOrigin, - wrapperStyle, - testID, - disallowInterruption, -}: Props) => { +const SimpleScaleButton = forwardRef(function SimpleScaleButton( + { + children, + duration, + exclusive, + minLongPressDuration, + onLongPress, + onLongPressEnded, + shouldLongPressHoldPress, + isLongPress, + hapticType, + enableHapticFeedback, + onPress, + scaleTo, + transformOrigin, + wrapperStyle, + testID, + disallowInterruption, + }: Props, + ref +) { const onNativePress = useCallback( ({ nativeEvent: { type } }: any) => { if (type === 'longPress') { @@ -191,43 +198,47 @@ const SimpleScaleButton = ({ testID={testID} transformOrigin={transformOrigin} disallowInterruption={disallowInterruption} + ref={ref} > {children} ); -}; +}); -export default function ButtonPressAnimation({ - backgroundColor = 'transparent', - borderRadius = 0, - children, - contentContainerStyle, - disabled, - duration = 160, - exclusive, - minLongPressDuration = 500, - onLayout, - onLongPress, - onLongPressEnded, - shouldLongPressHoldPress, - onPress, - overflowMargin = OVERFLOW_MARGIN, - reanimatedButton, - scaleTo = 0.86, - skipTopMargin, - style, - testID, - transformOrigin, - wrapperStyle, - hapticType = 'selection', - enableHapticFeedback = true, - disallowInterruption = false, -}: Props) { +export default forwardRef(function ButtonPressAnimation( + { + backgroundColor = 'transparent', + borderRadius = 0, + children, + contentContainerStyle, + disabled, + duration = 160, + exclusive, + minLongPressDuration = 500, + onLayout, + onLongPress, + onLongPressEnded, + shouldLongPressHoldPress, + onPress, + overflowMargin = OVERFLOW_MARGIN, + reanimatedButton, + scaleTo = 0.86, + skipTopMargin, + style, + testID, + transformOrigin, + wrapperStyle, + hapticType = 'selection', + enableHapticFeedback = true, + disallowInterruption = false, + }: Props, + ref +) { const normalizedTransformOrigin = useMemo(() => normalizeTransformOrigin(transformOrigin), [transformOrigin]); const ButtonElement = reanimatedButton ? ScaleButton : SimpleScaleButton; return disabled ? ( - + {children} ) : ( @@ -253,13 +264,14 @@ export default function ButtonPressAnimation({ transformOrigin={normalizedTransformOrigin} wrapperStyle={wrapperStyle} disallowInterruption={disallowInterruption} + ref={ref} > {children} ); -} +}); const sx = StyleSheet.create({ overflow: { diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index 5d39be24b32..d63f31eb009 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -86,23 +86,13 @@ const CollectiblesHeader = () => { ], } : { - actionKey: `${sortCriterion}|${SortDirection.Desc}`, + actionKey: `${sortCriterion}|${nftSortDirection === SortDirection.Asc ? SortDirection.Desc : SortDirection.Asc}`, actionTitle: i18n.t(i18n.l.nfts.sort[sortCriterion]), menuState: 'off', }), }; })} - selectItem={actionKey => { - const sort = actionKey as NftSort; - if (IS_ANDROID) { - const [criterion, direction] = parseNftSort(sort); - if (criterion !== nftSort) return updateNFTSort(sort); - const toggledDirection = direction === SortDirection.Asc ? SortDirection.Desc : SortDirection.Asc; - updateNFTSort(`${criterion}|${toggledDirection}`); - } else { - updateNFTSort(sort); - } - }} + selectItem={updateNFTSort} icon={getIconForSortType(nftSort)} text={i18n.t(i18n.l.nfts.sort[nftSort])} /> diff --git a/src/components/list/ListHeaderMenu.tsx b/src/components/list/ListHeaderMenu.tsx index e59b335a940..d95d80c091a 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -8,7 +8,7 @@ import { haptics } from '@/utils'; type ListHeaderMenuProps = { selected: NftSort; menuItems: MenuConfig['menuItems']; - selectItem: (item: string) => void; + selectItem: (item: NftSort) => void; icon: string; text: string; }; @@ -23,12 +23,12 @@ export function ListHeaderMenu({ menuItems, selectItem, icon, text }: ListHeader const onPressMenuItem = ({ nativeEvent: { actionKey: item } }: { nativeEvent: { actionKey: string } }) => { haptics.selection(); - selectItem(item); + selectItem(item as NftSort); }; return ( - + Date: Wed, 16 Oct 2024 02:09:41 -0300 Subject: [PATCH 63/64] lint --- ios/Rainbow.xcodeproj/project.pbxproj | 375 ------------------ .../ButtonPressAnimation.android.tsx | 6 + yarn.lock | 15 - 3 files changed, 6 insertions(+), 390 deletions(-) diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index fe764ab630d..95b164bdef6 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 0299CE7B2886202800B5C7E7 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0299CE7A2886202800B5C7E7 /* NotificationService.m */; }; 0299CE7F2886202800B5C7E7 /* ImageNotification.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0299CE772886202800B5C7E7 /* ImageNotification.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 135F623E8FF08A7A779DBB59 /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AC56C617B9C2032ED43DBD5 /* libPods-Rainbow.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -25,11 +24,8 @@ 15D66137277A751C0082F041 /* SelectTokenIntent.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 15D66139277A751C0082F041 /* SelectTokenIntent.intentdefinition */; }; 15E531D5242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */; }; 15E531DA242DAB7100797B89 /* NotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 15E531D9242DAB7100797B89 /* NotificationManager.m */; }; - 1A546771DB0C32A214F8DA75 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FAF343BC9A04F7FB981F2D3F /* libPods-ImageNotification.a */; }; 24979E8920F84250007EB0DA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 24979E7720F84004007EB0DA /* GoogleService-Info.plist */; }; - 267E162D22C3B7F129888447 /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FDA33853985FF984942F1070 /* libPods-PriceWidgetExtension.a */; }; 4D098C2F2811A9A5006A801A /* RNStartTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D098C2E2811A9A5006A801A /* RNStartTime.m */; }; - 4D337D44AABFFB7BFABDD027 /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 933E17FCCFBF5FB66A730E0B /* libPods-SelectTokenIntent.a */; }; 6630540924A38A1900E5B030 /* RainbowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 6630540824A38A1900E5B030 /* RainbowText.m */; }; 6635730624939991006ACFA6 /* SafeStoreReview.m in Sources */ = {isa = PBXBuildFile; fileRef = 6635730524939991006ACFA6 /* SafeStoreReview.m */; }; 6655FFB425BB2B0700642961 /* ThemeModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 6655FFB325BB2B0700642961 /* ThemeModule.m */; }; @@ -142,7 +138,6 @@ C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E8D2945956C9619768A8D361 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C92E589571C27FFE89AB10 /* ExpoModulesProvider.swift */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; - EDE3164FD32FFCE390544F9B /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,7 +198,6 @@ /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; - 00A32D3367A149262C6C4D33 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; 0299CE772886202800B5C7E7 /* ImageNotification.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ImageNotification.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -211,16 +205,13 @@ 0299CE7A2886202800B5C7E7 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 0299CE7C2886202800B5C7E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0299CE852886246C00B5C7E7 /* libFirebaseCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libFirebaseCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 11D30486FEF77968C16FDF7D /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Rainbow/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Rainbow/main.m; sourceTree = ""; }; - 15004743AF62EF3EE384E855 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; 152643462B9AD97E004AC9AA /* InjectedJSBundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = InjectedJSBundle.js; path = ../InjectedJSBundle.js; sourceTree = ""; }; - 1539A52384919E5109C2C9B2 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 157155032418733F009B698B /* RainbowRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RainbowRelease.entitlements; path = Rainbow/RainbowRelease.entitlements; sourceTree = ""; }; 157155042418734C009B698B /* RainbowDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RainbowDebug.entitlements; path = Rainbow/RainbowDebug.entitlements; sourceTree = ""; }; 15C3987D2880EDFF006033AC /* og@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "og@3x.png"; sourceTree = ""; }; @@ -239,7 +230,6 @@ 15E531D4242B28EF00797B89 /* UIImageViewWithPersistentAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewWithPersistentAnimations.swift; sourceTree = ""; }; 15E531D8242DAB7100797B89 /* NotificationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationManager.h; sourceTree = ""; }; 15E531D9242DAB7100797B89 /* NotificationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationManager.m; sourceTree = ""; }; - 239585D5B37F4FBD1470F62D /* Pods-ImageNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.debug.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.debug.xcconfig"; sourceTree = ""; }; 24979E3620F84003007EB0DA /* Protobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Protobuf.framework; path = Frameworks/Protobuf.framework; sourceTree = ""; }; 24979E7420F84004007EB0DA /* FirebaseAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseAnalytics.framework; path = Frameworks/FirebaseAnalytics.framework; sourceTree = ""; }; 24979E7520F84004007EB0DA /* FirebaseCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCore.framework; path = Frameworks/FirebaseCore.framework; sourceTree = ""; }; @@ -252,16 +242,11 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; - 388CCCEFC8BB73A757574C99 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; - 3BBE9F9F842B95CFF7E85DB8 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 3FEB7D565693086934123364 /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; 43C92E589571C27FFE89AB10 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Rainbow/ExpoModulesProvider.swift"; sourceTree = ""; }; - 4AC56C617B9C2032ED43DBD5 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 4D098C2D2811A979006A801A /* RNStartTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNStartTime.h; sourceTree = ""; }; 4D098C2E2811A9A5006A801A /* RNStartTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNStartTime.m; sourceTree = ""; }; - 5D4F7BDB5458BA6BF08E99B6 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; 6630540824A38A1900E5B030 /* RainbowText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowText.m; sourceTree = ""; }; 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; @@ -277,17 +262,12 @@ 66A1FEBB24ACBBE600C3F539 /* RNCMPortal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCMPortal.m; path = "../src/react-native-cool-modals/ios/RNCMPortal.m"; sourceTree = ""; }; 66A28EAF24CAF1B500410A88 /* TestFlight.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestFlight.m; sourceTree = ""; }; 66A29CCA2511074500481F4A /* ReaHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaHeader.h; sourceTree = SOURCE_ROOT; }; - 6B24FBC9AFF649E6E37FF985 /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; - 933E17FCCFBF5FB66A730E0B /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 9DEADFA4826D4D0BAA950D21 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFIRMessaging.a; sourceTree = ""; }; - 9E9F7E918EFBFBAF381621C7 /* Pods-Rainbow.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.staging.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.staging.xcconfig"; sourceTree = ""; }; A4277D9E23CBD1910042BAF4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - A4C7384F262FF1BCFABB462D /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ButtonManager.m; sourceTree = ""; }; - A53309386F4BFBD43C6866D6 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; AA0B1CB82B00C5E100EAF77D /* SF-Mono-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Semibold.otf"; path = "../src/assets/fonts/SF-Mono-Semibold.otf"; sourceTree = ""; }; AA0B1CB92B00C5E100EAF77D /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Mono-Bold.otf"; path = "../src/assets/fonts/SF-Mono-Bold.otf"; sourceTree = ""; }; AA0B1CBA2B00C5E100EAF77D /* SF-Pro-Rounded-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Black.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Black.otf"; sourceTree = ""; }; @@ -297,7 +277,6 @@ AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - AE024FCA8C28D21E9E1EA378 /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; @@ -346,9 +325,6 @@ C1C61A81272CBDA100E5C0B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C1C61A902731A05700E5C0B3 /* RainbowTokenList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowTokenList.swift; sourceTree = ""; }; C1EB012E2731B68400830E70 /* TokenDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenDetails.swift; sourceTree = ""; }; - C37D3C9100B762F75F550BB5 /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - C3AB85CEFE6AA92587D648DA /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; - C83648862B87E1FB90335EFA /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; C97EAD8B2BD6C6DF00322D53 /* RCTDeviceUUID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDeviceUUID.m; sourceTree = ""; }; C97EAD8C2BD6C6DF00322D53 /* RCTDeviceUUID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDeviceUUID.h; sourceTree = ""; }; C9B378A02C5159880085E5D0 /* OpenInRainbow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenInRainbow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -361,15 +337,9 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CF8B843EAE21E6291C0C6702 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; - D657340CCBF77C82579CE8A1 /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; D755E71324B04FEE9C691D14 /* libRNFirebase.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFirebase.a; sourceTree = ""; }; - E060F33194FF363EC0431250 /* Pods-ImageNotification.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.release.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.release.xcconfig"; sourceTree = ""; }; - EAF3EBD98BA704022718B359 /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; - FAF343BC9A04F7FB981F2D3F /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - FDA33853985FF984942F1070 /* libPods-PriceWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PriceWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -377,7 +347,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1A546771DB0C32A214F8DA75 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -387,7 +356,6 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - 135F623E8FF08A7A779DBB59 /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,7 +365,6 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 267E162D22C3B7F129888447 /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -406,7 +373,6 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - 4D337D44AABFFB7BFABDD027 /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -583,10 +549,6 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - FAF343BC9A04F7FB981F2D3F /* libPods-ImageNotification.a */, - FDA33853985FF984942F1070 /* libPods-PriceWidgetExtension.a */, - 4AC56C617B9C2032ED43DBD5 /* libPods-Rainbow.a */, - 933E17FCCFBF5FB66A730E0B /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -750,22 +712,6 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - 239585D5B37F4FBD1470F62D /* Pods-ImageNotification.debug.xcconfig */, - E060F33194FF363EC0431250 /* Pods-ImageNotification.release.xcconfig */, - 3FEB7D565693086934123364 /* Pods-ImageNotification.localrelease.xcconfig */, - 5D4F7BDB5458BA6BF08E99B6 /* Pods-ImageNotification.staging.xcconfig */, - 00A32D3367A149262C6C4D33 /* Pods-PriceWidgetExtension.debug.xcconfig */, - EAF3EBD98BA704022718B359 /* Pods-PriceWidgetExtension.release.xcconfig */, - 6B24FBC9AFF649E6E37FF985 /* Pods-PriceWidgetExtension.localrelease.xcconfig */, - 11D30486FEF77968C16FDF7D /* Pods-PriceWidgetExtension.staging.xcconfig */, - A4C7384F262FF1BCFABB462D /* Pods-Rainbow.debug.xcconfig */, - 15004743AF62EF3EE384E855 /* Pods-Rainbow.release.xcconfig */, - 3BBE9F9F842B95CFF7E85DB8 /* Pods-Rainbow.localrelease.xcconfig */, - 9E9F7E918EFBFBAF381621C7 /* Pods-Rainbow.staging.xcconfig */, - AE024FCA8C28D21E9E1EA378 /* Pods-SelectTokenIntent.debug.xcconfig */, - D657340CCBF77C82579CE8A1 /* Pods-SelectTokenIntent.release.xcconfig */, - 388CCCEFC8BB73A757574C99 /* Pods-SelectTokenIntent.localrelease.xcconfig */, - CF8B843EAE21E6291C0C6702 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -821,11 +767,9 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 1A70A0F491DB72617BEC1E3E /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - CC6F07DBBF6C5DBF6BE2736C /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -840,17 +784,12 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - B84C4CAD33EC131FD4694010 /* [CP] Check Pods Manifest.lock */, AEAD56A917FF4051986EE3E6 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - 727A03F9C34280788B4EB7B0 /* [CP] Embed Pods Frameworks */, - 64400C8C24B919E9DC12C927 /* [CP] Copy Pods Resources */, - 9CC18903234C511474C4FE5A /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -870,11 +809,9 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - 24E3DE0C1A318179029F909F /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - E5478161AB3BE9AD9BFD4290 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -889,11 +826,9 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - C239424DF07543274D084131 /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - 82752F7D7CA23C0EBC84AF92 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1124,196 +1059,6 @@ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n"; showEnvVarsInLog = 0; }; - 1A70A0F491DB72617BEC1E3E /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 24E3DE0C1A318179029F909F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PriceWidgetExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 64400C8C24B919E9DC12C927 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 727A03F9C34280788B4EB7B0 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 82752F7D7CA23C0EBC84AF92 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 9CC18903234C511474C4FE5A /* [CP-User] [RNFB] Core Configuration */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", - ); - name = "[CP-User] [RNFB] Core Configuration"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; - }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; - }; AEAD56A917FF4051986EE3E6 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -1333,110 +1078,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Rainbow/expo-configure-project.sh\"\n"; }; - B84C4CAD33EC131FD4694010 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C239424DF07543274D084131 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - CC6F07DBBF6C5DBF6BE2736C /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseRemoteConfig/FirebaseRemoteConfig_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - E5478161AB3BE9AD9BFD4290 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1623,7 +1264,6 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 239585D5B37F4FBD1470F62D /* Pods-ImageNotification.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1674,7 +1314,6 @@ }; 0299CE812886202800B5C7E7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E060F33194FF363EC0431250 /* Pods-ImageNotification.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1726,7 +1365,6 @@ }; 0299CE822886202800B5C7E7 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3FEB7D565693086934123364 /* Pods-ImageNotification.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1775,7 +1413,6 @@ }; 0299CE832886202800B5C7E7 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5D4F7BDB5458BA6BF08E99B6 /* Pods-ImageNotification.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -1824,7 +1461,6 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A4C7384F262FF1BCFABB462D /* Pods-Rainbow.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1902,7 +1538,6 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 15004743AF62EF3EE384E855 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2023,7 +1658,6 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9E9F7E918EFBFBAF381621C7 /* Pods-Rainbow.staging.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2140,7 +1774,6 @@ }; 2C87B79A2197FA1900682EC4 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3BBE9F9F842B95CFF7E85DB8 /* Pods-Rainbow.localrelease.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2314,7 +1947,6 @@ }; C16DCF6A272BA6F100FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 00A32D3367A149262C6C4D33 /* Pods-PriceWidgetExtension.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2364,7 +1996,6 @@ }; C16DCF6B272BA6F100FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EAF3EBD98BA704022718B359 /* Pods-PriceWidgetExtension.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2415,7 +2046,6 @@ }; C16DCF6C272BA6F100FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6B24FBC9AFF649E6E37FF985 /* Pods-PriceWidgetExtension.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2463,7 +2093,6 @@ }; C16DCF6D272BA6F100FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 11D30486FEF77968C16FDF7D /* Pods-PriceWidgetExtension.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2511,7 +2140,6 @@ }; C16DCFA0272BAB9600FF5C78 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AE024FCA8C28D21E9E1EA378 /* Pods-SelectTokenIntent.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2559,7 +2187,6 @@ }; C16DCFA1272BAB9600FF5C78 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D657340CCBF77C82579CE8A1 /* Pods-SelectTokenIntent.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2608,7 +2235,6 @@ }; C16DCFA2272BAB9600FF5C78 /* LocalRelease */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 388CCCEFC8BB73A757574C99 /* Pods-SelectTokenIntent.localrelease.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; @@ -2654,7 +2280,6 @@ }; C16DCFA3272BAB9600FF5C78 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CF8B843EAE21E6291C0C6702 /* Pods-SelectTokenIntent.staging.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; diff --git a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx index 77bcf901680..e2b0276fbe8 100644 --- a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx +++ b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx @@ -133,6 +133,8 @@ const ScaleButton = forwardRef(function ScaleButton( }); return ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore @@ -198,6 +200,8 @@ const SimpleScaleButton = forwardRef(function SimpleScaleButton( testID={testID} transformOrigin={transformOrigin} disallowInterruption={disallowInterruption} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore ref={ref} > {children} @@ -238,6 +242,8 @@ export default forwardRef(function ButtonPressAnimation( const ButtonElement = reanimatedButton ? ScaleButton : SimpleScaleButton; return disabled ? ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore {children} diff --git a/yarn.lock b/yarn.lock index 2f9bb4fdeec..cb5669f2082 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13721,21 +13721,6 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.5.7": - version: 6.5.7 - resolution: "elliptic@npm:6.5.7" - dependencies: - bn.js: "npm:^4.11.9" - brorand: "npm:^1.1.0" - hash.js: "npm:^1.0.0" - hmac-drbg: "npm:^1.0.1" - inherits: "npm:^2.0.4" - minimalistic-assert: "npm:^1.0.1" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 10c0/799959b6c54ea3564e8961f35abdf8c77e37617f3051614b05ab1fb6a04ddb65bd1caa75ed1bae375b15dda312a0f79fed26ebe76ecf05c5a7af244152a601b8 - languageName: node - linkType: hard - "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" From 12099e0cbe79a4ed792aa29b79c785497ed8ad23 Mon Sep 17 00:00:00 2001 From: gregs Date: Wed, 16 Oct 2024 03:18:57 -0300 Subject: [PATCH 64/64] lint --- src/components/gas/GasSpeedButton.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/gas/GasSpeedButton.tsx b/src/components/gas/GasSpeedButton.tsx index 2506342a029..6fc390dd7bb 100644 --- a/src/components/gas/GasSpeedButton.tsx +++ b/src/components/gas/GasSpeedButton.tsx @@ -29,8 +29,8 @@ import { EthCoinIcon } from '../coin-icon/EthCoinIcon'; import { ChainId } from '@/chains/types'; import { chainsGasSpeeds } from '@/chains'; import { ThemeContextProps, useTheme } from '@/theme'; -import { OnPressMenuItemEventObject } from 'react-native-ios-context-menu'; import { ParsedAddressAsset } from '@/entities'; +import { GasSpeed } from '@/__swaps__/types/gas'; const { GAS_EMOJIS, GAS_ICONS, GasSpeedOrder, CUSTOM, URGENT, NORMAL, FAST, getGasLabel } = gasUtils; @@ -361,9 +361,7 @@ const GasSpeedButton = ({ }, [chainId, crossChainServiceTime, inputCurrency, navigate, outputCurrency]); const handlePressMenuItem = useCallback( - ({ nativeEvent: { actionKey } }: OnPressMenuItemEventObject) => { - handlePressSpeedOption(actionKey); - }, + ({ nativeEvent: { actionKey } }: { nativeEvent: { actionKey: GasSpeed } }) => handlePressSpeedOption(actionKey), [handlePressSpeedOption] ); @@ -393,8 +391,6 @@ const GasSpeedButton = ({ const menuConfig = useMemo(() => { const menuOptions = speedOptions?.map(gasOption => { - if (IS_ANDROID) return gasOption; - const totalGwei = add(gasFeeParamsBySpeed[gasOption]?.maxBaseFee?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei); const estimatedGwei = add(currentBlockParams?.baseFeePerGas?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei); @@ -459,7 +455,7 @@ const GasSpeedButton = ({ isAnchoredToRight isMenuPrimaryAction onPressActionSheet={handlePressActionSheet} - options={menuConfig.menuItems} + options={speedOptions} useActionSheetFallback={false} wrapNativeComponent={false} > @@ -470,14 +466,12 @@ const GasSpeedButton = ({ return ( {pager} @@ -489,6 +483,7 @@ const GasSpeedButton = ({ handlePressActionSheet, handlePressMenuItem, menuConfig, + speedOptions, rawColorForAsset, selectedGasFeeOption, showGasOptions,