Skip to content

Latest commit

 

History

History
 
 

mobile

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Mobile (Celo Wallet)

Overview

This is a wallet application for the Celo platform. It's a self-soverign wallet that enables anyone to onboard onto the Celo network, manage their currencies, and send payments.

Architecture

The app uses React Native and a geth light node.

Setup

You must have the celo-monorepo successfully set up and built before setting up and running the mobile wallet.

To do this, follow the setup instructions.

Next, install watchman.

# On a mac
brew install watchman

iOS

Enroll in the Apple Developer Program

In order to successfully set up your iOS development environment you will need to enroll in the Apple Developer Program. It is recommended that you enroll from an iOS device by downloading the Apple Developer App in the App Store. Using the app will result in the fastest processing of your enrollment.

If you are a cLabs employee, please ask to be added to the cLabs iOS development team.

Install Xcode

Xcode is needed to build and deploy the mobile wallet to your iOS device. If you do not have an iOS device, Xcode can be used to emulate one.

Install Xcode 11.4 (an Apple Developer Account is needed to access this link).

We do not recommend installing Xcode through the App Store as it can auto update and become incompatible with our projects.

Note that using the method above, you can have multiple versions of Xcode installed in parallel if you'd like. Simply use different names for the different version of Xcode in your computer's Applications folder (e.g., Xcode10.3.app and Xcode11.app).

Install Cocopods, Bundler, and download project dependencies

Make sure you are in the ios directory of the mobile package before running the following:

# install cocopods and bundler if you don't already have it
gem install cocoapods
gem install bundler
# download the project dependencies
bundle install
# run inside mobile/ios
bundle exec pod install

If your machine does not recognize the gem command, you may need to download Ruby first.

Android

Install Java

We need Java to be able to build and deploy the mobile app to Android devices. Android currently only builds correctly with Java 8. (Using OpenJDK because of Oracle being Oracle).

MacOS

Install by running the following:

brew install cask
brew tap homebrew/cask-versions
brew cask install homebrew/cask-versions/adoptopenjdk8

Alternatively, install Jenv to manage multiple Java versions:

brew install jenv
eval "$(jenv init -)"
jenv add /Library/Java/JavaVirtualMachines/<java8 version here>/Contents/Home
Linux

Install by running the following:

sudo apt install openjdk-8-jdk

Install Android Dev Tools

MacOS

Install the Android SDK and platform tools:

brew cask install android-sdk
brew cask install android-platform-tools

Next install Android Studio and add the Android NDK.

Execute the following (and make sure the lines are in your ~/.bash_profile).

Note that these paths may differ on your machine. You can find the path to the SDK and NDK via the Android Studio menu.

export ANDROID_HOME=/usr/local/share/android-sdk
export ANDROID_NDK=/usr/local/share/android-ndk
export ANDROID_SDK_ROOT=/usr/local/share/android-sdk
# this is an optional gradle configuration that should make builds faster
export GRADLE_OPTS='-Dorg.gradle.daemon=true -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"'

Then install the Android 29 platform:

sdkmanager 'platforms;android-29'
Linux

You can download the complete Android Studio and SDK from the Android Developer download site.

The steps are:

  1. Unpack the .zip file you downloaded to an appropriate location for your applications, such as within /usr/local/ for your user profile, or /opt/ for shared users.

    If you're using a 64-bit version of Linux, make sure you first install the required libraries for 64-bit machines.

  2. To launch Android Studio, open a terminal, navigate to the android-studio/bin/ directory, and execute studio.sh.

  3. Select whether you want to import previous Android Studio settings or not, then click OK.

  4. The Android Studio Setup Wizard guides you through the rest of the setup, which includes downloading Android SDK components that are required for development.

You can find the complete instructions about how to install the tools in Linux environments in the Documentation page.

Optional: Install an Android emulator

Configure an emulator using the Android SDK Manager

Install the Android 29 system image and create an Android Virtual Device:

sdkmanager "system-images;android-29;default;x86_64"
avdmanager create avd --force --name Pixel_API_29_AOSP_x86_64 --device pixel -k "system-images;android-29;default;x86_64"

Execute the following and add it to your ~/.bash_profile:

export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$PATH

Run the emulator with:

emulator -avd Pixel_API_29_AOSP_x86_64
Install Genymotion Emulator Manager

Another Android emulator option is Genymotion.

MacOS
brew cask install genymotion

Under OSX High Sierra and later, you'll get a message that you need to approve it in System Preferences > Security & Privacy > General.

Do that, and then repeat the line above.

Then make sure the ADB path is set correctly in Genymotion — set Preferences > ADB > Use custom Android SDK tools to /usr/local/share/android-sdk (same as $ANDROID_HOME)

Linux

You can download the Linux version of Genymotion from the fun zone! (you need to sign in first).

After having the binary you only need to run the installer:

sudo ./genymotion-3.0.2-linux_x64.bin

Running the mobile wallet

The below steps should help you successfully run the mobile wallet on either a USB connected or emulated device. For additional information and troublshooting see the React Native docs.

