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.
The app uses React Native and a geth light node.
You need to install Java 8, the Android SDK, Yarn, and Node 8 to run the app.
To do this, follow the setup instructions
This makes Gradle faster:
export GRADLE_OPTS='-Dorg.gradle.daemon=true -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"'
-
If you haven't already, run
yarn
from the monorepo root to install dependencies. -
Attach your device or start an emulated one.
You can verify if your device is properly connecting to ADB with
adb devices
. More information about running the app on Android devices can be found on the React Native docs. -
Compile the project and start the bundler with
yarn run dev:android OR yarn run dev:ios
This will build the app in a device (physical or emulated) and open a terminal with a js server.
Note: We've seen some issues running the metro bundler from iTerm
In order to debug, you should run:
yarn run dev:show-menu
A menu will pop-up in the app and you should hit Start Remote JS Debugging
.
This will 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
.
You will probably want to open the dev menu again and enable Live Reloading
and Hot Reloading
to make development faster.
The RN debugger app bundles together the Redux and Chrome dev tools nicely.
Start the emulator and load up the app. Then run the following to start react devtools.
yarn run react-devtools
It should automatically connect to the running app, and includes a profiler (second tab). Start recorder with the profiler, using the app, and the 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.
By default, we have the alfajores
network set up. If you have other testnets
that you want to use with the app, update .env.ENV-NAME
and packages/mobile/.env.ENV-NAME
with
the new network name and settings, then rebuild the app. Note that this will assume the testnets
have a corresponding /blockchain-api
and /notification-service
set up.
By default, the mobile wallet app runs geth in ultralight 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 zero sync (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 ZERO_SYNC_ENABLED_INITIALLY
parameter in the .env file linked above. When zero sync 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 zero sync mode, the wallet signs transactions locally in web3 then sends them to the trusted node.
To debug network requests in zero sync 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.
To execute the suite of tests, run yarn test
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.
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
We use redux-saga-test-plan to test complex sagas.
See [src/identity/verification.test.ts
] for an example.
We use Detox for E2E testing. In order to run the tests locally, you must have the proper emulator set up. Emulator installation instructions are in the setup instructions.
Please set 123456
as the pin code in the emulator, since the e2e tests rely on
that.
Next, the VM snapshot settings should be modified:
- Close all apps and lock the emulator (go to lock screen).
- Power off the emulator
- Power it back on and go to emulator settings (... button) -> Snapshots -> Settings
- Set Auto-Save to No
For information on how to run and extend the e2e tests, refer to the e2e readme.
The app can be build 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:
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
# With fastlane:
bundle install
bundle exec fastlane android build_apk env:YOUR_BUILDING_VARIANT sdkEnv:YOUR_SDK_ENV
# 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.
On android, the wallet app uses the SMS Retriever API to automatically input codes during phone number verification.
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.
- Go to the play console for the relevant app, Release management > App signing, and download the App signing certificate.
- Use this script to generate the hash code: https://github.com/michalbrz/sms-retriever-hash-generator
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.
Our Celo app has three types of codes.
- Javascript code - generated from Typescript, this runs in Javascript interpreter.
- Java bytecode - This runs on Dalvik/Art Virtual Machine.
- 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:
- NDKCrashService
- Initialization of the NDKCrashService
- Sentry code to read NDK crash logs on restart
There are two major differencs in ZeroSync mode:
- Geth won't run at all. The web3 would instead connect to -forno.celo-testnet.org using an https provider, for example, https://integration-forno.celo-testnet.org.
- Changes to sendTransactionAsyncWithWeb3Signing in walletkit to poll after sending a transaction for the transaction to succeed. This is needed because http provider, unliked web sockets and IPC provider, does not support subscriptions.
Websockets (ws
) would have been a better choicee 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.
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