diff --git a/packages/mobile/.env b/packages/mobile/.env index 9ff7761f7a7..125c2e2b3e1 100644 --- a/packages/mobile/.env +++ b/packages/mobile/.env @@ -17,3 +17,7 @@ APP_STORE_ID=1482389446 APP_DISPLAY_NAME=Celo (dev) IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.dev.plist DYNAMIC_LINK_DOMAIN=http://l.celo.org +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/.env.alfajores b/packages/mobile/.env.alfajores index 5db3261e5f2..b04f192b4c2 100644 --- a/packages/mobile/.env.alfajores +++ b/packages/mobile/.env.alfajores @@ -1,7 +1,7 @@ ENVIRONMENT=alfajores DEFAULT_TESTNET=alfajores SMS_RETRIEVER_APP_SIGNATURE=GH+4Okn6nOW -# If FORNO_ENABLED_INITIALLY, local geth will not run initially. +# If FORNO_ENABLED_INITIALLY, local geth will not run initially. # If toggled on, it will use DEFAULT_SYNC_MODE. See src/geth/consts.ts for more info FORNO_ENABLED_INITIALLY=false DEFAULT_SYNC_MODE=5 @@ -15,3 +15,7 @@ APP_STORE_ID=1482389446 APP_DISPLAY_NAME=Celo IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.alfajores.plist DYNAMIC_LINK_DOMAIN=http://l.celo.org +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/.env.integration b/packages/mobile/.env.integration index 3a14942f8ee..23c4f8e92b4 100644 --- a/packages/mobile/.env.integration +++ b/packages/mobile/.env.integration @@ -1,7 +1,7 @@ ENVIRONMENT=integration DEFAULT_TESTNET=alfajores SMS_RETRIEVER_APP_SIGNATURE=l5k6LvdPDXS -# If FORNO_ENABLED_INITIALLY, local geth will not run initially. +# If FORNO_ENABLED_INITIALLY, local geth will not run initially. # If toggled on, it will use DEFAULT_SYNC_MODE. See src/geth/consts.ts for more info FORNO_ENABLED_INITIALLY=false DEFAULT_SYNC_MODE=5 @@ -13,3 +13,7 @@ SHOW_GET_INVITE_LINK=false APP_BUNDLE_ID=org.celo.mobile.integration APP_DISPLAY_NAME=Celo (int) IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.integration.plist +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/.env.mainnet b/packages/mobile/.env.mainnet index 10c7ae4847c..a5335f9e475 100644 --- a/packages/mobile/.env.mainnet +++ b/packages/mobile/.env.mainnet @@ -1,7 +1,7 @@ ENVIRONMENT=mainnet DEFAULT_TESTNET=mainnet SMS_RETRIEVER_APP_SIGNATURE=bU9E4ctGtIW -# If FORNO_ENABLED_INITIALLY, local geth will not run initially. +# If FORNO_ENABLED_INITIALLY, local geth will not run initially. # If toggled on, it will use DEFAULT_SYNC_MODE. See src/geth/consts.ts for more info FORNO_ENABLED_INITIALLY=false DEFAULT_SYNC_MODE=5 @@ -15,3 +15,7 @@ APP_STORE_ID=1520414263 APP_DISPLAY_NAME=Valora IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.mainnet.plist DYNAMIC_LINK_DOMAIN=https://vlra.app +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/.env.pilot b/packages/mobile/.env.pilot index 0ef7a66f8f2..70e645171d7 100644 --- a/packages/mobile/.env.pilot +++ b/packages/mobile/.env.pilot @@ -1,7 +1,7 @@ ENVIRONMENT=pilot DEFAULT_TESTNET=pilot SMS_RETRIEVER_APP_SIGNATURE=1SlgTw9pFW5 -# If FORNO_ENABLED_INITIALLY, local geth will not run initially. +# If FORNO_ENABLED_INITIALLY, local geth will not run initially. # If toggled on, it will use DEFAULT_SYNC_MODE. See src/geth/consts.ts for more info FORNO_ENABLED_INITIALLY=true DEFAULT_SYNC_MODE=5 @@ -13,3 +13,7 @@ SHOW_GET_INVITE_LINK=false APP_BUNDLE_ID=org.celo.mobile.pilot APP_DISPLAY_NAME=Celo (pilot) IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.pilot.plist +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/.env.test b/packages/mobile/.env.test index 1aadeee85a6..a23e6c111d0 100644 --- a/packages/mobile/.env.test +++ b/packages/mobile/.env.test @@ -14,3 +14,7 @@ SHOW_GET_INVITE_LINK=true APP_BUNDLE_ID=org.celo.mobile.test APP_DISPLAY_NAME=Celo (test) IOS_GOOGLE_SERVICE_PLIST=GoogleService-Info.dev.plist +GETH_USE_FULL_NODE_DISCOVERY=true +GETH_USE_STATIC_NODES=true +# Only for development, use with caution +GETH_START_HTTP_RPC_SERVER=false diff --git a/packages/mobile/README.md b/packages/mobile/README.md index 6f0bd1f0d03..b69ae0a593a 100644 --- a/packages/mobile/README.md +++ b/packages/mobile/README.md @@ -395,6 +395,26 @@ There are two major differences in Forno mode: 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](https://github.com/ethereum/go-ethereum/issues/16423). 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://:8545` + ### Troubleshooting #### `Activity class {org.celo.mobile.staging/org.celo.mobile.MainActivity} does not exist.` diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 3e9c29664a6..922973265f6 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - Analytics (3.8.0) - boost-for-react-native (1.63.0) - - CeloBlockchain (0.0.315) + - CeloBlockchain (0.0.316) - DoubleConversion (1.1.6) - FBLazyVector (0.62.2) - FBReactNativeSpec (0.62.2): @@ -743,7 +743,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Analytics: fcf79ebc393a7d77befb8d43ce296353ba9ede3d boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c - CeloBlockchain: e332160cd94e43856a198aba7aa0af567021b3bd + CeloBlockchain: ea6c85d1a1a74eacf28830d47dd01ad239d1779c DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245 FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e diff --git a/packages/mobile/package.json b/packages/mobile/package.json index 397ad950b05..b5f3645a8d2 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -39,7 +39,7 @@ ] }, "dependencies": { - "@celo/client": "0.0.315", + "@celo/client": "0.0.316", "@celo/contractkit": "0.4.13-dev", "@celo/react-components": "1.0.0", "@celo/react-native-sms-retriever": "git+https://github.com/celo-org/react-native-sms-retriever#b88e502", @@ -105,7 +105,7 @@ "react-native-flag-secure-android": "git://github.com/kristiansorens/react-native-flag-secure-android#e234251", "react-native-fs": "^2.16.6", "react-native-gesture-handler": "^1.6.1", - "react-native-geth": "https://github.com/celo-org/react-native-geth#6beb2b3", + "react-native-geth": "https://github.com/celo-org/react-native-geth#1ba8817", "react-native-keep-awake": "^4.0.0", "react-native-keyboard-aware-scroll-view": "^0.9.1", "react-native-keychain": "6.0.0", diff --git a/packages/mobile/src/config.ts b/packages/mobile/src/config.ts index 3fbc471c32a..ad6411f87d8 100644 --- a/packages/mobile/src/config.ts +++ b/packages/mobile/src/config.ts @@ -76,6 +76,14 @@ export const FORNO_ENABLED_INITIALLY = Config.FORNO_ENABLED_INITIALLY export const DEFAULT_SYNC_MODE: GethSyncMode = Config.DEFAULT_SYNC_MODE ? new BigNumber(Config.DEFAULT_SYNC_MODE).toNumber() : GethSyncMode.Lightest +export const GETH_USE_FULL_NODE_DISCOVERY = stringToBoolean( + Config.GETH_USE_FULL_NODE_DISCOVERY || 'true' +) +export const GETH_USE_STATIC_NODES = stringToBoolean(Config.GETH_USE_STATIC_NODES || 'true') +// NOTE: Development purposes only +export const GETH_START_HTTP_RPC_SERVER = stringToBoolean( + Config.GETH_START_HTTP_RPC_SERVER || 'false' +) // SECRETS export const SEGMENT_API_KEY = keyOrUndefined(secretsFile, Config.SECRETS_KEY, 'SEGMENT_API_KEY') diff --git a/packages/mobile/src/geth/geth.ts b/packages/mobile/src/geth/geth.ts index db253199d98..3c1478d8b6a 100644 --- a/packages/mobile/src/geth/geth.ts +++ b/packages/mobile/src/geth/geth.ts @@ -6,7 +6,7 @@ import * as RNFS from 'react-native-fs' import GethBridge, { NodeConfig } from 'react-native-geth' import { GethEvents } from 'src/analytics/Events' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' -import { DEFAULT_TESTNET } from 'src/config' +import { DEFAULT_TESTNET, GETH_START_HTTP_RPC_SERVER } from 'src/config' import { SYNCING_MAX_PEERS } from 'src/geth/consts' import networkConfig from 'src/geth/networkConfig' import Logger from 'src/utils/Logger' @@ -78,9 +78,9 @@ function getFolder(filePath: string) { return filePath.substr(0, filePath.lastIndexOf('/')) } -async function setupGeth(sync: boolean = true): Promise { +async function setupGeth(sync: boolean = true, bootnodeEnodes: string[]): Promise { Logger.debug('Geth@newGeth', 'Configure and create new Geth') - const { nodeDir, syncMode } = networkConfig + const { nodeDir, useDiscovery, syncMode } = networkConfig const genesis: string = await readGenesisBlockFile(nodeDir) const networkID: number = GenesisBlockUtils.getChainIdFromGenesis(genesis) @@ -88,7 +88,7 @@ async function setupGeth(sync: boolean = true): Promise { const maxPeers = sync ? SYNCING_MAX_PEERS : 0 - const gethOptions: NodeConfig = { + let gethOptions: NodeConfig = { nodeDir, networkID, genesis, @@ -96,6 +96,23 @@ async function setupGeth(sync: boolean = true): Promise { maxPeers, useLightweightKDF: true, ipcPath: IPC_PATH, + noDiscovery: !useDiscovery, + } + + if (useDiscovery) { + Logger.debug('Geth@newGeth', 'Using discovery, bootnodes = ' + bootnodeEnodes) + gethOptions.bootnodeEnodes = bootnodeEnodes + } + + if (__DEV__ && GETH_START_HTTP_RPC_SERVER) { + Logger.debug('Geth@newGeth', 'Starting HTTP RPC server') + gethOptions = { + ...gethOptions, + httpHost: '0.0.0.0', + httpPort: 8545, + httpVirtualHosts: '*', + httpModules: 'admin,debug,eth,istanbul,les,net,rpc,txpool,web3', + } } // Setup Logging @@ -121,23 +138,31 @@ export async function initGeth(shouldStartNode: boolean = true): Promise { if (!ok) { throw FailedToFetchGenesisBlockError } }), - ensureStaticNodesInitialized().then((ok) => { - if (!ok) { - throw FailedToFetchStaticNodesError + (async () => { + if (shouldStartNode && (useDiscovery || useStaticNodes)) { + staticNodes = await getStaticNodes() } - }), + Logger.info('Geth@init', `Got static nodes: ${staticNodes}`) + return initializeStaticNodesFile(useStaticNodes ? staticNodes : []) + })(), ]) ValoraAnalytics.track(GethEvents.create_geth_start) try { - await setupGeth(shouldStartNode) + // Use staticNodes as bootnodes because they support v4 and v5 discovery, + // and there are many of them. + // The dedicated bootnode currently only supports v4. + await setupGeth(shouldStartNode, staticNodes) } catch (error) { ValoraAnalytics.track(GethEvents.create_geth_error, { error: error.message }) throw error @@ -178,25 +203,25 @@ export function isProviderConnectionError(error: any) { .includes(PROVIDER_CONNECTION_ERROR) } -async function ensureStaticNodesInitialized(): Promise { - const { nodeDir } = networkConfig - Logger.debug('Geth@ensureStaticNodesInitialized', 'initializing static nodes') - let enodes: string | null = null +async function getStaticNodes(): Promise { try { - enodes = await StaticNodeUtils.getStaticNodesAsync(DEFAULT_TESTNET) + const enodesStr = await StaticNodeUtils.getStaticNodesAsync(DEFAULT_TESTNET) + return JSON.parse(enodesStr) } catch (error) { Logger.error( `Failed to get static nodes for network ${DEFAULT_TESTNET},` + `the node will not be able to sync with the network till restart`, error ) - return false - } - if (enodes != null) { - await writeStaticNodes(nodeDir, enodes) - return true + throw FailedToFetchStaticNodesError } - return false +} + +// Writes static nodes to the correct location +async function initializeStaticNodesFile(staticNodes: string[]): Promise { + const { nodeDir } = networkConfig + Logger.debug('Geth@initializeStaticNodesFile', 'initializing static nodes') + return writeStaticNodes(nodeDir, JSON.stringify(staticNodes)) } export async function stopGethIfInitialized() { @@ -268,8 +293,9 @@ function getStaticNodesFile(nodeDir: string) { } async function writeStaticNodes(nodeDir: string, enodes: string) { - console.info(`writeStaticNodes enodes are "${enodes}"`) + Logger.info('Geth@writeStaticNodes', `enodes are "${enodes}"`) const staticNodesFile = getStaticNodesFile(nodeDir) + Logger.info('Geth@writeStaticNodes', `static nodes file is ${staticNodesFile}"`) await RNFS.mkdir(getFolder(staticNodesFile)) await deleteFileIfExists(staticNodesFile) await RNFS.writeFile(staticNodesFile, enodes, 'utf8') diff --git a/packages/mobile/src/geth/networkConfig.ts b/packages/mobile/src/geth/networkConfig.ts index db4f5c6409c..69053d59c75 100644 --- a/packages/mobile/src/geth/networkConfig.ts +++ b/packages/mobile/src/geth/networkConfig.ts @@ -1,4 +1,10 @@ -import { DEFAULT_SYNC_MODE, DEFAULT_TESTNET, FORNO_ENABLED_INITIALLY } from 'src/config' +import { + DEFAULT_SYNC_MODE, + DEFAULT_TESTNET, + FORNO_ENABLED_INITIALLY, + GETH_USE_FULL_NODE_DISCOVERY, + GETH_USE_STATIC_NODES, +} from 'src/config' import { GethSyncMode } from 'src/geth/consts' import Logger from 'src/utils/Logger' @@ -21,6 +27,8 @@ interface NetworkConfig { odisUrl: string // Phone Number Privacy service url odisPubKey: string signMoonpayUrl: string + useDiscovery: boolean + useStaticNodes: boolean } const odisUrlStaging = 'https://us-central1-celo-phone-number-privacy-stg.cloudfunctions.net' @@ -40,6 +48,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.alfajoresstaging]: { nodeDir: `.${Testnets.alfajoresstaging}`, @@ -49,6 +59,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.alfajores]: { nodeDir: `.${Testnets.alfajores}`, @@ -59,6 +71,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisPubKey: 'kPoRxWdEdZ/Nd3uQnp3FJFs54zuiS+ksqvOm9x8vY6KHPG8jrfqysvIRU0wtqYsBKA7SoAsICMBv8C/Fb2ZpDOqhSqvr/sZbZoHmQfvbqrzbtDIPvUIrHgRS0ydJCMsA', signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.pilot]: { nodeDir: `.${Testnets.pilot}`, @@ -68,6 +82,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.pilotstaging]: { nodeDir: `.${Testnets.pilotstaging}`, @@ -77,6 +93,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.baklavastaging]: { nodeDir: `.${Testnets.baklavastaging}`, @@ -86,6 +104,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.baklava]: { nodeDir: `.${Testnets.baklava}`, @@ -95,6 +115,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisUrl: odisUrlStaging, odisPubKey: odisPubKeyStaging, signMoonpayUrl: signMoonpayUrlStaging, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, [Testnets.mainnet]: { nodeDir: `.${Testnets.mainnet}`, @@ -105,6 +127,8 @@ const networkConfigs: { [testnet: string]: NetworkConfig } = { odisPubKey: 'FvreHfLmhBjwxHxsxeyrcOLtSonC9j7K3WrS4QapYsQH6LdaDTaNGmnlQMfFY04Bp/K4wAvqQwO9/bqPVCKf8Ze8OZo8Frmog4JY4xAiwrsqOXxug11+htjEe1pj4uMA', signMoonpayUrl: signMoonpayUrlProd, + useDiscovery: GETH_USE_FULL_NODE_DISCOVERY, + useStaticNodes: GETH_USE_STATIC_NODES, }, } diff --git a/packages/mobile/src/geth/saga.test.ts b/packages/mobile/src/geth/saga.test.ts index d866cd4ea89..10d2c2ce001 100644 --- a/packages/mobile/src/geth/saga.test.ts +++ b/packages/mobile/src/geth/saga.test.ts @@ -12,7 +12,7 @@ describe(initGethSaga, () => { beforeEach(() => { const getStaticNodesAsync = StaticNodeUtils.getStaticNodesAsync as jest.Mock - getStaticNodesAsync.mockReturnValue(Promise.resolve('enodes')) + getStaticNodesAsync.mockReturnValue(Promise.resolve('["enode://foo"]')) const getGenesisBlockAsync = GenesisBlockUtils.getGenesisBlockAsync as jest.Mock getGenesisBlockAsync.mockReturnValue(Promise.resolve({})) const MockGethBridge = (GethBridge as unknown) as Record diff --git a/yarn.lock b/yarn.lock index fd89cabe444..f0c578b2fe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2781,10 +2781,10 @@ resolved "https://registry.yarnpkg.com/@celo/base/-/base-0.0.1.tgz#81e83aa430cba2a7424a4c016af58356404ea707" integrity sha512-0hsd6hOUvt3o1DbiZKGpKghYhGQsu3eGApqHsXZtWRINHaakfxFGtZgI1r8u1iwqXenlAImCZ91P0WYNwsqI9A== -"@celo/client@0.0.315": - version "0.0.315" - resolved "https://registry.yarnpkg.com/@celo/client/-/client-0.0.315.tgz#1672802254d0533ab08b7ecd03f68b153ec3e6a4" - integrity sha512-oNfvf5rv4PzkjRdhqJNvCQOf0FSFTLaV6yO1+KufVh6Clcxsr1ClntJAajjV3GdDoHf8qUzBJ4KDcjU/51Un0w== +"@celo/client@0.0.316": + version "0.0.316" + resolved "https://registry.yarnpkg.com/@celo/client/-/client-0.0.316.tgz#a97c75797ab9262c917d742ead37f31c5f7fcf6c" + integrity sha512-VSooiAB7rWGLLboJ0NG67KKE99NLs4gZGWpLkMg6vTWgkhLKeOJgqxmIw8FK6kYGzpLQORG8Znui/Trr1hZIAw== "@celo/contractkit@0.3.1": version "0.3.1" @@ -28431,9 +28431,9 @@ react-native-gesture-handler@^1.6.1: invariant "^2.2.4" prop-types "^15.7.2" -"react-native-geth@https://github.com/celo-org/react-native-geth#6beb2b3": +"react-native-geth@https://github.com/celo-org/react-native-geth#1ba8817": version "1.0.0" - resolved "https://github.com/celo-org/react-native-geth#6beb2b396c9217813290957915db430bb5187e2d" + resolved "https://github.com/celo-org/react-native-geth#1ba881742838f5c6fc001e40505cba58ec9af505" react-native-iphone-x-helper@^1.0.3, react-native-iphone-x-helper@^1.2.1: version "1.2.1"