Note: We've seen some issues running the metro bundler from iTerm

  1. If you haven't already, run yarn from the monorepo root to install dependencies.

  2. Attach your device or start an emulated one.

iOS

  1. Launch Xcode and use it to open the directory celo.xcworkspace. Confirm your iOS device has been detected by Xcode.

  2. Build the project by pressing the play button in the top left corner or selecting Product > Build from the Xcode menu bar.

  3. From the mobile directory run yarn run dev:ios.

Android

  1. Follow these instructions to enable Developer Options on your Android device.

  2. Unplug and replug your device. You'll be prompted to accept the connection and shown a public key (corresponding to the abd_key.pub file in ~/.android)

  3. To confirm your device is properly connected, running adb devices from the terminal should reflect your connected device. If it lists a device as "unauthorized", make sure you've accepted the prompt or troubleshoot here.

  4. From the mobile directory run yarn run dev:android.

Running in forno (data saver) mode

By default, the mobile wallet app runs geth in lightest sync mode where all the epoch headers are fetched. The default sync mode is defined in packages/mobile/.env file.

To run the wallet in forno (Data Saver) mode, using a trusted node rather than the local geth node as a provider, turn it on from the Data Saver page in settings or update the FORNO_ENABLED_INITIALLY parameter in the .env file linked above. When forno mode is turned back off, the wallet will switch to the default sync mode as specified in the .env file. By default, the trusted node is https://{TESTNET}-forno.celo-testnet.org, however any trusted node can be used by updating DEFAULT_FORNO_URL. In forno mode, the wallet signs transactions locally in web3 then sends them to the trusted node.

To debug network requests in forno mode, we use Charles, a proxy for monitoring network traffic to see Celo JSON RPC calls and responses. Follow instructions here to configure Charles to proxy a test device.

Debugging & App Profiling

Debugging

To avoid debugging errors, ensure your device and laptop are connected to the same WiFi network even if they are connected via USB.

  1. Either shake the device or run yarn run dev:show-menu (only for Android) to open up the developer menu.

  2. Select Debug (iOS) or Start Remote JS Debugging (Android). This should open a new tab in your browser with React Native logger in the console. In order to get a full picture, the console's filter should be set to All levels.

  3. For the fastest development experience, you likely want to open the developer menu again and ensure Fast Reloading (iOS) or Live Reloading and Hot Reloading (Android) is enabled.

Optional: Install React Native Debugger

The React Native Debugger bundles together the Redux and Chrome dev tools nicely and provides a clean debugging environment.

App Profiling

Run yarn run react-devtools. It should automatically connect to the running app, and includes a profiler (second tab). Start recording with the profiler, use the app, and then stop recording.

The flame graph provides a view of each component and sub-component. The width is proportional to how long it took to load. If it is grey, it was not re-rendered at that 'commit' or DOM change. Details on the react native profiler are here. The biggest thing to look for are large number of renders when no state has changed. Reducing renders can be done via pure components in React or overloading the should component update method example here.

Testing

To execute the suite of tests, run yarn test.

Snapshot testing

We use Jest snapshot testing to assert that no intentional changes to the component tree have been made without explicit developer intention. See an example at [src/send/SendAmount.test.tsx]. If your snapshot is expected to deviate, you can update the snapshot with the -u or --updateSnapshot flag when running the test.

React component unit testing

We use react-native-testing-library to unit test react components. It allows for deep rendering and interaction with the rendered tree to assert proper reactions to user interaction and input. See an example at [src/send/SendAmount.test.tsx] or read more about the docs

Saga testing

We use redux-saga-test-plan to test complex sagas. See [src/identity/verification.test.ts] for an example.

End-to-End testing

We use Detox for E2E testing. In order to run the tests locally, you must have the proper emulator set up. Follow the instructions in e2e/README.md.

Once setup is done, you can run the tests with yarn test:e2e:android or yarn test:e2e:ios.

Building APKs / Bundles

You can create your own custom build of the app via the command line or in Android Studio. For an exact set of commands, refer to the lanes in fastlane/FastFile. For convinience, the basic are described below:

Creating a fake keystore

If you have not yet created a keystore, one will be required to generate a release APKs / bundles:

cd android/app
keytool -genkey -v -keystore celo-release-key.keystore -alias celo-key-alias -storepass celoFakeReleaseStorePass -keypass celoFakeReleaseKeyPass -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"
export CELO_RELEASE_STORE_PASSWORD=celoFakeReleaseStorePass
export CELO_RELEASE_KEY_PASSWORD=celoFakeReleaseKeyPass

Building an APK or Bundle

# With fastlane:
bundle install
bundle exec fastlane android build_apk env:YOUR_BUILDING_VARIANT

