Skip to content

Commit

Permalink
Mobile Wallet Protocol / Txn cleanup (#6061)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
walmat authored and ibrahimtaveras00 committed Aug 30, 2024
1 parent ab48c68 commit 35d3e52
Show file tree
Hide file tree
Showing 49 changed files with 2,886 additions and 1,959 deletions.
1 change: 1 addition & 0 deletions globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
12 changes: 12 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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`)
Expand Down Expand Up @@ -1956,6 +1963,7 @@ SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- Branch
- CocoaAsyncSocket
- CoinbaseWalletSDK
- Firebase
- FirebaseABTesting
- FirebaseAnalytics
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2257,6 +2267,7 @@ SPEC CHECKSUMS:
Branch: d99436c6f3d5b2529ba948d273e47e732830f207
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
CoinbaseWalletSDK: bd6aa4f5a6460d4279e09e115969868e134126fb
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
FasterImage: af05a76f042ca3654c962b658fdb01cb4d31caee
FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2
Expand All @@ -2282,6 +2293,7 @@ SPEC CHECKSUMS:
MetricsReporter: 99596ee5003c69949ed2f50acc34aee83c42f843
MMKV: 817ba1eea17421547e01e087285606eb270a8dcb
MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73
MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d
nanopb: 438bc412db1928dac798aa6fd75726007be04262
PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
205 changes: 52 additions & 153 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,47 @@
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';
import { getOrCreateDeviceId, securelyHashWalletAddress } from '@/analytics/utils';
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();
Expand All @@ -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<InitialRoute>(null);
const eventSubscription = useRef<ReturnType<typeof AppState.addEventListener> | null>(null);
const branchListenerRef = useRef<ReturnType<typeof branch.subscribe> | null>(null);
const navigatorRef = useRef<NavigationContainerRef<RootStackParamList> | 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<RootStackParamList>) => {
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 (
<Portal>
<View style={containerStyle}>
<View style={sx.container}>
{initialRoute && (
<InitialRouteContext.Provider value={initialRoute}>
<RoutesComponent ref={handleNavigatorRef} />
<Routes ref={handleNavigatorRef} />
<PortalConsumer />
</InitialRouteContext.Provider>
)}
<OfflineToast />
</View>
<NotificationsHandler walletReady={walletReady} />
<DeeplinkHandler initialRoute={initialRoute} walletReady={walletReady} />
<AppStateChangeHandler walletReady={walletReady} />
</Portal>
);
}

export type AppStore = typeof store;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];

const AppWithRedux = connect<unknown, AppDispatch, unknown, RootState>(state => ({
const AppWithRedux = connect<AppProps, AppDispatch, AppProps, AppState>(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();
Expand Down Expand Up @@ -301,19 +198,21 @@ function Root() {
prefetchDefaultFavorites();
}}
>
<SafeAreaProvider>
<MainThemeProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<RainbowContextWrapper>
<SharedValuesProvider>
<ErrorBoundary>
<AppWithRedux walletReady={false} />
</ErrorBoundary>
</SharedValuesProvider>
</RainbowContextWrapper>
</GestureHandlerRootView>
</MainThemeProvider>
</SafeAreaProvider>
<MobileWalletProtocolProvider secureStorage={ls.mwp} sessionExpiryDays={7}>
<SafeAreaProvider>
<MainThemeProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<RainbowContextWrapper>
<SharedValuesProvider>
<ErrorBoundary>
<AppWithRedux walletReady={false} />
</ErrorBoundary>
</SharedValuesProvider>
</RainbowContextWrapper>
</GestureHandlerRootView>
</MainThemeProvider>
</SafeAreaProvider>
</MobileWalletProtocolProvider>
</PersistQueryClientProvider>
</RecoilRoot>
</ReduxProvider>
Expand Down
38 changes: 38 additions & 0 deletions src/components/AppStateChangeHandler.tsx
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof AppState.addEventListener> | 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;
}
Loading

0 comments on commit 35d3e52

Please sign in to comment.