Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Add WalletConnect auth #3

Merged
merged 14 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ buck-out/
# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/

# Expo
.expo
125 changes: 43 additions & 82 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,58 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* Generated with the TypeScript template
* https://github.com/react-native-community/react-native-template-typescript
*
* @format
* Example React Native App using XMTP.
*/
import React, {memo} from 'react';

import React from 'react';
import {
Button,
Platform,
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';

// Polyfill necessary xmtp-js libraries for React Native.
import './polyfills.js';
import {Wallet} from 'ethers';
import {utils as SecpUtils} from '@noble/secp256k1';
import {Client} from '@xmtp/xmtp-js';

import {Colors} from 'react-native/Libraries/NewAppScreen';
import Home from './components/Home';
import * as Linking from 'expo-linking';
import {Text} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import WalletConnectProvider, {
QrcodeModal,
RenderQrcodeModalProps,
} from '@walletconnect/react-native-dapp';
import AsyncStorage from '@react-native-async-storage/async-storage';

const App = () => {
const isDarkMode = useColorScheme() === 'dark';
const APP_SCHEME = 'examplexmtp';

const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
const prefix = Linking.createURL('/', {scheme: APP_SCHEME});

const App = () => {
const linking = {
prefixes: [prefix],
};

const account = new Wallet(SecpUtils.randomPrivateKey());
const QRCodeComponent = (props: RenderQrcodeModalProps) => {
if (!props.visible) {
return null;
}
return <QrcodeModal division={4} {...props} />;
};
const QRCodeModal = memo(QRCodeComponent);

return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Text style={styles.sectionTitle}>Example Chat App</Text>
<Text>{account.address}</Text>
<Button title="Send a gm" onPress={() => sendGm(account)} />
</View>
</ScrollView>
</SafeAreaView>
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<WalletConnectProvider
redirectUrl={`${APP_SCHEME}://`}
bridge="https://bridge.walletconnect.org"
renderQrcodeModal={props => <QRCodeModal {...props} />}
clientMeta={{
description: 'Sign in with XMTP',
url: 'https://xmtp.org',
icons: ['https://avatars.githubusercontent.com/u/82580170'],
name: 'XMTP',
}}
storageOptions={{
// @ts-expect-error: Internal
asyncStorage: AsyncStorage,
}}>
<Home />
</WalletConnectProvider>
</NavigationContainer>
);
};

async function sendGm(account: Wallet) {
console.log('creating xmtp client');
try {
const xmtp = await Client.create(account);
const conversation = await xmtp.conversations.newConversation(
'ENTER RECEIVING ACCOUNT ADDRESS HERE',
);
const message = await conversation.send(
`gm! ${Platform.OS === 'ios' ? 'from iOS' : 'from Android'}`,
);
console.log('sent message: ' + message.content);
} catch (error) {
console.log(`error creating client: ${error}`);
}
}

const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});