# Or, manually
cd android/
./gradlew clean
./gradlew bundle{YOUR_BUILDING_VARIANT}JsAndAssets
# For an APK:
./gradlew assemble{YOUR_BUILDING_VARIANT} -x bundle{YOUR_BUILDING_VARIANT}JsAndAssets
# Or for a bundle:
./gradlew bundle{YOUR_BUILDING_VARIANT} -x bundle{YOUR_BUILDING_VARIANT}JsAndAssets

Where YOUR_BUILD_VARIANT can be any of the app's build variants, such as debug or release.

Other

Configuring the SMS Retriever

On Android, the wallet app uses the SMS Retriever API to automatically input codes during phone number verification. When creating a new app build type this needs to be properly configured.

The service that route SMS messages to the app needs to be configured to append this app signature to the message. The hash depends on both the bundle id and the signing certificate. Since we use Google Play signing, we need to download the certificate.

  1. Go to the play console for the relevant app, Release management > App signing, and download the App signing certificate.
  2. Use this script to generate the hash code: https://github.com/michalbrz/sms-retriever-hash-generator

Generating GraphQL Types

We're using GraphQL Code Generator to properly type GraphQL queries. If you make a change to a query, run yarn build:gen-graphql-types to update the typings in the typings directory.

How we handle Geth crashes in wallet app on Android

Our Celo app has three types of codes.

  1. Javascript code - generated from Typescript, this runs in Javascript interpreter.
  2. Java bytecode - this runs on Dalvik/Art Virtual Machine.
  3. Native code - Geth code is written in Golang which compiles to native code, this runs directly on the CPU, no virtual machines involved.

One should note that, on iOS, there is no byte code and therefore, there are only two layers, one is the Javascript code, and the other is the Native code. Till now, we have been blind towards native crashes except Google Playstore logs.

Sentry, the crash logging mechanism we use, can catch both Javascript Errors as well as unhandled Java exceptions. It, however, does not catch Native crashes. There are quite a few tools to catch native crashes like Bugsnag and Crashlytics. They would have worked for us under normal circumstances. However, the Geth code produced by the Gomobile library and Go compiler logs a major chunk of information about the crash at Error level and not at the Fatal level. We hypothesize that this leads to incomplete stack traces showing up in Google Play store health checks. This issue is publicly known but has not been fixed.

We cannot use libraries like Bugsnag since they do not allow us to extract logcat logs immediately after the crash. Therefore, We use jndcrash, which uses ndcrash and enable us to log the logcat logs immediately after a native crash. We capture the results into a file and on next restart Sentry reads it. We need to do this two-step setup because once a native crash happens, running code to upload the data would be fragile. An error in sentry looks like this

Relevant code references:

  1. NDKCrashService
  2. Initialization of the NDKCrashService
  3. Sentry code to read NDK crash logs on restart

There are two major differences in Forno mode:

  1. Geth won't run at all. Instead, web3 connects to -forno.celo-testnet.org using an https provider, for example, https://integration-forno.celo-testnet.org.
  2. Transactions will be signed locally by contractkit.

Why do we use http(s) provider?

Websockets (ws) would have been a better choice but we cannot use unencrypted ws provider since it would be bad to send plain-text data from a privacy perspective. Geth does not support wss by default. And Kubernetes does not support it either. This forced us to use https provider.

Attaching to the geth instance

Android

  1. Start geth's HTTP RPC server by setting the config variable GETH_START_HTTP_RPC_SERVER to true. This is meant for development purposes only and can be a serious vulnerability if used in production.
  2. Forward traffic from your computer's port 8545 to the android device's: adb forward tcp:8545 tcp:8545
  3. Using a geth binary on your computer, run geth attach http://localhost:8545

iOS

We need the IP address of the iOS device. If it is being run in a simulator, the IP address is 127.0.0.1. If not running in a simulator:

  1. Ensure the iOS device is on the same network as your computer.
  2. Find the device's local IP address by going to the Settings app, Wi-Fi, and tapping the 'i' next to the network.

To attach:

  1. Start geth's HTTP RPC server by setting the config variable GETH_START_HTTP_RPC_SERVER to true. This is meant for development purposes only and can be a serious vulnerability if used in production.
  2. Using a geth binary on your computer, run geth attach http://<DEVICE_IP_ADDRESS>:8545

Troubleshooting

Activity class {org.celo.mobile.staging/org.celo.mobile.MainActivity} does not exist.

From time to time the app refuses to start showing this error:

557 actionable tasks: 525 executed, 32 up-to-date
info Running /usr/local/share/android-sdk/platform-tools/adb -s PL2GARH861213542 reverse tcp:8081 tcp:8081
info Starting the app on PL2GARH861213542 (/usr/local/share/android-sdk/platform-tools/adb -s PL2GARH861213542 shell am start -n org.celo.mobile.staging/org.celo.mobile.MainActivity)...
Starting: Intent { cmp=org.celo.mobile.staging/org.celo.mobile.MainActivity }
Error type 3
Error: Activity class {org.celo.mobile.staging/org.celo.mobile.MainActivity} does not exist.

Solution:

$ adb kill-server && adb start-server
* daemon not running; starting now at tcp:5037
* daemon started successfully