From 5516162871fafbf55c6c9c0230f879ae7bd5d5fe Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 30 Aug 2024 13:13:20 -0400 Subject: [PATCH 01/13] Fix for charts using USD for points instead of user selected currency (#6051) * fix chart points in wrong currency * format --- src/graphql/queries/metadata.graphql | 13 +++++++-- src/hooks/charts/useChartInfo.ts | 32 +++++++++++++++++---- src/hooks/charts/useChartThrottledPoints.ts | 1 + 3 files changed, 39 insertions(+), 7 deletions(-) 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/hooks/charts/useChartInfo.ts b/src/hooks/charts/useChartInfo.ts index 17b2adf72c8..e2402eb2e9f 100644 --- a/src/hooks/charts/useChartInfo.ts +++ b/src/hooks/charts/useChartInfo.ts @@ -4,6 +4,7 @@ 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'; const chartTimes = ['hour', 'day', 'week', 'month', 'year'] as const; @@ -15,9 +16,19 @@ const getChartTimeArg = (selected: ChartTime) => export type ChartData = { x: number; y: number }; -const fetchPriceChart = async (time: ChartTime, chainId: ChainId, 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] }); @@ -25,7 +36,17 @@ const fetchPriceChart = async (time: ChartTime, chainId: ChainId, address: strin }, [] as ChartData[]); }; -export const usePriceChart = ({ mainnetAddress, address, chainId }: { mainnetAddress?: string; address: string; chainId: ChainId }) => { +export const usePriceChart = ({ + mainnetAddress, + address, + currency, + chainId, +}: { + mainnetAddress?: string; + address: string; + currency: SupportedCurrencyKey; + chainId: ChainId; +}) => { const { setParams } = useNavigation(); const updateChartType = useCallback( (type: ChartTime) => { @@ -41,8 +62,9 @@ export const usePriceChart = ({ mainnetAddress, address, chainId }: { mainnetAdd const chartType = params?.chartType ?? DEFAULT_CHART_TYPE; const query = useQuery({ queryFn: async () => { - const chart = await fetchPriceChart(chartType, chainId, address); - if (!chart && mainnetAddress) return fetchPriceChart(chartType, ChainId.mainnet, 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 713f6fe8122..3586efe49c6 100644 --- a/src/hooks/charts/useChartThrottledPoints.ts +++ b/src/hooks/charts/useChartThrottledPoints.ts @@ -100,6 +100,7 @@ export default function useChartThrottledPoints({ address: asset.address, chainId: asset.chainId, mainnetAddress: asset?.mainnet_address || asset?.mainnetAddress, + currency: nativeCurrency, }); const [throttledPoints, setThrottledPoints] = useState(() => traverseData({ nativePoints: [], points: [] }, chart)); From 265bc2526bc1cb1e6b1cbd85c55d290906d7e8dd Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 30 Aug 2024 13:40:04 -0400 Subject: [PATCH 02/13] Fix Android navigation mode detection (#6053) * fix android navigation bar height on specific devices * rm unused import * fix call to native module on ios --- .../main/java/me/rainbow/MainApplication.kt | 2 + .../NavbarHeight/NavbarHeightModule.java | 91 ++++ .../NavbarHeight/NavbarHeightPackage.java | 28 + ios/Rainbow.xcodeproj/project.pbxproj | 514 +++++++++--------- src/components/DappBrowser/Dimensions.ts | 4 +- src/handlers/localstorage/theme.ts | 2 +- src/helpers/statusBarHelper.ts | 5 - src/navigation/SwipeNavigator.tsx | 4 +- src/utils/deviceUtils.ts | 13 +- 9 files changed, 385 insertions(+), 278 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 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/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index e586a215e55..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 */; }; - 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 */; }; + 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 */; }; - 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 */; }; @@ -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 */; }; @@ -139,8 +139,8 @@ C9B378BB2C515A860085E5D0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B378BA2C515A860085E5D0 /* ShareViewController.swift */; }; C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; + F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,14 +203,14 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* RainbowTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RainbowTests.m; sourceTree = ""; }; - 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 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; }; - 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 = ""; }; + 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 = ""; }; @@ -248,19 +248,21 @@ 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 = ""; }; 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 = ""; }; + 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 = ""; }; + 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 = ""; }; 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 +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; }; - 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 = ""; }; + 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 = ""; }; @@ -291,8 +295,9 @@ AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; + 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,8 +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 = ""; }; - 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; }; + 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 = ""; }; @@ -354,14 +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 = ""; }; - 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 = ""; }; + 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; }; - FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B8CD55BCF7A0AB6EFF7E2107 /* 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 */, - 86913359E129076B2E94167D /* 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 */, - CE27C1EECE64497C72CC5B3A /* 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 */, - 8A822C422360198E62AD7BFA /* 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 */, - 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */, - D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */, - 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */, - BCDB57FCE228B5380B3618D3 /* 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 = ( - 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 */, + 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 = ( - FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */, + 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - BB2F78BEBE245D7D48C41820 /* [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 = ( - 2D9D26600F4C9E0D02F62A88 /* [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 */, - 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */, - F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */, - 3340F01A333AFB189AD85476 /* [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 = ( - 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */, + E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - BB62BF1016C4CA862C515CFC /* [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 = ( - 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */, + F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - EE77FD4F5751D4E858F705B1 /* [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,29 +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; }; - 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */ = { + 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1133,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"; }; - 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */ = { + 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1148,32 +1126,50 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6B4739E5535DF7E99F0022D2 /* [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", + "${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] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + "${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-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */ = { + 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1188,35 +1184,20 @@ 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; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + 89D80C8A947BAC7AC4044C2B /* [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-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", @@ -1228,6 +1209,15 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1242,13 +1232,22 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */ = { + 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1284,49 +1283,28 @@ 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 */ = { + A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseABTesting/FirebaseABTesting_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", @@ -1338,15 +1316,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1361,22 +1330,13 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */ = { + E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1391,7 +1351,47 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-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; + }; + 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 = ( + ); + 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; @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4AE8D68F8252F11480F2D99E /* 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 = 6657A4835F27FC0FFFFBF340 /* 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 = 0C374E26CF6ED7C5C2C823ED /* 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 = E00C89EED34D89AE4F2A3462 /* 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 = 7A533038ADA944A6493A66D1 /* 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; @@ -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 = 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */; + baseConfigurationReference = 7A04E036E763AF405B3F9877 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4C6224CF2221F931780B3CE6 /* 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; @@ -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 = 8148434135865DD9B63D5991 /* 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; @@ -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 = E2E91009BF4E2373D0EAA867 /* 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 = 675045CF6F69ECCABFA7ED3C /* 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 = B04D80FD63CAEB682250EB0A /* 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 = B7B49161FD8417A90F7CB9A8 /* 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 = 33BC79874A51AA3880F7C4AC /* 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 = E1179CDF5117977A37C590EE /* 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 = 0EF5903F6DA2DDF4181D7872 /* 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 = FD8F690D66D4FC3337BC4317 /* 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/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/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/helpers/statusBarHelper.ts b/src/helpers/statusBarHelper.ts index 47d431a1feb..e8b5f72221e 100644 --- a/src/helpers/statusBarHelper.ts +++ b/src/helpers/statusBarHelper.ts @@ -1,5 +1,4 @@ import { ColorValue, StatusBar, StatusBarAnimation } from 'react-native'; -import { getSoftMenuBarHeight } from 'react-native-extra-dimensions-android'; export const setTranslucent = (translucent: boolean): void => { 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/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx index e08d2197300..361fbf5d462 100644 --- a/src/navigation/SwipeNavigator.tsx +++ b/src/navigation/SwipeNavigator.tsx @@ -7,7 +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 { isUsingButtonNavigation } from '@/helpers/statusBarHelper'; +import { isUsingButtonNavigation } from '@/utils/deviceUtils'; import { useAccountAccentColor, useAccountSettings, useCoinListEdited, useDimensions, usePendingTransactions } from '@/hooks'; import { useRemoteConfig } from '@/model/remoteConfig'; import RecyclerListViewScrollToTopProvider, { @@ -51,7 +51,7 @@ function getTabBarHeight() { return 82; } if (!isUsingButtonNavigation()) { - return 72; + return 82; } return 48; } 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; +}; From 5597286839ab0f16cf7870c2bf5e4cb6031109e5 Mon Sep 17 00:00:00 2001 From: gregs Date: Fri, 30 Aug 2024 14:59:00 -0300 Subject: [PATCH 03/13] nfts sorting direction (#6045) * sortDirection * asc/desc menu * imports * imports * i18n * Merge remote-tracking branch origin/develop into gregs/app-1756-sorting-nfts-on-android-is-broken * Merge remote-tracking branch origin/develop into gregs/app-1756-sorting-nfts-on-android-is-broken --- .../WrappedCollectiblesHeader.tsx | 53 +++++++++++++++---- src/components/list/ListHeaderMenu.tsx | 13 ++--- .../native-context-menu/contextMenu.js | 6 --- .../native-context-menu/contextMenu.tsx | 7 +++ src/hooks/useNFTsSortBy.ts | 35 ++++++------ src/hooks/useRefreshAccountData.ts | 8 ++- src/hooks/useWalletSectionsData.ts | 5 +- src/hooks/useWatchPendingTxs.ts | 17 +++--- src/languages/en_US.json | 6 ++- src/resources/nfts/index.ts | 27 +++++++--- 10 files changed, 110 insertions(+), 67 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 bb233041e82..1954febef5c 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { Box, Inline, Text } from '@/design-system'; import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; -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 { colors } from '@/styles'; 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: i18n.t(i18n.l.nfts.sort.order.asc), + icon: { + iconType: 'SYSTEM', + iconValue: 'arrow.up.circle', + iconTint: nftSortDirection === SortDirection.Asc ? undefined : colors.grey, + }, + }, + { + actionKey: `${sortCriterion}|${SortDirection.Desc}`, + actionTitle: i18n.t(i18n.l.nfts.sort.order.desc), + 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..48ad813b2b6 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -2,18 +2,13 @@ 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 { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; - -type MenuItem = { - actionKey: string; - actionTitle: string; - menuState?: 'on' | 'off'; -}; +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 345f20132a7..a37f69b6a16 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -1,31 +1,32 @@ -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) => { + 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 mmkv.getString(getStorageKey(accountAddress)) as NftCollectionSortCriterion; + return parseNftSort(mmkv.getString(getStorageKey(accountAddress))); }; -export default function useNftSort(): { - nftSort: NftCollectionSortCriterion; - updateNFTSort: (sortBy: NftCollectionSortCriterion) => void; -} { - const { accountAddress } = useAccountSettings(); - const [nftSort, setNftSort] = useMMKVString(getStorageKey(accountAddress)); +export type NftSort = `${NftCollectionSortCriterion}|${SortDirection}`; - const updateNFTSort = useCallback( - (sortBy: NftCollectionSortCriterion) => { - setNftSort(sortBy); - }, - [setNftSort] - ); +export function useNftSort() { + const { accountAddress } = useAccountSettings(); + const [nftSortData, setNftSortData] = useMMKVString(getStorageKey(accountAddress)); + const { sortBy, sortDirection } = parseNftSort(nftSortData); return { - updateNFTSort, - nftSort: (nftSort as NftCollectionSortCriterion) || NftCollectionSortCriterion.MostRecent, + updateNFTSort: (nftSort: NftSort) => setNftSortData(nftSort), + nftSort: sortBy, + nftSortDirection: sortDirection, }; } diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index f9cb7ab786c..67897f81319 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -9,9 +9,8 @@ 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'; -import { nftsQueryKey } from '@/resources/nfts'; +import { invalidateAddressNftsQueries } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; -import useNftSort from './useNFTsSortBy'; import { Address } from 'viem'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; import useWallets from './useWallets'; @@ -22,7 +21,6 @@ export default function useRefreshAccountData() { const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); - const { nftSort } = useNftSort(); const { connectedToHardhat } = useConnectedToHardhatStore(); const { wallets } = useWallets(); @@ -33,7 +31,7 @@ export default function useRefreshAccountData() { ); const fetchAccountData = useCallback(async () => { - queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort })); + 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 })); @@ -55,7 +53,7 @@ export default function useRefreshAccountData() { logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`)); throw error; } - }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, nftSort, profilesEnabled]); + }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, profilesEnabled]); const refresh = useCallback(async () => { if (isRefreshing) return; diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 72e89e3de55..88bd12c8be4 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -4,13 +4,13 @@ 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/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 20ddd2169fe..26da4995160 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,8 +1,5 @@ -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 { MinedTransaction, RainbowTransaction } from '@/entities/transactions/transaction'; import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { RainbowError, logger } from '@/logger'; import { getProvider } from '@/handlers/web3'; @@ -10,12 +7,14 @@ import { consolidatedTransactionsQueryKey } from '@/resources/transactions/conso import { RainbowNetworkObjects } from '@/networks'; import { queryClient } from '@/react-query/queryClient'; import { getTransactionFlashbotStatus } from '@/handlers/transactions'; -import { usePendingTransactionsStore } from '@/state/pendingTransactions'; +import { ChainId } from '@/networks/types'; +import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery'; +import { invalidateAddressNftsQueries } from '@/resources/nfts'; 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 { ChainId } from '@/networks/types'; +import useAccountSettings from './useAccountSettings'; import { staleBalancesStore } from '@/state/staleBalances'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; @@ -49,7 +48,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => testnetMode: !!connectedToHardhat, }) ); - queryClient.invalidateQueries(nftsQueryKey({ address, sortBy: getNftSortForAddress(address) })); + invalidateAddressNftsQueries(address); }, [address, connectedToHardhat, nativeCurrency] ); diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 3b0836c40a4..b2c1194593f 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1321,7 +1321,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", diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index 1730cb71592..c7c92601830 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,21 +1,32 @@ import { QueryFunction, useQuery } from '@tanstack/react-query'; -import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; +import { QueryConfigWithSelect, createQueryKey, queryClient } from '@/react-query'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; 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 { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; import { ChainId } from '@/networks/types'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour 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: 1 }); + +export const invalidateAddressNftsQueries = (address: string) => { + queryClient.invalidateQueries(createQueryKey('nfts', { address })); +}; export const nftListingQueryKey = ({ contractAddress, @@ -53,8 +64,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)); @@ -77,15 +88,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 1bfb04e2a4abe3af79379818bda08ba7d395b2fc Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:00:54 -0400 Subject: [PATCH 04/13] More WC changes (#6064) * more wc refactor * separate push notifications from wc listeners --- src/App.tsx | 5 +-- src/walletConnect/index.tsx | 65 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 02c836a846e..541791f9e3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; -import { initListeners as initWalletConnectListeners } from '@/walletConnect'; +import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; @@ -144,12 +144,13 @@ function App({ walletReady }: AppProps) { } identifyFlow(); eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + initWalletConnectListeners(); const p1 = analyticsV2.initializeRudderstack(); const p2 = setupDeeplinking(); const p3 = saveFCMToken(); Promise.all([p1, p2, p3]).then(() => { - initWalletConnectListeners(); + initWalletConnectPushNotifications(); PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); analyticsV2.track(analyticsV2.event.applicationDidMount); }); diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 4eee7e3a9c6..66128be8dd5 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -9,7 +9,7 @@ import { formatJsonRpcResult, formatJsonRpcError } from '@json-rpc-tools/utils'; import { gretch } from 'gretchen'; import messaging from '@react-native-firebase/messaging'; import WalletConnectCore, { Core } from '@walletconnect/core'; -import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; +import Client, { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'; import { isHexString } from '@ethersproject/bytes'; import { toUtf8String } from '@ethersproject/strings'; @@ -94,7 +94,6 @@ export function maybeGoBackAndClearHasPendingRedirect({ delay = 0 }: { delay?: n /** * MAY BE UNDEFINED if WC v2 hasn't been instantiated yet */ -let syncWeb3WalletClient: Awaited> | undefined; let lastConnector: string | undefined = undefined; @@ -102,46 +101,41 @@ let walletConnectCore: WalletConnectCore | undefined; let web3WalletClient: ReturnType<(typeof Web3Wallet)['init']> | undefined; -let initPromise: ReturnType<(typeof Web3Wallet)['init']> | null = null; +let initPromise: Promise | undefined = undefined; + +let syncWeb3WalletClient: Client | undefined = undefined; export const initializeWCv2 = async () => { - walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); - - web3WalletClient = Web3Wallet.init({ - core: walletConnectCore, - metadata: { - name: '🌈 Rainbow', - description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', - url: 'https://rainbow.me', - icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], - redirect: { - native: 'rainbow://wc', - universal: 'https://rnbwapp.com/wc', + if (!walletConnectCore) { + walletConnectCore = new Core({ projectId: WC_PROJECT_ID }); + } + + if (!web3WalletClient) { + // eslint-disable-next-line require-atomic-updates + web3WalletClient = Web3Wallet.init({ + core: walletConnectCore, + metadata: { + name: '🌈 Rainbow', + description: 'Rainbow makes exploring Ethereum fun and accessible 🌈', + url: 'https://rainbow.me', + icons: ['https://avatars2.githubusercontent.com/u/48327834?s=200&v=4'], + redirect: { + native: 'rainbow://wc', + universal: 'https://rnbwapp.com/wc', + }, }, - }, - }); + }); + } + return web3WalletClient; }; -// this function ensures we only initialize the client once export async function getWeb3WalletClient() { - if (!syncWeb3WalletClient) { - if (!initPromise) { - if (web3WalletClient) { - initPromise = web3WalletClient.then(client => { - syncWeb3WalletClient = client; - return client; - }); - } else { - await initializeWCv2(); - return getWeb3WalletClient(); - } - } - // Wait for the initialization promise to resolve - return initPromise; - } else { - return syncWeb3WalletClient; + if (!initPromise) { + initPromise = initializeWCv2(); } + + return initPromise; } /** @@ -385,11 +379,14 @@ export async function initListeners() { events.emit('walletConnectV2SessionDeleted'); }, 500); }); +} +export async function initWalletConnectPushNotifications() { try { const token = await getFCMToken(); if (token) { + const client = await getWeb3WalletClient(); const client_id = await client.core.crypto.getClientId(); // initial subscription From 12abd5e659b93b9b154473f4318db2304b0f7f54 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:30:15 -0600 Subject: [PATCH 05/13] Swaps performance improvements, logic fixes (#6050) * Clean up swap coin icons, fix flashes * Fix useAnimatedReaction dependencies in swaps Without a dependency array, useAnimatedReaction reacts to changes in any value used throughout the useAnimatedReaction, whereas an empty dependency array limits reactions to changes in values used in the prepare function. * Show disabled paste button when clipboard is empty Previously it was fully hidden * Make hasClipboardData react to clipboard changes * Remove redundant Flashbots disabling The row containing the switch already gets hidden when Flashbots is disabled * Expose runOnUIImmediately This is an alternative to runOnUI that allows bypassing reanimated's internal update batching and the accompanying possibility of a delay before code gets executed on the UI thread * Remove redundant resetting of hasEnoughFundsForGas It's already set to undefined any time the gas fee is recalculated in SyncGasStateToSharedValues * Clean up swap confirm button logic to reduce jank * Stop auto-fetching quotes for certain error cases * Use runOnUIImmediately for time-sensitive interactions * Reduce hold to swap time from 500ms to 400ms * Fix max button edge cases * Migrate token lists from FlashList to FlatList FlatList seems to be more performant overall, and far fewer instances of blank list cells * Reduce jank when the input asset's chain changes * Improve the inputValues useAnimatedReaction equality check Some unnecessary updates were getting through with the current !== previous check * Fix outputAmount zero value handling * Add missing hold_to_bridge i18n string * Move gas calculations to the UI thread * Add missing export * Fix userAssets RegExp bug Certain escape characters like "\" previously triggered a crash if entered into the input asset search * Revert "Expose runOnUIImmediately" This reverts commit cdc7b0fb7b8b5dc17047f5db2ca909def9497c1b. * Revert "Use runOnUIImmediately for time-sensitive interactions" This reverts commit 436e79296ccabec2ff4202bdc3ea0010452b8f84. # Conflicts: # src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx --- src/__swaps__/screens/Swap/Swap.tsx | 3 +- .../components/AnimatedChainImage.ios.tsx | 66 +++-------- .../Swap/components/AnimatedSwapCoinIcon.tsx | 69 +++++------ .../Swap/components/ExchangeRateBubble.tsx | 3 +- .../screens/Swap/components/GasPanel.tsx | 3 +- .../screens/Swap/components/ReviewPanel.tsx | 18 +-- .../screens/Swap/components/SearchInput.tsx | 3 +- .../Swap/components/SearchInputButton.tsx | 55 +++++---- .../Swap/components/SwapActionButton.tsx | 3 +- .../Swap/components/SwapBottomPanel.tsx | 6 +- .../Swap/components/SwapOutputAsset.tsx | 3 +- .../screens/Swap/components/SwapSlider.tsx | 24 +++- .../components/TokenList/TokenToBuyList.tsx | 65 ++++++----- .../components/TokenList/TokenToSellList.tsx | 17 +-- .../Swap/hooks/useNativeAssetForChain.ts | 7 +- .../Swap/hooks/useSearchCurrencyLists.ts | 30 +++-- .../Swap/hooks/useSwapInputsController.ts | 17 ++- .../screens/Swap/hooks/useSwapTextStyles.ts | 5 +- .../SyncSwapStateAndSharedValues.tsx | 107 ++++++++++++------ .../screens/Swap/providers/swap-provider.tsx | 79 +++++++------ src/__swaps__/utils/swaps.ts | 2 +- src/components/DappBrowser/constants.ts | 2 +- src/components/animations/AnimatedSpinner.tsx | 3 +- src/hooks/useClipboard.ts | 6 +- src/languages/en_US.json | 1 + src/state/assets/userAssets.ts | 3 +- 26 files changed, 334 insertions(+), 266 deletions(-) diff --git a/src/__swaps__/screens/Swap/Swap.tsx b/src/__swaps__/screens/Swap/Swap.tsx index b3b8f05f449..f04d1e23b9f 100644 --- a/src/__swaps__/screens/Swap/Swap.tsx +++ b/src/__swaps__/screens/Swap/Swap.tsx @@ -164,7 +164,8 @@ const WalletAddressObserver = () => { if (didWalletAddressChange) { runOnJS(setNewInputAsset)(); } - } + }, + [] ); return null; diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index 3ed30eca49b..7e34b1ac740 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -12,17 +12,14 @@ 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 { useAnimatedProps } from 'react-native-reanimated'; -import { AddressOrEth } from '@/__swaps__/types/assets'; +import { useAnimatedProps, useDerivedValue } from 'react-native-reanimated'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; import { DEFAULT_FASTER_IMAGE_CONFIG } from '@/components/images/ImgixImage'; import { globalColors } from '@/design-system'; -import { customChainIdsToAssetNames } from '@/__swaps__/utils/chains'; -import { AddressZero } from '@ethersproject/constants'; -import { ETH_ADDRESS } from '@/references'; import { IS_ANDROID } from '@/env'; import { PIXEL_RATIO } from '@/utils/deviceUtils'; import { useSwapContext } from '../providers/swap-provider'; +import { BLANK_BASE64_PIXEL } from '@/components/DappBrowser/constants'; const networkBadges = { [ChainId.mainnet]: Image.resolveAssetSource(EthereumBadge).uri, @@ -47,19 +44,6 @@ const networkBadges = { [ChainId.degen]: Image.resolveAssetSource(DegenBadge).uri, }; -const getCustomChainIconUrlWorklet = (chainId: ChainId, address: AddressOrEth) => { - 'worklet'; - - if (!chainId || !customChainIdsToAssetNames[chainId]) return ''; - const baseUrl = 'https://raw.githubusercontent.com/rainbow-me/assets/master/blockchains/'; - - if (address === AddressZero || address === ETH_ADDRESS) { - return `${baseUrl}${customChainIdsToAssetNames[chainId]}/info/logo.png`; - } else { - return `${baseUrl}${customChainIdsToAssetNames[chainId]}/assets/${address}/logo.png`; - } -}; - export function AnimatedChainImage({ assetType, showMainnetBadge = false, @@ -70,42 +54,28 @@ export function AnimatedChainImage({ size?: number; }) { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); - const asset = assetType === 'input' ? internalSelectedInputAsset : internalSelectedOutputAsset; - - const animatedIconSource = useAnimatedProps(() => { - const base = { - source: { - ...DEFAULT_FASTER_IMAGE_CONFIG, - borderRadius: IS_ANDROID ? (size / 2) * PIXEL_RATIO : size / 2, - url: '', - }, - }; - if (!asset?.value) { - if (!showMainnetBadge) { - return base; - } - base.source.url = networkBadges[ChainId.mainnet]; - return base; - } + const url = useDerivedValue(() => { + const asset = assetType === 'input' ? internalSelectedInputAsset : internalSelectedOutputAsset; + const chainId = asset?.value?.chainId; - if (networkBadges[asset.value.chainId]) { - if (!showMainnetBadge && asset.value.chainId === ChainId.mainnet) { - return base; - } - base.source.url = networkBadges[asset.value.chainId]; - return base; - } + let url = 'eth'; - const url = getCustomChainIconUrlWorklet(asset.value.chainId, asset.value.address); - if (url) { - base.source.url = url; - return base; + if (chainId !== undefined && !(!showMainnetBadge && chainId === ChainId.mainnet)) { + url = networkBadges[chainId]; } - - return base; + return url; }); + const animatedIconSource = useAnimatedProps(() => ({ + source: { + ...DEFAULT_FASTER_IMAGE_CONFIG, + base64Placeholder: BLANK_BASE64_PIXEL, + borderRadius: IS_ANDROID ? (size / 2) * PIXEL_RATIO : size / 2, + url: url.value, + }, + })); + return ( {/* ⚠️ TODO: This works but we should figure out how to type this correctly to avoid this error */} diff --git a/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx b/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx index 230b007c130..cb9314825e3 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx @@ -41,69 +41,54 @@ export const AnimatedSwapCoinIcon = memo(function FeedCoinIcon({ showBadge?: boolean; }) { const { isDarkMode, colors } = useTheme(); - const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); + const asset = assetType === 'input' ? internalSelectedInputAsset : internalSelectedOutputAsset; + const size = small ? 16 : large ? 36 : 32; const didErrorForUniqueId = useSharedValue(undefined); - const size = small ? 16 : large ? 36 : 32; - // Shield animated props from unnecessary updates to avoid flicker - const coinIconUrl = useDerivedValue(() => asset.value?.icon_url ?? ''); + const coinIconUrl = useDerivedValue(() => asset.value?.icon_url || ''); const animatedIconSource = useAnimatedProps(() => { return { source: { ...DEFAULT_FASTER_IMAGE_CONFIG, borderRadius: IS_ANDROID ? (size / 2) * PIXEL_RATIO : undefined, - transitionDuration: 0, url: coinIconUrl.value, }, }; }); - const animatedCoinIconWrapperStyles = useAnimatedStyle(() => { - const showEmptyState = !asset.value?.uniqueId; - const showFallback = didErrorForUniqueId.value === asset.value?.uniqueId; - const shouldDisplay = !showFallback && !showEmptyState; - - return { - shadowColor: shouldDisplay ? (isDarkMode ? colors.shadow : asset.value?.shadowColor['light']) : 'transparent', - }; - }); - - const animatedCoinIconStyles = useAnimatedStyle(() => { - const showEmptyState = !asset.value?.uniqueId; - const showFallback = didErrorForUniqueId.value === asset.value?.uniqueId; - const shouldDisplay = !showFallback && !showEmptyState; - - return { - display: shouldDisplay ? 'flex' : 'none', - pointerEvents: shouldDisplay ? 'auto' : 'none', - opacity: withTiming(shouldDisplay ? 1 : 0, fadeConfig), - }; - }); - - const animatedEmptyStateStyles = useAnimatedStyle(() => { + const visibility = useDerivedValue(() => { const showEmptyState = !asset.value?.uniqueId; + const showFallback = !showEmptyState && (didErrorForUniqueId.value === asset.value?.uniqueId || !asset.value?.icon_url); + const showCoinIcon = !showFallback && !showEmptyState; - return { - display: showEmptyState ? 'flex' : 'none', - opacity: withTiming(showEmptyState ? 1 : 0, fadeConfig), - }; + return { showCoinIcon, showEmptyState, showFallback }; }); - const animatedFallbackStyles = useAnimatedStyle(() => { - const showEmptyState = !asset.value?.uniqueId; - const showFallback = !showEmptyState && didErrorForUniqueId.value === asset.value?.uniqueId; - - return { - display: showFallback ? 'flex' : 'none', - pointerEvents: showFallback ? 'auto' : 'none', - opacity: withTiming(showFallback ? 1 : 0, fadeConfig), - }; - }); + const animatedCoinIconWrapperStyles = useAnimatedStyle(() => ({ + shadowColor: visibility.value.showCoinIcon ? (isDarkMode ? colors.shadow : asset.value?.shadowColor['light']) : 'transparent', + })); + + const animatedCoinIconStyles = useAnimatedStyle(() => ({ + display: visibility.value.showCoinIcon ? 'flex' : 'none', + pointerEvents: visibility.value.showCoinIcon ? 'auto' : 'none', + opacity: withTiming(visibility.value.showCoinIcon ? 1 : 0, fadeConfig), + })); + + const animatedEmptyStateStyles = useAnimatedStyle(() => ({ + display: visibility.value.showEmptyState ? 'flex' : 'none', + opacity: withTiming(visibility.value.showEmptyState ? 1 : 0, fadeConfig), + })); + + const animatedFallbackStyles = useAnimatedStyle(() => ({ + display: visibility.value.showFallback ? 'flex' : 'none', + pointerEvents: visibility.value.showFallback ? 'auto' : 'none', + opacity: withTiming(visibility.value.showFallback ? 1 : 0, fadeConfig), + })); return ( diff --git a/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx b/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx index 8f4de13dece..17a14866819 100644 --- a/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx +++ b/src/__swaps__/screens/Swap/components/ExchangeRateBubble.tsx @@ -130,7 +130,8 @@ export const ExchangeRateBubble = () => { break; } } - } + }, + [] ); const bubbleVisibilityWrapper = useAnimatedStyle(() => { diff --git a/src/__swaps__/screens/Swap/components/GasPanel.tsx b/src/__swaps__/screens/Swap/components/GasPanel.tsx index 85167b3e817..d7534155908 100644 --- a/src/__swaps__/screens/Swap/components/GasPanel.tsx +++ b/src/__swaps__/screens/Swap/components/GasPanel.tsx @@ -428,7 +428,8 @@ export function GasPanel() { if (previous === NavigationSteps.SHOW_GAS && current !== NavigationSteps.SHOW_GAS) { runOnJS(saveCustomGasSettings)(); } - } + }, + [] ); const styles = useAnimatedStyle(() => { diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 029407d9310..ecad3bde19a 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -1,7 +1,6 @@ import { AnimatedChainImage } from '@/__swaps__/screens/Swap/components/AnimatedChainImage'; 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 { useEstimatedTime } from '@/__swaps__/utils/meteorology'; import { @@ -27,11 +26,9 @@ import { useColorMode, useForegroundColor, } from '@/design-system'; -import { useAccountSettings } from '@/hooks'; import * as i18n from '@/languages'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; -import { getNetworkObject } from '@/networks'; import { swapsStore, useSwapsStore } from '@/state/swaps/swapsStore'; import { getNativeAssetForNetwork } from '@/utils/ethereumUtils'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; @@ -64,11 +61,8 @@ const MAX_SLIPPAGE_LABEL = i18n.t(i18n.l.exchange.slippage_tolerance); const ESTIMATED_NETWORK_FEE_LABEL = i18n.t(i18n.l.gas.network_fee); const RainbowFee = () => { - const { nativeCurrency } = useAccountSettings(); const { isDarkMode } = useColorMode(); - const { isFetching, isQuoteStale, quote, internalSelectedInputAsset } = useSwapContext(); - - const { nativeAsset } = useNativeAssetForChain({ inputAsset: internalSelectedInputAsset }); + const { isFetching, isQuoteStale, quote } = useSwapContext(); const index = useSharedValue(0); const rainbowFee = useSharedValue([UNKNOWN_LABEL, UNKNOWN_LABEL]); @@ -104,7 +98,8 @@ const RainbowFee = () => { if (!current.isQuoteStale && !current.isFetching && current.quote && !(current.quote as QuoteError)?.error) { runOnJS(calculateRainbowFeeFromQuoteData)(current.quote as Quote | CrosschainQuote); } - } + }, + [] ); return ( @@ -139,15 +134,10 @@ function EstimatedArrivalTime() { function FlashbotsToggle() { const { SwapSettings } = useSwapContext(); - const inputAssetChainId = swapsStore(state => state.inputAsset?.chainId) ?? ChainId.mainnet; - const isFlashbotsEnabledForNetwork = getNetworkObject({ chainId: inputAssetChainId }).features.flashbots; - const flashbotsToggleValue = useDerivedValue(() => isFlashbotsEnabledForNetwork && SwapSettings.flashbots.value); - return ( diff --git a/src/__swaps__/screens/Swap/components/SearchInput.tsx b/src/__swaps__/screens/Swap/components/SearchInput.tsx index 8b215bf64f7..d764efacd92 100644 --- a/src/__swaps__/screens/Swap/components/SearchInput.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInput.tsx @@ -70,7 +70,8 @@ export const SearchInput = ({ if (output) runOnJS(onOutputSearchQueryChange)(''); else runOnJS(onInputSearchQueryChange)(''); } - } + }, + [] ); return ( diff --git a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx index 999b3ee9c48..30c5a60f673 100644 --- a/src/__swaps__/screens/Swap/components/SearchInputButton.tsx +++ b/src/__swaps__/screens/Swap/components/SearchInputButton.tsx @@ -9,6 +9,7 @@ import * as i18n from '@/languages'; import { THICK_BORDER_WIDTH } from '../constants'; import { useClipboard } from '@/hooks'; import { TIMING_CONFIGS } from '@/components/animations/animationConfigs'; +import { triggerHapticFeedback } from '@/screens/points/constants'; const CANCEL_LABEL = i18n.t(i18n.l.button.cancel); const CLOSE_LABEL = i18n.t(i18n.l.button.close); @@ -51,31 +52,45 @@ export const SearchInputButton = ({ return PASTE_LABEL; }); - const onPaste = useCallback(() => { - Clipboard.getString().then(text => { - // to prevent users from mistakingly pasting long ass texts when copying the wrong thing - // we slice the string to 42 which is the size of a eth address, - // no token name query search should be that big anyway - const v = text.trim().slice(0, 42); - pastedSearchInputValue.value = v; - useSwapsStore.setState({ outputSearchQuery: v }); - }); - }, []); + const onPaste = useCallback( + (isPasteDisabled: boolean) => { + if (isPasteDisabled) { + triggerHapticFeedback('notificationError'); + return; + } - const buttonVisibilityStyle = useAnimatedStyle(() => { + Clipboard.getString().then(text => { + // Slice the pasted text to the length of an ETH address + const v = text.trim().slice(0, 42); + pastedSearchInputValue.value = v; + useSwapsStore.setState({ outputSearchQuery: v }); + }); + }, + [pastedSearchInputValue] + ); + + const buttonInfo = useDerivedValue(() => { const isInputSearchFocused = inputProgress.value === NavigationSteps.SEARCH_FOCUSED; const isOutputSearchFocused = outputProgress.value === NavigationSteps.SEARCH_FOCUSED; + const isOutputTokenListFocused = outputProgress.value === NavigationSteps.TOKEN_LIST_FOCUSED; - const isVisible = - isInputSearchFocused || - isOutputSearchFocused || - (output && (internalSelectedOutputAsset.value || hasClipboardData)) || - (!output && internalSelectedInputAsset.value); + const isVisible = isInputSearchFocused || isOutputSearchFocused || output || (!output && !!internalSelectedInputAsset.value); + const isPasteDisabled = output && !internalSelectedOutputAsset.value && isOutputTokenListFocused && !hasClipboardData; + const visibleOpacity = isPasteDisabled ? 0.4 : 1; + + return { + isPasteDisabled, + isVisible, + visibleOpacity, + }; + }); + + const buttonVisibilityStyle = useAnimatedStyle(() => { return { - display: isVisible ? 'flex' : 'none', - opacity: isVisible ? withTiming(1, TIMING_CONFIGS.slowerFadeConfig) : 0, - pointerEvents: isVisible ? 'auto' : 'none', + display: buttonInfo.value.isVisible ? 'flex' : 'none', + opacity: buttonInfo.value.isVisible ? withTiming(buttonInfo.value.visibleOpacity, TIMING_CONFIGS.tabPressConfig) : 0, + pointerEvents: buttonInfo.value.isVisible ? 'auto' : 'none', }; }); @@ -88,7 +103,7 @@ export const SearchInputButton = ({ onPressWorklet={() => { 'worklet'; if (output && outputProgress.value === NavigationSteps.TOKEN_LIST_FOCUSED && !internalSelectedOutputAsset.value) { - runOnJS(onPaste)(); + runOnJS(onPaste)(buttonInfo.value.isPasteDisabled); } if (isSearchFocused.value || (output && internalSelectedOutputAsset.value) || (!output && internalSelectedInputAsset.value)) { diff --git a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx index 4530a28db5d..4d8f2dab126 100644 --- a/src/__swaps__/screens/Swap/components/SwapActionButton.tsx +++ b/src/__swaps__/screens/Swap/components/SwapActionButton.tsx @@ -194,7 +194,8 @@ const HoldProgress = ({ holdProgress }: { holdProgress: SharedValue }) = if (current && current !== previous) { runOnJS(transformColor)(getColorValueForThemeWorklet(current, isDarkMode, true)); } - } + }, + [] ); return ( diff --git a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx index 87298a06eb8..1bb554d9617 100644 --- a/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx +++ b/src/__swaps__/screens/Swap/components/SwapBottomPanel.tsx @@ -22,7 +22,8 @@ import { SwapActionButton } from './SwapActionButton'; import { SettingsPanel } from './SettingsPanel'; import { SPRING_CONFIGS } from '@/components/animations/animationConfigs'; import { triggerHapticFeedback } from '@/screens/points/constants'; -import { LONG_PRESS_DURATION_IN_MS } from '@/components/buttons/hold-to-authorize/constants'; + +const HOLD_TO_SWAP_DURATION_MS = 400; export function SwapBottomPanel() { const { isDarkMode } = useColorMode(); @@ -104,6 +105,7 @@ export function SwapBottomPanel() { icon={icon} iconStyle={confirmButtonIconStyle} label={label} + longPressDuration={HOLD_TO_SWAP_DURATION_MS} disabled={disabled} onPressWorklet={() => { 'worklet'; @@ -132,7 +134,7 @@ export function SwapBottomPanel() { holdProgress.value = 0; holdProgress.value = withTiming( 100, - { duration: LONG_PRESS_DURATION_IN_MS, easing: Easing.inOut(Easing.sin) }, + { duration: HOLD_TO_SWAP_DURATION_MS, easing: Easing.inOut(Easing.sin) }, isFinished => { if (isFinished) { holdProgress.value = 0; diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx index b3546fef19c..c2fbd147ea8 100644 --- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx +++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx @@ -79,7 +79,8 @@ function SwapOutputAmount() { v => { 'worklet'; runOnJS(setIsPasteEnabled)(v); - } + }, + [] ); return ( diff --git a/src/__swaps__/screens/Swap/components/SwapSlider.tsx b/src/__swaps__/screens/Swap/components/SwapSlider.tsx index 5919a88da4b..42581fd1087 100644 --- a/src/__swaps__/screens/Swap/components/SwapSlider.tsx +++ b/src/__swaps__/screens/Swap/components/SwapSlider.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useRef } from 'react'; import { StyleSheet, View } from 'react-native'; import * as i18n from '@/languages'; -import { PanGestureHandler, TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; +import { PanGestureHandler, State, TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler'; import Animated, { interpolate, interpolateColor, @@ -204,21 +204,27 @@ export const SwapSlider = ({ overshoot.value = calculateOvershoot(overshootX, maxOverscroll); } }, - onFinish: (event, ctx: { startX: number }) => { + onFinish: (event, ctx: { exceedsMax?: boolean; startX: number }) => { const onFinished = () => { overshoot.value = withSpring(0, SPRING_CONFIGS.sliderConfig); + if (xPercentage.value >= 0.995) { if (isQuoteStale.value === 1) { runOnJS(onChangeWrapper)(1); } sliderXPosition.value = withSpring(width, SPRING_CONFIGS.snappySpringConfig); + } else if (event.state === State.FAILED) { + SwapInputController.quoteFetchingInterval.start(); + return; } else if (xPercentage.value < 0.005) { runOnJS(onChangeWrapper)(0); sliderXPosition.value = withSpring(0, SPRING_CONFIGS.snappySpringConfig); isQuoteStale.value = 0; isFetching.value = false; - } else { + } else if (ctx.startX !== sliderXPosition.value) { runOnJS(onChangeWrapper)(xPercentage.value); + } else { + SwapInputController.quoteFetchingInterval.start(); } }; @@ -392,9 +398,15 @@ export const SwapSlider = ({ }); return ( - + - + @@ -419,8 +431,8 @@ export const SwapSlider = ({ {MAX_LABEL} diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx index fd78a631b8c..407e41d7fc6 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToBuyList.tsx @@ -1,3 +1,4 @@ +import { FlatList } from 'react-native'; import { COIN_ROW_WITH_PADDING_HEIGHT, CoinRow } from '@/__swaps__/screens/Swap/components/CoinRow'; import { ListEmpty } from '@/__swaps__/screens/Swap/components/TokenList/ListEmpty'; import { AssetToBuySectionId, useSearchCurrencyLists } from '@/__swaps__/screens/Swap/hooks/useSearchCurrencyLists'; @@ -16,9 +17,7 @@ import * as i18n from '@/languages'; import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; -import { FlashList } from '@shopify/flash-list'; -import React, { ComponentType, forwardRef, memo, useCallback, useMemo } from 'react'; -import { ScrollViewProps } from 'react-native'; +import React, { memo, useCallback, useMemo } from 'react'; import Animated, { runOnUI, useAnimatedProps, useAnimatedStyle, withTiming } from 'react-native-reanimated'; import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants'; import { ChainSelection } from './ChainSelection'; @@ -73,15 +72,25 @@ export type HeaderItem = { listItemType: 'header'; id: AssetToBuySectionId; data export type CoinRowItem = SearchAsset & { listItemType: 'coinRow'; sectionId: AssetToBuySectionId }; export type TokenToBuyListItem = HeaderItem | CoinRowItem; -const ScrollViewWithRef = forwardRef(function ScrollViewWithRef(props, ref) { - const { outputProgress } = useSwapContext(); - const animatedListProps = useAnimatedProps(() => { - const isFocused = outputProgress.value === 2; - return { scrollIndicatorInsets: { bottom: 28 + (isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0) } }; - }); - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -}); +const getItemLayout = (data: ArrayLike | null | undefined, index: number) => { + if (!data) return { length: 0, offset: 0, index }; + + const item = data[index]; + const length = item?.listItemType === 'header' ? BUY_LIST_HEADER_HEIGHT : COIN_ROW_WITH_PADDING_HEIGHT; + + // Count headers up to this index + let headerCount = 0; + for (let i = 0; i < index; i++) { + if (data[i]?.listItemType === 'header') { + headerCount += 1; + } + } + + const coinRowCount = index - headerCount; + const offset = headerCount * BUY_LIST_HEADER_HEIGHT + coinRowCount * COIN_ROW_WITH_PADDING_HEIGHT; + + return { length, offset, index }; +}; export const TokenToBuyList = () => { const { internalSelectedInputAsset, internalSelectedOutputAsset, isFetching, isQuoteStale, outputProgress, setAsset } = useSwapContext(); @@ -130,12 +139,12 @@ export const TokenToBuyList = () => { return { height: bottomPadding }; }); - const averageItemSize = useMemo(() => { - const numberOfHeaders = sections.filter(section => section.listItemType === 'header').length; - const numberOfCoinRows = sections.filter(section => section.listItemType === 'coinRow').length; - const totalHeight = numberOfHeaders * BUY_LIST_HEADER_HEIGHT + numberOfCoinRows * COIN_ROW_WITH_PADDING_HEIGHT; - return totalHeight / (numberOfHeaders + numberOfCoinRows); - }, [sections]); + const animatedListProps = useAnimatedProps(() => { + const isFocused = outputProgress.value === 2; + return { + scrollIndicatorInsets: { bottom: 28 + (isFocused ? EXPANDED_INPUT_HEIGHT - FOCUSED_INPUT_HEIGHT : 0) }, + }; + }); if (isLoading) return null; @@ -145,18 +154,14 @@ export const TokenToBuyList = () => { return ( - } ListFooterComponent={} ListHeaderComponent={} contentContainerStyle={{ paddingBottom: 16 }} - // For some reason shallow copying the list data allows FlashList to more quickly pick up changes - data={sections.slice(0)} - estimatedFirstItemOffset={BUY_LIST_HEADER_HEIGHT} - estimatedItemSize={averageItemSize || undefined} - estimatedListSize={{ height: EXPANDED_INPUT_HEIGHT - 77, width: DEVICE_WIDTH - 24 }} - getItemType={item => item.listItemType} + data={sections} + getItemLayout={getItemLayout} keyExtractor={item => `${item.listItemType}-${item.listItemType === 'coinRow' ? item.uniqueId : item.id}`} renderItem={({ item }) => { if (item.listItemType === 'header') { @@ -180,7 +185,15 @@ export const TokenToBuyList = () => { /> ); }} - renderScrollComponent={ScrollViewWithRef as ComponentType} + renderScrollComponent={props => { + return ( + + ); + }} style={{ height: EXPANDED_INPUT_HEIGHT - 77, width: DEVICE_WIDTH - 24 }} /> diff --git a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx index f28acfd8dfb..38e95e2475a 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/TokenToSellList.tsx @@ -1,3 +1,4 @@ +import { FlatList } from 'react-native'; import { COIN_ROW_WITH_PADDING_HEIGHT, CoinRow } from '@/__swaps__/screens/Swap/components/CoinRow'; import { ListEmpty } from '@/__swaps__/screens/Swap/components/TokenList/ListEmpty'; import { useSwapContext } from '@/__swaps__/screens/Swap/providers/swap-provider'; @@ -10,7 +11,6 @@ import * as i18n from '@/languages'; import { userAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; -import { FlashList } from '@shopify/flash-list'; import React, { useCallback, useMemo } from 'react'; import Animated, { runOnUI, useAnimatedProps, useAnimatedStyle } from 'react-native-reanimated'; import { EXPANDED_INPUT_HEIGHT, FOCUSED_INPUT_HEIGHT } from '../../constants'; @@ -22,6 +22,12 @@ const isInitialInputAssetNull = () => { return !swapsStore.getState().inputAsset; }; +const getItemLayout = (_: ArrayLike | null | undefined, index: number) => ({ + length: COIN_ROW_WITH_PADDING_HEIGHT, + offset: COIN_ROW_WITH_PADDING_HEIGHT * index, + index, +}); + export const TokenToSellList = () => { const skipDelayedMount = useMemo(() => isInitialInputAssetNull(), []); const shouldMount = useDelayedMount({ skipDelayedMount }); @@ -80,16 +86,13 @@ const TokenToSellListComponent = () => { }); return ( - } ListFooterComponent={} ListHeaderComponent={} contentContainerStyle={{ paddingBottom: 16 }} - // For some reason shallow copying the list data allows FlashList to more quickly pick up changes - data={userAssetIds.slice(0)} - estimatedFirstItemOffset={SELL_LIST_HEADER_HEIGHT} - estimatedItemSize={COIN_ROW_WITH_PADDING_HEIGHT} - estimatedListSize={{ height: EXPANDED_INPUT_HEIGHT - 77, width: DEVICE_WIDTH - 24 }} + data={userAssetIds} + getItemLayout={getItemLayout} keyExtractor={uniqueId => uniqueId} renderItem={({ item: uniqueId }) => { return handleSelectToken(asset)} output={false} uniqueId={uniqueId} />; diff --git a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts index 95c886d31d0..5d124bde586 100644 --- a/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts +++ b/src/__swaps__/screens/Swap/hooks/useNativeAssetForChain.ts @@ -21,11 +21,12 @@ export const useNativeAssetForChain = ({ inputAsset }: { inputAsset: SharedValue useAnimatedReaction( () => chainId.value, - (currentChainId, previoudChainId) => { - if (currentChainId !== previoudChainId) { + (currentChainId, previousChainId) => { + if (currentChainId !== previousChainId) { runOnJS(getNativeAssetForNetwork)(currentChainId); } - } + }, + [] ); return { diff --git a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts index a1ba4437ff2..87815861728 100644 --- a/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts +++ b/src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts @@ -259,23 +259,35 @@ export function useSearchCurrencyLists() { }); // Delays the state set by a frame or two to give animated UI that responds to selectedOutputChainId.value - // a moment to update before the heavy re-renders kicked off by these state changes occur. + // a moment to update before the heavy re-renders kicked off by these state changes occur. This is used + // when the user changes the selected chain in the output token list. const debouncedStateSet = useDebouncedCallback(setState, 20, { leading: false, trailing: true }); + // This is used when the input asset is changed. To avoid a heavy re-render while the input bubble is collapsing, + // we use a longer delay as in this case the list is not visible, so it doesn't need to react immediately. + const changedInputAssetStateSet = useDebouncedCallback(setState, 600, { leading: false, trailing: true }); + useAnimatedReaction( () => ({ isCrosschainSearch: assetToSell.value ? assetToSell.value.chainId !== selectedOutputChainId.value : false, toChainId: selectedOutputChainId.value ?? ChainId.mainnet, }), (current, previous) => { - if (previous && (current.isCrosschainSearch !== previous.isCrosschainSearch || current.toChainId !== previous.toChainId)) { - runOnJS(debouncedStateSet)({ - fromChainId: assetToSell.value ? assetToSell.value.chainId ?? ChainId.mainnet : undefined, - isCrosschainSearch: current.isCrosschainSearch, - toChainId: current.toChainId, - }); - } - } + const toChainIdChanged = previous && current.toChainId !== previous.toChainId; + const isCrosschainSearchChanged = previous && current.isCrosschainSearch !== previous.isCrosschainSearch; + + if (!toChainIdChanged && !isCrosschainSearchChanged) return; + + const newState = { + fromChainId: assetToSell.value ? assetToSell.value.chainId ?? ChainId.mainnet : undefined, + isCrosschainSearch: current.isCrosschainSearch, + toChainId: current.toChainId, + }; + + if (toChainIdChanged) runOnJS(debouncedStateSet)(newState); + else if (isCrosschainSearchChanged) runOnJS(changedInputAssetStateSet)(newState); + }, + [] ); const selectTopSearchResults = useCallback( diff --git a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts index 1953277fb87..3f07c1ec368 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapInputsController.ts @@ -32,6 +32,7 @@ import { useCallback } from 'react'; import { SharedValue, runOnJS, runOnUI, useAnimatedReaction, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; import { useDebouncedCallback } from 'use-debounce'; import { NavigationSteps } from './useSwapNavigation'; +import { deepEqualWorklet } from '@/worklets/comparisons'; const REMOTE_CONFIG = getRemoteConfig(); @@ -701,7 +702,8 @@ export function useSwapInputsController({ if (areBothAssetsSet) { fetchQuoteAndAssetPrices(); } - } + }, + [] ); /** @@ -722,7 +724,8 @@ export function useSwapInputsController({ }); fetchQuoteAndAssetPrices(); } - } + }, + [] ); /** @@ -752,7 +755,8 @@ export function useSwapInputsController({ } } } - } + }, + [] ); /** @@ -771,11 +775,11 @@ export function useSwapInputsController({ values: inputValues.value, }), (current, previous) => { - if (previous && current !== previous) { + if (previous && !deepEqualWorklet(current, previous)) { // Handle updating input values based on the input method if (inputMethod.value === 'slider' && internalSelectedInputAsset.value && current.sliderXPosition !== previous.sliderXPosition) { // If the slider position changes - if (percentageToSwap.value === 0) { + if (current.sliderXPosition === 0) { resetValuesToZeroWorklet({ updateSlider: false }); } else { // If the change set the slider position to > 0 @@ -870,7 +874,8 @@ export function useSwapInputsController({ } } } - } + }, + [] ); return { debouncedFetchQuote, diff --git a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts index 6404bb425e9..92facea2c77 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapTextStyles.ts @@ -87,7 +87,10 @@ export function useSwapTextStyles({ }); const isOutputZero = useDerivedValue(() => { - const isZero = !internalSelectedOutputAsset.value || equalWorklet(inputValues.value.outputAmount, 0); + const isZero = + !internalSelectedOutputAsset.value || + (inputValues.value.outputAmount === 0 && inputMethod.value !== 'slider') || + (inputMethod.value === 'slider' && equalWorklet(inputValues.value.outputAmount, 0)); return isZero; }); diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx index 090ec7a11a7..843fcc616d6 100644 --- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx +++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx @@ -1,26 +1,28 @@ +import BigNumber from 'bignumber.js'; import { divWorklet, greaterThanWorklet, + isNumberStringWorklet, lessThanOrEqualToWorklet, lessThanWorklet, mulWorklet, powWorklet, subWorklet, + sumWorklet, toFixedWorklet, toScaledIntegerWorklet, } from '@/__swaps__/safe-math/SafeMath'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; import { ChainId } from '@/networks/types'; -import { add } from '@/__swaps__/utils/numbers'; import { ParsedAddressAsset } from '@/entities'; import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset'; import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps'; +import { deepEqualWorklet } from '@/worklets/comparisons'; import { debounce } from 'lodash'; -import { useEffect } from 'react'; -import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; -import { formatUnits } from 'viem'; +import { useEffect, useMemo } from 'react'; +import { runOnJS, runOnUI, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; import { create } from 'zustand'; -import { calculateGasFee } from '../hooks/useEstimatedGasFee'; +import { GasSettings } from '../hooks/useCustomGas'; import { useSelectedGas } from '../hooks/useSelectedGas'; import { useSwapEstimatedGasLimit } from '../hooks/useSwapEstimatedGasLimit'; import { useSwapContext } from './swap-provider'; @@ -62,7 +64,7 @@ export const SyncQuoteSharedValuesToState = () => { // needed and was previously resulting in errors in useEstimatedGasFee. if (isSwappingMoreThanAvailableBalance) return; - if (!previous || current !== previous) { + if (!deepEqualWorklet(current, previous)) { runOnJS(setInternalSyncedSwapStore)({ assetToBuy: assetToBuy.value, assetToSell: assetToSell.value, @@ -70,18 +72,48 @@ export const SyncQuoteSharedValuesToState = () => { quote: current, }); } - } + }, + [] ); return null; }; -const getHasEnoughFundsForGas = (quote: Quote, gasFee: string, nativeNetworkAsset: ParsedAddressAsset | undefined) => { +export function calculateGasFeeWorklet(gasSettings: GasSettings, gasLimit: string) { + 'worklet'; + const amount = gasSettings.isEIP1559 ? sumWorklet(gasSettings.maxBaseFee, gasSettings.maxPriorityFee || '0') : gasSettings.gasPrice; + return mulWorklet(gasLimit, amount); +} + +export function formatUnitsWorklet(value: string, decimals: number) { + 'worklet'; + let display = value; + const negative = display.startsWith('-'); + if (negative) display = display.slice(1); + + display = display.padStart(decimals, '0'); + + // eslint-disable-next-line prefer-const + let [integer, fraction] = [display.slice(0, display.length - decimals), display.slice(display.length - decimals)]; + fraction = fraction.replace(/(0+)$/, ''); + return `${negative ? '-' : ''}${integer || '0'}${fraction ? `.${fraction}` : ''}`; +} + +const getHasEnoughFundsForGasWorklet = ({ + gasFee, + nativeNetworkAsset, + quoteValue, +}: { + gasFee: string; + nativeNetworkAsset: ParsedAddressAsset | undefined; + quoteValue: string; +}) => { + 'worklet'; if (!nativeNetworkAsset) return false; - const userBalance = nativeNetworkAsset.balance?.amount || '0'; - const quoteValue = quote.value?.toString() || '0'; - const totalNativeSpentInTx = formatUnits(BigInt(add(quoteValue, gasFee)), nativeNetworkAsset.decimals); + const userBalance = nativeNetworkAsset.balance?.amount || '0'; + const safeGasFee = isNumberStringWorklet(gasFee) ? gasFee : '0'; + const totalNativeSpentInTx = formatUnitsWorklet(sumWorklet(quoteValue, safeGasFee), nativeNetworkAsset.decimals); return lessThanOrEqualToWorklet(totalNativeSpentInTx, userBalance); }; @@ -89,7 +121,8 @@ const getHasEnoughFundsForGas = (quote: Quote, gasFee: string, nativeNetworkAsse export function SyncGasStateToSharedValues() { const { hasEnoughFundsForGas, internalSelectedInputAsset } = useSwapContext(); - const { assetToSell, chainId = ChainId.mainnet, quote } = useSyncedSwapQuoteStore(); + const initialChainId = useMemo(() => internalSelectedInputAsset.value?.chainId || ChainId.mainnet, [internalSelectedInputAsset]); + const { assetToSell, chainId = initialChainId, quote } = useSyncedSwapQuoteStore(); const gasSettings = useSelectedGas(chainId); const { data: userNativeNetworkAsset, isLoading: isLoadingNativeNetworkAsset } = useUserNativeNetworkAsset(chainId); @@ -122,35 +155,44 @@ export function SyncGasStateToSharedValues() { }); } } - } + }, + [] ); useEffect(() => { - hasEnoughFundsForGas.value = undefined; - if (!gasSettings || !estimatedGasLimit || !quote || 'error' in quote || isLoadingNativeNetworkAsset) return; + const safeQuoteValue = quote && !('error' in quote) && quote.value ? new BigNumber(quote.value.toString()).toFixed() : '0'; + + runOnUI(() => { + hasEnoughFundsForGas.value = undefined; + if (!gasSettings || !estimatedGasLimit || !quote || 'error' in quote || isLoadingNativeNetworkAsset) return; - if (!userNativeNetworkAsset) { - hasEnoughFundsForGas.value = false; - return; - } + if (!userNativeNetworkAsset) { + hasEnoughFundsForGas.value = false; + return; + } - const gasFee = calculateGasFee(gasSettings, estimatedGasLimit); + const gasFee = calculateGasFeeWorklet(gasSettings, estimatedGasLimit); - const nativeGasFee = divWorklet(gasFee, powWorklet(10, userNativeNetworkAsset.decimals)); + const nativeGasFee = divWorklet(gasFee, powWorklet(10, userNativeNetworkAsset.decimals)); - const isEstimateOutsideRange = !!( - gasFeeRange.value && - (lessThanWorklet(nativeGasFee, gasFeeRange.value[0]) || greaterThanWorklet(nativeGasFee, gasFeeRange.value[1])) - ); + const isEstimateOutsideRange = !!( + gasFeeRange.value && + (lessThanWorklet(nativeGasFee, gasFeeRange.value[0]) || greaterThanWorklet(nativeGasFee, gasFeeRange.value[1])) + ); - // If the gas fee range hasn't been set or the estimated fee is outside the range, calculate the range based on the gas fee - if (nativeGasFee && (!gasFeeRange.value || isEstimateOutsideRange)) { - const lowerBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 - BUFFER_RATIO), userNativeNetworkAsset.decimals); - const upperBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 + BUFFER_RATIO), userNativeNetworkAsset.decimals); - gasFeeRange.value = [lowerBound, upperBound]; - } + // If the gas fee range hasn't been set or the estimated fee is outside the range, calculate the range based on the gas fee + if (nativeGasFee && (!gasFeeRange.value || isEstimateOutsideRange)) { + const lowerBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 - BUFFER_RATIO), userNativeNetworkAsset.decimals); + const upperBound = toFixedWorklet(mulWorklet(nativeGasFee, 1 + BUFFER_RATIO), userNativeNetworkAsset.decimals); + gasFeeRange.value = [lowerBound, upperBound]; + } - hasEnoughFundsForGas.value = getHasEnoughFundsForGas(quote, gasFee, userNativeNetworkAsset); + hasEnoughFundsForGas.value = getHasEnoughFundsForGasWorklet({ + gasFee, + nativeNetworkAsset: userNativeNetworkAsset, + quoteValue: safeQuoteValue, + }); + })(); return () => { hasEnoughFundsForGas.value = undefined; @@ -160,6 +202,7 @@ export function SyncGasStateToSharedValues() { gasFeeRange, gasSettings, hasEnoughFundsForGas, + internalSelectedInputAsset, quote, userNativeNetworkAsset, isLoadingNativeNetworkAsset, diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index 7c069fc1a20..4740f3b61c0 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -60,6 +60,7 @@ 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); +const holdToBridge = i18n.t(i18n.l.swap.actions.hold_to_bridge); const done = i18n.t(i18n.l.button.done); const enterAmount = i18n.t(i18n.l.swap.actions.enter_amount); const review = i18n.t(i18n.l.swap.actions.review); @@ -165,6 +166,8 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { const slippage = useSharedValue(getDefaultSlippageWorklet(initialSelectedInputAsset?.chainId || ChainId.mainnet, getRemoteConfig())); + const hasEnoughFundsForGas = useSharedValue(undefined); + const SwapInputController = useSwapInputsController({ focusedInput, lastTypedInput, @@ -664,12 +667,18 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { }; }, []); - const hasEnoughFundsForGas = useSharedValue(undefined); + // Stop auto-fetching if there is a quote error or no input asset balance useAnimatedReaction( - () => isFetching.value, - fetching => { - if (fetching) hasEnoughFundsForGas.value = undefined; - } + () => + SwapWarning.swapWarning.value.type === SwapWarningType.no_quote_available || + SwapWarning.swapWarning.value.type === SwapWarningType.no_route_found || + (internalSelectedInputAsset.value && equalWorklet(internalSelectedInputAsset.value.maxSwappableAmount, '0')), + (shouldStop, previous) => { + if (shouldStop && previous === false) { + SwapInputController.quoteFetchingInterval.stop(); + } + }, + [] ); const confirmButtonProps: SwapContextType['confirmButtonProps'] = useDerivedValue(() => { @@ -690,51 +699,55 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { return { label: selectToken, disabled: true, type: 'hold' }; } + const sellAsset = internalSelectedInputAsset.value; + const enoughFundsForSwap = + sellAsset && + !equalWorklet(sellAsset.maxSwappableAmount, '0') && + lessThanOrEqualToWorklet(SwapInputController.inputValues.value.inputAmount, sellAsset.maxSwappableAmount); + + if (!enoughFundsForSwap && hasEnoughFundsForGas.value !== undefined) { + return { label: insufficientFunds, disabled: true, type: 'hold' }; + } + const isInputZero = equalWorklet(SwapInputController.inputValues.value.inputAmount, 0); const isOutputZero = equalWorklet(SwapInputController.inputValues.value.outputAmount, 0); const userHasNotEnteredAmount = SwapInputController.inputMethod.value !== 'slider' && isInputZero && isOutputZero; - const userHasNotMovedSlider = SwapInputController.inputMethod.value === 'slider' && SwapInputController.percentageToSwap.value === 0; if (userHasNotEnteredAmount || userHasNotMovedSlider) { return { label: enterAmount, disabled: true, opacity: 1, type: 'hold' }; } - if ( - [SwapWarningType.no_quote_available, SwapWarningType.no_route_found, SwapWarningType.insufficient_liquidity].includes( - SwapWarning.swapWarning.value.type - ) - ) { - return { icon: '􀕹', label: review, disabled: true, type: 'hold' }; - } - - const sellAsset = internalSelectedInputAsset.value; - const enoughFundsForSwap = - sellAsset && lessThanOrEqualToWorklet(SwapInputController.inputValues.value.inputAmount, sellAsset.maxSwappableAmount); - - if (!enoughFundsForSwap) { - return { label: insufficientFunds, disabled: true, type: 'hold' }; - } + const holdLabel = swapInfo.value.isBridging ? holdToBridge : holdToSwap; + const reviewLabel = SwapSettings.degenMode.value ? holdLabel : review; const isQuoteError = quote.value && 'error' in quote.value; const isLoadingGas = !isQuoteError && hasEnoughFundsForGas.value === undefined; const isReviewSheetOpen = configProgress.value === NavigationSteps.SHOW_REVIEW || SwapSettings.degenMode.value; - if ((isFetching.value || isLoadingGas) && !isQuoteError) { - const disabled = (isReviewSheetOpen && (isFetching.value || isLoadingGas)) || !quote.value; + const isStale = + !!isQuoteStale.value && + (SwapInputController.inputMethod.value !== 'slider' || sliderPressProgress.value === SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT); + + if ((isFetching.value || isLoadingGas || isStale) && !isQuoteError) { + const disabled = (isReviewSheetOpen && (isFetching.value || isLoadingGas || isStale)) || !quote.value; const buttonType = isReviewSheetOpen ? 'hold' : 'tap'; return { label: fetchingPrices, disabled, type: buttonType }; } - const reviewLabel = SwapSettings.degenMode.value ? holdToSwap : review; + const quoteUnavailable = [ + SwapWarningType.no_quote_available, + SwapWarningType.no_route_found, + SwapWarningType.insufficient_liquidity, + ].includes(SwapWarning.swapWarning.value.type); - if (isQuoteError) { + if (quoteUnavailable || isQuoteError) { const icon = isReviewSheetOpen ? undefined : '􀕹'; return { icon, label: isReviewSheetOpen ? quoteError : reviewLabel, disabled: true, type: 'hold' }; } - if (!hasEnoughFundsForGas.value) { + if (hasEnoughFundsForGas.value === false) { const nativeCurrency = RainbowNetworkByChainId[sellAsset?.chainId || ChainId.mainnet].nativeCurrency; return { label: `${insufficient} ${nativeCurrency.symbol}`, @@ -744,23 +757,15 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } if (isReviewSheetOpen) { - return { icon: '􀎽', label: holdToSwap, disabled: false, type: 'hold' }; + const isDraggingSlider = !!isQuoteStale.value && sliderPressProgress.value !== SLIDER_COLLAPSED_HEIGHT / SLIDER_HEIGHT; + return { icon: '􀎽', label: holdLabel, disabled: isDraggingSlider, type: 'hold' }; } return { icon: '􀕹', label: reviewLabel, disabled: false, type: 'tap' }; }); const confirmButtonIconStyle = useAnimatedStyle(() => { - const isInputZero = equalWorklet(SwapInputController.inputValues.value.inputAmount, 0); - const isOutputZero = equalWorklet(SwapInputController.inputValues.value.outputAmount, 0); - - const sliderCondition = - SwapInputController.inputMethod.value === 'slider' && - (SwapInputController.percentageToSwap.value === 0 || isInputZero || isOutputZero); - const inputCondition = SwapInputController.inputMethod.value !== 'slider' && (isInputZero || isOutputZero) && !isFetching.value; - - const shouldHide = sliderCondition || inputCondition; - + const shouldHide = !confirmButtonProps.value.icon; return { display: shouldHide ? 'none' : 'flex', }; diff --git a/src/__swaps__/utils/swaps.ts b/src/__swaps__/utils/swaps.ts index 10f55dae1ad..04d31a75fdf 100644 --- a/src/__swaps__/utils/swaps.ts +++ b/src/__swaps__/utils/swaps.ts @@ -252,7 +252,7 @@ export function niceIncrementFormatter({ const niceIncrement = findNiceIncrement(inputAssetBalance); const incrementDecimalPlaces = countDecimalPlaces(niceIncrement); - if (percentageToSwap === 0 || equalWorklet(niceIncrement, 0)) return '0'; + if (percentageToSwap === 0 || equalWorklet(niceIncrement, 0)) return 0; if (percentageToSwap === 0.25) { const amount = mulWorklet(inputAssetBalance, 0.25); return valueBasedDecimalFormatter({ diff --git a/src/components/DappBrowser/constants.ts b/src/components/DappBrowser/constants.ts index 482bfd23962..e3bdae2b9bd 100644 --- a/src/components/DappBrowser/constants.ts +++ b/src/components/DappBrowser/constants.ts @@ -14,7 +14,7 @@ export const USER_AGENT = { }; export const USER_AGENT_APPLICATION_NAME = 'Rainbow'; -const BLANK_BASE64_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; +export const BLANK_BASE64_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; export const TAB_SCREENSHOT_FASTER_IMAGE_CONFIG: Partial = { // This placeholder avoids an occasional loading spinner flash diff --git a/src/components/animations/AnimatedSpinner.tsx b/src/components/animations/AnimatedSpinner.tsx index 2931e958c55..94647785d39 100644 --- a/src/components/animations/AnimatedSpinner.tsx +++ b/src/components/animations/AnimatedSpinner.tsx @@ -68,7 +68,8 @@ export const AnimatedSpinner = ({ }); } } - } + }, + [] ); return ( diff --git a/src/hooks/useClipboard.ts b/src/hooks/useClipboard.ts index 5d01aedd768..ede5478387d 100644 --- a/src/hooks/useClipboard.ts +++ b/src/hooks/useClipboard.ts @@ -1,5 +1,5 @@ import Clipboard from '@react-native-clipboard/clipboard'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useLayoutEffect, useState } from 'react'; import useAppState from './useAppState'; import { deviceUtils } from '@/utils'; @@ -27,7 +27,7 @@ export default function useClipboard() { ); // Get initial clipboardData - useEffect(() => { + useLayoutEffect(() => { if (deviceUtils.isIOS14) { checkClipboard(); } else if (!deviceUtils.hasClipboardProtection) { @@ -60,7 +60,7 @@ export default function useClipboard() { clipboard: clipboardData, enablePaste: deviceUtils.isIOS14 ? hasClipboardData : deviceUtils.hasClipboardProtection || !!clipboardData, getClipboard, - hasClipboardData, + hasClipboardData: hasClipboardData || !!clipboardData, setClipboard, }; } diff --git a/src/languages/en_US.json b/src/languages/en_US.json index b2c1194593f..7ca6238f554 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1987,6 +1987,7 @@ "swap": { "actions": { "hold_to_swap": "Hold to Swap", + "hold_to_bridge": "Hold to Bridge", "save": "Save", "enter_amount": "Enter Amount", "review": "Review", diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 2a97a31f284..0dda7203474 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -10,6 +10,7 @@ import { useConnectedToHardhatStore } from '../connectedToHardhat'; const SEARCH_CACHE_MAX_ENTRIES = 50; +const escapeRegExp = (string: string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const getSearchQueryKey = ({ filter, searchQuery }: { filter: UserAssetFilter; searchQuery: string }) => `${filter}${searchQuery}`; const getDefaultCacheKeys = (): Set => { @@ -160,7 +161,7 @@ export const userAssetsStore = createRainbowStore( return cachedData; } else { const chainIdFilter = filter === 'all' ? null : filter; - const searchRegex = inputSearchQuery.length > 0 ? new RegExp(inputSearchQuery, 'i') : null; + const searchRegex = inputSearchQuery.length > 0 ? new RegExp(escapeRegExp(inputSearchQuery), 'i') : null; const filteredIds = Array.from( selectUserAssetIds( From 259d66ac9d143804b991d1457d66a524067c3fd7 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 30 Aug 2024 15:01:04 -0400 Subject: [PATCH 06/13] Mobile Wallet Protocol / Txn cleanup (#6061) * lots of sign transaction sheet cleanup including original ticket fix * fix toHex ref * mwp * create mwp secure store and wrap app in provider * add android intents and clena up app.tsx * idk * changes * handshake request * basic impl * update some comments * some progress today at least * move business logic off App.tsx * move backup check to wallet screen * latest changes * use suspense and lazy to fix oop issue * expose config * recursively handle incoming actions * only process one incoming message at once * UNDO THIS PROBABLY * progress on personal sign * personal sign working * send transactions working * cleanup * fix lint and other calls to getRequestDisplayDetails * Update src/handlers/deeplinks.ts * fml * add switch eth chain * lint * fix lint * code review changes * cleanup App.tsx * fixes * code review changes * code review --- globals.d.ts | 1 + ios/Podfile.lock | 12 + package.json | 1 + src/App.tsx | 205 +- src/components/AppStateChangeHandler.tsx | 38 + src/components/DeeplinkHandler.tsx | 65 + src/components/FadeGradient.tsx | 47 + src/components/FadedScrollCard.tsx | 281 +++ .../MobileWalletProtocolListener.tsx | 51 + .../Transactions/TransactionDetailsCard.tsx | 127 ++ .../Transactions/TransactionDetailsRow.tsx | 90 + .../Transactions/TransactionIcons.tsx | 196 ++ .../Transactions/TransactionMessageCard.tsx | 113 + .../TransactionSimulatedEventRow.tsx | 109 + .../TransactionSimulationCard.tsx | 314 +++ src/components/Transactions/constants.ts | 121 + src/components/Transactions/types.ts | 18 + src/handlers/assets.ts | 2 +- src/handlers/deeplinks.ts | 19 +- src/helpers/accountInfo.ts | 7 +- src/helpers/dappNameHandler.ts | 3 + src/helpers/findWalletWithAccount.ts | 5 +- src/hooks/useApplicationSetup.ts | 56 + src/hooks/useCalculateGasLimit.ts | 74 + src/hooks/useConfirmTransaction.ts | 27 + src/hooks/useHasEnoughBalance.ts | 47 + src/hooks/useNonceForDisplay.ts | 32 + src/hooks/useProviderSetup.ts | 45 + src/hooks/useSubmitTransaction.ts | 56 + src/hooks/useTransactionSetup.ts | 66 + src/languages/en_US.json | 1 + src/navigation/Routes.android.tsx | 29 +- src/navigation/Routes.ios.tsx | 29 +- src/navigation/config.tsx | 3 +- src/notifications/tokens.ts | 2 +- src/parsers/requests.js | 6 +- src/redux/requests.ts | 6 +- src/redux/walletconnect.ts | 4 +- .../transactions/transactionSimulation.ts | 146 ++ src/screens/SignTransactionSheet.tsx | 1979 ++--------------- src/screens/WalletConnectApprovalSheet.js | 12 +- src/screens/WalletScreen/index.tsx | 6 + src/state/performance/operations.ts | 1 + src/storage/index.ts | 51 +- src/utils/formatDate.ts | 28 + src/utils/requestNavigationHandlers.ts | 277 ++- src/walletConnect/index.tsx | 2 +- tsconfig.json | 1 + yarn.lock | 34 + 49 files changed, 2886 insertions(+), 1959 deletions(-) create mode 100644 src/components/AppStateChangeHandler.tsx create mode 100644 src/components/DeeplinkHandler.tsx create mode 100644 src/components/FadeGradient.tsx create mode 100644 src/components/FadedScrollCard.tsx create mode 100644 src/components/MobileWalletProtocolListener.tsx create mode 100644 src/components/Transactions/TransactionDetailsCard.tsx create mode 100644 src/components/Transactions/TransactionDetailsRow.tsx create mode 100644 src/components/Transactions/TransactionIcons.tsx create mode 100644 src/components/Transactions/TransactionMessageCard.tsx create mode 100644 src/components/Transactions/TransactionSimulatedEventRow.tsx create mode 100644 src/components/Transactions/TransactionSimulationCard.tsx create mode 100644 src/components/Transactions/constants.ts create mode 100644 src/components/Transactions/types.ts create mode 100644 src/hooks/useApplicationSetup.ts create mode 100644 src/hooks/useCalculateGasLimit.ts create mode 100644 src/hooks/useConfirmTransaction.ts create mode 100644 src/hooks/useHasEnoughBalance.ts create mode 100644 src/hooks/useNonceForDisplay.ts create mode 100644 src/hooks/useProviderSetup.ts create mode 100644 src/hooks/useSubmitTransaction.ts create mode 100644 src/hooks/useTransactionSetup.ts create mode 100644 src/resources/transactions/transactionSimulation.ts create mode 100644 src/utils/formatDate.ts diff --git a/globals.d.ts b/globals.d.ts index 29d1107dba2..068c5a86e74 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -109,4 +109,5 @@ declare module 'react-native-dotenv' { export const REACT_NATIVE_RUDDERSTACK_WRITE_KEY: string; export const RUDDERSTACK_DATA_PLANE_URL: string; export const SILENCE_EMOJI_WARNINGS: boolean; + export const MWP_ENCRYPTION_KEY: string; } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1c400b0f1c1..f28ca0bae70 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,6 +4,9 @@ PODS: - BVLinearGradient (2.8.3): - React-Core - CocoaAsyncSocket (7.6.5) + - CoinbaseWalletSDK/Client (1.1.0) + - CoinbaseWalletSDK/Host (1.1.0): + - CoinbaseWalletSDK/Client - DoubleConversion (1.1.6) - FasterImage (1.6.2): - FasterImage/Nuke (= 1.6.2) @@ -171,6 +174,9 @@ PODS: - MMKV (1.3.9): - MMKVCore (~> 1.3.9) - MMKVCore (1.3.9) + - mobile-wallet-protocol-host (0.1.7): + - CoinbaseWalletSDK/Host + - React-Core - MultiplatformBleAdapter (0.1.9) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -1826,6 +1832,7 @@ DEPENDENCIES: - GoogleUtilities - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libwebp + - "mobile-wallet-protocol-host (from `../node_modules/@coinbase/mobile-wallet-protocol-host`)" - nanopb - PanModal (from `https://github.com/rainbow-me/PanModal`, commit `ab97d74279ba28c2891b47a5dc767ed4dd7cf994`) - Permission-Camera (from `../node_modules/react-native-permissions/ios/Camera`) @@ -1956,6 +1963,7 @@ SPEC REPOS: https://github.com/CocoaPods/Specs.git: - Branch - CocoaAsyncSocket + - CoinbaseWalletSDK - Firebase - FirebaseABTesting - FirebaseAnalytics @@ -2007,6 +2015,8 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 + mobile-wallet-protocol-host: + :path: "../node_modules/@coinbase/mobile-wallet-protocol-host" PanModal: :commit: ab97d74279ba28c2891b47a5dc767ed4dd7cf994 :git: https://github.com/rainbow-me/PanModal @@ -2257,6 +2267,7 @@ SPEC CHECKSUMS: Branch: d99436c6f3d5b2529ba948d273e47e732830f207 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 + CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 @@ -2282,6 +2293,7 @@ SPEC CHECKSUMS: MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843 MMKV: 817ba1eea17421547e01e087285606eb270a8dcb MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 + mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d nanopb: 438bc412db1928dac798aa6fd75726007be04262 PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3 diff --git a/package.json b/package.json index 52c229cff23..37c442c9cde 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@bradgarropy/use-countdown": "1.4.1", "@candlefinance/faster-image": "1.6.2", "@capsizecss/core": "3.0.0", + "@coinbase/mobile-wallet-protocol-host": "0.1.7", "@ensdomains/address-encoder": "0.2.16", "@ensdomains/content-hash": "2.5.7", "@ensdomains/eth-ens-namehash": "2.0.15", diff --git a/src/App.tsx b/src/App.tsx index 541791f9e3a..c2ea134d17b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,40 +1,31 @@ -import './languages'; +import '@/languages'; import * as Sentry from '@sentry/react-native'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { AppRegistry, AppState, AppStateStatus, Dimensions, InteractionManager, Linking, LogBox, View } from 'react-native'; -import branch from 'react-native-branch'; - +import React, { useCallback, useEffect, useState } from 'react'; +import { AppRegistry, Dimensions, LogBox, StyleSheet, View } from 'react-native'; +import { MobileWalletProtocolProvider } from '@coinbase/mobile-wallet-protocol-host'; +import { DeeplinkHandler } from '@/components/DeeplinkHandler'; +import { AppStateChangeHandler } from '@/components/AppStateChangeHandler'; +import { useApplicationSetup } from '@/hooks/useApplicationSetup'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; import { connect, Provider as ReduxProvider } from 'react-redux'; import { RecoilRoot } from 'recoil'; -import PortalConsumer from './components/PortalConsumer'; -import ErrorBoundary from './components/error-boundary/ErrorBoundary'; -import { OfflineToast } from './components/toasts'; -import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from './config/debug'; -import monitorNetwork from './debugging/network'; -import { Playground } from './design-system/playground/Playground'; -import handleDeeplink from './handlers/deeplinks'; -import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; -import RainbowContextWrapper from './helpers/RainbowContext'; -import isTestFlight from './helpers/isTestFlight'; +import PortalConsumer from '@/components/PortalConsumer'; +import ErrorBoundary from '@/components/error-boundary/ErrorBoundary'; +import { OfflineToast } from '@/components/toasts'; +import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from '@/config/debug'; +import monitorNetwork from '@/debugging/network'; +import { Playground } from '@/design-system/playground/Playground'; +import RainbowContextWrapper from '@/helpers/RainbowContext'; import * as keychain from '@/model/keychain'; -import { loadAddress } from './model/wallet'; -import { Navigation } from './navigation'; -import RoutesComponent from './navigation/Routes'; -import { PerformanceContextMap } from './performance/PerformanceContextMap'; -import { PerformanceTracking } from './performance/tracking'; -import { PerformanceMetrics } from './performance/tracking/types/PerformanceMetrics'; -import { PersistQueryClientProvider, persistOptions, queryClient } from './react-query'; -import store from './redux/store'; -import { walletConnectLoadState } from './redux/walletconnect'; -import { MainThemeProvider } from './theme/ThemeContext'; -import { branchListener } from './utils/branch'; -import { addressKey } from './utils/keychainConstants'; +import { Navigation } from '@/navigation'; +import { PersistQueryClientProvider, persistOptions, queryClient } from '@/react-query'; +import store, { AppDispatch, type AppState } from '@/redux/store'; +import { MainThemeProvider } from '@/theme/ThemeContext'; +import { addressKey } from '@/utils/keychainConstants'; import { SharedValuesProvider } from '@/helpers/SharedValuesContext'; -import { InitialRoute, InitialRouteContext } from '@/navigation/initialRoute'; -import Routes from '@/navigation/routesNames'; +import { InitialRouteContext } from '@/navigation/initialRoute'; import { Portal } from '@/react-native-cool-modals/Portal'; import { NotificationsHandler } from '@/notifications/NotificationsHandler'; import { analyticsV2 } from '@/analytics'; @@ -42,18 +33,15 @@ import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/util import { logger, RainbowError } from '@/logger'; import * as ls from '@/storage'; import { migrate } from '@/migrations'; -import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; -import { saveFCMToken } from '@/notifications/tokens'; import { initializeReservoirClient } from '@/resources/reservoir/client'; import { ReviewPromptAction } from '@/storage/schema'; -import { handleReviewPromptAction } from '@/utils/reviewAlert'; import { initializeRemoteConfig } from '@/model/remoteConfig'; import { NavigationContainerRef } from '@react-navigation/native'; -import { RootStackParamList } from './navigation/types'; +import { RootStackParamList } from '@/navigation/types'; import { Address } from 'viem'; -import { IS_DEV } from './env'; -import { checkIdentifierOnLaunch } from './model/backup'; -import { prefetchDefaultFavorites } from './resources/favorites'; +import { IS_DEV } from '@/env'; +import { prefetchDefaultFavorites } from '@/resources/favorites'; +import Routes from '@/navigation/Routes'; if (IS_DEV) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -62,140 +50,49 @@ if (IS_DEV) { enableScreens(); -const containerStyle = { flex: 1 }; +const sx = StyleSheet.create({ + container: { + flex: 1, + }, +}); interface AppProps { walletReady: boolean; } function App({ walletReady }: AppProps) { - const [appState, setAppState] = useState(AppState.currentState); - const [initialRoute, setInitialRoute] = useState(null); - const eventSubscription = useRef | null>(null); - const branchListenerRef = useRef | null>(null); - const navigatorRef = useRef | null>(null); - - const setupDeeplinking = useCallback(async () => { - const initialUrl = await Linking.getInitialURL(); - - branchListenerRef.current = await branchListener(url => { - logger.debug(`[App]: Branch: listener called`, {}, logger.DebugContext.deeplinks); - try { - handleDeeplink(url, initialRoute); - } catch (error) { - if (error instanceof Error) { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: error.message, - url, - }); - } else { - logger.error(new RainbowError(`[App]: Error opening deeplink`), { - message: 'Unknown error', - url, - }); - } - } - }); - - if (initialUrl) { - logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); - branch.openURL(initialUrl); - } - }, [initialRoute]); - - const identifyFlow = useCallback(async () => { - const address = await loadAddress(); - if (address) { - setTimeout(() => { - InteractionManager.runAfterInteractions(() => { - handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); - }); - }, 10_000); - - InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); - } - - setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); - }, []); - - const handleAppStateChange = useCallback( - (nextAppState: AppStateStatus) => { - if (appState === 'background' && nextAppState === 'active') { - store.dispatch(walletConnectLoadState()); - } - setAppState(nextAppState); - analyticsV2.track(analyticsV2.event.appStateChange, { - category: 'app state', - label: nextAppState, - }); - }, - [appState] - ); + const { initialRoute } = useApplicationSetup(); const handleNavigatorRef = useCallback((ref: NavigationContainerRef) => { - navigatorRef.current = ref; Navigation.setTopLevelNavigator(ref); }, []); - useEffect(() => { - if (!__DEV__ && isTestFlight) { - logger.debug(`[App]: Test flight usage - ${isTestFlight}`); - } - identifyFlow(); - eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); - initWalletConnectListeners(); - - const p1 = analyticsV2.initializeRudderstack(); - const p2 = setupDeeplinking(); - const p3 = saveFCMToken(); - Promise.all([p1, p2, p3]).then(() => { - initWalletConnectPushNotifications(); - PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); - analyticsV2.track(analyticsV2.event.applicationDidMount); - }); - - return () => { - eventSubscription.current?.remove(); - branchListenerRef.current?.(); - }; - }, []); - - useEffect(() => { - if (walletReady) { - logger.debug(`[App]: ✅ Wallet ready!`); - runWalletBackupStatusChecks(); - } - }, [walletReady]); - return ( - + {initialRoute && ( - + )} + + ); } -export type AppStore = typeof store; -export type RootState = ReturnType; -export type AppDispatch = AppStore['dispatch']; - -const AppWithRedux = connect(state => ({ +const AppWithRedux = connect(state => ({ walletReady: state.appState.walletReady, }))(App); function Root() { - const [initializing, setInitializing] = React.useState(true); + const [initializing, setInitializing] = useState(true); - React.useEffect(() => { + useEffect(() => { async function initializeApplication() { await initializeRemoteConfig(); await migrate(); @@ -301,19 +198,21 @@ function Root() { prefetchDefaultFavorites(); }} > - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/components/AppStateChangeHandler.tsx b/src/components/AppStateChangeHandler.tsx new file mode 100644 index 00000000000..ee8b29d2ebb --- /dev/null +++ b/src/components/AppStateChangeHandler.tsx @@ -0,0 +1,38 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { AppState, AppStateStatus, Linking } from 'react-native'; +import { analyticsV2 } from '@/analytics'; +import store from '@/redux/store'; +import { walletConnectLoadState } from '@/redux/walletconnect'; + +type AppStateChangeHandlerProps = { + walletReady: boolean; +}; + +export function AppStateChangeHandler({ walletReady }: AppStateChangeHandlerProps) { + const [appState, setAppState] = useState(AppState.currentState); + const eventSubscription = useRef | null>(null); + + const handleAppStateChange = useCallback( + (nextAppState: AppStateStatus) => { + if (appState === 'background' && nextAppState === 'active') { + store.dispatch(walletConnectLoadState()); + } + setAppState(nextAppState); + analyticsV2.track(analyticsV2.event.appStateChange, { + category: 'app state', + label: nextAppState, + }); + }, + [appState] + ); + + useEffect(() => { + if (!walletReady) return; + + eventSubscription.current = AppState.addEventListener('change', handleAppStateChange); + + return () => eventSubscription.current?.remove(); + }, [handleAppStateChange]); + + return null; +} diff --git a/src/components/DeeplinkHandler.tsx b/src/components/DeeplinkHandler.tsx new file mode 100644 index 00000000000..e34f7ef4e8b --- /dev/null +++ b/src/components/DeeplinkHandler.tsx @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { Linking } from 'react-native'; +import branch from 'react-native-branch'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import handleDeeplink from '@/handlers/deeplinks'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { logger, RainbowError } from '@/logger'; +import { branchListener } from '@/utils/branch'; + +type DeeplinkHandlerProps = { + initialRoute: InitialRoute; + walletReady: boolean; +}; + +export function DeeplinkHandler({ initialRoute, walletReady }: DeeplinkHandlerProps) { + const branchListenerRef = useRef | null>(null); + const { handleRequestUrl, sendFailureToClient } = useMobileWalletProtocolHost(); + + const setupDeeplinking = useCallback(async () => { + const initialUrl = await Linking.getInitialURL(); + + branchListenerRef.current = await branchListener(async url => { + logger.debug(`[App]: Branch listener called`, {}, logger.DebugContext.deeplinks); + + try { + handleDeeplink({ + url, + initialRoute, + handleRequestUrl, + sendFailureToClient, + }); + } catch (error) { + if (error instanceof Error) { + logger.error(new RainbowError('Error opening deeplink'), { + message: error.message, + url, + }); + } else { + logger.error(new RainbowError('Error opening deeplink'), { + message: 'Unknown error', + url, + }); + } + } + }); + + if (initialUrl) { + logger.debug(`[App]: has initial URL, opening with Branch`, { initialUrl }); + branch.openURL(initialUrl); + } + }, [handleRequestUrl, initialRoute, sendFailureToClient]); + + useEffect(() => { + if (!walletReady) return; + + setupDeeplinking(); + return () => { + if (branchListenerRef.current) { + branchListenerRef.current(); + } + }; + }, [setupDeeplinking, walletReady]); + + return null; +} diff --git a/src/components/FadeGradient.tsx b/src/components/FadeGradient.tsx new file mode 100644 index 00000000000..88fdade67a7 --- /dev/null +++ b/src/components/FadeGradient.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import Animated from 'react-native-reanimated'; + +import { Box, globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +type FadeGradientProps = { side: 'top' | 'bottom'; style?: StyleProp>> }; + +export const FadeGradient = ({ side, style }: FadeGradientProps) => { + const { colors, isDarkMode } = useTheme(); + + const isTop = side === 'top'; + const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; + const transparentColor = colors.alpha(solidColor, 0); + + return ( + + + + ); +}; diff --git a/src/components/FadedScrollCard.tsx b/src/components/FadedScrollCard.tsx new file mode 100644 index 00000000000..bf8a1ed9c39 --- /dev/null +++ b/src/components/FadedScrollCard.tsx @@ -0,0 +1,281 @@ +import React, { useCallback, useState } from 'react'; +import { TouchableWithoutFeedback } from 'react-native'; +import Animated, { + Easing, + SharedValue, + interpolate, + interpolateColor, + measure, + runOnJS, + runOnUI, + useAnimatedReaction, + useAnimatedRef, + useAnimatedStyle, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; + +import { globalColors } from '@/design-system'; + +import { useTheme } from '@/theme'; + +import { useDimensions } from '@/hooks'; +import { FadeGradient } from '@/components/FadeGradient'; + +const COLLAPSED_CARD_HEIGHT = 56; +const MAX_CARD_HEIGHT = 176; + +const CARD_BORDER_WIDTH = 1.5; + +const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +type FadedScrollCardProps = { + cardHeight: SharedValue; + children: React.ReactNode; + contentHeight: SharedValue; + expandedCardBottomInset?: number; + expandedCardTopInset?: number; + initialScrollEnabled?: boolean; + isExpanded: boolean; + onPressCollapsedCard?: () => void; + skipCollapsedState?: boolean; +}; + +export const FadedScrollCard = ({ + cardHeight, + children, + contentHeight, + expandedCardBottomInset = 120, + expandedCardTopInset = 120, + initialScrollEnabled, + isExpanded, + onPressCollapsedCard, + skipCollapsedState, +}: FadedScrollCardProps) => { + const { height: deviceHeight, width: deviceWidth } = useDimensions(); + const { isDarkMode } = useTheme(); + + const cardRef = useAnimatedRef(); + + const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); + const [isFullyExpanded, setIsFullyExpanded] = useState(false); + + const yPosition = useSharedValue(0); + + const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); + + const containerStyle = useAnimatedStyle(() => { + return { + height: + cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState + ? interpolate( + cardHeight.value, + [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], + [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], + 'clamp' + ) + : undefined, + zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), + }; + }); + + const backdropStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const cardStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); + + const outputRange = [0, 0]; + + const yPos = -yPosition.value; + const offset = + deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight - (yPosition.value + expandedCardHeight); + + if (yPos + expandedCardTopInset + offset >= deviceHeight - expandedCardBottomInset) { + outputRange.push(0); + } else { + outputRange.push(deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight); + } + + return { + borderColor: interpolateColor( + cardHeight.value, + [0, MAX_CARD_HEIGHT, expandedCardHeight], + isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] + ), + height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, + position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', + transform: [ + { + translateY: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, expandedCardHeight], outputRange), + }, + ], + }; + }); + + const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { + return { + transform: skipCollapsedState + ? undefined + : [ + { + translateY: interpolate( + cardHeight.value, + [ + 0, + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT + ? MAX_CARD_HEIGHT + : contentHeight.value + CARD_BORDER_WIDTH * 2, + maxExpandedHeight, + ], + [-2, -2, 0, 0] + ), + }, + ], + }; + }); + + const shadowStyle = useAnimatedStyle(() => { + const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; + return { + shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), + }; + }); + + const handleContentSizeChange = useCallback( + (width: number, height: number) => { + contentHeight.value = Math.round(height); + }, + [contentHeight] + ); + + const handleOnLayout = useCallback(() => { + runOnUI(() => { + if (cardHeight.value === MAX_CARD_HEIGHT) { + const measurement = measure(cardRef); + if (measurement === null) { + return; + } + if (yPosition.value !== measurement.pageY) { + yPosition.value = measurement.pageY; + } + } + })(); + }, [cardHeight, cardRef, yPosition]); + + useAnimatedReaction( + () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), + ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { + if ( + isFullyExpanded !== previous?.isFullyExpanded || + isExpanded !== previous?.isExpanded || + contentHeight !== previous?.contentHeight + ) { + if (isFullyExpanded) { + const expandedCardHeight = + contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; + if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { + cardHeight.value = withTiming(expandedCardHeight, timingConfig); + } else { + runOnJS(setIsFullyExpanded)(false); + } + } else if (isExpanded) { + cardHeight.value = withTiming( + contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, + timingConfig + ); + } else { + cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); + } + + const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); + runOnJS(setScrollEnabled)(enableScroll); + } + } + ); + + return ( + + { + if (isFullyExpanded) { + setIsFullyExpanded(false); + } + }} + pointerEvents={isFullyExpanded ? 'auto' : 'none'} + style={[ + { + backgroundColor: 'rgba(0, 0, 0, 0.6)', + height: deviceHeight * 3, + left: -deviceWidth * 0.5, + position: 'absolute', + top: -deviceHeight, + width: deviceWidth * 2, + zIndex: -1, + }, + backdropStyle, + ]} + /> + + + + { + if (!isFullyExpanded) { + setIsFullyExpanded(true); + } else setIsFullyExpanded(false); + } + } + > + {children} + + + + + + + + ); +}; diff --git a/src/components/MobileWalletProtocolListener.tsx b/src/components/MobileWalletProtocolListener.tsx new file mode 100644 index 00000000000..1c5dd960d4e --- /dev/null +++ b/src/components/MobileWalletProtocolListener.tsx @@ -0,0 +1,51 @@ +import { useEffect, useRef } from 'react'; +import { addDiagnosticLogListener, getAndroidIntentUrl, useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { handleMobileWalletProtocolRequest } from '@/utils/requestNavigationHandlers'; +import { logger, RainbowError } from '@/logger'; +import { IS_ANDROID, IS_DEV } from '@/env'; + +export const MobileWalletProtocolListener = () => { + const { message, handleRequestUrl, sendFailureToClient, ...mwpProps } = useMobileWalletProtocolHost(); + const lastMessageUuidRef = useRef(null); + + useEffect(() => { + if (message && lastMessageUuidRef.current !== message.uuid) { + lastMessageUuidRef.current = message.uuid; + try { + handleMobileWalletProtocolRequest({ request: message, ...mwpProps }); + } catch (error) { + logger.error(new RainbowError('Error handling Mobile Wallet Protocol request'), { + error, + }); + } + } + }, [message, mwpProps]); + + useEffect(() => { + if (IS_DEV) { + const removeListener = addDiagnosticLogListener(event => { + logger.debug(`[MobileWalletProtocolListener]: Diagnostic log event: ${JSON.stringify(event, null, 2)}`); + }); + + return () => removeListener(); + } + }, []); + + useEffect(() => { + if (IS_ANDROID) { + (async function handleAndroidIntent() { + const intentUrl = await getAndroidIntentUrl(); + if (intentUrl) { + const response = await handleRequestUrl(intentUrl); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + } + })(); + } + }, [handleRequestUrl, sendFailureToClient]); + + return null; +}; diff --git a/src/components/Transactions/TransactionDetailsCard.tsx b/src/components/Transactions/TransactionDetailsCard.tsx new file mode 100644 index 00000000000..307af8def77 --- /dev/null +++ b/src/components/Transactions/TransactionDetailsCard.tsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react'; +import * as i18n from '@/languages'; +import Animated, { interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { abbreviations, ethereumUtils } from '@/utils'; +import { TransactionSimulationMeta } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/networks/types'; + +import { getNetworkObject } from '@/networks'; +import { TransactionDetailsRow } from '@/components/Transactions/TransactionDetailsRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { IconContainer } from '@/components/Transactions/TransactionIcons'; +import { formatDate } from '@/utils/formatDate'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; + +interface TransactionDetailsCardProps { + chainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + meta: TransactionSimulationMeta | undefined; + methodName: string; + noChanges: boolean; + nonce: string | undefined; + toAddress: string; +} + +export const TransactionDetailsCard = ({ + chainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + meta, + methodName, + noChanges, + nonce, + toAddress, +}: TransactionDetailsCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const [isExpanded, setIsExpanded] = useState(false); + + const currentNetwork = getNetworkObject({ chainId }); + + const listStyle = useAnimatedStyle(() => ({ + opacity: interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; + + const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); + const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; + const showTransferToRow = !!meta?.transferTo?.address; + // Hide DetailsCard if balance is insufficient once loaded + if (!isLoading && isBalanceEnough === false) { + return <>; + } + return ( + setIsExpanded(true)} + > + + + + + + 􁙠 + + + + {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} + + + + + + {} + {!!(meta?.to?.address || toAddress || showTransferToRow) && ( + + ethereumUtils.openAddressInBlockExplorer({ + address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', + chainId, + }) + } + value={ + meta?.to?.name || + abbreviations.address(meta?.to?.address || toAddress, 4, 6) || + meta?.to?.address || + toAddress || + meta?.transferTo?.address || + '' + } + /> + )} + {showFunctionRow && } + {!!meta?.to?.sourceCodeStatus && } + {!!meta?.to?.created && } + {nonce && } + + + + + ); +}; diff --git a/src/components/Transactions/TransactionDetailsRow.tsx b/src/components/Transactions/TransactionDetailsRow.tsx new file mode 100644 index 00000000000..e32b6cdcd7a --- /dev/null +++ b/src/components/Transactions/TransactionDetailsRow.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { ChainImage } from '@/components/coin-icon/ChainImage'; +import { Box, Inline, Text } from '@/design-system'; + +import { DetailIcon, DetailBadge, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { SMALL_CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { DetailType, DetailInfo } from '@/components/Transactions/types'; +import { ChainId } from '@/networks/types'; + +interface TransactionDetailsRowProps { + chainId?: ChainId; + detailType: DetailType; + onPress?: () => void; + value: string; +} + +export const TransactionDetailsRow = ({ chainId, detailType, onPress, value }: TransactionDetailsRowProps) => { + const detailInfo: DetailInfo = infoForDetailType[detailType]; + + return ( + + + + + + {detailInfo.label} + + + + {detailType === 'function' && } + {detailType === 'sourceCodeVerification' && ( + + )} + {detailType === 'chain' && chainId && } + {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( + + {value} + + )} + {(detailType === 'contract' || detailType === 'to') && ( + + + + + 􀂄 + + + + + )} + + + + ); +}; + +const infoForDetailType: { [key: string]: DetailInfo } = { + chain: { + icon: '􀤆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), + }, + contract: { + icon: '􀉆', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), + }, + to: { + icon: '􀉩', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), + }, + function: { + icon: '􀡅', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), + }, + sourceCodeVerification: { + icon: '􀕹', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), + }, + dateCreated: { + icon: '􀉉', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), + }, + nonce: { + icon: '􀆃', + label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), + }, +}; diff --git a/src/components/Transactions/TransactionIcons.tsx b/src/components/Transactions/TransactionIcons.tsx new file mode 100644 index 00000000000..53ef21fcd6e --- /dev/null +++ b/src/components/Transactions/TransactionIcons.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { AnimatePresence, MotiView } from 'moti'; + +import { Bleed, Box, Text, globalColors, useForegroundColor } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; +import { infoForEventType, motiTimingConfig } from '@/components/Transactions/constants'; + +import { useTheme } from '@/theme'; +import { DetailInfo, EventInfo, EventType } from '@/components/Transactions/types'; + +export const EventIcon = ({ eventType }: { eventType: EventType }) => { + const eventInfo: EventInfo = infoForEventType[eventType]; + + const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; + const isWarningIcon = + eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; + + return ( + + {!hideInnerFill && ( + + )} + + {eventInfo.icon} + + + ); +}; + +export const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { + return ( + + + {detailInfo.icon} + + + ); +}; + +export const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { + const { colors, isDarkMode } = useTheme(); + const separatorTertiary = useForegroundColor('separatorTertiary'); + + const infoForBadgeType: { + [key: string]: { + backgroundColor: string; + borderColor: string; + label?: string; + text: TextColor; + textOpacity?: number; + }; + } = { + function: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + text: 'labelQuaternary', + }, + unknown: { + backgroundColor: 'transparent', + borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), + label: 'Unknown', + text: 'labelQuaternary', + }, + unverified: { + backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, + borderColor: colors.alpha(colors.red, 0.02), + label: 'Unverified', + text: 'red', + textOpacity: 0.76, + }, + verified: { + backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, + borderColor: colors.alpha(colors.green, 0.02), + label: 'Verified', + text: 'green', + textOpacity: 0.76, + }, + }; + + return ( + + + + {infoForBadgeType[type].label || value} + + + + ); +}; + +export const VerifiedBadge = () => { + return ( + + + + + 􀇻 + + + + ); +}; + +export const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { + return ( + + {visible && ( + + + + + + 􀁣 + + + + + )} + + ); +}; + +export const IconContainer = ({ + children, + hitSlop, + opacity, + size = 20, +}: { + children: React.ReactNode; + hitSlop?: number; + opacity?: number; + size?: number; +}) => { + // Prevent wide icons from being clipped + const extraHorizontalSpace = 4; + + return ( + + + {children} + + + ); +}; diff --git a/src/components/Transactions/TransactionMessageCard.tsx b/src/components/Transactions/TransactionMessageCard.tsx new file mode 100644 index 00000000000..3e946bf6d6d --- /dev/null +++ b/src/components/Transactions/TransactionMessageCard.tsx @@ -0,0 +1,113 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import * as i18n from '@/languages'; +import { TouchableWithoutFeedback } from 'react-native'; +import { useSharedValue } from 'react-native-reanimated'; + +import { ButtonPressAnimation } from '@/components/animations'; +import { Bleed, Box, Inline, Stack, Text } from '@/design-system'; + +import { useClipboard } from '@/hooks'; +import { logger } from '@/logger'; +import { isSignTypedData } from '@/utils/signingMethods'; + +import { RPCMethod } from '@/walletConnect/types'; +import { sanitizeTypedData } from '@/utils/signingUtils'; +import { + estimateMessageHeight, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, +} from '@/components/Transactions/constants'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { AnimatedCheckmark, IconContainer } from '@/components/Transactions/TransactionIcons'; + +type TransactionMessageCardProps = { + expandedCardBottomInset: number; + message: string; + method: RPCMethod; +}; + +export const TransactionMessageCard = ({ expandedCardBottomInset, message, method }: TransactionMessageCardProps) => { + const { setClipboard } = useClipboard(); + const [didCopy, setDidCopy] = useState(false); + + let displayMessage = message; + if (isSignTypedData(method)) { + try { + const parsedMessage = JSON.parse(message); + const sanitizedMessage = sanitizeTypedData(parsedMessage); + displayMessage = sanitizedMessage; + } catch (error) { + logger.warn(`[TransactionMessageCard]: Error parsing signed typed data for ${method}`, { + error, + }); + } + + displayMessage = JSON.stringify(displayMessage, null, 4); + } + + const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); + + const cardHeight = useSharedValue( + estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 + ); + const contentHeight = useSharedValue(estimatedMessageHeight); + + const handleCopyPress = useCallback( + (message: string) => { + if (didCopy) return; + setClipboard(message); + setDidCopy(true); + const copyTimer = setTimeout(() => { + setDidCopy(false); + }, 2000); + return () => clearTimeout(copyTimer); + }, + [didCopy, setClipboard] + ); + + return ( + MAX_CARD_HEIGHT} + isExpanded + skipCollapsedState + > + + + + + + 􀙤 + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} + + + + handleCopyPress(message)}> + + + + + + {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} + + + + + + + + + {displayMessage} + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulatedEventRow.tsx b/src/components/Transactions/TransactionSimulatedEventRow.tsx new file mode 100644 index 00000000000..ce4d26052eb --- /dev/null +++ b/src/components/Transactions/TransactionSimulatedEventRow.tsx @@ -0,0 +1,109 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import { Image, PixelRatio } from 'react-native'; + +import { Bleed, Box, Inline, Text } from '@/design-system'; + +import { useTheme } from '@/theme'; +import { TransactionAssetType, TransactionSimulationAsset } from '@/graphql/__generated__/metadataPOST'; +import { Network } from '@/networks/types'; +import { convertAmountToNativeDisplay, convertRawAmountToBalance } from '@/helpers/utilities'; + +import { useAccountSettings } from '@/hooks'; + +import { maybeSignUri } from '@/handlers/imgix'; +import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; +import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; +import { EventInfo, EventType } from '@/components/Transactions/types'; +import { infoForEventType, CARD_ROW_HEIGHT } from '@/components/Transactions/constants'; +import { EventIcon } from '@/components/Transactions/TransactionIcons'; +import { ethereumUtils } from '@/utils'; + +type TransactionSimulatedEventRowProps = { + amount: string | 'unlimited'; + asset: TransactionSimulationAsset | undefined; + eventType: EventType; + price?: number | undefined; +}; + +export const TransactionSimulatedEventRow = ({ amount, asset, eventType, price }: TransactionSimulatedEventRowProps) => { + const theme = useTheme(); + const { nativeCurrency } = useAccountSettings(); + + const chainId = ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet); + + const { data: externalAsset } = useExternalToken({ + address: asset?.assetCode || '', + chainId, + currency: nativeCurrency, + }); + + const eventInfo: EventInfo = infoForEventType[eventType]; + + const formattedAmount = useMemo(() => { + if (!asset) return; + + const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; + const assetDisplayName = + asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; + const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; + + const displayAmount = + asset?.decimals === 0 + ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` + : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; + + const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; + + return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; + }, [amount, asset, eventInfo?.amountPrefix]); + + const url = maybeSignUri(asset?.iconURL, { + fm: 'png', + w: 16 * PixelRatio.get(), + }); + + const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; + + const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); + + return ( + + + + + + + {eventInfo.label} + + {showUSD && ( + + {formattedPrice} + + )} + + + + + {asset?.type !== TransactionAssetType.Nft ? ( + + ) : ( + + )} + + + {formattedAmount} + + + + + ); +}; diff --git a/src/components/Transactions/TransactionSimulationCard.tsx b/src/components/Transactions/TransactionSimulationCard.tsx new file mode 100644 index 00000000000..e491aeaf6a7 --- /dev/null +++ b/src/components/Transactions/TransactionSimulationCard.tsx @@ -0,0 +1,314 @@ +import React, { useMemo } from 'react'; +import * as i18n from '@/languages'; +import Animated, { + interpolate, + useAnimatedReaction, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; + +import { Box, Inline, Stack, Text } from '@/design-system'; +import { TextColor } from '@/design-system/color/palettes'; + +import { TransactionErrorType, TransactionSimulationResult, TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; + +import { getNetworkObject } from '@/networks'; +import { isEmpty } from 'lodash'; +import { TransactionSimulatedEventRow } from '@/components/Transactions/TransactionSimulatedEventRow'; +import { FadedScrollCard } from '@/components/FadedScrollCard'; +import { EventIcon, IconContainer } from '@/components/Transactions/TransactionIcons'; +import { + COLLAPSED_CARD_HEIGHT, + MAX_CARD_HEIGHT, + CARD_ROW_HEIGHT, + CARD_BORDER_WIDTH, + EXPANDED_CARD_TOP_INSET, + rotationConfig, + timingConfig, +} from '@/components/Transactions/constants'; +import { ChainId } from '@/networks/types'; + +interface TransactionSimulationCardProps { + chainId: ChainId; + expandedCardBottomInset: number; + isBalanceEnough: boolean | undefined; + isLoading: boolean; + txSimulationApiError: unknown; + isPersonalSignRequest: boolean; + noChanges: boolean; + simulation: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; + walletBalance: { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; + }; +} + +export const TransactionSimulationCard = ({ + chainId, + expandedCardBottomInset, + isBalanceEnough, + isLoading, + txSimulationApiError, + isPersonalSignRequest, + noChanges, + simulation, + simulationError, + simulationScanResult, + walletBalance, +}: TransactionSimulationCardProps) => { + const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); + const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); + const spinnerRotation = useSharedValue(0); + + const listStyle = useAnimatedStyle(() => ({ + opacity: noChanges + ? withTiming(1, timingConfig) + : interpolate( + cardHeight.value, + [ + COLLAPSED_CARD_HEIGHT, + contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, + ], + [0, 1] + ), + })); + + const spinnerStyle = useAnimatedStyle(() => { + return { + transform: [{ rotate: `${spinnerRotation.value}deg` }], + }; + }); + + useAnimatedReaction( + () => ({ isLoading, isPersonalSignRequest }), + ({ isLoading, isPersonalSignRequest }, previous = { isLoading: false, isPersonalSignRequest: false }) => { + if (isLoading && !previous?.isLoading) { + spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); + } else if ( + (!isLoading && previous?.isLoading) || + (isPersonalSignRequest && !previous?.isPersonalSignRequest && previous?.isLoading) + ) { + spinnerRotation.value = withTiming(360, timingConfig); + } + }, + [isLoading, isPersonalSignRequest] + ); + const renderSimulationEventRows = useMemo(() => { + if (isBalanceEnough === false) return null; + + return ( + <> + {simulation?.approvals?.map(change => { + return ( + + ); + })} + {simulation?.out?.map(change => { + return ( + + ); + })} + {simulation?.in?.map(change => { + return ( + + ); + })} + + ); + }, [isBalanceEnough, simulation]); + + const titleColor: TextColor = useMemo(() => { + if (isLoading) { + return 'label'; + } + if (isBalanceEnough === false) { + return 'blue'; + } + if (noChanges || isPersonalSignRequest || txSimulationApiError) { + return 'labelQuaternary'; + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return 'orange'; + } + if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { + return 'red'; + } + return 'label'; + }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, isPersonalSignRequest, txSimulationApiError]); + + const titleText = useMemo(() => { + if (isLoading) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); + } + if (isBalanceEnough === false) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); + } + if (txSimulationApiError || isPersonalSignRequest) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); + } + if (simulationScanResult === TransactionScanResultType.Warning) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); + } + if (simulationScanResult === TransactionScanResultType.Malicious) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); + } + if (noChanges) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); + } + if (simulationError) { + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); + } + return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); + }, [ + isBalanceEnough, + isLoading, + noChanges, + simulationError, + simulationScanResult, + isPersonalSignRequest, + txSimulationApiError, + walletBalance?.symbol, + ]); + + const isExpanded = useMemo(() => { + if (isLoading || isPersonalSignRequest) { + return false; + } + const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; + return shouldExpandOnLoad; + }, [isBalanceEnough, isLoading, isPersonalSignRequest, noChanges, simulation, simulationError]); + + return ( + + + + + {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( + + ) : ( + + {!isLoading && noChanges && !isPersonalSignRequest ? ( + + {/* The extra space avoids icon clipping */} + {'􀻾 '} + + ) : ( + + + 􀬨 + + + )} + + )} + + {titleText} + + + {/* TODO: Unhide once we add explainer sheets */} + {/* + + + + + 􀁜 + + + + + */} + + + + {isBalanceEnough === false ? ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { + symbol: walletBalance?.symbol, + network: getNetworkObject({ chainId }).name, + })} + + ) : ( + <> + {isPersonalSignRequest && ( + + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} + + + )} + {txSimulationApiError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.tx_api_error)} + + )} + {simulationError && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} + + )} + {simulationScanResult === TransactionScanResultType.Warning && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} + + )} + {simulationScanResult === TransactionScanResultType.Malicious && ( + + {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} + + )} + + )} + {renderSimulationEventRows} + + + + + ); +}; diff --git a/src/components/Transactions/constants.ts b/src/components/Transactions/constants.ts new file mode 100644 index 00000000000..79e6e0d8df5 --- /dev/null +++ b/src/components/Transactions/constants.ts @@ -0,0 +1,121 @@ +import * as i18n from '@/languages'; +import { Screens } from '@/state/performance/operations'; +import { safeAreaInsetValues } from '@/utils'; +import { TransitionConfig } from 'moti'; +import { Easing } from 'react-native-reanimated'; +import { EventInfo } from '@/components/Transactions/types'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; + +export const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; +export const GAS_BUTTON_SPACE = + 30 + // GasSpeedButton height + 24; // Between GasSpeedButton and bottom of sheet + +export const EXPANDED_CARD_BOTTOM_INSET = + SCREEN_BOTTOM_INSET + + 24 + // Between bottom of sheet and bottom of Cancel/Confirm + 52 + // Cancel/Confirm height + 24 + // Between Cancel/Confirm and wallet avatar row + 44 + // Wallet avatar row height + 24; // Between wallet avatar row and bottom of expandable area + +export const COLLAPSED_CARD_HEIGHT = 56; +export const MAX_CARD_HEIGHT = 176; + +export const CARD_ROW_HEIGHT = 12; +export const SMALL_CARD_ROW_HEIGHT = 10; +export const CARD_BORDER_WIDTH = 1.5; + +export const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; + +export const rotationConfig = { + duration: 2100, + easing: Easing.linear, +}; + +export const timingConfig = { + duration: 300, + easing: Easing.bezier(0.2, 0, 0, 1), +}; + +export const motiTimingConfig: TransitionConfig = { + duration: 225, + easing: Easing.bezier(0.2, 0, 0, 1), + type: 'timing', +}; + +export const SCREEN_FOR_REQUEST_SOURCE = { + [RequestSource.BROWSER]: Screens.DAPP_BROWSER, + [RequestSource.WALLETCONNECT]: Screens.WALLETCONNECT, + [RequestSource.MOBILE_WALLET_PROTOCOL]: Screens.MOBILE_WALLET_PROTOCOL, +}; + +export const CHARACTERS_PER_LINE = 40; +export const LINE_HEIGHT = 11; +export const LINE_GAP = 9; + +export const estimateMessageHeight = (message: string) => { + const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); + const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; + + return messageHeight; +}; + +export const infoForEventType: { [key: string]: EventInfo } = { + send: { + amountPrefix: '- ', + icon: '􀁷', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), + textColor: 'red', + }, + receive: { + amountPrefix: '+ ', + icon: '􀁹', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), + textColor: 'green', + }, + approve: { + amountPrefix: '', + icon: '􀎤', + iconColor: 'green', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), + textColor: 'label', + }, + revoke: { + amountPrefix: '', + icon: '􀎠', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), + textColor: 'label', + }, + failed: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), + textColor: 'red', + }, + insufficientBalance: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'blue', + label: '', + textColor: 'blue', + }, + MALICIOUS: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'red', + label: '', + textColor: 'red', + }, + WARNING: { + amountPrefix: '', + icon: '􀇿', + iconColor: 'orange', + label: '', + textColor: 'orange', + }, +}; diff --git a/src/components/Transactions/types.ts b/src/components/Transactions/types.ts new file mode 100644 index 00000000000..ce9eaa8b3c3 --- /dev/null +++ b/src/components/Transactions/types.ts @@ -0,0 +1,18 @@ +import { TextColor } from '@/design-system/color/palettes'; + +export type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; + +export type EventInfo = { + amountPrefix: string; + icon: string; + iconColor: TextColor; + label: string; + textColor: TextColor; +}; + +export type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; + +export type DetailInfo = { + icon: string; + label: string; +}; diff --git a/src/handlers/assets.ts b/src/handlers/assets.ts index 164ae1c436a..2df864afce7 100644 --- a/src/handlers/assets.ts +++ b/src/handlers/assets.ts @@ -9,7 +9,7 @@ export function isNativeAsset(address: string, chainId: ChainId) { return getNetworkObject({ chainId }).nativeCurrency.address.toLowerCase() === address?.toLowerCase(); } -export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: any, provider: any) { +export async function getOnchainAssetBalance({ address, decimals, symbol }: any, userAddress: any, chainId: ChainId, provider: any) { // Check if it's the native chain asset if (isNativeAsset(address, chainId)) { return getOnchainNativeAssetBalance({ decimals, symbol }, userAddress, provider); diff --git a/src/handlers/deeplinks.ts b/src/handlers/deeplinks.ts index c8914e691bd..33316a8c0a0 100644 --- a/src/handlers/deeplinks.ts +++ b/src/handlers/deeplinks.ts @@ -19,6 +19,13 @@ import { FiatProviderName } from '@/entities/f2c'; import { getPoapAndOpenSheetWithQRHash, getPoapAndOpenSheetWithSecretWord } from '@/utils/poaps'; import { queryClient } from '@/react-query'; import { pointsReferralCodeQueryKey } from '@/resources/points'; +import { useMobileWalletProtocolHost } from '@coinbase/mobile-wallet-protocol-host'; +import { InitialRoute } from '@/navigation/initialRoute'; + +interface DeeplinkHandlerProps extends Pick, 'handleRequestUrl' | 'sendFailureToClient'> { + url: string; + initialRoute: InitialRoute; +} /* * You can test these deeplinks with the following command: @@ -26,7 +33,7 @@ import { pointsReferralCodeQueryKey } from '@/resources/points'; * `xcrun simctl openurl booted "https://link.rainbow.me/0x123"` */ -export default async function handleDeeplink(url: string, initialRoute: any = null) { +export default async function handleDeeplink({ url, initialRoute, handleRequestUrl, sendFailureToClient }: DeeplinkHandlerProps) { if (!url) { logger.warn(`[handleDeeplink]: No url provided`); return; @@ -200,6 +207,16 @@ export default async function handleDeeplink(url: string, initialRoute: any = nu break; } + case 'wsegue': { + const response = await handleRequestUrl(url); + if (response.error) { + // Return error to client app if session is expired or invalid + const { errorMessage, decodedRequest } = response.error; + await sendFailureToClient(errorMessage, decodedRequest); + } + break; + } + default: { logger.debug(`[handleDeeplink]: default`, { url }); const addressOrENS = pathname?.split('/profile/')?.[1] ?? pathname?.split('/')?.[1]; diff --git a/src/helpers/accountInfo.ts b/src/helpers/accountInfo.ts index d694523cb71..be67dab7d58 100644 --- a/src/helpers/accountInfo.ts +++ b/src/helpers/accountInfo.ts @@ -1,3 +1,4 @@ +import { RainbowAccount } from '@/model/wallet'; import { removeFirstEmojiFromString, returnStringFirstEmoji } from '../helpers/emojiHandler'; import { address } from '../utils/abbreviations'; import { addressHashedEmoji, isValidImagePath } from '../utils/profileUtils'; @@ -17,8 +18,10 @@ export function getAccountProfileInfo(selectedWallet: any, walletNames: any, acc const accountENS = walletNames?.[accountAddress]; - const selectedAccount = selectedWallet.addresses?.find((account: any) => account.address === accountAddress); - + const lowerCaseAccountAddress = accountAddress.toLowerCase(); + const selectedAccount = selectedWallet.addresses?.find( + (account: RainbowAccount) => account.address?.toLowerCase() === lowerCaseAccountAddress + ); if (!selectedAccount) { return {}; } diff --git a/src/helpers/dappNameHandler.ts b/src/helpers/dappNameHandler.ts index b679aebd122..a3c06dd4c66 100644 --- a/src/helpers/dappNameHandler.ts +++ b/src/helpers/dappNameHandler.ts @@ -5,6 +5,9 @@ export const getDappHostname = memoFn(url => { // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. const urlObject = new URL(url); let hostname; + if (!urlObject.hostname) { + return null; + } const subdomains = urlObject.hostname.split('.'); if (subdomains.length === 2) { hostname = urlObject.hostname; diff --git a/src/helpers/findWalletWithAccount.ts b/src/helpers/findWalletWithAccount.ts index 109371d2b82..21de1be494e 100644 --- a/src/helpers/findWalletWithAccount.ts +++ b/src/helpers/findWalletWithAccount.ts @@ -1,11 +1,12 @@ -import { RainbowWallet } from '@/model/wallet'; +import { RainbowAccount, RainbowWallet } from '@/model/wallet'; export function findWalletWithAccount(wallets: { [key: string]: RainbowWallet }, accountAddress: string): RainbowWallet | undefined { const sortedKeys = Object.keys(wallets).sort(); let walletWithAccount: RainbowWallet | undefined; + const lowerCaseAccountAddress = accountAddress.toLowerCase(); sortedKeys.forEach(key => { const wallet = wallets[key]; - const found = wallet.addresses?.find((account: any) => account.address === accountAddress); + const found = wallet.addresses?.find((account: RainbowAccount) => account.address?.toLowerCase() === lowerCaseAccountAddress); if (found) { walletWithAccount = wallet; } diff --git a/src/hooks/useApplicationSetup.ts b/src/hooks/useApplicationSetup.ts new file mode 100644 index 00000000000..0ce196b74b8 --- /dev/null +++ b/src/hooks/useApplicationSetup.ts @@ -0,0 +1,56 @@ +import { useCallback, useEffect, useState } from 'react'; +import { logger, RainbowError } from '@/logger'; +import { InteractionManager } from 'react-native'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { ReviewPromptAction } from '@/storage/schema'; +import { loadAddress } from '@/model/wallet'; +import { InitialRoute } from '@/navigation/initialRoute'; +import { PerformanceContextMap } from '@/performance/PerformanceContextMap'; +import Routes from '@/navigation/routesNames'; +import { checkIdentifierOnLaunch } from '@/model/backup'; +import { analyticsV2 } from '@/analytics'; +import { saveFCMToken } from '@/notifications/tokens'; +import { initListeners as initWalletConnectListeners, initWalletConnectPushNotifications } from '@/walletConnect'; +import isTestFlight from '@/helpers/isTestFlight'; +import { PerformanceTracking } from '@/performance/tracking'; +import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics'; + +export function useApplicationSetup() { + const [initialRoute, setInitialRoute] = useState(null); + + const identifyFlow = useCallback(async () => { + const address = await loadAddress(); + if (address) { + setTimeout(() => { + InteractionManager.runAfterInteractions(() => { + handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); + }); + }, 10_000); + + InteractionManager.runAfterInteractions(checkIdentifierOnLaunch); + } + + setInitialRoute(address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + PerformanceContextMap.set('initialRoute', address ? Routes.SWIPE_LAYOUT : Routes.WELCOME_SCREEN); + }, []); + + useEffect(() => { + if (!IS_DEV && isTestFlight) { + logger.debug(`[App]: Test flight usage - ${isTestFlight}`); + } + identifyFlow(); + initWalletConnectListeners(); + + Promise.all([analyticsV2.initializeRudderstack(), saveFCMToken()]) + .catch(error => { + logger.error(new RainbowError('Failed to initialize rudderstack or save FCM token', error)); + }) + .finally(() => { + initWalletConnectPushNotifications(); + PerformanceTracking.finishMeasuring(PerformanceMetrics.loadRootAppComponent); + analyticsV2.track(analyticsV2.event.applicationDidMount); + }); + }, [identifyFlow]); + + return { initialRoute }; +} diff --git a/src/hooks/useCalculateGasLimit.ts b/src/hooks/useCalculateGasLimit.ts new file mode 100644 index 00000000000..1ae0800caae --- /dev/null +++ b/src/hooks/useCalculateGasLimit.ts @@ -0,0 +1,74 @@ +import { useEffect, useRef, useCallback } from 'react'; +import { estimateGas, web3Provider, toHex } from '@/handlers/web3'; +import { convertHexToString, omitFlatten } from '@/helpers/utilities'; +import { logger, RainbowError } from '@/logger'; +import { getNetworkObject } from '@/networks'; +import { ethereumUtils } from '@/utils'; +import { hexToNumber, isHex } from 'viem'; +import { isEmpty } from 'lodash'; +import { InteractionManager } from 'react-native'; +import { GasFeeParamsBySpeed } from '@/entities'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { useGas } from '@/hooks'; +import { ChainId } from '@/networks/types'; + +type CalculateGasLimitProps = { + isMessageRequest: boolean; + gasFeeParamsBySpeed: GasFeeParamsBySpeed; + provider: StaticJsonRpcProvider | null; + req: any; + updateTxFee: ReturnType['updateTxFee']; + chainId: ChainId; +}; + +export const useCalculateGasLimit = ({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + chainId, +}: CalculateGasLimitProps) => { + const calculatingGasLimit = useRef(false); + + const calculateGasLimit = useCallback(async () => { + calculatingGasLimit.current = true; + const txPayload = req; + if (isHex(txPayload?.type)) { + txPayload.type = hexToNumber(txPayload?.type); + } + let gas = txPayload.gasLimit || txPayload.gas; + + try { + logger.debug('WC: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); + const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); + const rawGasLimit = await estimateGas(cleanTxPayload, provider); + logger.debug('WC: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); + if (rawGasLimit) { + gas = toHex(rawGasLimit); + } + } catch (error) { + logger.error(new RainbowError('WC: error estimating gas'), { error }); + } finally { + logger.debug('WC: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); + + const networkObject = getNetworkObject({ chainId }); + if (chainId && networkObject.gas.OptimismTxFee) { + const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider || web3Provider); + updateTxFee(gas, null, l1GasFeeOptimism); + } else { + updateTxFee(gas, null); + } + } + }, [chainId, req, updateTxFee, provider]); + + useEffect(() => { + if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { + InteractionManager.runAfterInteractions(() => { + calculateGasLimit(); + }); + } + }, [calculateGasLimit, gasFeeParamsBySpeed, isMessageRequest, provider]); + + return { calculateGasLimit }; +}; diff --git a/src/hooks/useConfirmTransaction.ts b/src/hooks/useConfirmTransaction.ts new file mode 100644 index 00000000000..6600cead84b --- /dev/null +++ b/src/hooks/useConfirmTransaction.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; + +type UseConfirmTransactionProps = { + isMessageRequest: boolean; + isBalanceEnough: boolean | undefined; + isValidGas: boolean; + handleSignMessage: () => void; + handleConfirmTransaction: () => void; +}; + +export const useConfirmTransaction = ({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, +}: UseConfirmTransactionProps) => { + const onConfirm = useCallback(async () => { + if (isMessageRequest) { + return handleSignMessage(); + } + if (!isBalanceEnough || !isValidGas) return; + return handleConfirmTransaction(); + }, [isMessageRequest, isBalanceEnough, isValidGas, handleConfirmTransaction, handleSignMessage]); + + return { onConfirm }; +}; diff --git a/src/hooks/useHasEnoughBalance.ts b/src/hooks/useHasEnoughBalance.ts new file mode 100644 index 00000000000..4217091802c --- /dev/null +++ b/src/hooks/useHasEnoughBalance.ts @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { fromWei, greaterThanOrEqualTo } from '@/helpers/utilities'; +import BigNumber from 'bignumber.js'; +import { SelectedGasFee } from '@/entities'; +import { ChainId } from '@/networks/types'; + +type WalletBalance = { + amount: string | number; + display: string; + isLoaded: boolean; + symbol: string; +}; + +type BalanceCheckParams = { + isMessageRequest: boolean; + walletBalance: WalletBalance; + chainId: ChainId; + selectedGasFee: SelectedGasFee; + req: any; +}; + +export const useHasEnoughBalance = ({ isMessageRequest, walletBalance, chainId, selectedGasFee, req }: BalanceCheckParams) => { + const [isBalanceEnough, setIsBalanceEnough] = useState(); + + useEffect(() => { + if (isMessageRequest) { + setIsBalanceEnough(true); + return; + } + + const { gasFee } = selectedGasFee; + if (!walletBalance.isLoaded || !chainId || !gasFee?.estimatedFee) { + return; + } + + const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const balanceAmount = walletBalance.amount; + const value = req?.value ?? 0; + + const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); + const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + + setIsBalanceEnough(isEnough); + }, [isMessageRequest, chainId, selectedGasFee, walletBalance, req]); + + return { isBalanceEnough }; +}; diff --git a/src/hooks/useNonceForDisplay.ts b/src/hooks/useNonceForDisplay.ts new file mode 100644 index 00000000000..ca7b302bedb --- /dev/null +++ b/src/hooks/useNonceForDisplay.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { getNextNonce } from '@/state/nonces'; +import { ChainId } from '@/networks/types'; +import { logger, RainbowError } from '@/logger'; + +type UseNonceParams = { + isMessageRequest: boolean; + address: string; + chainId: ChainId; +}; + +export const useNonceForDisplay = ({ isMessageRequest, address, chainId }: UseNonceParams) => { + const [nonceForDisplay, setNonceForDisplay] = useState(); + + useEffect(() => { + if (!isMessageRequest && !nonceForDisplay) { + (async () => { + try { + const nonce = await getNextNonce({ address, chainId }); + if (nonce || nonce === 0) { + const nonceAsString = nonce.toString(); + setNonceForDisplay(nonceAsString); + } + } catch (error) { + logger.error(new RainbowError(`[useNonceForDisplay]: Failed to get nonce for display: ${error}`)); + } + })(); + } + }, [address, chainId, isMessageRequest, nonceForDisplay]); + + return { nonceForDisplay }; +}; diff --git a/src/hooks/useProviderSetup.ts b/src/hooks/useProviderSetup.ts new file mode 100644 index 00000000000..bd1daa5b854 --- /dev/null +++ b/src/hooks/useProviderSetup.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; +import { getFlashbotsProvider, getProvider } from '@/handlers/web3'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { ethereumUtils } from '@/utils'; +import { getOnchainAssetBalance } from '@/handlers/assets'; +import { ParsedAddressAsset } from '@/entities'; +import { ChainId } from '@/networks/types'; + +export const useProviderSetup = (chainId: ChainId, address: string) => { + const [provider, setProvider] = useState(null); + const [nativeAsset, setNativeAsset] = useState(null); + + useEffect(() => { + const initProvider = async () => { + let p; + if (chainId === ChainId.mainnet) { + p = await getFlashbotsProvider(); + } else { + p = getProvider({ chainId }); + } + setProvider(p); + }; + initProvider(); + }, [chainId]); + + useEffect(() => { + const fetchNativeAsset = async () => { + if (provider) { + const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId, address }); + if (asset) { + const balance = await getOnchainAssetBalance(asset, address, chainId, provider); + if (balance) { + const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; + setNativeAsset(assetWithOnchainBalance); + } else { + setNativeAsset(asset); + } + } + } + }; + fetchNativeAsset(); + }, [address, chainId, provider]); + + return { provider, nativeAsset }; +}; diff --git a/src/hooks/useSubmitTransaction.ts b/src/hooks/useSubmitTransaction.ts new file mode 100644 index 00000000000..8d6f861ae25 --- /dev/null +++ b/src/hooks/useSubmitTransaction.ts @@ -0,0 +1,56 @@ +import { useCallback, useState } from 'react'; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { SCREEN_FOR_REQUEST_SOURCE } from '@/components/Transactions/constants'; +import { logger, RainbowError } from '@/logger'; + +export const useTransactionSubmission = ({ + isBalanceEnough, + accountInfo, + onConfirm, + source, +}: { + isBalanceEnough: boolean | undefined; + accountInfo: { isHardwareWallet: boolean }; + onConfirm: () => Promise; + source: RequestSource; +}) => { + const [isAuthorizing, setIsAuthorizing] = useState(false); + const { navigate } = useNavigation(); + + const onPressSend = useCallback(async () => { + if (isAuthorizing) return; + try { + setIsAuthorizing(true); + await onConfirm(); + } catch (error) { + logger.error(new RainbowError(`[useTransactionSubmission]: Error while sending transaction: ${error}`)); + } finally { + setIsAuthorizing(false); + } + }, [isAuthorizing, onConfirm]); + + const submitFn = useCallback( + () => + performanceTracking.getState().executeFn({ + fn: async () => { + if (!isBalanceEnough) { + navigate(Routes.ADD_CASH_SHEET); + return; + } + if (accountInfo.isHardwareWallet) { + navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); + } else { + await onPressSend(); + } + }, + operation: TimeToSignOperation.CallToAction, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + })(), + [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] + ); + + return { submitFn, isAuthorizing }; +}; diff --git a/src/hooks/useTransactionSetup.ts b/src/hooks/useTransactionSetup.ts new file mode 100644 index 00000000000..910a53e9da9 --- /dev/null +++ b/src/hooks/useTransactionSetup.ts @@ -0,0 +1,66 @@ +import * as i18n from '@/languages'; +import { useCallback, useEffect, useState } from 'react'; +import { InteractionManager } from 'react-native'; +import useGas from '@/hooks/useGas'; +import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; +import { analytics } from '@/analytics'; +import { event } from '@/analytics/event'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; +import { ChainId } from '@/networks/types'; + +type TransactionSetupParams = { + chainId: ChainId; + startPollingGasFees: ReturnType['startPollingGasFees']; + stopPollingGasFees: ReturnType['stopPollingGasFees']; + isMessageRequest: boolean; + transactionDetails: any; + source: RequestSource; +}; + +export const useTransactionSetup = ({ + chainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, +}: TransactionSetupParams) => { + const [methodName, setMethodName] = useState(null); + + const fetchMethodName = useCallback( + async (data: string) => { + const methodSignaturePrefix = data.substr(0, 10); + try { + const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, chainId); + if (name) { + setMethodName(name); + } + } catch (e) { + setMethodName(data); + } + }, + [chainId] + ); + + useEffect(() => { + InteractionManager.runAfterInteractions(() => { + if (chainId) { + if (!isMessageRequest) { + startPollingGasFees(chainId); + fetchMethodName(transactionDetails?.payload?.params?.[0].data); + } else { + setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); + } + analytics.track(event.txRequestShownSheet, { source }); + } + }); + + return () => { + if (!isMessageRequest) { + stopPollingGasFees(); + } + }; + }, [isMessageRequest, chainId, transactionDetails?.payload?.params, source, fetchMethodName, startPollingGasFees, stopPollingGasFees]); + + return { methodName }; +}; diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 7ca6238f554..7b76a8919ef 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2841,6 +2841,7 @@ "unavailable_personal_sign": "Simulation for personal signs is not yet supported", "unavailable_zora_network": "Simulation on Zora is not yet supported", "failed_to_simulate": "The simulation failed, which suggests your transaction is likely to fail. This may be an issue with the app you’re using.", + "tx_api_error": "We are unable to determine whether or not your transaction will succeed or fail. Proceed with caution.", "warning": "No known malicious behavior was detected, but this transaction has characteristics that may pose a risk to your wallet.", "malicious": "Signing this transaction could result in losing access to everything in your wallet." }, diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx index 39d424b8c06..007652f2302 100644 --- a/src/navigation/Routes.android.tsx +++ b/src/navigation/Routes.android.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -90,6 +90,7 @@ import { SwapScreen } from '@/__swaps__/screens/Swap/Swap'; import { useRemoteConfig } from '@/model/remoteConfig'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const OuterStack = createStackNavigator(); @@ -269,25 +270,13 @@ function AuthNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx index c468af96ecb..4f1a8e96407 100644 --- a/src/navigation/Routes.ios.tsx +++ b/src/navigation/Routes.ios.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import { NavigationContainer } from '@react-navigation/native'; +import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import React, { useContext } from 'react'; import { AddCashSheet } from '../screens/AddCash'; @@ -104,6 +104,7 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import CheckIdentifierScreen from '@/screens/CheckIdentifierScreen'; import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPanel'; import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel'; +import { RootStackParamList } from './types'; const Stack = createStackNavigator(); const NativeStack = createNativeStackNavigator(); @@ -292,25 +293,13 @@ function NativeStackNavigator() { ); } -const AppContainerWithAnalytics = React.forwardRef( - ( - props: { - onReady: () => void; - }, - ref - ) => ( - - - - - - ) -); +const AppContainerWithAnalytics = React.forwardRef, { onReady: () => void }>((props, ref) => ( + + + + + +)); AppContainerWithAnalytics.displayName = 'AppContainerWithAnalytics'; diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx index 182550aa6c8..19663d09165 100644 --- a/src/navigation/config.tsx +++ b/src/navigation/config.tsx @@ -27,6 +27,7 @@ import { BottomSheetNavigationOptions } from '@/navigation/bottom-sheet/types'; import { Box } from '@/design-system'; import { IS_ANDROID } from '@/env'; import { SignTransactionSheetRouteProp } from '@/screens/SignTransactionSheet'; +import { RequestSource } from '@/utils/requestNavigationHandlers'; import { ChainId, chainIdToNameMapping } from '@/networks/types'; export const sharedCoolModalTopOffset = safeAreaInsetValues.top; @@ -276,7 +277,7 @@ export const signTransactionSheetConfig = { options: ({ route }: { route: SignTransactionSheetRouteProp }) => ({ ...buildCoolModalConfig({ ...route.params, - backgroundOpacity: route?.params?.source === 'walletconnect' ? 1 : 0.7, + backgroundOpacity: route?.params?.source === RequestSource.WALLETCONNECT ? 1 : 0.7, cornerRadius: 0, springDamping: 1, topOffset: 0, diff --git a/src/notifications/tokens.ts b/src/notifications/tokens.ts index 8c88b9ee1bf..4bc8b78a88d 100644 --- a/src/notifications/tokens.ts +++ b/src/notifications/tokens.ts @@ -2,7 +2,7 @@ import messaging from '@react-native-firebase/messaging'; import { getLocal, saveLocal } from '@/handlers/localstorage/common'; import { getPermissionStatus } from '@/notifications/permissions'; -import { logger, RainbowError } from '@/logger'; +import { logger } from '@/logger'; export const registerTokenRefreshListener = () => messaging().onTokenRefresh(fcmToken => { diff --git a/src/parsers/requests.js b/src/parsers/requests.js index 3bf47986390..9b8747334d8 100644 --- a/src/parsers/requests.js +++ b/src/parsers/requests.js @@ -9,7 +9,7 @@ import { isSignTypedData, SIGN, PERSONAL_SIGN, SEND_TRANSACTION, SIGN_TRANSACTIO import { isAddress } from '@ethersproject/address'; import { toUtf8String } from '@ethersproject/strings'; -export const getRequestDisplayDetails = (payload, nativeCurrency, chainId) => { +export const getRequestDisplayDetails = async (payload, nativeCurrency, chainId) => { const timestampInMs = Date.now(); if (payload.method === SEND_TRANSACTION || payload.method === SIGN_TRANSACTION) { const transaction = Object.assign(payload?.params?.[0] ?? null); @@ -75,9 +75,9 @@ const getMessageDisplayDetails = (message, timestampInMs) => ({ timestampInMs, }); -const getTransactionDisplayDetails = (transaction, nativeCurrency, timestampInMs, chainId) => { +const getTransactionDisplayDetails = async (transaction, nativeCurrency, timestampInMs, chainId) => { const tokenTransferHash = smartContractMethods.token_transfer.hash; - const nativeAsset = ethereumUtils.getNativeAssetForNetwork({ chainId }); + const nativeAsset = await ethereumUtils.getNativeAssetForNetwork({ chainId }); if (transaction.data === '0x') { const value = fromWei(convertHexToString(transaction.value)); const priceUnit = nativeAsset?.price?.value ?? 0; diff --git a/src/redux/requests.ts b/src/redux/requests.ts index a15206fbace..7733af73291 100644 --- a/src/redux/requests.ts +++ b/src/redux/requests.ts @@ -71,7 +71,7 @@ export interface WalletconnectRequestData extends RequestData { /** * Display details loaded for a request. */ -interface RequestDisplayDetails { +export interface RequestDisplayDetails { /** * Data loaded for the request, depending on the type of request. */ @@ -154,7 +154,7 @@ export const addRequestToApprove = icons?: string[]; } ) => - (dispatch: Dispatch, getState: AppGetState) => { + async (dispatch: Dispatch, getState: AppGetState) => { const { requests } = getState().requests; const { walletConnectors } = getState().walletconnect; const { accountAddress, network, nativeCurrency } = getState().settings; @@ -163,7 +163,7 @@ export const addRequestToApprove = const chainId = walletConnector._chainId; // @ts-expect-error "_accounts" is private. const address = walletConnector._accounts[0]; - const displayDetails = getRequestDisplayDetails(payload, nativeCurrency, chainId); + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, chainId); const oneHourAgoTs = Date.now() - EXPIRATION_THRESHOLD_IN_MS; // @ts-expect-error This fails to compile as `displayDetails` does not // always return an object with `timestampInMs`. Still, the error thrown diff --git a/src/redux/walletconnect.ts b/src/redux/walletconnect.ts index f75a286e044..c4658b7e090 100644 --- a/src/redux/walletconnect.ts +++ b/src/redux/walletconnect.ts @@ -561,7 +561,9 @@ const listenOnNewMessages = return; } const { requests: pendingRequests } = getState().requests; - const request = !pendingRequests[requestId] ? dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) : null; + const request = !pendingRequests[requestId] + ? await dispatch(addRequestToApprove(clientId, peerId, requestId, payload, peerMeta)) + : null; if (request) { handleWalletConnectRequest(request); InteractionManager.runAfterInteractions(() => { diff --git a/src/resources/transactions/transactionSimulation.ts b/src/resources/transactions/transactionSimulation.ts new file mode 100644 index 00000000000..c8e0a556ccd --- /dev/null +++ b/src/resources/transactions/transactionSimulation.ts @@ -0,0 +1,146 @@ +import { createQueryKey, QueryConfig, QueryFunctionArgs } from '@/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { RainbowError, logger } from '@/logger'; +import { metadataPOSTClient } from '@/graphql'; +import { TransactionErrorType, TransactionScanResultType, TransactionSimulationResult } from '@/graphql/__generated__/metadataPOST'; +import { isNil } from 'lodash'; +import { RequestData } from '@/redux/requests'; +import { ChainId } from '@/networks/types'; + +type SimulationArgs = { + address: string; + chainId: ChainId; + isMessageRequest: boolean; + nativeCurrency: string; + req: any; // Replace 'any' with the correct type for 'req' + requestMessage: string; + simulationUnavailable: boolean; + transactionDetails: RequestData; +}; + +type SimulationResult = { + simulationData: TransactionSimulationResult | undefined; + simulationError: TransactionErrorType | undefined; + simulationScanResult: TransactionScanResultType | undefined; +}; + +const simulationQueryKey = ({ + address, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, +}: SimulationArgs) => + createQueryKey( + 'txSimulation', + { + address, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage, + simulationUnavailable, + transactionDetails, + }, + { persisterVersion: 1 } + ); + +const fetchSimulation = async ({ + queryKey: [{ address, chainId, isMessageRequest, nativeCurrency, req, requestMessage, simulationUnavailable, transactionDetails }], +}: QueryFunctionArgs): Promise => { + try { + let simulationData; + + if (isMessageRequest) { + simulationData = await metadataPOSTClient.simulateMessage({ + address, + chainId, + message: { + method: transactionDetails?.payload?.method, + params: [requestMessage], + }, + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateMessage?.error?.type, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { + return { + simulationData: simulationData.simulateMessage?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateMessage?.scanning?.result, + }; + } + } else { + simulationData = await metadataPOSTClient.simulateTransactions({ + chainId, + currency: nativeCurrency?.toLowerCase(), + transactions: [ + { + from: req?.from, + to: req?.to, + data: req?.data || '0x', + value: req?.value || '0x0', + }, + ], + domain: transactionDetails?.dappUrl, + }); + + if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { + return { + simulationData: { in: [], out: [], approvals: [] }, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions?.[0]?.scanning?.result, + }; + } else if (simulationData?.simulateTransactions?.[0]?.error) { + return { + simulationData: undefined, + simulationError: simulationData?.simulateTransactions?.[0]?.error?.type, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } else if (simulationData.simulateTransactions?.[0]?.simulation) { + return { + simulationData: simulationData.simulateTransactions[0]?.simulation, + simulationError: undefined, + simulationScanResult: simulationData?.simulateTransactions[0]?.scanning?.result, + }; + } + } + + return { + simulationData: undefined, + simulationError: undefined, + simulationScanResult: undefined, + }; + } catch (error) { + logger.error(new RainbowError('Error while simulating'), { error }); + throw error; + } +}; + +export const useSimulation = ( + args: SimulationArgs, + config: QueryConfig> = {} +) => { + return useQuery(simulationQueryKey(args), fetchSimulation, { + enabled: !!args.address && !!args.chainId, + retry: 3, + refetchOnWindowFocus: false, + staleTime: Infinity, + ...config, + }); +}; diff --git a/src/screens/SignTransactionSheet.tsx b/src/screens/SignTransactionSheet.tsx index 7f48aa81aaa..11be9876981 100644 --- a/src/screens/SignTransactionSheet.tsx +++ b/src/screens/SignTransactionSheet.tsx @@ -1,68 +1,31 @@ -/* eslint-disable no-nested-ternary */ -import BigNumber from 'bignumber.js'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { AnimatePresence, MotiView, TransitionConfig } from 'moti'; +import React, { useCallback, useMemo } from 'react'; +import { AnimatePresence, MotiView } from 'moti'; import * as i18n from '@/languages'; -import { Image, InteractionManager, PixelRatio, ScrollView, StyleProp, TouchableWithoutFeedback, ViewStyle } from 'react-native'; -import LinearGradient from 'react-native-linear-gradient'; -import Animated, { - Easing, - SharedValue, - interpolate, - interpolateColor, - measure, - runOnJS, - runOnUI, - useAnimatedReaction, - useAnimatedRef, - useAnimatedStyle, - useSharedValue, - withRepeat, - withTiming, -} from 'react-native-reanimated'; +import { Image, InteractionManager, PixelRatio, ScrollView } from 'react-native'; +import Animated from 'react-native-reanimated'; import { Transaction } from '@ethersproject/transactions'; -import { ButtonPressAnimation } from '@/components/animations'; import { ChainImage } from '@/components/coin-icon/ChainImage'; import { SheetActionButton } from '@/components/sheet'; import { Bleed, Box, Columns, Inline, Inset, Stack, Text, globalColors, useBackgroundColor, useForegroundColor } from '@/design-system'; -import { TextColor } from '@/design-system/color/palettes'; -import { NewTransaction, ParsedAddressAsset } from '@/entities'; +import { NewTransaction } from '@/entities'; import { useNavigation } from '@/navigation'; import { useTheme } from '@/theme'; -import { abbreviations, deviceUtils, ethereumUtils, safeAreaInsetValues } from '@/utils'; +import { deviceUtils } from '@/utils'; import { PanGestureHandler } from 'react-native-gesture-handler'; import { RouteProp, useRoute } from '@react-navigation/native'; -import { metadataPOSTClient } from '@/graphql'; -import { - TransactionAssetType, - TransactionErrorType, - TransactionSimulationAsset, - TransactionSimulationMeta, - TransactionSimulationResult, - TransactionScanResultType, -} from '@/graphql/__generated__/metadataPOST'; -import { Network, ChainId, chainIdToNameMapping } from '@/networks/types'; -import { - convertAmountToNativeDisplay, - convertHexToString, - convertRawAmountToBalance, - delay, - fromWei, - greaterThan, - greaterThanOrEqualTo, - omitFlatten, -} from '@/helpers/utilities'; +import { TransactionScanResultType } from '@/graphql/__generated__/metadataPOST'; +import { ChainId } from '@/networks/types'; +import { convertHexToString, delay, greaterThan, omitFlatten } from '@/helpers/utilities'; import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { getAccountProfileInfo } from '@/helpers/accountInfo'; -import { useAccountSettings, useClipboard, useDimensions, useGas, useSwitchWallet, useWallets } from '@/hooks'; +import { useAccountSettings, useGas, useSwitchWallet, useWallets } from '@/hooks'; import ImageAvatar from '@/components/contacts/ImageAvatar'; import { ContactAvatar } from '@/components/contacts'; import { IS_IOS } from '@/env'; -import { estimateGas, estimateGasWithPadding, getFlashbotsProvider, getProvider, toHex } from '@/handlers/web3'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { estimateGasWithPadding, getProvider, toHex } from '@/handlers/web3'; import { GasSpeedButton } from '@/components/gas'; import { getNetworkObject } from '@/networks'; import { RainbowError, logger } from '@/logger'; @@ -72,96 +35,60 @@ import { SIGN_TYPED_DATA, SIGN_TYPED_DATA_V4, isMessageDisplayType, - isPersonalSign as checkIsPersonalSign, - isSignTypedData, + isPersonalSign, } from '@/utils/signingMethods'; -import { isEmpty, isNil } from 'lodash'; -import Routes from '@/navigation/routesNames'; +import { isNil } from 'lodash'; import { parseGasParamsForTransaction } from '@/parsers/gas'; import { loadWallet, sendTransaction, signPersonalMessage, signTransaction, signTypedDataMessage } from '@/model/wallet'; import { analyticsV2 as analytics } from '@/analytics'; import { maybeSignUri } from '@/handlers/imgix'; -import { RPCMethod } from '@/walletConnect/types'; import { isAddress } from '@ethersproject/address'; -import { methodRegistryLookupAndParse } from '@/utils/methodRegistry'; -import { sanitizeTypedData } from '@/utils/signingUtils'; import { hexToNumber, isHex } from 'viem'; import { addNewTransaction } from '@/state/pendingTransactions'; import { getNextNonce } from '@/state/nonces'; -import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; -import { useExternalToken } from '@/resources/assets/externalAssetsQuery'; import { RequestData } from '@/redux/requests'; import { RequestSource } from '@/utils/requestNavigationHandlers'; import { event } from '@/analytics/event'; -import { getOnchainAssetBalance } from '@/handlers/assets'; -import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; -import { AddressOrEth } from '@/__swaps__/types/assets'; - -const COLLAPSED_CARD_HEIGHT = 56; -const MAX_CARD_HEIGHT = 176; - -const CARD_ROW_HEIGHT = 12; -const SMALL_CARD_ROW_HEIGHT = 10; -const CARD_BORDER_WIDTH = 1.5; - -const EXPANDED_CARD_TOP_INSET = safeAreaInsetValues.top + 72; -const SCREEN_BOTTOM_INSET = safeAreaInsetValues.bottom + 20; - -const GAS_BUTTON_SPACE = - 30 + // GasSpeedButton height - 24; // Between GasSpeedButton and bottom of sheet - -const EXPANDED_CARD_BOTTOM_INSET = - SCREEN_BOTTOM_INSET + - 24 + // Between bottom of sheet and bottom of Cancel/Confirm - 52 + // Cancel/Confirm height - 24 + // Between Cancel/Confirm and wallet avatar row - 44 + // Wallet avatar row height - 24; // Between wallet avatar row and bottom of expandable area - -const rotationConfig = { - duration: 2100, - easing: Easing.linear, -}; - -const timingConfig = { - duration: 300, - easing: Easing.bezier(0.2, 0, 0, 1), -}; - -const motiTimingConfig: TransitionConfig = { - duration: 225, - easing: Easing.bezier(0.2, 0, 0, 1), - type: 'timing', -}; +import { performanceTracking, TimeToSignOperation } from '@/state/performance/performance'; +import { useSimulation } from '@/resources/transactions/transactionSimulation'; +import { TransactionSimulationCard } from '@/components/Transactions/TransactionSimulationCard'; +import { TransactionDetailsCard } from '@/components/Transactions/TransactionDetailsCard'; +import { TransactionMessageCard } from '@/components/Transactions/TransactionMessageCard'; +import { VerifiedBadge } from '@/components/Transactions/TransactionIcons'; +import { + SCREEN_FOR_REQUEST_SOURCE, + EXPANDED_CARD_BOTTOM_INSET, + GAS_BUTTON_SPACE, + motiTimingConfig, + SCREEN_BOTTOM_INSET, + infoForEventType, +} from '@/components/Transactions/constants'; +import { useCalculateGasLimit } from '@/hooks/useCalculateGasLimit'; +import { useTransactionSetup } from '@/hooks/useTransactionSetup'; +import { useHasEnoughBalance } from '@/hooks/useHasEnoughBalance'; +import { useNonceForDisplay } from '@/hooks/useNonceForDisplay'; +import { useProviderSetup } from '@/hooks/useProviderSetup'; +import { useTransactionSubmission } from '@/hooks/useSubmitTransaction'; +import { useConfirmTransaction } from '@/hooks/useConfirmTransaction'; type SignTransactionSheetParams = { transactionDetails: RequestData; onSuccess: (hash: string) => void; onCancel: (error?: Error) => void; onCloseScreen: (canceled: boolean) => void; - network: Network; chainId: ChainId; address: string; source: RequestSource; }; -const SCREEN_FOR_REQUEST_SOURCE = { - browser: Screens.DAPP_BROWSER, - walletconnect: Screens.WALLETCONNECT, -}; - export type SignTransactionSheetRouteProp = RouteProp<{ SignTransactionSheet: SignTransactionSheetParams }, 'SignTransactionSheet'>; export const SignTransactionSheet = () => { - const { goBack, navigate } = useNavigation(); + const { goBack } = useNavigation(); const { colors, isDarkMode } = useTheme(); const { accountAddress, nativeCurrency } = useAccountSettings(); - const [simulationData, setSimulationData] = useState(); - const [simulationError, setSimulationError] = useState(undefined); - const [simulationScanResult, setSimulationScanResult] = useState(undefined); const { params: routeParams } = useRoute(); const { wallets, walletNames } = useWallets(); @@ -171,28 +98,24 @@ export const SignTransactionSheet = () => { onSuccess: onSuccessCallback, onCancel: onCancelCallback, onCloseScreen: onCloseScreenCallback, - chainId: currentChainId, - address: currentAddress, + chainId, + address: specifiedAddress, // for request type specific handling source, } = routeParams; - const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + console.log({ specifiedAddress }); + + const addressToUse = specifiedAddress ?? accountAddress; - const isPersonalSign = checkIsPersonalSign(transactionDetails.payload.method); + const { provider, nativeAsset } = useProviderSetup(chainId, addressToUse); + + const isMessageRequest = isMessageDisplayType(transactionDetails.payload.method); + const isPersonalSignRequest = isPersonalSign(transactionDetails.payload.method); const label = useForegroundColor('label'); const surfacePrimary = useBackgroundColor('surfacePrimary'); - const [provider, setProvider] = useState(null); - const [isAuthorizing, setIsAuthorizing] = useState(false); - const [isLoading, setIsLoading] = useState(!isPersonalSign); - const [methodName, setMethodName] = useState(null); - const calculatingGasLimit = useRef(false); - const [isBalanceEnough, setIsBalanceEnough] = useState(); - const [nonceForDisplay, setNonceForDisplay] = useState(); - - const [nativeAsset, setNativeAsset] = useState(null); const formattedDappUrl = useMemo(() => { try { const { hostname } = new URL(transactionDetails?.dappUrl); @@ -202,106 +125,16 @@ export const SignTransactionSheet = () => { } }, [transactionDetails]); - const { - gasLimit, - isValidGas, - startPollingGasFees, - stopPollingGasFees, - isSufficientGas, - updateTxFee, - selectedGasFee, - gasFeeParamsBySpeed, - } = useGas(); - - const simulationUnavailable = isPersonalSign; - - const itemCount = (simulationData?.in?.length || 0) + (simulationData?.out?.length || 0) + (simulationData?.approvals?.length || 0); - - const noChanges = !!(simulationData && itemCount === 0) && simulationScanResult === TransactionScanResultType.Ok; - const req = transactionDetails?.payload?.params?.[0]; const request = useMemo(() => { return isMessageRequest - ? { message: transactionDetails?.displayDetails?.request } + ? { message: transactionDetails?.displayDetails?.request || '' } : { ...transactionDetails?.displayDetails?.request, - nativeAsset: nativeAsset, + nativeAsset, }; }, [isMessageRequest, transactionDetails?.displayDetails?.request, nativeAsset]); - const calculateGasLimit = useCallback(async () => { - calculatingGasLimit.current = true; - const txPayload = req; - if (isHex(txPayload?.type)) { - txPayload.type = hexToNumber(txPayload?.type); - } - // use the default - let gas = txPayload.gasLimit || txPayload.gas; - - const provider = getProvider({ chainId: currentChainId }); - try { - // attempt to re-run estimation - logger.debug('[SignTransactionSheet]: Estimating gas limit', { gas }, logger.DebugContext.walletconnect); - // safety precaution: we want to ensure these properties are not used for gas estimation - const cleanTxPayload = omitFlatten(txPayload, ['gas', 'gasLimit', 'gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas']); - const rawGasLimit = await estimateGas(cleanTxPayload, provider); - logger.debug('[SignTransactionSheet]: Estimated gas limit', { rawGasLimit }, logger.DebugContext.walletconnect); - if (rawGasLimit) { - gas = toHex(rawGasLimit); - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: error estimating gas'), { error }); - } finally { - logger.debug('[SignTransactionSheet]: Setting gas limit to', { gas: convertHexToString(gas) }, logger.DebugContext.walletconnect); - const networkObject = getNetworkObject({ chainId: currentChainId }); - if (networkObject && networkObject.gas.OptimismTxFee) { - const l1GasFeeOptimism = await ethereumUtils.calculateL1FeeOptimism(txPayload, provider); - updateTxFee(gas, null, l1GasFeeOptimism); - } else { - updateTxFee(gas, null); - } - } - }, [currentChainId, req, updateTxFee]); - - const fetchMethodName = useCallback( - async (data: string) => { - const methodSignaturePrefix = data.substr(0, 10); - try { - const { name } = await methodRegistryLookupAndParse(methodSignaturePrefix, currentChainId); - if (name) { - setMethodName(name); - } - } catch (e) { - setMethodName(data); - } - }, - [currentChainId] - ); - - // start polling for gas and get fn name - useEffect(() => { - InteractionManager.runAfterInteractions(() => { - if (currentChainId) { - if (!isMessageRequest) { - startPollingGasFees(currentChainId); - fetchMethodName(transactionDetails?.payload?.params[0].data); - } else { - setMethodName(i18n.t(i18n.l.wallet.message_signing.request)); - } - analytics.track(event.txRequestShownSheet), { source }; - } - }); - }, [isMessageRequest, startPollingGasFees, fetchMethodName, transactionDetails?.payload?.params, source, currentChainId]); - - // get gas limit - useEffect(() => { - if (!isEmpty(gasFeeParamsBySpeed) && !calculatingGasLimit.current && !isMessageRequest && provider) { - InteractionManager.runAfterInteractions(() => { - calculateGasLimit(); - }); - } - }, [calculateGasLimit, gasLimit, gasFeeParamsBySpeed, isMessageRequest, provider, updateTxFee]); - const walletBalance = useMemo(() => { return { amount: nativeAsset?.balance?.amount || 0, @@ -311,168 +144,77 @@ export const SignTransactionSheet = () => { }; }, [nativeAsset?.balance?.amount, nativeAsset?.balance?.display, nativeAsset?.symbol]); - // check native balance is sufficient - useEffect(() => { - if (isMessageRequest) { - setIsBalanceEnough(true); - return; - } + const { gasLimit, isValidGas, startPollingGasFees, stopPollingGasFees, updateTxFee, selectedGasFee, gasFeeParamsBySpeed } = useGas(); - const { gasFee } = selectedGasFee; - if (!walletBalance?.isLoaded || !currentChainId || !gasFee?.estimatedFee) { - return; - } + const { methodName } = useTransactionSetup({ + chainId, + startPollingGasFees, + stopPollingGasFees, + isMessageRequest, + transactionDetails, + source, + }); - // Get the TX fee Amount - const txFeeAmount = fromWei(gasFee?.maxFee?.value?.amount ?? 0); + const { isBalanceEnough } = useHasEnoughBalance({ + isMessageRequest, + walletBalance, + chainId, + selectedGasFee, + req, + }); + + useCalculateGasLimit({ + isMessageRequest, + gasFeeParamsBySpeed, + provider, + req, + updateTxFee, + chainId, + }); - // Get the ETH balance - const balanceAmount = walletBalance?.amount ?? 0; + const { nonceForDisplay } = useNonceForDisplay({ + isMessageRequest, + address: addressToUse, + chainId, + }); - // Get the TX value - const txPayload = req; - const value = txPayload?.value ?? 0; + const { + data: simulationResult, + isLoading: txSimulationLoading, + error: txSimulationApiError, + } = useSimulation( + { + address: addressToUse, + chainId, + isMessageRequest, + nativeCurrency, + req, + requestMessage: request.message, + simulationUnavailable: isPersonalSignRequest, + transactionDetails, + }, + { + enabled: !isPersonalSignRequest, + } + ); - // Check that there's enough ETH to pay for everything! - const totalAmount = new BigNumber(fromWei(value)).plus(txFeeAmount); - const isEnough = greaterThanOrEqualTo(balanceAmount, totalAmount); + const itemCount = + (simulationResult?.simulationData?.in?.length || 0) + + (simulationResult?.simulationData?.out?.length || 0) + + (simulationResult?.simulationData?.approvals?.length || 0); - setIsBalanceEnough(isEnough); - }, [isMessageRequest, isSufficientGas, selectedGasFee, walletBalance, req, currentChainId]); + const noChanges = + !!(simulationResult?.simulationData && itemCount === 0) && simulationResult?.simulationScanResult === TransactionScanResultType.Ok; const accountInfo = useMemo(() => { - const selectedWallet = wallets ? findWalletWithAccount(wallets, currentAddress) : undefined; - const profileInfo = getAccountProfileInfo(selectedWallet, walletNames, currentAddress); + const selectedWallet = wallets ? findWalletWithAccount(wallets, addressToUse) : undefined; + const profileInfo = getAccountProfileInfo(selectedWallet, walletNames, addressToUse); return { ...profileInfo, - address: currentAddress, + address: addressToUse, isHardwareWallet: !!selectedWallet?.deviceId, }; - }, [wallets, currentAddress, walletNames]); - - useEffect(() => { - const initProvider = async () => { - let p; - // check on this o.O - if (currentChainId === ChainId.mainnet) { - p = await getFlashbotsProvider(); - } else { - p = getProvider({ chainId: currentChainId }); - } - - setProvider(p); - }; - initProvider(); - }, [currentChainId, setProvider]); - - useEffect(() => { - (async () => { - const asset = await ethereumUtils.getNativeAssetForNetwork({ chainId: currentChainId, address: accountInfo.address }); - if (asset && provider) { - const balance = await getOnchainAssetBalance(asset, accountInfo.address, currentChainId, provider); - if (balance) { - const assetWithOnchainBalance: ParsedAddressAsset = { ...asset, balance }; - setNativeAsset(assetWithOnchainBalance); - } else { - setNativeAsset(asset); - } - } - })(); - }, [accountInfo.address, currentChainId, provider]); - - useEffect(() => { - (async () => { - if (!isMessageRequest && !nonceForDisplay) { - try { - const nonce = await getNextNonce({ address: currentAddress, chainId: currentChainId }); - if (nonce || nonce === 0) { - const nonceAsString = nonce.toString(); - setNonceForDisplay(nonceAsString); - } - } catch (error) { - console.error('Failed to get nonce for display:', error); - } - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [accountInfo.address, currentChainId, getNextNonce, isMessageRequest]); - - useEffect(() => { - const timeout = setTimeout(async () => { - try { - let simulationData; - if (isMessageRequest) { - // Message Signing - simulationData = await metadataPOSTClient.simulateMessage({ - address: accountAddress, - chainId: currentChainId, - message: { - method: transactionDetails?.payload?.method, - params: [request.message], - }, - domain: transactionDetails?.dappUrl, - }); - // Handle message simulation response - if (isNil(simulationData?.simulateMessage?.simulation) && isNil(simulationData?.simulateMessage?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } else if (simulationData?.simulateMessage?.error && !simulationUnavailable) { - setSimulationError(simulationData?.simulateMessage?.error?.type); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - setSimulationData(undefined); - } else if (simulationData.simulateMessage?.simulation && !simulationUnavailable) { - setSimulationData(simulationData.simulateMessage?.simulation); - setSimulationScanResult(simulationData?.simulateMessage?.scanning?.result); - } - } else { - // TX Signing - simulationData = await metadataPOSTClient.simulateTransactions({ - chainId: currentChainId, - currency: nativeCurrency?.toLowerCase(), - transactions: [ - { - from: req?.from, - to: req?.to, - data: req?.data, - value: req?.value || '0x0', - }, - ], - domain: transactionDetails?.dappUrl, - }); - // Handle TX simulation response - if (isNil(simulationData?.simulateTransactions?.[0]?.simulation) && isNil(simulationData?.simulateTransactions?.[0]?.error)) { - setSimulationData({ in: [], out: [], approvals: [] }); - setSimulationScanResult(simulationData?.simulateTransactions?.[0]?.scanning?.result); - } else if (simulationData?.simulateTransactions?.[0]?.error) { - setSimulationError(simulationData?.simulateTransactions?.[0]?.error?.type); - setSimulationData(undefined); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } else if (simulationData.simulateTransactions?.[0]?.simulation) { - setSimulationData(simulationData.simulateTransactions[0]?.simulation); - setSimulationScanResult(simulationData?.simulateTransactions[0]?.scanning?.result); - } - } - } catch (error) { - logger.error(new RainbowError('[SignTransactionSheet]: Error while simulating'), { error }); - } finally { - setIsLoading(false); - } - }, 750); - - return () => { - clearTimeout(timeout); - }; - }, [ - accountAddress, - currentChainId, - isMessageRequest, - isPersonalSign, - nativeCurrency, - req, - request.message, - simulationUnavailable, - transactionDetails, - ]); + }, [wallets, addressToUse, walletNames]); const closeScreen = useCallback( (canceled: boolean) => @@ -520,86 +262,12 @@ export const SignTransactionSheet = () => { [accountInfo.isHardwareWallet, closeScreen, onCancelCallback, source, transactionDetails?.payload?.method] ); - const handleSignMessage = useCallback(async () => { - const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); - let response = null; - - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { - return; - } - - const existingWallet = await performanceTracking.getState().executeFn({ - fn: loadWallet, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.KeychainRead, - })({ - address: accountInfo.address, - provider, - timeTracking: { - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.Authentication, - }, - }); - - if (!existingWallet) { - return; - } - switch (transactionDetails?.payload?.method) { - case PERSONAL_SIGN: - response = await performanceTracking.getState().executeFn({ - fn: signPersonalMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - case SIGN_TYPED_DATA_V4: - case SIGN_TYPED_DATA: - response = await performanceTracking.getState().executeFn({ - fn: signTypedDataMessage, - screen: SCREEN_FOR_REQUEST_SOURCE[source], - operation: TimeToSignOperation.SignTransaction, - })(message, existingWallet); - break; - default: - break; - } - - if (response?.result) { - analytics.track(event.txRequestApprove, { - source, - requestType: 'signature', - dappName: transactionDetails?.dappName, - dappUrl: transactionDetails?.dappUrl, - isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), - }); - onSuccessCallback?.(response.result); - - closeScreen(false); - } else { - await onCancel(response?.error); - } - }, [ - transactionDetails?.payload?.params, - transactionDetails?.payload?.method, - transactionDetails?.dappName, - transactionDetails?.dappUrl, - currentChainId, - accountInfo.address, - accountInfo.isHardwareWallet, - source, - onSuccessCallback, - closeScreen, - onCancel, - ]); - const handleConfirmTransaction = useCallback(async () => { const sendInsteadOfSign = transactionDetails.payload.method === SEND_TRANSACTION; const txPayload = req; let { gas } = txPayload; const gasLimitFromPayload = txPayload?.gasLimit; - if (!currentChainId) return; + if (!chainId) return; try { logger.debug( '[SignTransactionSheet]: gas suggested by dapp', @@ -638,7 +306,7 @@ export const SignTransactionSheet = () => { const gasParams = parseGasParamsForTransaction(selectedGasFee); const calculatedGasLimit = gas || gasLimitFromPayload || gasLimit; - const nonce = await getNextNonce({ address: accountInfo.address, chainId: currentChainId }); + const nonce = await getNextNonce({ address: accountInfo.address, chainId }); let txPayloadUpdated = { ...cleanTxPayload, ...gasParams, @@ -654,11 +322,11 @@ export const SignTransactionSheet = () => { let response = null; try { - if (!currentChainId) { + if (!chainId) { return; } - const provider = getProvider({ chainId: currentChainId }); - if (!provider) { + const providerToUse = provider || getProvider({ chainId }); + if (!providerToUse) { return; } const existingWallet = await performanceTracking.getState().executeFn({ @@ -667,7 +335,7 @@ export const SignTransactionSheet = () => { operation: TimeToSignOperation.KeychainRead, })({ address: accountInfo.address, - provider, + provider: providerToUse, timeTracking: { screen: SCREEN_FOR_REQUEST_SOURCE[source], operation: TimeToSignOperation.Authentication, @@ -685,8 +353,8 @@ export const SignTransactionSheet = () => { screen: SCREEN_FOR_REQUEST_SOURCE[source], operation: TimeToSignOperation.BroadcastTransaction, })({ - existingWallet: existingWallet, - provider, + existingWallet, + provider: providerToUse, transaction: txPayloadUpdated, }); } else { @@ -696,7 +364,7 @@ export const SignTransactionSheet = () => { operation: TimeToSignOperation.SignTransaction, })({ existingWallet, - provider, + provider: providerToUse, transaction: txPayloadUpdated, }); } @@ -714,7 +382,7 @@ export const SignTransactionSheet = () => { if (sendInsteadOfSign && sendResult?.hash) { txDetails = { status: 'pending', - chainId: currentChainId, + chainId, asset: displayDetails?.request?.asset || nativeAsset, contract: { name: transactionDetails.dappName, @@ -724,18 +392,18 @@ export const SignTransactionSheet = () => { from: displayDetails?.request?.from, gasLimit, hash: sendResult.hash, - network: ethereumUtils.getNetworkFromChainId(currentChainId) || Network.mainnet, + network: getNetworkObject({ chainId }).value, nonce: sendResult.nonce, to: displayDetails?.request?.to, value: sendResult.value.toString(), type: 'contract_interaction', ...gasParams, }; - if (accountAddress?.toLowerCase() === txDetails.from?.toLowerCase()) { + if (accountInfo.address?.toLowerCase() === txDetails.from?.toLowerCase()) { addNewTransaction({ transaction: txDetails, - chainId: currentChainId, - address: accountAddress, + chainId, + address: accountInfo.address, }); txSavedInCurrentWallet = true; } @@ -746,7 +414,7 @@ export const SignTransactionSheet = () => { dappName: transactionDetails.dappName, dappUrl: transactionDetails.dappUrl, isHardwareWallet: accountInfo.isHardwareWallet, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + network: getNetworkObject({ chainId }).value, }); if (!sendInsteadOfSign) { @@ -766,7 +434,7 @@ export const SignTransactionSheet = () => { await switchToWalletWithAddress(txDetails?.from as string); addNewTransaction({ transaction: txDetails as NewTransaction, - chainId: currentChainId, + chainId, address: txDetails?.from as string, }); }); @@ -777,7 +445,7 @@ export const SignTransactionSheet = () => { dappUrl: transactionDetails?.dappUrl, formattedDappUrl, rpcMethod: req?.method, - network: ethereumUtils.getNetworkFromChainId(currentChainId), + network: getNetworkObject({ chainId }).value, }); // If the user is using a hardware wallet, we don't want to close the sheet on an error if (!accountInfo.isHardwareWallet) { @@ -791,7 +459,7 @@ export const SignTransactionSheet = () => { transactionDetails.dappUrl, transactionDetails.imageUrl, req, - currentChainId, + chainId, selectedGasFee, gasLimit, accountInfo.address, @@ -800,57 +468,106 @@ export const SignTransactionSheet = () => { source, closeScreen, nativeAsset, - accountAddress, onSuccessCallback, switchToWalletWithAddress, formattedDappUrl, onCancel, ]); - const onConfirm = useCallback(async () => { - if (isMessageRequest) { - return handleSignMessage(); - } - if (!isBalanceEnough || !isValidGas) return; - return handleConfirmTransaction(); - }, [handleConfirmTransaction, handleSignMessage, isBalanceEnough, isMessageRequest, isValidGas]); + const handleSignMessage = useCallback(async () => { + const message = transactionDetails?.payload?.params.find((p: string) => !isAddress(p)); + let response = null; - const onPressSend = useCallback(async () => { - if (isAuthorizing) return; - setIsAuthorizing(true); - try { - await onConfirm(); - setIsAuthorizing(false); - } catch (error) { - setIsAuthorizing(false); + const providerToUse = provider || getProvider({ chainId }); + if (!providerToUse) { + return; } - }, [isAuthorizing, onConfirm]); - const submitFn = useCallback( - () => - performanceTracking.getState().executeFn({ - fn: async () => { - if (!isBalanceEnough) { - navigate(Routes.ADD_CASH_SHEET); - return; - } - if (accountInfo.isHardwareWallet) { - navigate(Routes.HARDWARE_WALLET_TX_NAVIGATOR, { submit: onPressSend }); - } else { - await onPressSend(); - } - }, - operation: TimeToSignOperation.CallToAction, + const existingWallet = await performanceTracking.getState().executeFn({ + fn: loadWallet, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.KeychainRead, + })({ + address: accountInfo.address, + provider: providerToUse, + timeTracking: { screen: SCREEN_FOR_REQUEST_SOURCE[source], - })(), - [accountInfo.isHardwareWallet, isBalanceEnough, navigate, onPressSend, source] - ); + operation: TimeToSignOperation.Authentication, + }, + }); + + if (!existingWallet) { + return; + } + switch (transactionDetails?.payload?.method) { + case PERSONAL_SIGN: + response = await performanceTracking.getState().executeFn({ + fn: signPersonalMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + case SIGN_TYPED_DATA_V4: + case SIGN_TYPED_DATA: + response = await performanceTracking.getState().executeFn({ + fn: signTypedDataMessage, + screen: SCREEN_FOR_REQUEST_SOURCE[source], + operation: TimeToSignOperation.SignTransaction, + })(message, existingWallet); + break; + default: + break; + } + + if (response?.result) { + analytics.track(event.txRequestApprove, { + source, + requestType: 'signature', + dappName: transactionDetails?.dappName, + dappUrl: transactionDetails?.dappUrl, + isHardwareWallet: accountInfo.isHardwareWallet, + network: getNetworkObject({ chainId }).value, + }); + onSuccessCallback?.(response.result); + + closeScreen(false); + } else { + await onCancel(response?.error); + } + }, [ + transactionDetails?.payload?.params, + transactionDetails?.payload?.method, + transactionDetails?.dappName, + transactionDetails?.dappUrl, + chainId, + accountInfo.address, + accountInfo.isHardwareWallet, + source, + onSuccessCallback, + closeScreen, + onCancel, + ]); + + const { onConfirm } = useConfirmTransaction({ + isMessageRequest, + isBalanceEnough, + isValidGas, + handleSignMessage, + handleConfirmTransaction, + }); + + const { submitFn } = useTransactionSubmission({ + isBalanceEnough, + accountInfo, + onConfirm, + source, + }); const onPressCancel = useCallback(() => onCancel(), [onCancel]); const expandedCardBottomInset = EXPANDED_CARD_BOTTOM_INSET + (isMessageRequest ? 0 : GAS_BUTTON_SPACE); - const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!currentChainId && !!selectedGasFee?.gasFee?.estimatedFee); + const canPressConfirm = isMessageRequest || (!!walletBalance?.isLoaded && !!chainId && !!selectedGasFee?.gasFee?.estimatedFee); return ( @@ -899,8 +616,9 @@ export const SignTransactionSheet = () => { { > {transactionDetails.dappName} - {source === 'browser' && } + {source === RequestSource.BROWSER && } {isMessageRequest @@ -921,33 +639,36 @@ export const SignTransactionSheet = () => { - {isMessageRequest ? ( - ) : ( - { ) : ( - {!!currentChainId && walletBalance?.isLoaded && ( + {!!chainId && walletBalance?.isLoaded && ( - + {`${walletBalance?.display} ${i18n.t(i18n.l.walletconnect.simulation.profile_section.on_network, { - network: getNetworkObject({ chainId: currentChainId })?.name, + network: getNetworkObject({ chainId })?.name, })}`} @@ -1018,7 +739,7 @@ export const SignTransactionSheet = () => { /> { disabled={!canPressConfirm} size="big" weight="heavy" - // eslint-disable-next-line react/jsx-props-no-spreading - {...((simulationError || (simulationScanResult && simulationScanResult !== TransactionScanResultType.Ok)) && { - color: simulationScanResult === TransactionScanResultType.Warning ? 'orange' : colors.red, - })} + color={ + simulationResult?.simulationError || + (simulationResult?.simulationScanResult && simulationResult?.simulationScanResult !== TransactionScanResultType.Ok) + ? simulationResult?.simulationScanResult === TransactionScanResultType.Warning + ? 'orange' + : colors.red + : undefined + } /> @@ -1043,7 +768,7 @@ export const SignTransactionSheet = () => { )} - {source === 'browser' && ( + {source === RequestSource.BROWSER && ( { { ); }; - -interface SimulationCardProps { - chainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - isPersonalSign: boolean; - noChanges: boolean; - simulation: TransactionSimulationResult | undefined; - simulationError: TransactionErrorType | undefined; - simulationScanResult: TransactionScanResultType | undefined; - walletBalance: { - amount: string | number; - display: string; - isLoaded: boolean; - symbol: string; - }; -} - -const SimulationCard = ({ - chainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - isPersonalSign, - noChanges, - simulation, - simulationError, - simulationScanResult, - walletBalance, -}: SimulationCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const spinnerRotation = useSharedValue(0); - - const simulationUnavailable = isPersonalSign; - - const listStyle = useAnimatedStyle(() => ({ - opacity: noChanges - ? withTiming(1, timingConfig) - : interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const spinnerStyle = useAnimatedStyle(() => { - return { - transform: [{ rotate: `${spinnerRotation.value}deg` }], - }; - }); - - useAnimatedReaction( - () => ({ isLoading, simulationUnavailable }), - ({ isLoading, simulationUnavailable }, previous = { isLoading: false, simulationUnavailable: false }) => { - if (isLoading && !previous?.isLoading) { - spinnerRotation.value = withRepeat(withTiming(360, rotationConfig), -1, false); - } else if ( - (!isLoading && previous?.isLoading) || - (simulationUnavailable && !previous?.simulationUnavailable && previous?.isLoading) - ) { - spinnerRotation.value = withTiming(360, timingConfig); - } - }, - [isLoading, simulationUnavailable] - ); - const renderSimulationEventRows = useMemo(() => { - if (isBalanceEnough === false) return null; - - return ( - <> - {simulation?.approvals?.map(change => { - return ( - - ); - })} - {simulation?.out?.map(change => { - return ( - - ); - })} - {simulation?.in?.map(change => { - return ( - - ); - })} - - ); - }, [isBalanceEnough, simulation]); - - const titleColor: TextColor = useMemo(() => { - if (isLoading) { - return 'label'; - } - if (isBalanceEnough === false) { - return 'blue'; - } - if (noChanges || simulationUnavailable) { - return 'labelQuaternary'; - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return 'orange'; - } - if (simulationError || simulationScanResult === TransactionScanResultType.Malicious) { - return 'red'; - } - return 'label'; - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable]); - - const titleText = useMemo(() => { - if (isLoading) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulating); - } - if (isBalanceEnough === false) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.not_enough_native_balance, { symbol: walletBalance?.symbol }); - } - if (simulationUnavailable) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_unavailable); - } - if (simulationScanResult === TransactionScanResultType.Warning) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.proceed_carefully); - } - if (simulationScanResult === TransactionScanResultType.Malicious) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.suspicious_transaction); - } - if (noChanges) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.no_changes); - } - if (simulationError) { - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail); - } - return i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.simulation_result); - }, [isBalanceEnough, isLoading, noChanges, simulationError, simulationScanResult, simulationUnavailable, walletBalance?.symbol]); - - const isExpanded = useMemo(() => { - if (isLoading || isPersonalSign) { - return false; - } - const shouldExpandOnLoad = isBalanceEnough === false || (!isEmpty(simulation) && !noChanges) || !!simulationError; - return shouldExpandOnLoad; - }, [isBalanceEnough, isLoading, isPersonalSign, noChanges, simulation, simulationError]); - - return ( - - - - - {!isLoading && (simulationError || isBalanceEnough === false || simulationScanResult !== TransactionScanResultType.Ok) ? ( - - ) : ( - - {!isLoading && noChanges && !simulationUnavailable ? ( - - {/* The extra space avoids icon clipping */} - {'􀻾 '} - - ) : ( - - - 􀬨 - - - )} - - )} - - {titleText} - - - {/* TODO: Unhide once we add explainer sheets */} - {/* - - - - - 􀁜 - - - - - */} - - - - {isBalanceEnough === false ? ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.need_more_native, { - symbol: walletBalance?.symbol, - network: chainIdToNameMapping[chainId], - })} - - ) : ( - <> - {simulationUnavailable && isPersonalSign && ( - - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.unavailable_personal_sign)} - - - )} - {simulationError && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.failed_to_simulate)} - - )} - {simulationScanResult === TransactionScanResultType.Warning && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.warning)}{' '} - - )} - {simulationScanResult === TransactionScanResultType.Malicious && ( - - {i18n.t(i18n.l.walletconnect.simulation.simulation_card.messages.malicious)} - - )} - - )} - {renderSimulationEventRows} - - - - - ); -}; - -interface DetailsCardProps { - chainId: ChainId; - expandedCardBottomInset: number; - isBalanceEnough: boolean | undefined; - isLoading: boolean; - meta: TransactionSimulationMeta | undefined; - methodName: string; - noChanges: boolean; - nonce: string | undefined; - toAddress: string; -} - -const DetailsCard = ({ - chainId, - expandedCardBottomInset, - isBalanceEnough, - isLoading, - meta, - methodName, - noChanges, - nonce, - toAddress, -}: DetailsCardProps) => { - const cardHeight = useSharedValue(COLLAPSED_CARD_HEIGHT); - const contentHeight = useSharedValue(COLLAPSED_CARD_HEIGHT - CARD_BORDER_WIDTH * 2); - const [isExpanded, setIsExpanded] = useState(false); - - const listStyle = useAnimatedStyle(() => ({ - opacity: interpolate( - cardHeight.value, - [ - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight.value + CARD_BORDER_WIDTH * 2, - ], - [0, 1] - ), - })); - - const collapsedTextColor: TextColor = isLoading ? 'labelQuaternary' : 'blue'; - - const showFunctionRow = meta?.to?.function || (methodName && methodName.substring(0, 2) !== '0x'); - const isContract = showFunctionRow || meta?.to?.created || meta?.to?.sourceCodeStatus; - const showTransferToRow = !!meta?.transferTo?.address; - // Hide DetailsCard if balance is insufficient once loaded - if (!isLoading && isBalanceEnough === false) { - return <>; - } - return ( - setIsExpanded(true)} - > - - - - - - 􁙠 - - - - {i18n.t(i18n.l.walletconnect.simulation.details_card.title)} - - - - - - {} - {!!(meta?.to?.address || toAddress || showTransferToRow) && ( - - ethereumUtils.openAddressInBlockExplorer({ - address: meta?.to?.address || toAddress || meta?.transferTo?.address || '', - chainId, - }) - } - value={ - meta?.to?.name || - abbreviations.address(meta?.to?.address || toAddress, 4, 6) || - meta?.to?.address || - toAddress || - meta?.transferTo?.address || - '' - } - /> - )} - {showFunctionRow && } - {!!meta?.to?.sourceCodeStatus && } - {!!meta?.to?.created && } - {nonce && } - - - - - ); -}; - -const MessageCard = ({ - expandedCardBottomInset, - message, - method, -}: { - expandedCardBottomInset: number; - message: string; - method: RPCMethod; -}) => { - const { setClipboard } = useClipboard(); - const [didCopy, setDidCopy] = useState(false); - - let displayMessage = message; - if (isSignTypedData(method)) { - try { - const parsedMessage = JSON.parse(message); - const sanitizedMessage = sanitizeTypedData(parsedMessage); - displayMessage = sanitizedMessage; - // eslint-disable-next-line no-empty - } catch (e) { - logger.warn('[SignTransactionSheet]: Error while parsing message'); - } - - displayMessage = JSON.stringify(displayMessage, null, 4); - } - - const estimatedMessageHeight = useMemo(() => estimateMessageHeight(displayMessage), [displayMessage]); - - const cardHeight = useSharedValue( - estimatedMessageHeight > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : estimatedMessageHeight + CARD_BORDER_WIDTH * 2 - ); - const contentHeight = useSharedValue(estimatedMessageHeight); - - const handleCopyPress = useCallback( - (message: string) => { - if (didCopy) return; - setClipboard(message); - setDidCopy(true); - const copyTimer = setTimeout(() => { - setDidCopy(false); - }, 2000); - return () => clearTimeout(copyTimer); - }, - [didCopy, setClipboard] - ); - - return ( - MAX_CARD_HEIGHT} - isExpanded - skipCollapsedState - > - - - - - - 􀙤 - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.title)} - - - - handleCopyPress(message)}> - - - - - - {i18n.t(i18n.l.walletconnect.simulation.message_card.copy)} - - - - - - - - - {displayMessage} - - - - ); -}; - -const SimulatedEventRow = ({ - amount, - asset, - eventType, - price, -}: { - amount: string | 'unlimited'; - asset: TransactionSimulationAsset | undefined; - eventType: EventType; - price?: number | undefined; -}) => { - const theme = useTheme(); - const { nativeCurrency } = useAccountSettings(); - const { data: externalAsset } = useExternalToken({ - address: (asset?.assetCode || '') as AddressOrEth, - chainId: ethereumUtils.getChainIdFromNetwork((asset?.network as Network) || Network.mainnet), - currency: nativeCurrency, - }); - - const eventInfo: EventInfo = infoForEventType[eventType]; - - const formattedAmount = useMemo(() => { - if (!asset) return; - - const nftFallbackSymbol = parseFloat(amount) > 1 ? 'NFTs' : 'NFT'; - const assetDisplayName = - asset?.type === TransactionAssetType.Nft ? asset?.name || asset?.symbol || nftFallbackSymbol : asset?.symbol || asset?.name; - const shortenedDisplayName = assetDisplayName.length > 12 ? `${assetDisplayName.slice(0, 12).trim()}…` : assetDisplayName; - - const displayAmount = - asset?.decimals === 0 - ? `${amount}${shortenedDisplayName ? ' ' + shortenedDisplayName : ''}` - : convertRawAmountToBalance(amount, { decimals: asset?.decimals || 18, symbol: shortenedDisplayName }, 3, true).display; - - const unlimitedApproval = `${i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.unlimited)} ${asset?.symbol}`; - - return `${eventInfo.amountPrefix}${amount === 'UNLIMITED' ? unlimitedApproval : displayAmount}`; - }, [amount, asset, eventInfo?.amountPrefix]); - - const url = maybeSignUri(asset?.iconURL, { - fm: 'png', - w: 16 * PixelRatio.get(), - }); - - const showUSD = (eventType === 'send' || eventType === 'receive') && !!price; - - const formattedPrice = price && convertAmountToNativeDisplay(price, nativeCurrency); - - return ( - - - - - - - {eventInfo.label} - - {showUSD && ( - - {formattedPrice} - - )} - - - - - {asset?.type !== TransactionAssetType.Nft ? ( - - ) : ( - - )} - - - {formattedAmount} - - - - - ); -}; - -const DetailRow = ({ - chainId, - detailType, - onPress, - value, -}: { - chainId?: ChainId; - detailType: DetailType; - onPress?: () => void; - value: string; -}) => { - const detailInfo: DetailInfo = infoForDetailType[detailType]; - - return ( - - - - - - {detailInfo.label} - - - - {detailType === 'function' && } - {detailType === 'sourceCodeVerification' && ( - - )} - {detailType === 'chain' && chainId && } - {detailType !== 'function' && detailType !== 'sourceCodeVerification' && ( - - {value} - - )} - {(detailType === 'contract' || detailType === 'to') && ( - - - - - 􀂄 - - - - - )} - - - - ); -}; - -const EventIcon = ({ eventType }: { eventType: EventType }) => { - const eventInfo: EventInfo = infoForEventType[eventType]; - - const hideInnerFill = eventType === 'approve' || eventType === 'revoke'; - const isWarningIcon = - eventType === 'failed' || eventType === 'insufficientBalance' || eventType === 'MALICIOUS' || eventType === 'WARNING'; - - return ( - - {!hideInnerFill && ( - - )} - - {eventInfo.icon} - - - ); -}; - -const DetailIcon = ({ detailInfo }: { detailInfo: DetailInfo }) => { - return ( - - - {detailInfo.icon} - - - ); -}; - -const DetailBadge = ({ type, value }: { type: 'function' | 'unknown' | 'unverified' | 'verified'; value: string }) => { - const { colors, isDarkMode } = useTheme(); - const separatorTertiary = useForegroundColor('separatorTertiary'); - - const infoForBadgeType: { - [key: string]: { - backgroundColor: string; - borderColor: string; - label?: string; - text: TextColor; - textOpacity?: number; - }; - } = { - function: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - text: 'labelQuaternary', - }, - unknown: { - backgroundColor: 'transparent', - borderColor: isDarkMode ? separatorTertiary : colors.alpha(separatorTertiary, 0.025), - label: 'Unknown', - text: 'labelQuaternary', - }, - unverified: { - backgroundColor: isDarkMode ? colors.alpha(colors.red, 0.05) : globalColors.red10, - borderColor: colors.alpha(colors.red, 0.02), - label: 'Unverified', - text: 'red', - textOpacity: 0.76, - }, - verified: { - backgroundColor: isDarkMode ? colors.alpha(colors.green, 0.05) : globalColors.green10, - borderColor: colors.alpha(colors.green, 0.02), - label: 'Verified', - text: 'green', - textOpacity: 0.76, - }, - }; - - return ( - - - - {infoForBadgeType[type].label || value} - - - - ); -}; - -const VerifiedBadge = () => { - return ( - - - - - 􀇻 - - - - ); -}; - -const AnimatedCheckmark = ({ visible }: { visible: boolean }) => { - return ( - - {visible && ( - - - - - - 􀁣 - - - - - )} - - ); -}; - -const FadedScrollCard = ({ - cardHeight, - children, - contentHeight, - expandedCardBottomInset = 120, - expandedCardTopInset = 120, - initialScrollEnabled, - isExpanded, - onPressCollapsedCard, - skipCollapsedState, -}: { - cardHeight: SharedValue; - children: React.ReactNode; - contentHeight: SharedValue; - expandedCardBottomInset?: number; - expandedCardTopInset?: number; - initialScrollEnabled?: boolean; - isExpanded: boolean; - onPressCollapsedCard?: () => void; - skipCollapsedState?: boolean; -}) => { - const { height: deviceHeight, width: deviceWidth } = useDimensions(); - const { isDarkMode } = useTheme(); - - const cardRef = useAnimatedRef(); - - const [scrollEnabled, setScrollEnabled] = useState(initialScrollEnabled); - const [isFullyExpanded, setIsFullyExpanded] = useState(false); - - const yPosition = useSharedValue(0); - - const maxExpandedHeight = deviceHeight - (expandedCardBottomInset + expandedCardTopInset); - - const containerStyle = useAnimatedStyle(() => { - return { - height: - cardHeight.value > MAX_CARD_HEIGHT || !skipCollapsedState - ? interpolate( - cardHeight.value, - [MAX_CARD_HEIGHT, MAX_CARD_HEIGHT, maxExpandedHeight], - [cardHeight.value, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT], - 'clamp' - ) - : undefined, - zIndex: interpolate(cardHeight.value, [0, MAX_CARD_HEIGHT, MAX_CARD_HEIGHT + 1], [1, 1, 2], 'clamp'), - }; - }); - - const backdropStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - opacity: canExpandFully && isFullyExpanded ? withTiming(1, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const cardStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - const expandedCardHeight = Math.min(contentHeight.value + CARD_BORDER_WIDTH * 2, maxExpandedHeight); - return { - borderColor: interpolateColor( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - isDarkMode ? ['#1F2023', '#1F2023', '#242527'] : ['#F5F7F8', '#F5F7F8', '#FBFCFD'] - ), - height: cardHeight.value > MAX_CARD_HEIGHT ? cardHeight.value : undefined, - position: canExpandFully && isFullyExpanded ? 'absolute' : 'relative', - transform: [ - { - translateY: interpolate( - cardHeight.value, - [0, MAX_CARD_HEIGHT, expandedCardHeight], - [ - 0, - 0, - -yPosition.value + - expandedCardTopInset + - (deviceHeight - (expandedCardBottomInset + expandedCardTopInset) - expandedCardHeight) - - (yPosition.value + expandedCardHeight >= deviceHeight - expandedCardBottomInset - ? 0 - : deviceHeight - expandedCardBottomInset - yPosition.value - expandedCardHeight), - ] - ), - }, - ], - }; - }); - - const centerVerticallyWhenCollapsedStyle = useAnimatedStyle(() => { - return { - transform: skipCollapsedState - ? undefined - : [ - { - translateY: interpolate( - cardHeight.value, - [ - 0, - COLLAPSED_CARD_HEIGHT, - contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT - ? MAX_CARD_HEIGHT - : contentHeight.value + CARD_BORDER_WIDTH * 2, - maxExpandedHeight, - ], - [-2, -2, 0, 0] - ), - }, - ], - }; - }); - - const shadowStyle = useAnimatedStyle(() => { - const canExpandFully = contentHeight.value + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT; - return { - shadowOpacity: canExpandFully && isFullyExpanded ? withTiming(isDarkMode ? 0.9 : 0.16, timingConfig) : withTiming(0, timingConfig), - }; - }); - - const handleContentSizeChange = useCallback( - (width: number, height: number) => { - contentHeight.value = Math.round(height); - }, - [contentHeight] - ); - - const handleOnLayout = useCallback(() => { - runOnUI(() => { - if (cardHeight.value === MAX_CARD_HEIGHT) { - const measurement = measure(cardRef); - if (measurement === null) { - return; - } - if (yPosition.value !== measurement.pageY) { - yPosition.value = measurement.pageY; - } - } - })(); - }, [cardHeight, cardRef, yPosition]); - - useAnimatedReaction( - () => ({ contentHeight: contentHeight.value, isExpanded, isFullyExpanded }), - ({ contentHeight, isExpanded, isFullyExpanded }, previous) => { - if ( - isFullyExpanded !== previous?.isFullyExpanded || - isExpanded !== previous?.isExpanded || - contentHeight !== previous?.contentHeight - ) { - if (isFullyExpanded) { - const expandedCardHeight = - contentHeight + CARD_BORDER_WIDTH * 2 > maxExpandedHeight ? maxExpandedHeight : contentHeight + CARD_BORDER_WIDTH * 2; - if (contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT && cardHeight.value >= MAX_CARD_HEIGHT) { - cardHeight.value = withTiming(expandedCardHeight, timingConfig); - } else { - runOnJS(setIsFullyExpanded)(false); - } - } else if (isExpanded) { - cardHeight.value = withTiming( - contentHeight + CARD_BORDER_WIDTH * 2 > MAX_CARD_HEIGHT ? MAX_CARD_HEIGHT : contentHeight + CARD_BORDER_WIDTH * 2, - timingConfig - ); - } else { - cardHeight.value = withTiming(COLLAPSED_CARD_HEIGHT, timingConfig); - } - - const enableScroll = isExpanded && contentHeight + CARD_BORDER_WIDTH * 2 > (isFullyExpanded ? maxExpandedHeight : MAX_CARD_HEIGHT); - runOnJS(setScrollEnabled)(enableScroll); - } - } - ); - - return ( - - { - if (isFullyExpanded) { - setIsFullyExpanded(false); - } - }} - pointerEvents={isFullyExpanded ? 'auto' : 'none'} - style={[ - { - backgroundColor: 'rgba(0, 0, 0, 0.6)', - height: deviceHeight * 3, - left: -deviceWidth * 0.5, - position: 'absolute', - top: -deviceHeight, - width: deviceWidth * 2, - zIndex: -1, - }, - backdropStyle, - ]} - /> - - - - { - if (!isFullyExpanded) { - setIsFullyExpanded(true); - } else setIsFullyExpanded(false); - } - } - > - {children} - - - - - - - - ); -}; - -const FadeGradient = ({ side, style }: { side: 'top' | 'bottom'; style?: StyleProp>> }) => { - const { colors, isDarkMode } = useTheme(); - - const isTop = side === 'top'; - const solidColor = isDarkMode ? globalColors.white10 : '#FBFCFD'; - const transparentColor = colors.alpha(solidColor, 0); - - return ( - - - - ); -}; - -const IconContainer = ({ - children, - hitSlop, - opacity, - size = 20, -}: { - children: React.ReactNode; - hitSlop?: number; - opacity?: number; - size?: number; -}) => { - // Prevent wide icons from being clipped - const extraHorizontalSpace = 4; - - return ( - - - {children} - - - ); -}; - -type EventType = 'send' | 'receive' | 'approve' | 'revoke' | 'failed' | 'insufficientBalance' | 'MALICIOUS' | 'WARNING'; - -type EventInfo = { - amountPrefix: string; - icon: string; - iconColor: TextColor; - label: string; - textColor: TextColor; -}; - -const infoForEventType: { [key: string]: EventInfo } = { - send: { - amountPrefix: '- ', - icon: '􀁷', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.send), - textColor: 'red', - }, - receive: { - amountPrefix: '+ ', - icon: '􀁹', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.receive), - textColor: 'green', - }, - approve: { - amountPrefix: '', - icon: '􀎤', - iconColor: 'green', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.approve), - textColor: 'label', - }, - revoke: { - amountPrefix: '', - icon: '􀎠', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.event_row.types.revoke), - textColor: 'label', - }, - failed: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: i18n.t(i18n.l.walletconnect.simulation.simulation_card.titles.likely_to_fail), - textColor: 'red', - }, - insufficientBalance: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'blue', - label: '', - textColor: 'blue', - }, - MALICIOUS: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'red', - label: '', - textColor: 'red', - }, - WARNING: { - amountPrefix: '', - icon: '􀇿', - iconColor: 'orange', - label: '', - textColor: 'orange', - }, -}; - -type DetailType = 'chain' | 'contract' | 'to' | 'function' | 'sourceCodeVerification' | 'dateCreated' | 'nonce'; - -type DetailInfo = { - icon: string; - label: string; -}; - -const infoForDetailType: { [key: string]: DetailInfo } = { - chain: { - icon: '􀤆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.chain), - }, - contract: { - icon: '􀉆', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract), - }, - to: { - icon: '􀉩', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.to), - }, - function: { - icon: '􀡅', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.function), - }, - sourceCodeVerification: { - icon: '􀕹', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.source_code), - }, - dateCreated: { - icon: '􀉉', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.contract_created), - }, - nonce: { - icon: '􀆃', - label: i18n.t(i18n.l.walletconnect.simulation.details_card.types.nonce), - }, -}; - -const CHARACTERS_PER_LINE = 40; -const LINE_HEIGHT = 11; -const LINE_GAP = 9; - -const estimateMessageHeight = (message: string) => { - const estimatedLines = Math.ceil(message.length / CHARACTERS_PER_LINE); - const messageHeight = estimatedLines * LINE_HEIGHT + (estimatedLines - 1) * LINE_GAP + CARD_ROW_HEIGHT + 24 * 3 - CARD_BORDER_WIDTH * 2; - - return messageHeight; -}; - -const formatDate = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - const diffTime = Math.abs(now.getTime() - date.getTime()); - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - const diffWeeks = Math.floor(diffDays / 7); - const diffMonths = Math.floor(diffDays / 30.44); - - if (diffDays === 0) { - return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); - } else if (diffDays === 1) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; - } else if (diffDays < 7) { - return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; - } else if (diffWeeks === 1) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; - } else if (diffDays < 30.44) { - return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; - } else if (diffMonths === 1) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; - } else if (diffDays < 365.25) { - return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; - } else { - return date.toLocaleString('default', { month: 'short', year: 'numeric' }); - } -}; diff --git a/src/screens/WalletConnectApprovalSheet.js b/src/screens/WalletConnectApprovalSheet.js index 81fc33d918d..ffc166f11c6 100644 --- a/src/screens/WalletConnectApprovalSheet.js +++ b/src/screens/WalletConnectApprovalSheet.js @@ -406,11 +406,13 @@ export default function WalletConnectApprovalSheet() { - - {isScam && '􁅏 '} - {isVerified && '􀇻 '} - {formattedDappUrl} - + {formattedDappUrl && ( + + {isScam && '􁅏 '} + {isVerified && '􀇻 '} + {formattedDappUrl} + + )} diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx index d78336c324e..e8b297d502e 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -28,6 +28,8 @@ import { IS_ANDROID } from '@/env'; import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync'; import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync'; import { UserAssetsSync } from '@/state/sync/UserAssetsSync'; +import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener'; +import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents'; const WalletPage = styled(Page)({ ...position.sizeAsObject('100%'), @@ -81,6 +83,7 @@ const WalletScreen: React.FC = ({ navigation, route }) => { if (walletReady) { loadAccountLateData(); loadGlobalLateData(); + runWalletBackupStatusChecks(); } }, [loadAccountLateData, loadGlobalLateData, walletReady]); @@ -122,6 +125,9 @@ const WalletScreen: React.FC = ({ navigation, route }) => { + + {/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */} + ); diff --git a/src/state/performance/operations.ts b/src/state/performance/operations.ts index f30133ce5c6..a042200fc26 100644 --- a/src/state/performance/operations.ts +++ b/src/state/performance/operations.ts @@ -4,6 +4,7 @@ export enum Screens { SEND = 'Send', SEND_ENS = 'SendENS', WALLETCONNECT = 'WalletConnect', + MOBILE_WALLET_PROTOCOL = 'MobileWalletProtocol', } type RouteValues = (typeof Screens)[keyof typeof Screens]; diff --git a/src/storage/index.ts b/src/storage/index.ts index 824c7288ce7..14446bfbe4c 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -3,6 +3,7 @@ import { MMKV } from 'react-native-mmkv'; import { Account, Cards, Campaigns, Device, Review } from '@/storage/schema'; import { EthereumAddress, RainbowTransaction } from '@/entities'; import { Network } from '@/networks/types'; +import { SecureStorage } from '@coinbase/mobile-wallet-protocol-host'; /** * Generic storage class. DO NOT use this directly. Instead, use the exported @@ -12,8 +13,8 @@ export class Storage { protected sep = ':'; protected store: MMKV; - constructor({ id }: { id: string }) { - this.store = new MMKV({ id }); + constructor({ id, encryptionKey }: { id: string; encryptionKey?: string }) { + this.store = new MMKV({ id, encryptionKey }); } /** @@ -50,6 +51,13 @@ export class Storage { this.store.delete(scopes.join(this.sep)); } + /** + * Clear all values from storage + */ + clear() { + this.store.clearAll(); + } + /** * Remove many values from the same storage scope by keys * @@ -59,6 +67,21 @@ export class Storage { removeMany(scopes: [...Scopes], keys: Key[]) { keys.forEach(key => this.remove([...scopes, key])); } + + /** + * Encrypt the storage with a new key + * @param newEncryptionKey - The new encryption key + */ + encrypt(newEncryptionKey: string): void { + this.store.recrypt(newEncryptionKey); + } + + /** + * Remove encryption from the storage + */ + removeEncryption(): void { + this.store.recrypt(undefined); + } } /** @@ -88,3 +111,27 @@ export const cards = new Storage<[], Cards>({ id: 'cards' }); export const identifier = new Storage<[], { identifier: string }>({ id: 'identifier', }); + +/** + * Mobile Wallet Protocol storage + * + * @todo - fix any type here + */ +const mwpStorage = new Storage<[], { [key: string]: string }>({ id: 'mwp', encryptionKey: process.env.MWP_ENCRYPTION_KEY }); + +export const mwp: SecureStorage = { + get: async function (key: string): Promise { + const dataJson = mwpStorage.get([key]); + if (dataJson === undefined) { + return undefined; + } + return Promise.resolve(JSON.parse(dataJson) as T); + }, + set: async function (key: string, value: T): Promise { + const encoded = JSON.stringify(value); + mwpStorage.set([key], encoded); + }, + remove: async function (key: string): Promise { + mwpStorage.remove([key]); + }, +}; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts new file mode 100644 index 00000000000..8bb40dc1897 --- /dev/null +++ b/src/utils/formatDate.ts @@ -0,0 +1,28 @@ +import * as i18n from '@/languages'; + +export const formatDate = (dateString: string) => { + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now.getTime() - date.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30.44); + + if (diffDays === 0) { + return i18n.t(i18n.l.walletconnect.simulation.formatted_dates.today); + } else if (diffDays === 1) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.day_ago)}`; + } else if (diffDays < 7) { + return `${diffDays} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.days_ago)}`; + } else if (diffWeeks === 1) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.week_ago)}`; + } else if (diffDays < 30.44) { + return `${diffWeeks} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.weeks_ago)}`; + } else if (diffMonths === 1) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.month_ago)}`; + } else if (diffDays < 365.25) { + return `${diffMonths} ${i18n.t(i18n.l.walletconnect.simulation.formatted_dates.months_ago)}`; + } else { + return date.toLocaleString('default', { month: 'short', year: 'numeric' }); + } +}; diff --git a/src/utils/requestNavigationHandlers.ts b/src/utils/requestNavigationHandlers.ts index 52f5a2a334f..e238b86c396 100644 --- a/src/utils/requestNavigationHandlers.ts +++ b/src/utils/requestNavigationHandlers.ts @@ -22,10 +22,256 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount'; import { enableActionsOnReadOnlyWallet } from '@/config'; import walletTypes from '@/helpers/walletTypes'; import watchingAlert from './watchingAlert'; +import { + EthereumAction, + isEthereumAction, + isHandshakeAction, + PersonalSignAction, + RequestMessage, + useMobileWalletProtocolHost, +} from '@coinbase/mobile-wallet-protocol-host'; +import { logger, RainbowError } from '@/logger'; +import { noop } from 'lodash'; +import { toUtf8String } from '@ethersproject/strings'; +import { BigNumber } from '@ethersproject/bignumber'; import { Address } from 'viem'; import { ChainId } from '@/networks/types'; -export type RequestSource = 'walletconnect' | 'browser'; +export enum RequestSource { + WALLETCONNECT = 'walletconnect', + BROWSER = 'browser', + MOBILE_WALLET_PROTOCOL = 'mobile-wallet-protocol', +} + +// Mobile Wallet Protocol + +interface HandleMobileWalletProtocolRequestProps + extends Omit, 'message' | 'handleRequestUrl' | 'sendFailureToClient'> { + request: RequestMessage; +} + +const constructEthereumActionPayload = (action: EthereumAction) => { + if (action.method === 'eth_sendTransaction') { + const { weiValue, fromAddress, toAddress, actionSource, gasPriceInWei, ...rest } = action.params; + return [ + { + ...rest, + from: fromAddress, + to: toAddress, + value: weiValue, + }, + ]; + } + + return Object.values(action.params); +}; + +const supportedMobileWalletProtocolActions: string[] = [ + 'eth_requestAccounts', + 'eth_sendTransaction', + 'eth_signTypedData_v4', + 'personal_sign', + 'wallet_switchEthereumChain', +]; + +export const handleMobileWalletProtocolRequest = async ({ + request, + fetchClientAppMetadata, + approveHandshake, + rejectHandshake, + approveAction, + rejectAction, + session, +}: HandleMobileWalletProtocolRequestProps): Promise => { + logger.debug(`Handling Mobile Wallet Protocol request: ${request.uuid}`); + + const { selected } = store.getState().wallets; + const { accountAddress } = store.getState().settings; + + let addressToUse = accountAddress; + let chainIdToUse = ChainId.mainnet; + + const isReadOnlyWallet = selected?.type === walletTypes.readOnly; + if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { + logger.debug('Rejecting request due to read-only wallet'); + watchingAlert(); + return Promise.reject(new Error('This wallet is read-only.')); + } + + const handleAction = async (currentIndex: number): Promise => { + const action = request.actions[currentIndex]; + logger.debug(`Handling action: ${action.kind}`); + + if (isHandshakeAction(action)) { + logger.debug(`Processing handshake action for ${action.appId}`); + const chainIds = Object.values(ChainId).filter(value => typeof value === 'number') as number[]; + const receivedTimestamp = Date.now(); + + const dappMetadata = await fetchClientAppMetadata(); + return new Promise((resolve, reject) => { + const routeParams: WalletconnectApprovalSheetRouteParams = { + receivedTimestamp, + meta: { + chainIds, + dappName: dappMetadata?.appName || dappMetadata?.appUrl || action.appName || action.appIconUrl || action.appId || '', + dappUrl: dappMetadata?.appUrl || action.appId || '', + imageUrl: maybeSignUri(dappMetadata?.iconUrl || action.appIconUrl), + isWalletConnectV2: false, + peerId: '', + dappScheme: action.callback, + proposedChainId: request.account?.networkId || chainIdToUse, + proposedAddress: request.account?.address || addressToUse, + }, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + timedOut: false, + callback: async (approved, chainId, address) => { + addressToUse = address; + chainIdToUse = chainId; + + if (approved) { + logger.debug(`Handshake approved for ${action.appId}`); + const success = await approveHandshake(dappMetadata); + resolve(success); + } else { + logger.debug(`Handshake rejected for ${action.appId}`); + await rejectHandshake('User rejected the handshake'); + reject('User rejected the handshake'); + } + }, + }; + + Navigation.handleAction( + Routes.WALLET_CONNECT_APPROVAL_SHEET, + routeParams, + getActiveRoute()?.name === Routes.WALLET_CONNECT_APPROVAL_SHEET + ); + }); + } else if (isEthereumAction(action)) { + logger.debug(`Processing ethereum action: ${action.method}`); + if (!supportedMobileWalletProtocolActions.includes(action.method)) { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type ${action.method}`)); + await rejectAction(action, { + message: 'Unsupported action type', + code: 4001, + }); + return false; + } + + if (action.method === 'wallet_switchEthereumChain') { + const chainId = BigNumber.from(action.params.chainId).toNumber(); + const isSupportedChain = Object.values(ChainId).includes(chainId); + if (!isSupportedChain) { + await rejectAction(action, { + message: 'Unsupported chain', + code: 4001, + }); + return false; + } + + await approveAction(action, { value: 'null' }); + return true; + } + + // NOTE: This is a workaround to approve the eth_requestAccounts action if the previous action was a handshake action. + const previousAction = request.actions[currentIndex - 1]; + if (previousAction && isHandshakeAction(previousAction)) { + logger.debug('Approving eth_requestAccounts'); + await approveAction(action, { + value: JSON.stringify({ + chain: request.account?.chain ?? 'eth', + networkId: chainIdToUse, + address: addressToUse, + }), + }); + return true; + } + + const nativeCurrency = store.getState().settings.nativeCurrency; + + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + if ((action as PersonalSignAction).params.message && (action as PersonalSignAction).params.message.type === 'Buffer') { + // @ts-expect-error - coinbase host protocol types are NOT correct e.g. {"data": [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100], "type": "Buffer"} + const messageFromBuffer = toUtf8String(Buffer.from((action as PersonalSignAction).params.message.data, 'hex')); + (action as PersonalSignAction).params.message = messageFromBuffer; + } + + const payload = { + method: action.method, + params: constructEthereumActionPayload(action), + }; + + const displayDetails = await getRequestDisplayDetails(payload, nativeCurrency, request.account?.networkId ?? ChainId.mainnet); + const address = (action as PersonalSignAction).params.address || request.account?.address || accountAddress; + const requestWithDetails: RequestData = { + dappName: session?.dappName ?? session?.dappId ?? '', + dappUrl: session?.dappURL ?? '', + imageUrl: session?.dappImageURL ?? '', + address, + chainId: request.account?.networkId ?? ChainId.mainnet, + payload, + displayDetails, + }; + + return new Promise((resolve, reject) => { + const onSuccess = async (result: string) => { + logger.debug(`Ethereum action approved: [${action.method}]: ${result}`); + const success = await approveAction(action, { value: JSON.stringify(result) }); + resolve(success); + }; + + const onCancel = async (error?: Error) => { + if (error) { + logger.debug(`Ethereum action rejected: [${action.method}]: ${error.message}`); + await rejectAction(action, { + message: error.message, + code: 4001, + }); + reject(error.message); + } else { + logger.debug(`Ethereum action rejected: [${action.method}]: User rejected request`); + await rejectAction(action, { + message: 'User rejected request', + code: 4001, + }); + reject('User rejected request'); + } + }; + + Navigation.handleAction(Routes.CONFIRM_REQUEST, { + transactionDetails: requestWithDetails, + onSuccess, + onCancel, + onCloseScreen: noop, + chainId: request.account?.networkId ?? ChainId.mainnet, + address, + source: RequestSource.MOBILE_WALLET_PROTOCOL, + }); + }); + } else { + logger.error(new RainbowError(`[handleMobileWalletProtocolRequest]: Unsupported action type, ${action}`)); + return false; + } + }; + + const handleActions = async (actions: typeof request.actions, currentIndex: number = 0): Promise => { + if (currentIndex >= actions.length) { + logger.debug(`All actions completed successfully: ${actions.length}`); + return true; + } + + logger.debug(`Processing action ${currentIndex + 1} of ${actions.length}`); + const success = await handleAction(currentIndex); + if (success) { + return handleActions(actions, currentIndex + 1); + } else { + // stop processing if an action fails + return false; + } + }; + + // start processing actions starting at index 0 + return handleActions(request.actions); +}; // Dapp Browser @@ -58,7 +304,7 @@ export const handleDappBrowserConnectionPrompt = ( proposedChainId: dappData.chainId, proposedAddress: dappData.address, }, - source: 'browser', + source: RequestSource.BROWSER, timedOut: false, callback: async (approved, approvedChainId, accountAddress) => { if (approved) { @@ -83,16 +329,31 @@ export const handleDappBrowserConnectionPrompt = ( }); }; -export const handleDappBrowserRequest = async (request: Omit): Promise => { +const findWalletForAddress = async (address: string) => { + if (!address.trim()) { + return Promise.reject(new Error('Invalid address')); + } + const { wallets } = store.getState().wallets; - const selectedWallet = findWalletWithAccount(wallets!, request.address); - const isReadOnlyWallet = selectedWallet!.type === walletTypes.readOnly; + const selectedWallet = findWalletWithAccount(wallets!, address); + if (!selectedWallet) { + return Promise.reject(new Error('Wallet not found')); + } + + const isReadOnlyWallet = selectedWallet.type === walletTypes.readOnly; if (isReadOnlyWallet && !enableActionsOnReadOnlyWallet) { watchingAlert(); return Promise.reject(new Error('This wallet is read-only.')); } + + return selectedWallet; +}; + +export const handleDappBrowserRequest = async (request: Omit): Promise => { + await findWalletForAddress(request.address); + const nativeCurrency = store.getState().settings.nativeCurrency; - const displayDetails = getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); + const displayDetails = await getRequestDisplayDetails(request.payload, nativeCurrency, request.chainId); const requestWithDetails: RequestData = { ...request, @@ -124,7 +385,7 @@ export const handleDappBrowserRequest = async (request: Omit Date: Fri, 30 Aug 2024 17:17:19 -0300 Subject: [PATCH 07/13] Revert "nfts sorting direction (#6045)" (#6067) This reverts commit 5597286839ab0f16cf7870c2bf5e4cb6031109e5. --- .../WrappedCollectiblesHeader.tsx | 53 ++++--------------- src/components/list/ListHeaderMenu.tsx | 13 +++-- .../native-context-menu/contextMenu.js | 6 +++ .../native-context-menu/contextMenu.tsx | 7 --- src/hooks/useNFTsSortBy.ts | 35 ++++++------ src/hooks/useRefreshAccountData.ts | 8 +-- src/hooks/useWalletSectionsData.ts | 5 +- src/hooks/useWatchPendingTxs.ts | 17 +++--- src/languages/en_US.json | 6 +-- src/resources/nfts/index.ts | 27 +++------- 10 files changed, 67 insertions(+), 110 deletions(-) create mode 100644 src/components/native-context-menu/contextMenu.js delete 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 1954febef5c..bb233041e82 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -2,9 +2,8 @@ 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 { colors } from '@/styles'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; +import useNftSort from '@/hooks/useNFTsSortBy'; const TokenFamilyHeaderHeight = 48; @@ -31,7 +30,7 @@ const getMenuItemIcon = (value: NftCollectionSortCriterion) => { }; const CollectiblesHeader = () => { - const { nftSort, nftSortDirection, updateNFTSort } = useNftSort(); + const { nftSort, updateNFTSort } = useNftSort(); return ( { { - 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: i18n.t(i18n.l.nfts.sort.order.asc), - icon: { - iconType: 'SYSTEM', - iconValue: 'arrow.up.circle', - iconTint: nftSortDirection === SortDirection.Asc ? undefined : colors.grey, - }, - }, - { - actionKey: `${sortCriterion}|${SortDirection.Desc}`, - actionTitle: i18n.t(i18n.l.nfts.sort.order.desc), - 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)} + selected={nftSort} + menuItems={Object.entries(NftCollectionSortCriterion).map(([key, value]) => ({ + actionKey: value, + actionTitle: i18n.t(i18n.l.nfts.sort[value]), + icon: { iconType: 'SYSTEM', iconValue: getMenuItemIcon(value) }, + menuState: nftSort === key ? 'on' : 'off', + }))} + selectItem={string => updateNFTSort(string as NftCollectionSortCriterion)} 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 48ad813b2b6..f39df68a365 100644 --- a/src/components/list/ListHeaderMenu.tsx +++ b/src/components/list/ListHeaderMenu.tsx @@ -2,13 +2,18 @@ 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 { MenuConfig } from 'react-native-ios-context-menu'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; + +type MenuItem = { + actionKey: string; + actionTitle: string; + menuState?: 'on' | 'off'; +}; type ListHeaderMenuProps = { - selected: NftSort; - menuItems: MenuConfig['menuItems']; + selected: NftCollectionSortCriterion; + menuItems: MenuItem[]; 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 new file mode 100644 index 00000000000..fbe320a1a44 --- /dev/null +++ b/src/components/native-context-menu/contextMenu.js @@ -0,0 +1,6 @@ +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 deleted file mode 100644 index 95a134cb757..00000000000 --- a/src/components/native-context-menu/contextMenu.tsx +++ /dev/null @@ -1,7 +0,0 @@ -/* 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 a37f69b6a16..345f20132a7 100644 --- a/src/hooks/useNFTsSortBy.ts +++ b/src/hooks/useNFTsSortBy.ts @@ -1,32 +1,31 @@ -import { NftCollectionSortCriterion, SortDirection } from '@/graphql/__generated__/arc'; +import { useCallback } from 'react'; import { MMKV, useMMKVString } from 'react-native-mmkv'; import useAccountSettings from './useAccountSettings'; +import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; const mmkv = new MMKV(); const getStorageKey = (accountAddress: string) => `nfts-sort-${accountAddress}`; -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 mmkv.getString(getStorageKey(accountAddress)) as NftCollectionSortCriterion; }; -export type NftSort = `${NftCollectionSortCriterion}|${SortDirection}`; - -export function useNftSort() { +export default function useNftSort(): { + nftSort: NftCollectionSortCriterion; + updateNFTSort: (sortBy: NftCollectionSortCriterion) => void; +} { const { accountAddress } = useAccountSettings(); - const [nftSortData, setNftSortData] = useMMKVString(getStorageKey(accountAddress)); - const { sortBy, sortDirection } = parseNftSort(nftSortData); + const [nftSort, setNftSort] = useMMKVString(getStorageKey(accountAddress)); + + const updateNFTSort = useCallback( + (sortBy: NftCollectionSortCriterion) => { + setNftSort(sortBy); + }, + [setNftSort] + ); return { - updateNFTSort: (nftSort: NftSort) => setNftSortData(nftSort), - nftSort: sortBy, - nftSortDirection: sortDirection, + updateNFTSort, + nftSort: (nftSort as NftCollectionSortCriterion) || NftCollectionSortCriterion.MostRecent, }; } diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts index 67897f81319..f9cb7ab786c 100644 --- a/src/hooks/useRefreshAccountData.ts +++ b/src/hooks/useRefreshAccountData.ts @@ -9,8 +9,9 @@ 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'; -import { invalidateAddressNftsQueries } from '@/resources/nfts'; +import { nftsQueryKey } from '@/resources/nfts'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; +import useNftSort from './useNFTsSortBy'; import { Address } from 'viem'; import { addysSummaryQueryKey } from '@/resources/summary/summary'; import useWallets from './useWallets'; @@ -21,6 +22,7 @@ export default function useRefreshAccountData() { const { accountAddress, nativeCurrency } = useAccountSettings(); const [isRefreshing, setIsRefreshing] = useState(false); const profilesEnabled = useExperimentalFlag(PROFILES); + const { nftSort } = useNftSort(); const { connectedToHardhat } = useConnectedToHardhatStore(); const { wallets } = useWallets(); @@ -31,7 +33,7 @@ export default function useRefreshAccountData() { ); const fetchAccountData = useCallback(async () => { - invalidateAddressNftsQueries(accountAddress); + queryClient.invalidateQueries(nftsQueryKey({ address: accountAddress, sortBy: nftSort })); queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency })); queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency })); queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat })); @@ -53,7 +55,7 @@ export default function useRefreshAccountData() { logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`)); throw error; } - }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, profilesEnabled]); + }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, nftSort, profilesEnabled]); const refresh = useCallback(async () => { if (isRefreshing) return; diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 88bd12c8be4..72e89e3de55 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -4,13 +4,13 @@ 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, nftSortDirection } = useNftSort(); + const { nftSort } = useNftSort(); const { accountAddress, language, network, nativeCurrency } = useAccountSettings(); const { sendableUniqueTokens } = useSendableUniqueTokens(); @@ -32,7 +32,6 @@ export default function useWalletSectionsData({ } = useLegacyNFTs({ address: accountAddress, sortBy: nftSort, - sortDirection: nftSortDirection, }); const walletsWithBalancesAndNames = useWalletsWithBalancesAndNames(); diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts index 26da4995160..20ddd2169fe 100644 --- a/src/hooks/useWatchPendingTxs.ts +++ b/src/hooks/useWatchPendingTxs.ts @@ -1,5 +1,8 @@ +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 { MinedTransaction, RainbowTransaction } from '@/entities/transactions/transaction'; import { transactionFetchQuery } from '@/resources/transactions/transaction'; import { RainbowError, logger } from '@/logger'; import { getProvider } from '@/handlers/web3'; @@ -7,14 +10,12 @@ import { consolidatedTransactionsQueryKey } from '@/resources/transactions/conso 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 { useNonceStore } from '@/state/nonces'; import { usePendingTransactionsStore } from '@/state/pendingTransactions'; -import { useCallback, useMemo } from 'react'; +import { useNonceStore } from '@/state/nonces'; import { Address } from 'viem'; -import useAccountSettings from './useAccountSettings'; +import { nftsQueryKey } from '@/resources/nfts'; +import { getNftSortForAddress } from './useNFTsSortBy'; +import { ChainId } from '@/networks/types'; import { staleBalancesStore } from '@/state/staleBalances'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; @@ -48,7 +49,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) => testnetMode: !!connectedToHardhat, }) ); - invalidateAddressNftsQueries(address); + queryClient.invalidateQueries(nftsQueryKey({ address, sortBy: getNftSortForAddress(address) })); }, [address, connectedToHardhat, nativeCurrency] ); diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 7b76a8919ef..31697624327 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -1321,11 +1321,7 @@ "MOST_RECENT": "Recent", "most_recent": "Recent", "FLOOR_PRICE": "Floor Price", - "floor_price": "Floor Price", - "order": { - "asc": "Ascending order", - "desc": "Descending order" - } + "floor_price": "Floor Price" }, "empty": "Collectibles", "collect_now": "Collect Now", diff --git a/src/resources/nfts/index.ts b/src/resources/nfts/index.ts index c7c92601830..1730cb71592 100644 --- a/src/resources/nfts/index.ts +++ b/src/resources/nfts/index.ts @@ -1,32 +1,21 @@ import { QueryFunction, useQuery } from '@tanstack/react-query'; -import { QueryConfigWithSelect, createQueryKey, queryClient } from '@/react-query'; +import { QueryConfigWithSelect, createQueryKey } from '@/react-query'; import { fetchSimpleHashNFTListing } from '@/resources/nfts/simplehash'; import { simpleHashNFTToUniqueAsset } from '@/resources/nfts/simplehash/utils'; 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 { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; import { ChainId } from '@/networks/types'; const NFTS_STALE_TIME = 600000; // 10 minutes const NFTS_CACHE_TIME_EXTERNAL = 3600000; // 1 hour const NFTS_CACHE_TIME_INTERNAL = 604800000; // 1 week -export const nftsQueryKey = ({ - address, - sortBy, - sortDirection, -}: { - address: string; - sortBy: NftCollectionSortCriterion; - sortDirection: SortDirection; -}) => createQueryKey('nfts', { address, sortBy, sortDirection }, { persisterVersion: 1 }); - -export const invalidateAddressNftsQueries = (address: string) => { - queryClient.invalidateQueries(createQueryKey('nfts', { address })); -}; +export const nftsQueryKey = ({ address, sortBy }: { address: string; sortBy: NftCollectionSortCriterion }) => + createQueryKey('nfts', { address, sortBy }, { persisterVersion: 4 }); export const nftListingQueryKey = ({ contractAddress, @@ -64,8 +53,8 @@ interface NFTData { type NFTQueryKey = ReturnType; const fetchNFTData: QueryFunction = async ({ queryKey }) => { - const [{ address, sortBy, sortDirection }] = queryKey; - const queryResponse = await arcClient.getNFTs({ walletAddress: address, sortBy, sortDirection }); + const [{ address, sortBy }] = queryKey; + const queryResponse = await arcClient.getNFTs({ walletAddress: address, sortBy }); const nfts = queryResponse?.nftsV2?.map(nft => simpleHashNFTToUniqueAsset(nft, address)); @@ -88,17 +77,15 @@ 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, sortDirection }), fetchNFTData, { + const { data, error, isLoading, isInitialLoading } = useQuery(nftsQueryKey({ address, sortBy }), fetchNFTData, { cacheTime: isImportedWallet ? NFTS_CACHE_TIME_INTERNAL : NFTS_CACHE_TIME_EXTERNAL, enabled: !!address, retry: 3, From ffaa5a6b73066add501b625b92accf7605ba8c66 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 30 Aug 2024 16:19:43 -0400 Subject: [PATCH 08/13] fix pbx file (#6065) --- ios/Rainbow.xcodeproj/project.pbxproj | 514 +++++++++++++------------- 1 file changed, 265 insertions(+), 249 deletions(-) diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 2dcbfe439c8..e586a215e55 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 */; }; + 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */; }; + 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */; }; A4277D9F23CBD1910042BAF4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277D9E23CBD1910042BAF4 /* Extensions.swift */; }; A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; @@ -68,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 */; }; + 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 */; }; @@ -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 */; }; @@ -139,8 +139,8 @@ C9B378BB2C515A860085E5D0 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B378BA2C515A860085E5D0 /* ShareViewController.swift */; }; C9B378BE2C515A860085E5D0 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = C9B378BD2C515A860085E5D0 /* Base */; }; C9B378C22C515A860085E5D0 /* ShareWithRainbow.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9B378B82C515A860085E5D0 /* ShareWithRainbow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */; }; ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; }; - F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -203,14 +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 = ""; }; + 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; }; - 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 = ""; }; + 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; + 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.localrelease.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.localrelease.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rainbow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rainbow.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Rainbow/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Rainbow/AppDelegate.mm; sourceTree = ""; }; @@ -248,21 +248,19 @@ 24979E7C20F84004007EB0DA /* FirebaseCoreDiagnostics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FirebaseCoreDiagnostics.framework; path = Frameworks/FirebaseCoreDiagnostics.framework; sourceTree = ""; }; 24979E7D20F84005007EB0DA /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Frameworks/module.modulemap; sourceTree = ""; }; 24979E7E20F84005007EB0DA /* nanopb.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = nanopb.framework; path = Frameworks/nanopb.framework; sourceTree = ""; }; + 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.debug.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.debug.xcconfig"; sourceTree = ""; }; 3C379D5D20FD1F92009AF81F /* Rainbow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Rainbow.entitlements; path = Rainbow/Rainbow.entitlements; sourceTree = ""; }; 3CBE29CB2381E43800BE05AC /* Rainbow-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Rainbow-Bridging-Header.h"; sourceTree = ""; }; - 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 = ""; }; + 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 = ""; }; - 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 = ""; }; 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 = ""; }; @@ -273,19 +271,17 @@ 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 = ""; }; + 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; + 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Rainbow.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.release.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.release.xcconfig"; sourceTree = ""; }; + 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; + 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.localrelease.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.localrelease.xcconfig"; sourceTree = ""; }; 98AED33BAB4247CEBEF8464D /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 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 = ""; }; @@ -295,9 +291,8 @@ AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; AAA0EF342BF5A4AD00A19A53 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.localrelease.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.localrelease.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; - 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,8 @@ B5CE8FFD29A5758100EB1EFA /* pooly@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pooly@3x.png"; sourceTree = ""; }; B5D7F2EE29E8D41D003D6A54 /* finiliar@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@3x.png"; sourceTree = ""; }; B5D7F2EF29E8D41E003D6A54 /* finiliar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "finiliar@2x.png"; sourceTree = ""; }; - BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ImageNotification.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.staging.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.staging.xcconfig"; sourceTree = ""; }; + BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SelectTokenIntent.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C04D10EF25AFC8C1003BEF7A /* Extras.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Extras.json; sourceTree = ""; }; C11640E7274DC10B00C9120A /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; C1272389274EBBB6006AC743 /* CurrencyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyDetails.swift; sourceTree = ""; }; @@ -358,10 +354,14 @@ C9B378BA2C515A860085E5D0 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C9B378BD2C515A860085E5D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; C9B378BF2C515A860085E5D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 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 = ""; }; - 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 = ""; }; + E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.staging.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.staging.xcconfig"; sourceTree = ""; }; + E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.release.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.release.xcconfig"; sourceTree = ""; }; + E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; + FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SelectTokenIntent.staging.xcconfig"; path = "Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent.staging.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -369,7 +369,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F1AF02D17F8CA2658D40F850 /* libPods-ImageNotification.a in Frameworks */, + B8CD55BCF7A0AB6EFF7E2107 /* libPods-ImageNotification.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -379,7 +379,7 @@ files = ( ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */, C72F456C99A646399192517D /* libz.tbd in Frameworks */, - C5931485B6F3169BD50DFD30 /* libPods-Rainbow.a in Frameworks */, + 86913359E129076B2E94167D /* libPods-Rainbow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -389,7 +389,7 @@ files = ( C16DCF60272BA6EF00FF5C78 /* SwiftUI.framework in Frameworks */, C16DCF5E272BA6EF00FF5C78 /* WidgetKit.framework in Frameworks */, - 0C2E322C01EE0F31C4776094 /* libPods-PriceWidgetExtension.a in Frameworks */, + CE27C1EECE64497C72CC5B3A /* libPods-PriceWidgetExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,7 +398,7 @@ buildActionMask = 2147483647; files = ( C16DCF81272BAB9500FF5C78 /* Intents.framework in Frameworks */, - A2AAF523E5B2B1EBC8137D09 /* libPods-SelectTokenIntent.a in Frameworks */, + 8A822C422360198E62AD7BFA /* libPods-SelectTokenIntent.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,10 +575,10 @@ C16DCF80272BAB9500FF5C78 /* Intents.framework */, C16DCF8B272BAB9600FF5C78 /* IntentsUI.framework */, C9B378A12C5159880085E5D0 /* UniformTypeIdentifiers.framework */, - BB0FE095E68092461A6D109F /* libPods-ImageNotification.a */, - 42118C9C036DBC88A3A29F6C /* libPods-PriceWidgetExtension.a */, - 7A601D2BCA4F452ED5E54002 /* libPods-Rainbow.a */, - 9B6D10CD2C122665B8935B51 /* libPods-SelectTokenIntent.a */, + 025D857C7770B9D5627327FA /* libPods-ImageNotification.a */, + D346B122505933DBFA22D04A /* libPods-PriceWidgetExtension.a */, + 6875B4873D0949D4D7AAAE07 /* libPods-Rainbow.a */, + BCDB57FCE228B5380B3618D3 /* libPods-SelectTokenIntent.a */, ); name = Frameworks; sourceTree = ""; @@ -733,22 +733,22 @@ C640359C0E6575CE0A7ECD73 /* Pods */ = { isa = PBXGroup; children = ( - 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 */, + 4AE8D68F8252F11480F2D99E /* Pods-ImageNotification.debug.xcconfig */, + 6657A4835F27FC0FFFFBF340 /* Pods-ImageNotification.release.xcconfig */, + 0C374E26CF6ED7C5C2C823ED /* Pods-ImageNotification.localrelease.xcconfig */, + E00C89EED34D89AE4F2A3462 /* Pods-ImageNotification.staging.xcconfig */, + E2E91009BF4E2373D0EAA867 /* Pods-PriceWidgetExtension.debug.xcconfig */, + 675045CF6F69ECCABFA7ED3C /* Pods-PriceWidgetExtension.release.xcconfig */, + B04D80FD63CAEB682250EB0A /* Pods-PriceWidgetExtension.localrelease.xcconfig */, + B7B49161FD8417A90F7CB9A8 /* Pods-PriceWidgetExtension.staging.xcconfig */, + 7A533038ADA944A6493A66D1 /* Pods-Rainbow.debug.xcconfig */, + 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */, + 8148434135865DD9B63D5991 /* Pods-Rainbow.localrelease.xcconfig */, + 4C6224CF2221F931780B3CE6 /* Pods-Rainbow.staging.xcconfig */, + 33BC79874A51AA3880F7C4AC /* Pods-SelectTokenIntent.debug.xcconfig */, + E1179CDF5117977A37C590EE /* Pods-SelectTokenIntent.release.xcconfig */, + 0EF5903F6DA2DDF4181D7872 /* Pods-SelectTokenIntent.localrelease.xcconfig */, + FD8F690D66D4FC3337BC4317 /* Pods-SelectTokenIntent.staging.xcconfig */, ); path = Pods; sourceTree = ""; @@ -796,11 +796,11 @@ isa = PBXNativeTarget; buildConfigurationList = 0299CE842886202800B5C7E7 /* Build configuration list for PBXNativeTarget "ImageNotification" */; buildPhases = ( - 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */, + FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */, 0299CE732886202800B5C7E7 /* Sources */, 0299CE742886202800B5C7E7 /* Frameworks */, 0299CE752886202800B5C7E7 /* Resources */, - A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */, + BB2F78BEBE245D7D48C41820 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -815,16 +815,16 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Rainbow" */; buildPhases = ( - 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */, + 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, - E25F1DAB12B818820CCCC7DA /* [CP] Embed Pods Frameworks */, - 89D80C8A947BAC7AC4044C2B /* [CP] Copy Pods Resources */, - 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */, + 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */, + F7FE6D014143799E2E6894D9 /* [CP] Copy Pods Resources */, + 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); @@ -844,11 +844,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF6E272BA6F100FF5C78 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; buildPhases = ( - E01A15A23F18E701327D899B /* [CP] Check Pods Manifest.lock */, + 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */, C16DCF58272BA6EF00FF5C78 /* Sources */, C16DCF59272BA6EF00FF5C78 /* Frameworks */, C16DCF5A272BA6EF00FF5C78 /* Resources */, - 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */, + BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -863,11 +863,11 @@ isa = PBXNativeTarget; buildConfigurationList = C16DCF9F272BAB9600FF5C78 /* Build configuration list for PBXNativeTarget "SelectTokenIntent" */; buildPhases = ( - F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */, + 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */, C16DCF7B272BAB9500FF5C78 /* Sources */, C16DCF7C272BAB9500FF5C78 /* Frameworks */, C16DCF7D272BAB9500FF5C78 /* Resources */, - 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */, + EE77FD4F5751D4E858F705B1 /* [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,29 @@ shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -ex\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../node_modules/@sentry/cli/bin/sentry-cli\"\n\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$SENTRY_CLI react-native xcode $REACT_NATIVE_XCODE\\\"\"\n"; showEnvVarsInLog = 0; }; - 1D879D4B18B4DC04F228E2C7 /* [CP-User] [RNFB] Core Configuration */ = { + 2D9D26600F4C9E0D02F62A88 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Rainbow-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3340F01A333AFB189AD85476 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1111,7 +1133,7 @@ shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - 36D99880F36CB5AA484ABD9C /* [CP] Check Pods Manifest.lock */ = { + 3F02A98D7D1EC5BBE267C4DD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1126,50 +1148,32 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4B758949E4EFCB338FE38BB7 /* [CP] Copy Pods Resources */ = { + 6B4739E5535DF7E99F0022D2 /* [CP] Embed Pods Frameworks */ = { 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", + "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; 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}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); 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-Rainbow/Pods-Rainbow-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 779A2E9F0850050C42F07ED9 /* [CP] Check Pods Manifest.lock */ = { + 7B777DFB693FD28E1DB8F77A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1184,20 +1188,35 @@ 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 */ = { + 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-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"; + }; + BB2F78BEBE245D7D48C41820 /* [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", @@ -1209,15 +1228,6 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1232,22 +1242,13 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9DD9574443FD0F3A6EA845CD /* [CP] Copy Pods Resources */ = { + BB62BF1016C4CA862C515CFC /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1283,28 +1284,49 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { + EE77FD4F5751D4E858F705B1 /* [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 = "Upload Debug Symbols to Sentry"; + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseABTesting_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseRemoteConfig_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SelectTokenIntent/Pods-SelectTokenIntent-resources.sh\"\n"; + showEnvVarsInLog = 0; }; - A749F3F91297420815BF1CA6 /* [CP] Copy Pods Resources */ = { + F7FE6D014143799E2E6894D9 /* [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-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", @@ -1316,6 +1338,15 @@ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseMessaging/FirebaseMessaging_Privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/RNImageCropPickerPrivacyInfo.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/react-native-cameraroll/RNCameraRollPrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -1330,53 +1361,22 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseMessaging_Privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImageCropPickerPrivacyInfo.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCameraRollPrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageNotification/Pods-ImageNotification-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - E01A15A23F18E701327D899B /* [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; - }; - 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"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F54110CBD33132601634482D /* [CP] Check Pods Manifest.lock */ = { + FD10D710D0C7F8EEE6EB0F77 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1391,7 +1391,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SelectTokenIntent-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ImageNotification-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1583,7 +1583,7 @@ /* Begin XCBuildConfiguration section */ 0299CE802886202800B5C7E7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6432E4F9BF277F50B8A4A508 /* Pods-ImageNotification.debug.xcconfig */; + baseConfigurationReference = 4AE8D68F8252F11480F2D99E /* 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 = 6657A4835F27FC0FFFFBF340 /* 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 = 0C374E26CF6ED7C5C2C823ED /* 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 = E00C89EED34D89AE4F2A3462 /* 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 = 7A533038ADA944A6493A66D1 /* 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; @@ -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 = 6FF8A19FD539CD5755E2A363 /* Pods-Rainbow.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -1971,7 +1971,7 @@ }; 2C6A799821127ED9003AFB37 /* Staging */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4C646731A50EDF3C5FAF8373 /* Pods-Rainbow.staging.xcconfig */; + baseConfigurationReference = 4C6224CF2221F931780B3CE6 /* 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; @@ -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 = 8148434135865DD9B63D5991 /* 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; @@ -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 = E2E91009BF4E2373D0EAA867 /* 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 = 675045CF6F69ECCABFA7ED3C /* 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 = B04D80FD63CAEB682250EB0A /* 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 = B7B49161FD8417A90F7CB9A8 /* 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 = 33BC79874A51AA3880F7C4AC /* 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 = E1179CDF5117977A37C590EE /* 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 = 0EF5903F6DA2DDF4181D7872 /* 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 = FD8F690D66D4FC3337BC4317 /* 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; From 2a9b11d04282e9a0d90fce6c64ddd7ac0a3c70c0 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Tue, 3 Sep 2024 20:14:32 -0400 Subject: [PATCH 09/13] Tophat (#6043) * publish artifacts to s3 * publish artifacts to s3 with tophat url * run anywhere * zip before upload * refactor job * rearrange * zip the right folder * fix * missing " * another refactor * fix filename * another refactor * fix naming * username * testing real device * refactor again * force run * more updates * disable cache download * change signing defaults * target CI-9 * add xcode caching * fix aws src syntax * fix paths again * change order of actions * fix path * dont run while in draft * restore app store default profiles * switch profiles before building --- .github/workflows/macstadium-builds.yml | 111 ++++++++++++++++++ ...acstadium-e2e.yml => macstadium-tests.yml} | 69 +++++++---- ios/Rainbow.xcodeproj/project.pbxproj | 54 +++++---- ios/Rainbow/Info.plist | 106 ++++++++--------- ios/fastlane/Fastfile | 60 +++++++++- 5 files changed, 302 insertions(+), 98 deletions(-) create mode 100644 .github/workflows/macstadium-builds.yml rename .github/workflows/{macstadium-e2e.yml => macstadium-tests.yml} (64%) diff --git a/.github/workflows/macstadium-builds.yml b/.github/workflows/macstadium-builds.yml new file mode 100644 index 00000000000..555ec762658 --- /dev/null +++ b/.github/workflows/macstadium-builds.yml @@ -0,0 +1,111 @@ +name: iOS builds +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +jobs: + # Job to install dependencies + build: + runs-on: ["self-hosted"] + if: github.event.pull_request.draft == false + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: + contents: read + steps: + - 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 iOS app + run: yarn clean:ios > /dev/null 2>&1 || true + + - name: Set up ENV vars & scripts + env: + 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 + + - 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 install && yarn setup + + - name: Version debug + run: | + npx react-native info + + - name: Install pods + run: yarn install-bundle && yarn install-pods + + - uses: irgaly/xcode-cache@v1 + with: + key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} + restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- + + # TOPHAT iOS SIM + - name: Build the app in release mode for simulator + run: | + sed -i'' -e "s/IS_TESTING=true/IS_TESTING=false/" .env && rm -f .env-e + sed -i '' 's/match AppStore/match AdHoc/g' "ios/Rainbow.xcodeproj/project.pbxproj" + + xcodebuild -workspace ios/Rainbow.xcworkspace -scheme Rainbow -configuration Release -sdk iphonesimulator -derivedDataPath ios/build + APP_DIR=$(find . -name "*.app" | head -n 1) + cd $APP_DIR && zip -r ../../../../../../App.zip . + + # TOPHAT iOS DEVICE + - name: Build the app in release mode for iOS devices + env: + FASTLANE_USER: ${{ secrets.FASTLANE_USER }} + FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + run: | + cd ios && bundle exec fastlane ios build_device + - name: Upload builds to AWS S3 + env: + AWS_BUCKET: rainbow-app-team-production + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + COMMIT_HASH: ${{ github.sha }} + run: | + APP_FILE=$(find . -name "App.zip" | head -n 1) + aws s3 cp "${APP_FILE}" "s3://${AWS_BUCKET}/${BRANCH_NAME}/${COMMIT_HASH}.app.zip" + IPA_FILE=./ios/build/Rainbow.ipa + aws s3 cp "${IPA_FILE}" "s3://${AWS_BUCKET}/${BRANCH_NAME}/${COMMIT_HASH}.ipa" + + - name: Post comment to PR + if: github.event_name == 'pull_request' + env: + TOPHAT_GITHUB_TOKEN: ${{ secrets.TOPHAT_GITHUB_TOKEN }} + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + COMMIT_HASH: ${{ github.sha }} + run: | + COMMENT="Launch in [simulator](http://localhost:29070/install/ios?virtual=https://app-team.p.rainbow.me/${BRANCH_NAME}/${COMMIT_HASH}.app.zip) or [device](http://localhost:29070/install/ios?physical=https://app-team.p.rainbow.me/${BRANCH_NAME}/${COMMIT_HASH}.ipa) for ${COMMIT_HASH}" + curl -s -H "Authorization: token $TOPHAT_GITHUB_TOKEN" -X POST \ + -d "{\"body\":\"$COMMENT\"}" \ + "${{ github.event.pull_request.comments_url }}" + + + diff --git a/.github/workflows/macstadium-e2e.yml b/.github/workflows/macstadium-tests.yml similarity index 64% rename from .github/workflows/macstadium-e2e.yml rename to .github/workflows/macstadium-tests.yml index fd826523eaa..dae6d82cc72 100644 --- a/.github/workflows/macstadium-e2e.yml +++ b/.github/workflows/macstadium-tests.yml @@ -1,16 +1,15 @@ name: iOS e2e tests - on: [pull_request, workflow_dispatch] + jobs: - ios-e2e: + # Job to install dependencies + install-deps: runs-on: ["self-hosted"] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: contents: read - steps: - name: Checkout repo uses: actions/checkout@v4 @@ -29,8 +28,7 @@ jobs: git clone git@github.com:rainbow-me/rainbow-env.git mv rainbow-env/dotenv .env && rm -rf rainbow-env eval $CI_SCRIPTS - 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 @@ -48,7 +46,26 @@ jobs: - name: Install dependencies run: | - yarn cache clean --all && yarn install && yarn setup + yarn install && yarn setup + + - name: Upload Yarn cache + uses: actions/upload-artifact@v3 + with: + name: yarn-cache + path: | + .yarn/cache + .yarn/install-state.gz + + # Job for linting and unit tests + linting-and-unit-tests: + runs-on: ["self-hosted"] + needs: install-deps + steps: + - name: Download Yarn cache + uses: actions/download-artifact@v3 + with: + name: yarn-cache + path: .yarn - name: Check for frozen lockfile run: ./scripts/check-lockfile.sh @@ -62,24 +79,36 @@ jobs: - name: Unit tests run: yarn test + # iOS build and e2e tests + e2e-ios: + runs-on: ["self-hosted"] + needs: install-deps + steps: + - name: Download Yarn cache + uses: actions/download-artifact@v3 + with: + name: yarn-cache + path: .yarn + - name: Rebuild detox cache run: ./node_modules/.bin/detox clean-framework-cache && ./node_modules/.bin/detox build-framework-cache - + - name: Version debug run: | npx react-native info - + - name: Install pods run: yarn install-bundle && yarn install-pods - - - 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 ios.sim.release - - # change the '3' here to how many times you want the tests to rerun on failure + + - uses: irgaly/xcode-cache@v1 + with: + key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} + restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- + + # Detox iOS e2e tests - name: Run iOS e2e tests with retry - run: ./scripts/run-retry-tests.sh 3 + run: | + sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e + yarn detox build --configuration ios.sim.release + ./scripts/run-retry-tests.sh 3 + \ No newline at end of file diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index e586a215e55..f7203347248 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1652,12 +1652,14 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "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; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; @@ -1671,7 +1673,8 @@ 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 = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.ImageNotification"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -1864,11 +1867,13 @@ ASSETCATALOG_COMPILER_OPTIMIZATION = ""; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Rainbow/RainbowRelease.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; FRAMEWORK_SEARCH_PATHS = ( @@ -1905,7 +1910,8 @@ PRODUCT_BUNDLE_IDENTIFIER = me.rainbow; PRODUCT_NAME = Rainbow; PROVISIONING_PROFILE = ""; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow"; SWIFT_OBJC_BRIDGING_HEADER = "Rainbow-Bridging-Header.h"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; SWIFT_VERSION = 5.0; @@ -2331,11 +2337,13 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; @@ -2345,7 +2353,8 @@ 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 = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.PriceWidget"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2425,8 +2434,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 +2448,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; @@ -2517,11 +2526,13 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = SelectTokenIntent/SelectTokenIntent.entitlements; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L74NQAQB8H; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = L74NQAQB8H; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; @@ -2531,7 +2542,8 @@ 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 = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; @@ -2607,8 +2619,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 +2633,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; @@ -2711,7 +2723,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; @@ -2929,7 +2942,8 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; diff --git a/ios/Rainbow/Info.plist b/ios/Rainbow/Info.plist index 91736a5af8d..8273cab6e66 100644 --- a/ios/Rainbow/Info.plist +++ b/ios/Rainbow/Info.plist @@ -2,8 +2,6 @@ - RCTAsyncStorageExcludeFromBackup - CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -16,122 +14,122 @@ CFBundleAlternateIcons - pooly + adworld - UIPrerenderedIcon - CFBundleIconFiles - pooly + adworld - - poolboy - UIPrerenderedIcon - CFBundleIconFiles - - poolboy - - smol + farcaster - UIPrerenderedIcon - CFBundleIconFiles - smol + farcaster - - raindoge - UIPrerenderedIcon + + finiliar + CFBundleIconFiles - raindoge + finiliar + UIPrerenderedIcon + golddoge - UIPrerenderedIcon - CFBundleIconFiles golddoge - - pixel - UIPrerenderedIcon + + og + CFBundleIconFiles - pixel + og + UIPrerenderedIcon + optimism - UIPrerenderedIcon - CFBundleIconFiles optimism - - zora - UIPrerenderedIcon + + pixel + CFBundleIconFiles - zora + pixel - - finiliar - UIPrerenderedIcon + + poolboy + CFBundleIconFiles - finiliar + poolboy - - zorb - UIPrerenderedIcon + + pooly + CFBundleIconFiles - zorb + pooly - - farcaster - UIPrerenderedIcon + + raindoge + CFBundleIconFiles - farcaster + raindoge - - adworld - UIPrerenderedIcon + + smol + CFBundleIconFiles - adworld + smol + UIPrerenderedIcon + - og + zora + CFBundleIconFiles + + zora + UIPrerenderedIcon + + zorb + CFBundleIconFiles - og + zorb + UIPrerenderedIcon + CFBundlePrimaryIcon @@ -192,6 +190,8 @@ NSAllowsLocalNetworking + NSBluetoothAlwaysUsageDescription + This allows Rainbow to find and pair a Ledger Nano X NSCameraUsageDescription This lets you scan QR codes for connecting to dApps and discovering wallets NSFaceIDUsageDescription @@ -200,8 +200,6 @@ This allows Rainbow to save images to your Camera Roll NSPhotoLibraryUsageDescription This allows Rainbow to display selected photos as avatars across each of your wallet addresses - NSBluetoothAlwaysUsageDescription - This allows Rainbow to find and pair a Ledger Nano X NSUbiquitousContainers iCloud.me.rainbow @@ -218,6 +216,8 @@ SelectTokenIntent + RCTAsyncStorageExcludeFromBackup + UIAppFonts SF-Mono-Bold.otf diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index a3191ae123b..eafd2c4db2c 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -23,10 +23,6 @@ platform :ios do duration: 1200, # optional in_house: false, # optional but may be required if using match/sigh ) - - upload_symbols_to_crashlytics( - dsym_path: './Rainbow.app.dSYM.zip' - ) end desc "Submit a new Beta build to Apple TestFlight" @@ -67,7 +63,18 @@ platform :ios do scheme: "Rainbow", include_symbols: true, configuration: "Release", - export_method: "app-store" + export_method: "app-store", + export_options: { + signingStyle: "manual", + provisioningProfiles: { + "me.rainbow" => "match AppStore me.rainbow", + "me.rainbow.PriceWidget" => "match AppStore me.rainbow.PriceWidget", + "me.rainbow.SelectTokenIntent" => "match AppStore me.rainbow.SelectTokenIntent", + "me.rainbow.ImageNotification" => "match AppStore me.rainbow.ImageNotification", + "me.rainbow.OpenInRainbow" => "match AppStore me.rainbow.OpenInRainbow", + "me.rainbow.ShareWithRainbow" => "match AppStore me.rainbow.ShareWithRainbow", + }, + } ) pilot( @@ -77,4 +84,47 @@ platform :ios do ) end + desc "Create new build for iOS devices in release mode" + lane :build_device do + + update_info_plist( + plist_path: "Rainbow/Info.plist", + display_name: "Rainbow" + ) + + update_app_identifier( + app_identifier: "me.rainbow", + xcodeproj: "Rainbow.xcodeproj", + plist_path: "Rainbow/Info.plist", + ) + + match( + force: true, + type: "adhoc", + app_identifier: ["me.rainbow", "me.rainbow.PriceWidget", "me.rainbow.SelectTokenIntent", "me.rainbow.ImageNotification", "me.rainbow.OpenInRainbow", "me.rainbow.ShareWithRainbow"], + git_url: "git@github.com:rainbow-me/rainbow-code-signing.git", + ) + + gym( + workspace: "Rainbow.xcworkspace", + scheme: "Rainbow", + include_symbols: false, + export_method: "ad-hoc", + output_directory: "build", + output_name: "Rainbow.ipa", + archive_path: "build/Rainbow.xcarchive", + export_options: { + signingStyle: "manual", + provisioningProfiles: { + "me.rainbow" => "match AdHoc me.rainbow", + "me.rainbow.PriceWidget" => "match AdHoc me.rainbow.PriceWidget", + "me.rainbow.SelectTokenIntent" => "match AdHoc me.rainbow.SelectTokenIntent", + "me.rainbow.ImageNotification" => "match AdHoc me.rainbow.ImageNotification", + "me.rainbow.OpenInRainbow" => "match AdHoc me.rainbow.OpenInRainbow", + "me.rainbow.ShareWithRainbow" => "match AdHoc me.rainbow.ShareWithRainbow", + }, + } + ) + + end end From 56c883175be028477a903a51cbcb44081a45a479 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 4 Sep 2024 12:36:41 -0400 Subject: [PATCH 10/13] fix bad merge from network -> chainId work (#6074) --- src/walletConnect/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index 0faf3a4ec5e..5a45f567b37 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -718,12 +718,10 @@ export async function onSessionRequest(event: SignClientTypes.EventArguments['se logger.debug(`[walletConnect]: handling request`, {}, logger.DebugContext.walletconnect); - const dappNetwork = ethereumUtils.getNetworkFromChainId(chainId); - const displayDetails = await getRequestDisplayDetails(event.params.request, nativeCurrency, dappNetwork); + const displayDetails = await getRequestDisplayDetails(event.params.request, nativeCurrency, chainId); const peerMeta = session.peer.metadata; const metadata = await fetchDappMetadata({ url: peerMeta.url, status: true }); - const dappName = metadata?.appName || peerMeta.name || lang.t(lang.l.walletconnect.unknown_url); const dappImage = metadata?.appLogo || peerMeta?.icons?.[0]; From 2af8a3eb078d670c97ff1f0cc375dedbf73a4fd8 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 4 Sep 2024 13:36:16 -0400 Subject: [PATCH 11/13] Hide collectibles based on feature flag (#6073) * hide collectibles section based on feature flag * change default remote config * re-enable for e2e * what happened to the linter --- .../RecyclerAssetList2/NFTEmptyState.tsx | 10 +++-- .../RecyclerAssetList2/NFTLoadingSkeleton.tsx | 8 ++++ .../WrappedCollectiblesHeader.tsx | 8 ++++ .../RecyclerAssetList2/WrappedNFT.tsx | 9 ++++ .../WrappedTokenFamilyHeader.tsx | 8 ++++ .../core/RawRecyclerList.tsx | 19 ++++++-- .../core/getLayoutProvider.tsx | 43 ++++++++++++++++++- .../profile-header/ProfileAvatarRow.tsx | 2 +- src/config/experimental.ts | 2 + src/config/experimentalHooks.ts | 1 - src/helpers/RainbowContext.tsx | 15 ++++++- src/hooks/useImportingWallet.ts | 2 +- src/hooks/useOnAvatarPress.ts | 2 +- src/model/remoteConfig.ts | 5 ++- src/screens/points/PointsScreen.tsx | 4 +- 15 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx index 26f131b8638..cae66bb27ae 100644 --- a/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx +++ b/src/components/asset-list/RecyclerAssetList2/NFTEmptyState.tsx @@ -3,7 +3,7 @@ import Animated from 'react-native-reanimated'; import { Box, Stack, Text, useColorMode } from '@/design-system'; import * as i18n from '@/languages'; import { TokenFamilyHeaderHeight } from './NFTLoadingSkeleton'; -import { MINTS, useExperimentalFlag } from '@/config'; +import { MINTS, NFTS_ENABLED, useExperimentalFlag } from '@/config'; import { useRemoteConfig } from '@/model/remoteConfig'; import { IS_TEST } from '@/env'; import { useMints } from '@/resources/mints'; @@ -44,7 +44,8 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp as={Animated.View} borderRadius={15} justifyContent="center" - paddingHorizontal="10px" + paddingVertical="12px" + paddingHorizontal="20px" style={[{ backgroundColor: isDarkMode ? SEPARATOR_COLOR : LIGHT_SEPARATOR_COLOR }]} > @@ -58,15 +59,18 @@ const LaunchFeaturedMintButton = ({ featuredMint }: LaunchFeaturedMintButtonProp }; export function NFTEmptyState() { - const { mints_enabled } = useRemoteConfig(); + const { mints_enabled, nfts_enabled } = useRemoteConfig(); const { accountAddress } = useAccountSettings(); const { data: { featuredMint }, } = useMints({ walletAddress: accountAddress }); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST; + if (!nftsEnabled) return null; + return ( { }; const NFTLoadingSkeleton = ({ items = 5 }) => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + + if (!nftsEnabled) return null; + return ( {[...Array(items)].map((_, index) => ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx index bb233041e82..de588f3e021 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedCollectiblesHeader.tsx @@ -4,6 +4,9 @@ import * as i18n from '@/languages'; import { ListHeaderMenu } from '@/components/list/ListHeaderMenu'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; import useNftSort from '@/hooks/useNFTsSortBy'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; const TokenFamilyHeaderHeight = 48; @@ -30,7 +33,12 @@ const getMenuItemIcon = (value: NftCollectionSortCriterion) => { }; const CollectiblesHeader = () => { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; const { nftSort, updateNFTSort } = useNftSort(); + + if (!nftsEnabled) return null; + return ( diff --git a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx index 17d7f6dd44e..9781c473237 100644 --- a/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx +++ b/src/components/asset-list/RecyclerAssetList2/WrappedTokenFamilyHeader.tsx @@ -2,6 +2,9 @@ import React from 'react'; import { TokenFamilyHeader } from '../../token-family'; import { useLatestCallback, useOpenFamilies } from '@/hooks'; import { ThemeContextProps } from '@/theme'; +import { useRemoteConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, useExperimentalFlag } from '@/config'; +import { IS_TEST } from '@/env'; type Props = { name: string; @@ -12,6 +15,9 @@ type Props = { }; export default React.memo(function WrappedTokenFamilyHeader({ name, total, image, theme, testID }: Props) { + const { nfts_enabled } = useRemoteConfig(); + const nftsEnabled = (useExperimentalFlag(NFTS_ENABLED) || nfts_enabled) && !IS_TEST; + const { openFamilies, updateOpenFamilies } = useOpenFamilies(); const isFamilyOpen = openFamilies[name]; @@ -21,6 +27,8 @@ export default React.memo(function WrappedTokenFamilyHeader({ name, total, image }) ); + if (!nftsEnabled) return null; + return ( { return r1.uid !== r2.uid; @@ -54,6 +57,8 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ scrollIndicatorInsets?: object; type?: AssetListType; }) { + const remoteConfig = useRemoteConfig(); + const experimentalConfig = useContext(RainbowContext).config; const currentDataProvider = useMemoOne(() => dataProvider.cloneWithRows(briefSectionsData), [briefSectionsData]); const { isCoinListEdited, setIsCoinListEdited } = useCoinListEdited(); const y = useRecyclerAssetListPosition()!; @@ -65,8 +70,16 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ const cardIds = useMemo(() => getCardIdsForScreen(name as keyof typeof Routes), [getCardIdsForScreen, name]); const layoutProvider = useMemo( - () => getLayoutProvider(briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet), - [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet] + () => + getLayoutProvider({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + remoteConfig, + experimentalConfig, + }), + [briefSectionsData, isCoinListEdited, cardIds, isReadOnlyWallet, experimentalConfig] ); const { accountAddress } = useAccountSettings(); diff --git a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx index 13619bef984..ffe67eafd07 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/getLayoutProvider.tsx @@ -2,6 +2,11 @@ import { Dimension, Layout, LayoutManager, LayoutProvider } from 'recyclerlistvi import ViewDimensions from './ViewDimensions'; import { BaseCellType, CellType } from './ViewTypes'; import { deviceUtils } from '@/utils'; +import { getRemoteConfig, RainbowConfig } from '@/model/remoteConfig'; +import { NFTS_ENABLED, REMOTE_CARDS, useExperimentalFlag } from '@/config'; +import { useContext } from 'react'; +import { RainbowContextType } from '@/helpers/RainbowContext'; +import { IS_TEST } from '@/env'; const getStyleOverridesForIndex = (indices: number[]) => (index: number) => { if (indices.includes(index)) { @@ -30,7 +35,24 @@ class BetterLayoutProvider extends LayoutProvider { } } -const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: boolean, cardIds: string[], isReadOnlyWallet: boolean) => { +const getLayoutProvider = ({ + briefSectionsData, + isCoinListEdited, + cardIds, + isReadOnlyWallet, + experimentalConfig, + remoteConfig, +}: { + briefSectionsData: BaseCellType[]; + isCoinListEdited: boolean; + cardIds: string[]; + isReadOnlyWallet: boolean; + experimentalConfig: ReturnType>['config']; + remoteConfig: RainbowConfig; +}) => { + const remoteCardsEnabled = (remoteConfig.remote_cards_enabled || experimentalConfig[REMOTE_CARDS]) && !IS_TEST; + const nftsEnabled = (remoteConfig.nfts_enabled || experimentalConfig[NFTS_ENABLED]) && !IS_TEST; + const indicesToOverride = []; for (let i = 0; i < briefSectionsData.length; i++) { const val = briefSectionsData[i]; @@ -55,7 +77,24 @@ const getLayoutProvider = (briefSectionsData: BaseCellType[], isCoinListEdited: dim.height = ViewDimensions[type].height; dim.width = ViewDimensions[type].width || dim.width; - if ((type === CellType.REMOTE_CARD_CAROUSEL && !cardIds.length) || (type === CellType.REMOTE_CARD_CAROUSEL && isReadOnlyWallet)) { + // NOTE: If NFTs are disabled, we don't want to render the NFTs section, so adjust the height to 0 + if ( + [ + CellType.NFTS_EMPTY, + CellType.NFTS_HEADER_SPACE_AFTER, + CellType.NFTS_HEADER_SPACE_BEFORE, + CellType.NFTS_HEADER, + CellType.NFTS_LOADING, + CellType.NFT, + CellType.FAMILY_HEADER, + ].includes(type) && + !nftsEnabled + ) { + dim.height = 0; + } + + // NOTE: If remote cards are disabled, we don't want to render the remote cards section, so adjust the height to 0 + if (type === CellType.REMOTE_CARD_CAROUSEL && (!remoteCardsEnabled || !cardIds.length || isReadOnlyWallet)) { dim.height = 0; } } diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx index 0e6e1fb85a0..19d0760d3c1 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileAvatarRow.tsx @@ -35,7 +35,7 @@ export function ProfileAvatarRow({ size = ProfileAvatarSize }: { size?: number } const ContextMenuButton = onAvatarPressProfile ? React.Fragment : ContextMenu; const handlePressMenuItem = useLatestCallback((e: any) => { - const index = avatarContextMenuConfig.menuItems?.findIndex(item => item.actionKey === e.nativeEvent.actionKey); + const index = avatarContextMenuConfig?.menuItems?.findIndex(item => item && item.actionKey === e.nativeEvent.actionKey); onSelectionCallback(index); }); diff --git a/src/config/experimental.ts b/src/config/experimental.ts index b380ec0b0ac..ba81ea0fc60 100644 --- a/src/config/experimental.ts +++ b/src/config/experimental.ts @@ -29,6 +29,7 @@ export const DAPP_BROWSER = 'Dapp Browser'; export const ETH_REWARDS = 'ETH Rewards'; export const DEGEN_MODE = 'Degen Mode'; export const FEATURED_RESULTS = 'Featured Results'; +export const NFTS_ENABLED = 'Nfts Enabled'; /** * A developer setting that pushes log lines to an array in-memory so that @@ -66,6 +67,7 @@ export const defaultConfig: Record = { [ETH_REWARDS]: { settings: true, value: false }, [DEGEN_MODE]: { settings: true, value: false }, [FEATURED_RESULTS]: { settings: true, value: false }, + [NFTS_ENABLED]: { settings: true, value: false }, }; const storageKey = 'config'; diff --git a/src/config/experimentalHooks.ts b/src/config/experimentalHooks.ts index 4fd975e45b3..f99fa930c37 100644 --- a/src/config/experimentalHooks.ts +++ b/src/config/experimentalHooks.ts @@ -6,7 +6,6 @@ import isTestFlight from '@/helpers/isTestFlight'; const useExperimentalFlag = (name: any) => { if (IS_DEV || isTestFlight) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'config' does not exist on type '{}'. // eslint-disable-next-line react-hooks/rules-of-hooks return useContext(RainbowContext).config[name]; } else { diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx index c0734b50313..27260d4c004 100644 --- a/src/helpers/RainbowContext.tsx +++ b/src/helpers/RainbowContext.tsx @@ -4,7 +4,7 @@ import { useSharedValue } from 'react-native-reanimated'; import DevButton from '../components/dev-buttons/DevButton'; import Emoji from '../components/text/Emoji'; import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug'; -import { defaultConfig } from '../config/experimental'; +import { defaultConfig } from '@/config/experimental'; import { useDispatch } from 'react-redux'; import { useTheme } from '../theme/ThemeContext'; @@ -16,7 +16,18 @@ import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; -export const RainbowContext = createContext({}); +export type RainbowContextType = { + config: Record; + setConfig: (newConfig: Record) => void; + setGlobalState: (newState: any) => void; +}; + +export const RainbowContext = createContext({ + config: {}, + setConfig: () => {}, + setGlobalState: () => {}, +}); + const storageKey = 'config'; const storage = new MMKV({ diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts index 1523bae55c4..c95e6871d6c 100644 --- a/src/hooks/useImportingWallet.ts +++ b/src/hooks/useImportingWallet.ts @@ -135,7 +135,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) { } setResolvedAddress(address); name = forceEmoji ? `${forceEmoji} ${input}` : input; - avatarUrl = avatarUrl || avatar?.imageUrl; + avatarUrl = avatarUrl || (avatar && avatar?.imageUrl); setBusy(false); startImportProfile(name, guardedForceColor, address, avatarUrl); analytics.track('Show wallet profile modal for ENS address', { diff --git a/src/hooks/useOnAvatarPress.ts b/src/hooks/useOnAvatarPress.ts index bda2d6f20f5..d4b3f3c9bac 100644 --- a/src/hooks/useOnAvatarPress.ts +++ b/src/hooks/useOnAvatarPress.ts @@ -238,7 +238,7 @@ export default ({ screenType = 'transaction' }: UseOnAvatarPressProps = {}) => { ].filter(x => x), }; - const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item.actionTitle).concat(ios ? ['Cancel'] : []); + const avatarActionSheetOptions = avatarContextMenuConfig.menuItems.map(item => item && item.actionTitle).concat(ios ? ['Cancel'] : []); const onAvatarPressProfile = useCallback(() => { navigate(Routes.PROFILE_SHEET, { diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts index bf79c65490d..e2fa3709f8c 100644 --- a/src/model/remoteConfig.ts +++ b/src/model/remoteConfig.ts @@ -92,6 +92,7 @@ export interface RainbowConfig extends Record degen_mode: boolean; featured_results: boolean; + nfts_enabled: boolean; } export const DEFAULT_CONFIG: RainbowConfig = { @@ -175,6 +176,7 @@ export const DEFAULT_CONFIG: RainbowConfig = { degen_mode: false, featured_results: false, + nfts_enabled: true, }; export async function fetchRemoteConfig(): Promise { @@ -230,7 +232,8 @@ export async function fetchRemoteConfig(): Promise { key === 'idfa_check_enabled' || key === 'rewards_enabled' || key === 'degen_mode' || - key === 'featured_results' + key === 'featured_results' || + key === 'nfts_enabled' ) { config[key] = entry.asBoolean(); } else { diff --git a/src/screens/points/PointsScreen.tsx b/src/screens/points/PointsScreen.tsx index 3d24b8ded00..61fe24d9f2d 100644 --- a/src/screens/points/PointsScreen.tsx +++ b/src/screens/points/PointsScreen.tsx @@ -75,7 +75,7 @@ export function PointsScreen() { navigate(Routes.CHANGE_WALLET_SHEET)} scaleTo={0.8} overflowMargin={50}> {accountImage ? ( @@ -83,7 +83,7 @@ export function PointsScreen() { )} - ) + ) : null } rightComponent={pointsNotificationsToggleEnabled ? : undefined} title={rewardsEnabled ? i18n.t(i18n.l.account.tab_rewards) : i18n.t(i18n.l.account.tab_points)} From 8030ef40f9ee94a2dda0b93dbbcc23c530ecac37 Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 4 Sep 2024 14:47:18 -0400 Subject: [PATCH 12/13] ens send fix (#6075) * fix * add gas estimation checks --- src/screens/SendConfirmationSheet.tsx | 12 ++++++++++-- src/screens/SendSheet.js | 10 +++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/screens/SendConfirmationSheet.tsx b/src/screens/SendConfirmationSheet.tsx index 9d1613e22b5..4059d09a924 100644 --- a/src/screens/SendConfirmationSheet.tsx +++ b/src/screens/SendConfirmationSheet.tsx @@ -669,8 +669,16 @@ export const SendConfirmationSheet = () => { /> {isENS && ( - /* @ts-expect-error JavaScript component */ - + )} diff --git a/src/screens/SendSheet.js b/src/screens/SendSheet.js index 3cff2615a5b..a2c9e95f960 100644 --- a/src/screens/SendSheet.js +++ b/src/screens/SendSheet.js @@ -762,7 +762,15 @@ export default function SendSheet(props) { const assetChainId = selected.chainId; const currentProviderChainId = currentProvider._network.chainId; - if (assetChainId === currentChainId && currentProviderChainId === currentChainId && isValidAddress && !isEmpty(selected)) { + if ( + !!accountAddress && + amountDetails.assetAmount !== '' && + Object.entries(selected).length && + assetChainId === currentChainId && + currentProviderChainId === currentChainId && + isValidAddress && + !isEmpty(selected) + ) { estimateGasLimit( { address: accountAddress, From 63936a6491c55f6c2c77e129d96a2d1f5c81c455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Wed, 4 Sep 2024 15:08:52 -0400 Subject: [PATCH 13/13] use correct chainId (#6076) --- src/redux/gas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/redux/gas.ts b/src/redux/gas.ts index c912960b074..a9567865af5 100644 --- a/src/redux/gas.ts +++ b/src/redux/gas.ts @@ -475,7 +475,6 @@ export const gasPricesStartPolling = defaultGasLimit, gasLimit, selectedGasFee, - chainId, selectedGasFee: lastSelectedGasFee, gasFeesBySpeed: lastGasFeesBySpeed, currentBlockParams,