export default App;
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,43 @@ This is a work in progress to demonstrate importing [xmtp-js](https://github.com
This code should only be **used experimentally** as we work to remove our dependency on [PeculiarVentures/webcrypto](https://github.com/PeculiarVentures/webcrypto) (a SubtleCrypto polyfill) as the library includes the following warning.
>At this time this solution should be considered suitable for research and experimentation, further code and security review is needed before utilization in a production application.

## Getting started
## Usage

1. Follow the [React Native guide](https://reactnative.dev/docs/environment-setup) to set up CLI environment
1. Clone this repo and `cd example_chat_rn`
1. Edit the blockchain account address in [App.tsx](https://github.com/xmtp/example-chat-react-native/blob/main/App.tsx)'s `sendGm()` to point to the recipient's address
1. Set the `RECIPIENT_ADDRESS` in [Home.tsx](https://github.com/xmtp/example-chat-react-native/blob/main/components/Home.tsx) to an address already authenticated with XMTP. If you do not have one, sign in on the `dev` network [here](https://xmtp.vercel.app/).
1. Run `npx pod-install` to install iOS dependencies
1. Run `npx react-native start` to start Metro
1. Run `npx react-native run-ios` or `npx react-native run-android` to run the app
1. Press the `Send a gm` button once the app is running
1. A message will arrive in the account provided in Step 3

## Blockers for production

1. Remove the `SubtleCrypto` dependency from `xmtp-js` or add a Node crypto workaround as proposed in [a proof of concept PR](https://github.com/xmtp/xmtp-js/pull/157). This will allow us to remove the [PeculiarVentures/webcrypto](https://github.com/PeculiarVentures/webcrypto) polyfill noted in the above [warning](#warning).
1. **[In Progress]** Add authentication to the app instead of generating a random private key for a sender account in `App.tsx`.
1. **[In Progress]** Remove the `SubtleCrypto` dependency from `xmtp-js` or add a Node crypto workaround as proposed in [a proof of concept PR](https://github.com/xmtp/xmtp-js/pull/157). This will allow us to remove the [PeculiarVentures/webcrypto](https://github.com/PeculiarVentures/webcrypto) polyfill noted in the above [warning](#warning).
1. **[Done]** ~~Add authentication to the app instead of generating a random private key for a sender account in `App.tsx`.~~
1. **[Done]** ~~Nice to have: Remove unnecessary local storage dependency from `xmtp-js` to remove the polyfill in `App.tsx`.~~

## Requirements

- The JavaScript engine used in the React Native app must include `BigInt` support as it is required by `xmtp-js` use of [paulmillr/noble-secp256k1](https://github.com/paulmillr/noble-secp256k1).
- `BigInt` is included in the following JS environments:
- Hermes v0.70 for both iOS and Android (used in this example)
- JavaScriptCore for iOS starting with iOS 14
- JavaScriptCore for iOS (iOS 14+)
- [V8](https://github.com/Kudo/react-native-v8) for Android

## Polyfills

- @azure/core-asynciterator-polyfill (necessary for Hermes only)
- @ethersproject/shims
- react-native-get-random-values
- crypto-browserify
- stream-browserify
- https-browserify
- events
- process
- react-native-get-random-values
- text-encoding
- web-streams-polyfill
- @peculiar/webcrypto (necessary for `SubtleCrypto` but need to remove)
- assert
- os
- url
- util
53 changes: 27 additions & 26 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example_chat_rn">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example_chat_rn">
<uses-permission android:name="android.permission.INTERNET"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="wc"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="examplexmtp"/>
</intent>
</queries>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme">
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="examplexmtp"/>
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.example_chat_rn;
import expo.modules.ReactActivityDelegateWrapper;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
Expand All @@ -22,7 +23,7 @@ protected String getMainComponentName() {
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new MainActivityDelegate(this, getMainComponentName());
return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new MainActivityDelegate(this, getMainComponentName()));
}

public static class MainActivityDelegate extends ReactActivityDelegate {
Expand Down
16 changes: 13 additions & 3 deletions android/app/src/main/java/com/example_chat_rn/MainApplication.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package com.example_chat_rn;
import android.content.res.Configuration;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;

import android.app.Application;
import android.content.Context;
Expand All @@ -16,7 +19,7 @@
public class MainApplication extends Application implements ReactApplication {

private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
new ReactNativeHostWrapper(this, new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
Expand All @@ -35,10 +38,10 @@ protected List<ReactPackage> getPackages() {
protected String getJSMainModuleName() {
return "index";
}
};
});

private final ReactNativeHost mNewArchitectureNativeHost =
new MainApplicationReactNativeHost(this);
new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this));

@Override
public ReactNativeHost getReactNativeHost() {
Expand All @@ -56,6 +59,7 @@ public void onCreate() {
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}

/**
Expand Down Expand Up @@ -88,4 +92,10 @@ private static void initializeFlipper(
}
}
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
}
3 changes: 3 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true")
include(":ReactAndroid:hermes-engine")
project(":ReactAndroid:hermes-engine").projectDir = file('../node_modules/react-native/ReactAndroid/hermes-engine')
}

apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
useExpoModules()
7 changes: 5 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"name": "example_chat_rn",
"displayName": "example_chat_rn"
}
"displayName": "example_chat_rn",
"expo": {
"scheme": "examplexmtp"
}
}
Loading