diff --git a/.circleci/config.yml b/.circleci/config.yml
index 56b17a43e6f..3d26c1b5449 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -440,7 +440,7 @@ jobs:
command: |
mkdir -p test-results/jest
# Tests fail with https://stackoverflow.com/questions/38558989/node-js-heap-out-of-memory without this
- NODE_OPTIONS="--max-old-space-size=4096" yarn --cwd packages/mobile test:ci
+ NODE_OPTIONS="--max-old-space-size=4096" yarn --cwd packages/mobile test:ci --runInBand
environment:
JEST_JUNIT_OUTPUT: test-results/jest/junit.xml
diff --git a/packages/mobile/android/app/src/main/res/values/strings.xml b/packages/mobile/android/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..55344e51920
--- /dev/null
+++ b/packages/mobile/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/mobile/android/settings.gradle b/packages/mobile/android/settings.gradle
index c7cd38fed52..00f740b71d0 100644
--- a/packages/mobile/android/settings.gradle
+++ b/packages/mobile/android/settings.gradle
@@ -1,3 +1,5 @@
rootProject.name = 'celo'
+include ':react-native-securerandom'
+project(':react-native-securerandom').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-securerandom/android')
apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
diff --git a/packages/mobile/branding/celo/src/brandingConfig.ts b/packages/mobile/branding/celo/src/brandingConfig.ts
index 75935836f45..78ac258cb05 100644
--- a/packages/mobile/branding/celo/src/brandingConfig.ts
+++ b/packages/mobile/branding/celo/src/brandingConfig.ts
@@ -1,4 +1,5 @@
export const APP_NAME = 'Celo Wallet'
+export const WEB_LINK = 'https://celo.org/'
export const CELO_FAUCET_LINK = 'https://celo.org/developers/wallet'
export const CELO_TERMS_LINK = 'https://celo.org/terms'
export const TOS_LINK = 'https://celo.org/user-agreement'
diff --git a/packages/mobile/ios/Podfile b/packages/mobile/ios/Podfile
index 43384bd11be..3f4a80f80b2 100644
--- a/packages/mobile/ios/Podfile
+++ b/packages/mobile/ios/Podfile
@@ -106,6 +106,8 @@ target "celo" do
permissions_path = '../../../node_modules/react-native-permissions/ios'
pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec"
+ pod 'RNSecureRandom', :path => '../../../node_modules/react-native-securerandom'
+
target "celoTests" do
inherit! :search_paths
end
diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock
index 8227d896683..c3e34ddfa4a 100644
--- a/packages/mobile/ios/Podfile.lock
+++ b/packages/mobile/ios/Podfile.lock
@@ -476,6 +476,8 @@ PODS:
- React
- RNScreens (2.7.0):
- React
+ - RNSecureRandom (1.0.0):
+ - React
- RNSentry (1.7.1):
- React
- Sentry (~> 5.2.0)
@@ -568,6 +570,7 @@ DEPENDENCIES:
- RNPermissions (from `../../../node_modules/react-native-permissions`)
- RNReanimated (from `../../../node_modules/react-native-reanimated`)
- RNScreens (from `../../../node_modules/react-native-screens`)
+ - RNSecureRandom (from `../../../node_modules/react-native-securerandom`)
- "RNSentry (from `../../../node_modules/@sentry/react-native`)"
- RNShare (from `../../../node_modules/react-native-share`)
- RNSVG (from `../../../node_modules/react-native-svg`)
@@ -738,6 +741,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-reanimated"
RNScreens:
:path: "../../../node_modules/react-native-screens"
+ RNSecureRandom:
+ :path: "../../../node_modules/react-native-securerandom"
RNSentry:
:path: "../../../node_modules/@sentry/react-native"
RNShare:
@@ -839,6 +844,7 @@ SPEC CHECKSUMS:
RNPermissions: ad71dd4f767ec254f2cd57592fbee02afee75467
RNReanimated: dd8c286ab5dd4ba36d3a7fef8bff7e08711b5476
RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706
+ RNSecureRandom: 0dcee021fdb3d50cd5cee5db0ebf583c42f5af0e
RNSentry: 2bae4ffee2e66376ef42cb845a494c3bde17bc56
RNShare: fea1801315aa8875d6db73a4010b14afcd568765
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
@@ -847,6 +853,6 @@ SPEC CHECKSUMS:
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
Yoga: 3ebccbdd559724312790e7742142d062476b698e
-PODFILE CHECKSUM: 470c646f25b89e063843bdec1cd889a649b38781
+PODFILE CHECKSUM: e38c669823181b25b0d68e1d32554a4d8041b562
COCOAPODS: 1.9.1
diff --git a/packages/mobile/ios/celo.xcodeproj/project.pbxproj b/packages/mobile/ios/celo.xcodeproj/project.pbxproj
index 051fd92da66..19120ed75c4 100644
--- a/packages/mobile/ios/celo.xcodeproj/project.pbxproj
+++ b/packages/mobile/ios/celo.xcodeproj/project.pbxproj
@@ -519,6 +519,7 @@
"${BUILT_PRODUCTS_DIR}/RNReanimated/RNReanimated.framework",
"${BUILT_PRODUCTS_DIR}/RNSVG/RNSVG.framework",
"${BUILT_PRODUCTS_DIR}/RNScreens/RNScreens.framework",
+ "${BUILT_PRODUCTS_DIR}/RNSecureRandom/RNSecureRandom.framework",
"${BUILT_PRODUCTS_DIR}/RNSentry/RNSentry.framework",
"${BUILT_PRODUCTS_DIR}/RNShare/RNShare.framework",
"${BUILT_PRODUCTS_DIR}/React-Core/React.framework",
@@ -586,6 +587,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNReanimated.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSVG.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNScreens.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSecureRandom.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNSentry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNShare.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
diff --git a/packages/mobile/locales/en-US/onboarding.json b/packages/mobile/locales/en-US/onboarding.json
index 5c6af6dba0c..2c85a2d9462 100644
--- a/packages/mobile/locales/en-US/onboarding.json
+++ b/packages/mobile/locales/en-US/onboarding.json
@@ -43,9 +43,10 @@
"loadingBody": "This can take up to 90 seconds"
},
"verificationEducation": {
- "title": "Confirm",
- "header": "Confirm phone number",
+ "title": "Phone Number",
+ "header": "Connect your phone number",
"body": "To make sure your number is really yours, we’re going to send you three messages that will cost about 0.05 cUSD each.\n\nConfirming your number takes about three minutes.",
+ "feelessBody": "Connecting takes about three minutes. To confirm your number, you’ll receive three messages.",
"start": "Start",
"resume": "Resume",
"doINeedToConfirm": "Do I need to confirm?",
diff --git a/packages/mobile/locales/es-419/onboarding.json b/packages/mobile/locales/es-419/onboarding.json
index ec862ffffd0..38823f1a841 100644
--- a/packages/mobile/locales/es-419/onboarding.json
+++ b/packages/mobile/locales/es-419/onboarding.json
@@ -43,9 +43,10 @@
"loadingBody": "Esto puede tardar hasta 90 segundos"
},
"verificationEducation": {
- "title": "Confirmar",
- "header": "Confirmar número de teléfono",
+ "title": "Número de Teléfono",
+ "header": "Conectá tu número de teléfono",
"body": "Para asegurarnos de que tu número sea realmente tuyo, te enviaremos tres mensajes de texto que costarán alrededor de 0.05 cUSD cada uno.\n\nConfirmar tu número tomará unos tres minutos.",
+ "feelessBody": "Confirmar tu número tomará unos tres minutos. Para confirmar tu número, recibirás tres mensajes.",
"start": "Comenzar",
"resume": "Reanudar",
"doINeedToConfirm": "¿Necesito confirmar?",
diff --git a/packages/mobile/package.json b/packages/mobile/package.json
index 2b8c26ea0d4..d85cdf3bad6 100644
--- a/packages/mobile/package.json
+++ b/packages/mobile/package.json
@@ -22,7 +22,7 @@
"dev:emulator:run": "$ANDROID_HOME/emulator/emulator -avd",
"test": "export TZ=UTC && ./scripts/sync_branding.sh -b celo && jest --silent",
"test:all": "yarn lint && yarn build:ts && yarn test",
- "test:ci": "yarn test --coverage --runInBand",
+ "test:ci": "yarn test --coverage",
"test:watch": "yarn test --watch --maxWorkers=4",
"test:verbose": "export TZ=UTC && jest --verbose",
"test:e2e:android": "./scripts/run_e2e.sh -p android",
@@ -107,6 +107,7 @@
"react-native-fs": "^2.16.6",
"react-native-gesture-handler": "^1.6.1",
"react-native-geth": "https://github.com/celo-org/react-native-geth#5864c09",
+ "react-native-google-safetynet": "https://github.com/celo-org/react-native-google-safetynet#8a0355f",
"react-native-keep-awake": "^4.0.0",
"react-native-keyboard-aware-scroll-view": "^0.9.1",
"react-native-keychain": "6.0.0",
@@ -120,6 +121,7 @@
"react-native-restart-android": "^0.0.7",
"react-native-safe-area-context": "^3.0.6",
"react-native-screens": "^2.7.0",
+ "react-native-securerandom": "^1.0.0",
"react-native-secure-randombytes": "^3.0.2",
"react-native-send-intent": "git+https://github.com/celo-org/react-native-send-intent#a0f4b00",
"react-native-share": "^3.3.0",
diff --git a/packages/mobile/scripts/sync_branding.sh b/packages/mobile/scripts/sync_branding.sh
index 6f9db048f28..5a598ecb388 100755
--- a/packages/mobile/scripts/sync_branding.sh
+++ b/packages/mobile/scripts/sync_branding.sh
@@ -21,7 +21,7 @@ echo $mobile_root
cd "$mobile_root"
# Please update the sha when valora branding updates are needed
-valora_branding_sha=c8f6b4b
+valora_branding_sha=cd51906
if [[ "$branding" == "valora" ]]; then
# prevents git from asking credentials
diff --git a/packages/mobile/secrets.json.enc b/packages/mobile/secrets.json.enc
index 97b1d0ade2a..cb9d577ee0f 100644
Binary files a/packages/mobile/secrets.json.enc and b/packages/mobile/secrets.json.enc differ
diff --git a/packages/mobile/src/config.ts b/packages/mobile/src/config.ts
index 62b262a188e..cdb1c0d3b54 100644
--- a/packages/mobile/src/config.ts
+++ b/packages/mobile/src/config.ts
@@ -96,6 +96,12 @@ export const MOONPAY_PUBLIC_KEY = keyOrUndefined(
Config.SECRETS_KEY,
'MOONPAY_PUBLIC_KEY'
)
+export const RECAPTCHA_SITE_KEY = keyOrUndefined(
+ secretsFile,
+ Config.SECRETS_KEY,
+ 'RECAPTCHA_SITE_KEY'
+)
+export const SAFETYNET_KEY = keyOrUndefined(secretsFile, Config.SECRETS_KEY, 'SAFETYNET_KEY')
export const MOONPAY_RATE_API = `https://api.moonpay.io/v3/currencies/celo/price?apiKey=${MOONPAY_PUBLIC_KEY}`
export const EXCHANGE_PROVIDER_LINKS: ExternalExchangeProvider[] = [
diff --git a/packages/mobile/src/flags.ts b/packages/mobile/src/flags.ts
index 190dee8eb43..c4d96774221 100644
--- a/packages/mobile/src/flags.ts
+++ b/packages/mobile/src/flags.ts
@@ -10,6 +10,7 @@ export const features = {
SHOW_CASH_OUT: false,
PNP_USE_DEK_FOR_AUTH: true,
USE_PHONE_NUMBER_PRIVACY: true,
+ KOMENCI: false,
}
export const pausedFeatures = {
diff --git a/packages/mobile/src/index.d.ts b/packages/mobile/src/index.d.ts
index 96f8fa42c7a..099191d0e05 100644
--- a/packages/mobile/src/index.d.ts
+++ b/packages/mobile/src/index.d.ts
@@ -15,6 +15,7 @@ declare module 'react-native-clock-sync'
declare module 'react-native-crypto'
declare module 'react-native-bip39'
declare module 'react-native-flag-secure-android'
+declare module 'react-native-google-safetynet'
declare module 'svgs'
declare module 'react-native-languages'
declare module 'react-native-version-check'
diff --git a/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap b/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap
index 3d77fda32a1..e3c14bcfdb0 100644
--- a/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap
+++ b/packages/mobile/src/onboarding/registration/__snapshots__/NameAndNumber.test.tsx.snap
@@ -271,6 +271,7 @@ exports[`NameAndNumberScreen renders correctly 1`] = `
-
+ >
+
+
-
+ >
+
+
-
+ >
+
+
-
+ >
+
+
-
+ >
+
+
{
+ const regionCode = getRegionCode(e164PhoneNumber || '')
+ const countries = new Countries(i18n.language)
+ return countries.getCountryByCodeAlpha2(regionCode || '')
+ }, [e164PhoneNumber, i18n.language])
+
useEffect(() => {
if (status.isVerified) {
dispatch(setNumberVerified(true))
@@ -60,10 +79,17 @@ function VerificationEducationScreen({ route, navigation }: Props) {
}, [])
)
- const onPressStart = (withoutRevealing: boolean) => {
- return () => {
- dispatch(setHasSeenVerificationNux(true))
- dispatch(startVerification(withoutRevealing))
+ const onStartVerification = () => {
+ const withoutRevealing = actionableAttestations.length >= numAttestationsRemaining
+ dispatch(setHasSeenVerificationNux(true))
+ dispatch(startVerification(withoutRevealing))
+ }
+
+ const onPressStart = async () => {
+ if (features.KOMENCI) {
+ await showCaptcha()
+ } else {
+ onStartVerification()
}
}
@@ -93,6 +119,26 @@ function VerificationEducationScreen({ route, navigation }: Props) {
setShowLearnMoreDialog(false)
}
+ const showCaptcha = async () => {
+ setIsCaptchaVisible(true)
+ const safetyNetAttestationResponse = await getSafetyNetAttestation()
+ Logger.info('SafetyNet attestation complete:', JSON.stringify(safetyNetAttestationResponse))
+ setSafetyNetAttestation(safetyNetAttestationResponse)
+ }
+ const hideCaptcha = () => setIsCaptchaVisible(false)
+
+ const handleCaptchaResolved = (res: any) => {
+ hideCaptcha()
+ const captchaCode = res?.nativeEvent?.data
+ if (captchaCode !== 'cancel' && captchaCode !== 'error') {
+ Logger.info('Captcha code received: ', captchaCode)
+ // TODO: Add some brief loading indicator and send captcha somewhere before continuing.
+ // Also, cache the session ID received.
+ // TODO: Before calling this, make sure |safetyNetAttestation| has finished loading on Android.
+ onStartVerification()
+ }
+ }
+
if (isLoading) {
return (
@@ -123,8 +169,7 @@ function VerificationEducationScreen({ route, navigation }: Props) {
)
} else if (isBalanceSufficient) {
// Sufficient balance
- const withoutRevealing = actionableAttestations.length >= numAttestationsRemaining
- bodyText = t('verificationEducation.body')
+ bodyText = t(`verificationEducation.${features.KOMENCI ? 'feelessBody' : 'body'}`)
firstButton = (