diff --git a/app/components/Nav/App/__snapshots__/index.test.js.snap b/app/components/Nav/App/__snapshots__/index.test.js.snap
index 83e5a735d4e..6eec58f9b46 100644
--- a/app/components/Nav/App/__snapshots__/index.test.js.snap
+++ b/app/components/Nav/App/__snapshots__/index.test.js.snap
@@ -160,11 +160,13 @@ exports[`App should render correctly 1`] = `
"childRouters": Object {
"AdvancedSettings": null,
"CompanySettings": null,
+ "ExperimentalSettings": null,
"GeneralSettings": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
"SyncWithExtensionView": null,
+ "WalletConnectSessionsView": null,
},
"getActionCreators": [Function],
"getActionForPathAndParams": [Function],
@@ -534,11 +536,13 @@ exports[`App should render correctly 1`] = `
"childRouters": Object {
"AdvancedSettings": null,
"CompanySettings": null,
+ "ExperimentalSettings": null,
"GeneralSettings": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
"SyncWithExtensionView": null,
+ "WalletConnectSessionsView": null,
},
"getActionCreators": [Function],
"getActionForPathAndParams": [Function],
diff --git a/app/components/Nav/Main/__snapshots__/index.test.js.snap b/app/components/Nav/Main/__snapshots__/index.test.js.snap
index a0c600cb0d6..a79e97cd38c 100644
--- a/app/components/Nav/Main/__snapshots__/index.test.js.snap
+++ b/app/components/Nav/Main/__snapshots__/index.test.js.snap
@@ -168,11 +168,13 @@ exports[`Main should render correctly 1`] = `
"childRouters": Object {
"AdvancedSettings": null,
"CompanySettings": null,
+ "ExperimentalSettings": null,
"GeneralSettings": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
"SyncWithExtensionView": null,
+ "WalletConnectSessionsView": null,
},
"getActionCreators": [Function],
"getActionForPathAndParams": [Function],
@@ -428,11 +430,13 @@ exports[`Main should render correctly 1`] = `
"childRouters": Object {
"AdvancedSettings": null,
"CompanySettings": null,
+ "ExperimentalSettings": null,
"GeneralSettings": null,
"RevealPrivateCredentialView": null,
"SecuritySettings": null,
"Settings": null,
"SyncWithExtensionView": null,
+ "WalletConnectSessionsView": null,
},
"getActionCreators": [Function],
"getActionForPathAndParams": [Function],
diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js
index f5db73089c4..6706c43510f 100644
--- a/app/components/Nav/Main/index.js
+++ b/app/components/Nav/Main/index.js
@@ -16,6 +16,7 @@ import GeneralSettings from '../../Views/GeneralSettings';
import AdvancedSettings from '../../Views/AdvancedSettings';
import AppInformation from '../../UI/AppInformation';
import SecuritySettings from '../../Views/SecuritySettings';
+import ExperimentalSettings from '../../Views/ExperimentalSettings';
import Wallet from '../../Views/Wallet';
import TransactionsView from '../../Views/TransactionsView';
import SyncWithExtension from '../../Views/SyncWithExtension';
@@ -25,6 +26,7 @@ import Collectible from '../../Views/Collectible';
import CollectibleView from '../../Views/CollectibleView';
import Send from '../../Views/Send';
import RevealPrivateCredential from '../../Views/RevealPrivateCredential';
+import WalletConnectSessions from '../../Views/WalletConnectSessions';
import QrScanner from '../../Views/QRScanner';
import LockScreen from '../../Views/LockScreen';
import ProtectYourAccount from '../../Views/ProtectYourAccount';
@@ -49,6 +51,13 @@ import { colors } from '../../../styles/common';
import LockManager from '../../../core/LockManager';
import OnboardingWizard from '../../UI/OnboardingWizard';
import FadeOutOverlay from '../../UI/FadeOutOverlay';
+import { hexToBN, fromWei } from '../../../util/number';
+import { setTransactionObject } from '../../../actions/transaction';
+import PersonalSign from '../../UI/PersonalSign';
+import TypedSign from '../../UI/TypedSign';
+import Modal from 'react-native-modal';
+import WalletConnect from '../../../core/WalletConnect';
+import WalletConnectSessionApproval from '../../UI/WalletConnectSessionApproval';
const styles = StyleSheet.create({
flex: {
@@ -59,6 +68,10 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center'
+ },
+ bottomModal: {
+ justifyContent: 'flex-end',
+ margin: 0
}
});
@@ -138,6 +151,9 @@ const MainNavigator = createStackNavigator(
SecuritySettings: {
screen: SecuritySettings
},
+ ExperimentalSettings: {
+ screen: ExperimentalSettings
+ },
CompanySettings: {
screen: AppInformation
},
@@ -146,6 +162,9 @@ const MainNavigator = createStackNavigator(
},
RevealPrivateCredentialView: {
screen: RevealPrivateCredential
+ },
+ WalletConnectSessionsView: {
+ screen: WalletConnectSessions
}
})
},
@@ -266,11 +285,24 @@ class Main extends Component {
/**
* Current onboarding wizard step
*/
- wizardStep: PropTypes.number
+ wizardStep: PropTypes.number,
+ /**
+ * Action that sets a transaction
+ */
+ setTransactionObject: PropTypes.func,
+ /**
+ * Object containing the information for the current transaction
+ */
+ transaction: PropTypes.object
};
state = {
- forceReload: false
+ forceReload: false,
+ signMessage: false,
+ signMessageParams: { data: '' },
+ signType: '',
+ walletConnectRequest: false,
+ walletConnectRequestInfo: {}
};
backgroundMode = false;
@@ -290,7 +322,6 @@ class Main extends Component {
TransactionsNotificationManager.init(this.props.navigation);
this.pollForIncomingTransactions();
AppState.addEventListener('change', this.handleAppStateChange);
-
this.lockManager = new LockManager(this.props.navigation, this.props.lockTime);
PushNotification.configure({
@@ -314,6 +345,55 @@ class Main extends Component {
}
}
});
+
+ Engine.context.TransactionController.hub.on('unapprovedTransaction', this.onUnapprovedTransaction);
+
+ Engine.context.PersonalMessageManager.hub.on('unapprovedMessage', messageParams => {
+ const { title: currentPageTitle, url: currentPageUrl } = messageParams.meta;
+ delete messageParams.meta;
+ this.setState({
+ signMessage: true,
+ signMessageParams: messageParams,
+ signType: 'personal',
+ currentPageTitle,
+ currentPageUrl
+ });
+ });
+
+ Engine.context.TypedMessageManager.hub.on('unapprovedMessage', messageParams => {
+ const { title: currentPageTitle, url: currentPageUrl } = messageParams.meta;
+ delete messageParams.meta;
+ this.setState({
+ signMessage: true,
+ signMessageParams: messageParams,
+ signType: 'typed',
+ currentPageTitle,
+ currentPageUrl
+ });
+ });
+
+ WalletConnect.hub.on('walletconnectSessionRequest', peerInfo => {
+ this.setState({ walletConnectRequest: true, walletConnectRequestInfo: peerInfo });
+ });
+ WalletConnect.init();
+ };
+
+ onUnapprovedTransaction = transactionMeta => {
+ if (this.props.transaction.value || this.props.transaction.to) {
+ return;
+ }
+ const {
+ transaction: { value, gas, gasPrice }
+ } = transactionMeta;
+ transactionMeta.transaction.value = hexToBN(value);
+ transactionMeta.transaction.readableValue = fromWei(transactionMeta.transaction.value);
+ transactionMeta.transaction.gas = hexToBN(gas);
+ transactionMeta.transaction.gasPrice = hexToBN(gasPrice);
+ this.props.setTransactionObject({
+ ...{ symbol: 'ETH', type: 'ETHER_TRANSACTION', assetType: 'ETH', id: transactionMeta.id },
+ ...transactionMeta.transaction
+ });
+ this.props.navigation.push('ApprovalView');
};
handleAppStateChange = appState => {
@@ -365,6 +445,9 @@ class Main extends Component {
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
this.lockManager.stopListening();
+ Engine.context.PersonalMessageManager.hub.removeAllListeners();
+ Engine.context.TypedMessageManager.hub.removeAllListeners();
+ Engine.context.TransactionController.hub.removeListener('unapprovedTransaction', this.onUnapprovedTransaction);
}
/**
@@ -375,24 +458,127 @@ class Main extends Component {
return wizardStep !== 5 && wizardStep > 0 && ;
};
+ onSignAction = () => {
+ this.setState({ signMessage: false });
+ };
+
+ renderSigningModal = () => {
+ const { signMessage, signMessageParams, signType, currentPageTitle, currentPageUrl } = this.state;
+ return (
+
+ {signType === 'personal' && (
+
+ )}
+ {signType === 'typed' && (
+
+ )}
+
+ );
+ };
+
+ onWalletConnectSessionApproval = () => {
+ const { peerId } = this.state.walletConnectRequestInfo;
+ this.setState({
+ walletConnectRequest: false,
+ walletConnectRequestInfo: {}
+ });
+ WalletConnect.hub.emit('walletconnectSessionRequest::approved', peerId);
+ };
+
+ onWalletConnectSessionRejected = () => {
+ const peerId = this.state.walletConnectRequestInfo.peerId;
+ this.setState({
+ walletConnectRequest: false,
+ walletConnectRequestInfo: {}
+ });
+ WalletConnect.hub.emit('walletconnectSessionRequest::rejected', peerId);
+ };
+
+ renderWalletConnectSessionRequestModal = () => {
+ const { walletConnectRequest, walletConnectRequestInfo } = this.state;
+
+ const meta = walletConnectRequestInfo.peerMeta || null;
+
+ return (
+
+
+
+ );
+ };
+
render() {
const { forceReload } = this.state;
return (
-
- {!forceReload ? : this.renderLoader()}
- {this.renderOnboardingWizard()}
-
-
-
-
+
+
+ {!forceReload ? : this.renderLoader()}
+ {this.renderOnboardingWizard()}
+
+
+
+
+ {this.renderSigningModal()}
+ {this.renderWalletConnectSessionRequestModal()}
+
);
}
}
const mapStateToProps = state => ({
lockTime: state.settings.lockTime,
- wizardStep: state.wizard.step
+ wizardStep: state.wizard.step,
+ transaction: state.transaction
+});
+
+const mapDispatchToProps = dispatch => ({
+ setTransactionObject: asset => dispatch(setTransactionObject(asset))
});
-export default connect(mapStateToProps)(Main);
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Main);
diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js
index d635042ace5..88dede90ae2 100644
--- a/app/components/UI/Navbar/index.js
+++ b/app/components/UI/Navbar/index.js
@@ -14,6 +14,7 @@ import URL from 'url-parse';
import { strings } from '../../../../locales/i18n';
import AppConstants from '../../../core/AppConstants';
import TabCountIcon from '../../UI/Tabs/TabCountIcon';
+import WalletConnect from '../../../core/WalletConnect';
const HOMEPAGE_URL = 'about:blank';
const styles = StyleSheet.create({
@@ -438,6 +439,8 @@ export function getWalletNavbarOptions(title, navigation) {
const onScanSuccess = data => {
if (data.target_address) {
navigation.navigate('SendView', { txMeta: data });
+ } else if (data.walletConnectURI) {
+ WalletConnect.newSession(data.walletConnectURI);
}
};
diff --git a/app/components/UI/WalletConnectSessionApproval/__snapshots__/index.test.js.snap b/app/components/UI/WalletConnectSessionApproval/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000000..bf4849675a0
--- /dev/null
+++ b/app/components/UI/WalletConnectSessionApproval/__snapshots__/index.test.js.snap
@@ -0,0 +1,290 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WalletConnectSessionApproval should render correctly 1`] = `
+
+
+
+
+ WALLETCONNECT REQUEST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Account 1
+
+
+
+
+
+
+
+ would like to
+ :
+
+
+
+ View your
+
+
+ public address
+
+
+
+
+
+ By clicking connect, you allow this dapp to view your public address. This is an important security step to protect your data from potential phishing risks.
+
+
+
+
+`;
diff --git a/app/components/UI/WalletConnectSessionApproval/index.js b/app/components/UI/WalletConnectSessionApproval/index.js
new file mode 100644
index 00000000000..79b5b18adde
--- /dev/null
+++ b/app/components/UI/WalletConnectSessionApproval/index.js
@@ -0,0 +1,246 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { StyleSheet, Text, View } from 'react-native';
+import Icon from 'react-native-vector-icons/FontAwesome';
+import ActionView from '../ActionView';
+import ElevatedView from 'react-native-elevated-view';
+import Identicon from '../Identicon';
+import { strings } from '../../../../locales/i18n';
+import { colors, fontStyles } from '../../../styles/common';
+import DeviceSize from '../../../util/DeviceSize';
+import WebsiteIcon from '../WebsiteIcon';
+import { renderAccountName } from '../../../util/address';
+
+const styles = StyleSheet.create({
+ root: {
+ backgroundColor: colors.white,
+ borderTopLeftRadius: 10,
+ borderTopRightRadius: 10,
+ minHeight: '70%',
+ paddingBottom: DeviceSize.isIphoneX() ? 20 : 0
+ },
+ wrapper: {
+ paddingHorizontal: 25
+ },
+ title: {
+ ...fontStyles.bold,
+ color: colors.fontPrimary,
+ fontSize: 14,
+ marginVertical: 24,
+ textAlign: 'center'
+ },
+ intro: {
+ ...fontStyles.normal,
+ textAlign: 'center',
+ color: colors.fontPrimary,
+ fontSize: 20,
+ marginVertical: 24
+ },
+ dappTitle: {
+ ...fontStyles.bold,
+ color: colors.fontPrimary,
+ fontSize: 20
+ },
+ permissions: {
+ alignItems: 'center',
+ borderBottomWidth: 1,
+ borderColor: colors.grey100,
+ borderTopWidth: 1,
+ display: 'flex',
+ flexDirection: 'row',
+ paddingHorizontal: 8,
+ paddingVertical: 16
+ },
+ permissionText: {
+ ...fontStyles.normal,
+ color: colors.fontPrimary,
+ flexGrow: 1,
+ flexShrink: 1,
+ fontSize: 14
+ },
+ permission: {
+ ...fontStyles.bold,
+ color: colors.fontPrimary,
+ fontSize: 14
+ },
+ warning: {
+ ...fontStyles.normal,
+ color: colors.fontPrimary,
+ fontSize: 14,
+ marginTop: 24
+ },
+ header: {
+ alignItems: 'flex-start',
+ display: 'flex',
+ flexDirection: 'row',
+ marginBottom: 12
+ },
+ headerTitle: {
+ ...fontStyles.normal,
+ color: colors.fontPrimary,
+ fontSize: 16,
+ textAlign: 'center'
+ },
+ selectedAddress: {
+ ...fontStyles.normal,
+ color: colors.fontPrimary,
+ fontSize: 16,
+ marginTop: 12,
+ textAlign: 'center'
+ },
+ headerUrl: {
+ ...fontStyles.normal,
+ color: colors.fontSecondary,
+ fontSize: 12,
+ textAlign: 'center'
+ },
+ dapp: {
+ alignItems: 'center',
+ paddingHorizontal: 14,
+ width: '50%'
+ },
+ graphic: {
+ alignItems: 'center',
+ position: 'absolute',
+ top: 12,
+ width: '100%'
+ },
+ check: {
+ alignItems: 'center',
+ height: 2,
+ width: '33%'
+ },
+ border: {
+ borderColor: colors.grey400,
+ borderStyle: 'dashed',
+ borderWidth: 1,
+ left: 0,
+ overflow: 'hidden',
+ position: 'absolute',
+ top: 12,
+ width: '100%',
+ zIndex: 1
+ },
+ checkWrapper: {
+ alignItems: 'center',
+ backgroundColor: colors.green500,
+ borderRadius: 12,
+ height: 24,
+ position: 'relative',
+ width: 24,
+ zIndex: 2
+ },
+ checkIcon: {
+ color: colors.white,
+ fontSize: 14,
+ lineHeight: 24
+ },
+ icon: {
+ borderRadius: 27,
+ marginBottom: 12,
+ height: 54,
+ width: 54
+ }
+});
+
+/**
+ * WalletConnect request approval component
+ */
+class WalletConnectSessionApproval extends Component {
+ static propTypes = {
+ /**
+ * Object containing current page title, url, and icon href
+ */
+ currentPageInformation: PropTypes.object,
+ /**
+ * Callback triggered on account access approval
+ */
+ onConfirm: PropTypes.func,
+ /**
+ * Callback triggered on account access rejection
+ */
+ onCancel: PropTypes.func,
+ /**
+ /* Identities object required to get account name
+ */
+ identities: PropTypes.object,
+ /**
+ * A string that represents the selected address
+ */
+ selectedAddress: PropTypes.string
+ };
+
+ render = () => {
+ const {
+ currentPageInformation: { title, url },
+ onConfirm,
+ onCancel,
+ selectedAddress,
+ identities
+ } = this.props;
+ return (
+
+
+
+ {strings('accountApproval.walletconnect_title')}
+
+
+
+
+
+
+
+
+ {title}
+
+
+ {url}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderAccountName(selectedAddress, identities)}
+
+
+
+
+ {title}
+ {strings('accountApproval.action')}:
+
+
+
+ {strings('accountApproval.permission')}
+ {strings('accountApproval.address')}
+
+
+
+ {strings('accountApproval.warning')}
+
+
+
+ );
+ };
+}
+
+const mapStateToProps = state => ({
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ identities: state.engine.backgroundState.PreferencesController.identities
+});
+
+export default connect(mapStateToProps)(WalletConnectSessionApproval);
diff --git a/app/components/UI/WalletConnectSessionApproval/index.test.js b/app/components/UI/WalletConnectSessionApproval/index.test.js
new file mode 100644
index 00000000000..e1feeefedac
--- /dev/null
+++ b/app/components/UI/WalletConnectSessionApproval/index.test.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import WalletConnectSessionApproval from './';
+import { shallow } from 'enzyme';
+import configureMockStore from 'redux-mock-store';
+
+const mockStore = configureMockStore();
+
+describe('WalletConnectSessionApproval', () => {
+ it('should render correctly', () => {
+ const initialState = {
+ engine: {
+ backgroundState: {
+ PreferencesController: {
+ selectedAddress: '0xe7E125654064EEa56229f273dA586F10DF96B0a1',
+ identities: { '0xe7E125654064EEa56229f273dA586F10DF96B0a1': { name: 'Account 1' } }
+ }
+ }
+ }
+ };
+
+ const wrapper = shallow(
+ ,
+ {
+ context: { store: mockStore(initialState) }
+ }
+ );
+ expect(wrapper.dive()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap b/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
index 4f2475c4806..60fd15309b9 100644
--- a/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
+++ b/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
@@ -172,48 +172,6 @@ exports[`Browser should render correctly 1`] = `
onSubmit={[Function]}
/>
-
{
+ const { PersonalMessageManager } = Engine.context;
+ try {
+ const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({
+ data: payload.params[1],
+ from: payload.params[0],
+ ...this.getPageMeta()
+ });
+ return Promise.resolve({ result: rawSig, jsonrpc: payload.jsonrpc, id: payload.id });
+ } catch (error) {
+ return Promise.reject({ error: error.message, jsonrpc: payload.jsonrpc, id: payload.id });
+ }
+ },
+ personal_sign: async payload => {
+ const { PersonalMessageManager } = Engine.context;
+ try {
+ const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({
+ data: payload.params[0],
+ from: payload.params[1],
+ ...this.getPageMeta()
+ });
+ return Promise.resolve({ result: rawSig, jsonrpc: payload.jsonrpc, id: payload.id });
+ } catch (error) {
+ return Promise.reject({ error: error.message, jsonrpc: payload.jsonrpc, id: payload.id });
+ }
+ },
+ eth_signTypedData: async payload => {
+ const { TypedMessageManager } = Engine.context;
+ try {
+ const rawSig = await TypedMessageManager.addUnapprovedMessageAsync(
+ {
+ data: payload.params[0],
+ from: payload.params[1],
+ ...this.getPageMeta()
+ },
+ 'V1'
+ );
+ return Promise.resolve({ result: rawSig, jsonrpc: payload.jsonrpc, id: payload.id });
+ } catch (error) {
+ return Promise.reject({ error: error.message, jsonrpc: payload.jsonrpc, id: payload.id });
+ }
+ },
+ eth_signTypedData_v3: async payload => {
+ const { TypedMessageManager } = Engine.context;
+ try {
+ const rawSig = await TypedMessageManager.addUnapprovedMessageAsync(
+ {
+ data: payload.params[1],
+ from: payload.params[0],
+ ...this.getPageMeta()
+ },
+ 'V3'
+ );
+ return Promise.resolve({ result: rawSig, jsonrpc: payload.jsonrpc, id: payload.id });
+ } catch (error) {
+ return Promise.reject({ error: error.message, jsonrpc: payload.jsonrpc, id: payload.id });
+ }
+ },
eth_requestAccounts: ({ hostname, params }) => {
const { approvedHosts, privacyMode, selectedAddress } = this.props;
const promise = new Promise((resolve, reject) => {
@@ -575,17 +636,6 @@ export class BrowserTab extends PureComponent {
await this.setState({ entryScriptWeb3: updatedentryScriptWeb3 + SPA_urlChangeListener });
- Engine.context.TransactionController.hub.on('unapprovedTransaction', this.onUnapprovedTransaction);
-
- Engine.context.PersonalMessageManager.hub.on('unapprovedMessage', messageParams => {
- if (!this.isTabActive()) return false;
- this.setState({ signMessage: true, signMessageParams: messageParams, signType: 'personal' });
- });
- Engine.context.TypedMessageManager.hub.on('unapprovedMessage', messageParams => {
- if (!this.isTabActive()) return false;
- this.setState({ signMessage: true, signMessageParams: messageParams, signType: 'typed' });
- });
-
Engine.context.AssetsController.hub.on('pendingSuggestedAsset', suggestedAssetMeta => {
if (!this.isTabActive()) return false;
this.setState({ watchAsset: true, suggestedAssetMeta });
@@ -643,25 +693,6 @@ export class BrowserTab extends PureComponent {
return true;
};
- onUnapprovedTransaction = transactionMeta => {
- if (!this.isTabActive()) return false;
- if (this.props.transaction.value || this.props.transaction.to) {
- return;
- }
- const {
- transaction: { value, gas, gasPrice }
- } = transactionMeta;
- transactionMeta.transaction.value = hexToBN(value);
- transactionMeta.transaction.readableValue = fromWei(transactionMeta.transaction.value);
- transactionMeta.transaction.gas = hexToBN(gas);
- transactionMeta.transaction.gasPrice = hexToBN(gasPrice);
- this.props.setTransactionObject({
- ...{ symbol: 'ETH', type: 'ETHER_TRANSACTION', assetType: 'ETH', id: transactionMeta.id },
- ...transactionMeta.transaction
- });
- this.props.navigation.push('ApprovalView');
- };
-
async loadUrl() {
if (!this.isTabActive()) return;
const { navigation } = this.props;
@@ -703,10 +734,7 @@ export class BrowserTab extends PureComponent {
componentWillUnmount() {
this.mounted = false;
// Remove all Engine listeners
- Engine.context.PersonalMessageManager.hub.removeAllListeners();
- Engine.context.TypedMessageManager.hub.removeAllListeners();
Engine.context.AssetsController.hub.removeAllListeners();
- Engine.context.TransactionController.hub.removeListener('unapprovedTransaction', this.onUnapprovedTransaction);
Engine.context.TransactionController.hub.removeListener('networkChange', this.reload);
if (Platform.OS === 'ios') {
this.state.scrollAnim && this.state.scrollAnim.removeAllListeners();
@@ -920,9 +948,6 @@ export class BrowserTab extends PureComponent {
ipfsWebsite: false,
showApprovalDialog: false,
showPhishingModal: false,
- signMessage: false,
- signMessageParams: { data: '' },
- signType: '',
timeout: false,
url: HOMEPAGE_URL,
scrollAnim,
@@ -1517,46 +1542,6 @@ export class BrowserTab extends PureComponent {
);
};
- onSignAction = () => {
- this.setState({ signMessage: false });
- };
-
- renderSigningModal = () => {
- const { signMessage, signMessageParams, signType, currentPageTitle, currentPageUrl } = this.state;
- return (
-
- {signType === 'personal' && (
-
- )}
- {signType === 'typed' && (
-
- )}
-
- );
- };
-
onCancelWatchAsset = () => {
this.setState({ watchAsset: false });
};
@@ -1610,7 +1595,7 @@ export class BrowserTab extends PureComponent {
backdropOpacity={0.7}
animationInTiming={300}
animationOutTiming={300}
- onSwipeComplete={this.onSignAction}
+ onSwipeComplete={this.onAccountsReject}
swipeDirection={'down'}
>
) : null}
{!isHidden && this.renderUrlModal()}
- {!isHidden && this.renderSigningModal()}
{!isHidden && this.renderApprovalModal()}
{!isHidden && this.renderPhishingModal()}
{!isHidden && this.renderWatchAssetModal()}
@@ -1760,7 +1744,6 @@ const mapStateToProps = state => ({
privacyMode: state.privacy.privacyMode,
searchEngine: state.settings.searchEngine,
whitelist: state.browser.whitelist,
- transaction: state.transaction,
activeTab: state.browser.activeTab
});
diff --git a/app/components/Views/ExperimentalSettings/__snapshots__/index.test.js.snap b/app/components/Views/ExperimentalSettings/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000000..20a1380162d
--- /dev/null
+++ b/app/components/Views/ExperimentalSettings/__snapshots__/index.test.js.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ExperimentalSettings should render correctly 1`] = `
+<_class
+ style={
+ Object {
+ "backgroundColor": "#FFFFFF",
+ "flex": 1,
+ "padding": 24,
+ "paddingBottom": 48,
+ }
+ }
+>
+
+
+
+
+ WalletConnect Sessions
+
+
+ View the list of active WalletConnect sessions
+
+
+ VIEW SESSIONS
+
+
+
+
+
+`;
diff --git a/app/components/Views/ExperimentalSettings/index.js b/app/components/Views/ExperimentalSettings/index.js
new file mode 100644
index 00000000000..a6f28734ba5
--- /dev/null
+++ b/app/components/Views/ExperimentalSettings/index.js
@@ -0,0 +1,78 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { StyleSheet, Text, ScrollView, View } from 'react-native';
+import StyledButton from '../../UI/StyledButton';
+import { colors, fontStyles } from '../../../styles/common';
+import { getNavigationOptionsTitle } from '../../UI/Navbar';
+import { strings } from '../../../../locales/i18n';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ backgroundColor: colors.white,
+ flex: 1,
+ padding: 24,
+ paddingBottom: 48
+ },
+ title: {
+ ...fontStyles.normal,
+ color: colors.fontPrimary,
+ fontSize: 20,
+ lineHeight: 20
+ },
+ desc: {
+ ...fontStyles.normal,
+ color: colors.grey500,
+ fontSize: 14,
+ lineHeight: 20,
+ marginTop: 12
+ },
+ setting: {
+ marginTop: 50
+ },
+ firstSetting: {
+ marginTop: 0
+ },
+ clearHistoryConfirm: {
+ marginTop: 18
+ },
+ inner: {
+ paddingBottom: 112
+ }
+});
+
+/**
+ * Main view for app Experimental Settings
+ */
+export default class ExperimentalSettings extends Component {
+ static propTypes = {
+ /**
+ /* navigation object required to push new views
+ */
+ navigation: PropTypes.object
+ };
+
+ static navigationOptions = ({ navigation }) =>
+ getNavigationOptionsTitle(strings('app_settings.experimental_title'), navigation);
+
+ goToWalletConnectSessions = () => {
+ this.props.navigation.navigate('WalletConnectSessionsView');
+ };
+
+ render = () => (
+
+
+
+ {strings('experimental_settings.wallet_connect_dapps')}
+ {strings('experimental_settings.wallet_connect_dapps_desc')}
+
+ {strings('experimental_settings.wallet_connect_dapps_cta').toUpperCase()}
+
+
+
+
+ );
+}
diff --git a/app/components/Views/ExperimentalSettings/index.test.js b/app/components/Views/ExperimentalSettings/index.test.js
new file mode 100644
index 00000000000..5c186d0fd6b
--- /dev/null
+++ b/app/components/Views/ExperimentalSettings/index.test.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import ExperimentalSettings from './';
+import configureMockStore from 'redux-mock-store';
+
+describe('ExperimentalSettings', () => {
+ const mockStore = configureMockStore();
+
+ it('should render correctly', () => {
+ const initialState = {
+ privacy: { approvedHosts: {}, privacyMode: true },
+ browser: { history: [] },
+ settings: { lockTime: 1000 },
+ engine: {
+ backgroundState: {
+ PreferencesController: { selectedAddress: '0x', identities: { '0x': { name: 'Account 1' } } },
+ AccountTrackerController: { accounts: {} }
+ }
+ }
+ };
+
+ const wrapper = shallow(
+ ,
+ {
+ context: { store: mockStore(initialState) }
+ }
+ );
+ expect(wrapper.dive()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/Views/QRScanner/index.js b/app/components/Views/QRScanner/index.js
index ca714b70b6c..4009b314342 100644
--- a/app/components/Views/QRScanner/index.js
+++ b/app/components/Views/QRScanner/index.js
@@ -91,6 +91,9 @@ export default class QrScanner extends Component {
} else if (content.split('metamask-sync:').length > 1) {
this.shouldReadBarCode = false;
data = { content };
+ } else if (content.split('wc:').length > 1) {
+ this.shouldReadBarCode = false;
+ data = { walletConnectURI: content };
} else {
// EIP-945 allows scanning arbitrary data
data = content;
diff --git a/app/components/Views/Settings/__snapshots__/index.test.js.snap b/app/components/Views/Settings/__snapshots__/index.test.js.snap
index 35498a93d7e..bba472242a1 100644
--- a/app/components/Views/Settings/__snapshots__/index.test.js.snap
+++ b/app/components/Views/Settings/__snapshots__/index.test.js.snap
@@ -26,6 +26,11 @@ exports[`Settings should render correctly 1`] = `
onPress={[Function]}
title="Security & Privacy"
/>
+
+ {
+ navigation.push('ExperimentalSettings');
+ }}
+ />
{
diff --git a/app/components/Views/WalletConnectSessions/__snapshots__/index.test.js.snap b/app/components/Views/WalletConnectSessions/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000000..05925be6e1f
--- /dev/null
+++ b/app/components/Views/WalletConnectSessions/__snapshots__/index.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`WalletConnectSessions should render correctly 1`] = `""`;
diff --git a/app/components/Views/WalletConnectSessions/index.js b/app/components/Views/WalletConnectSessions/index.js
new file mode 100644
index 00000000000..576e733a905
--- /dev/null
+++ b/app/components/Views/WalletConnectSessions/index.js
@@ -0,0 +1,168 @@
+import React, { Component } from 'react';
+import { Alert, ScrollView, SafeAreaView, StyleSheet, View, Text, TouchableOpacity } from 'react-native';
+import { colors, fontStyles } from '../../../styles/common';
+import { strings } from '../../../../locales/i18n';
+import { getNavigationOptionsTitle } from '../../UI/Navbar';
+import WebsiteIcon from '../../UI/WebsiteIcon';
+import AsyncStorage from '@react-native-community/async-storage';
+import ActionSheet from 'react-native-actionsheet';
+import WalletConnect from '../../../core/WalletConnect';
+import Logger from '../../../util/Logger';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ backgroundColor: colors.white,
+ flex: 1
+ },
+ scrollviewContent: {
+ paddingTop: 20
+ },
+ websiteIcon: {
+ width: 44,
+ height: 44
+ },
+ row: {
+ flexDirection: 'row',
+ paddingVertical: 10,
+ paddingHorizontal: 20,
+ borderBottomColor: colors.grey000,
+ borderBottomWidth: 1
+ },
+ info: {
+ marginLeft: 20,
+ flex: 1
+ },
+ name: {
+ ...fontStyles.bold,
+ fontSize: 16,
+ marginBottom: 10
+ },
+ desc: {
+ marginBottom: 10,
+ ...fontStyles.normal,
+ fontSize: 12
+ },
+ url: {
+ marginBottom: 10,
+ ...fontStyles.normal,
+ fontSize: 12,
+ color: colors.fontSecondary
+ },
+ emptyWrapper: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ emptyText: {
+ ...fontStyles.normal,
+ fontSize: 16
+ }
+});
+
+/**
+ * View that displays all the active WalletConnect Sessions
+ */
+export default class WalletConnectSessions extends Component {
+ static navigationOptions = ({ navigation }) =>
+ getNavigationOptionsTitle(strings(`experimental_settings.wallet_connect_dapps`), navigation);
+
+ state = {
+ sessions: []
+ };
+
+ actionSheet = null;
+
+ sessionToRemove = null;
+
+ componentDidMount() {
+ this.loadSessions();
+ }
+
+ loadSessions = async () => {
+ let sessions = [];
+ const sessionData = await AsyncStorage.getItem('@MetaMask:walletconnectSessions');
+ if (sessionData) {
+ sessions = JSON.parse(sessionData);
+ }
+ this.setState({ ready: true, sessions });
+ };
+
+ renderDesc = meta => {
+ const { description } = meta;
+ if (description) {
+ return {meta.description};
+ }
+ return null;
+ };
+
+ onLongPress = session => {
+ this.sessionToRemove = session;
+ this.actionSheet.show();
+ };
+
+ createActionSheetRef = ref => {
+ this.actionSheet = ref;
+ };
+
+ onActionSheetPress = index => (index === 0 ? this.killSession() : null);
+
+ killSession = async () => {
+ try {
+ await WalletConnect.killSession(this.sessionToRemove.peerId);
+ Alert.alert(
+ strings('walletconnect_sessions.session_ended_title'),
+ strings('walletconnect_sessions.session_ended_desc')
+ );
+ this.loadSessions();
+ } catch (e) {
+ Logger.error('WC: Failed to kill session', e);
+ }
+ };
+
+ renderSessions = () => {
+ const { sessions } = this.state;
+ return sessions.map(session => (
+ this.onLongPress(session)}
+ key={`session_${session.peerId}`}
+ style={styles.row}
+ >
+
+
+ {session.peerMeta.name}
+ {session.peerId}
+ {session.peerMeta.url}
+ {this.renderDesc(session.peerMeta)}
+
+
+ ));
+ };
+
+ renderEmpty = () => (
+
+ {strings('walletconnect_sessions.no_active_sessions')}
+
+ );
+
+ render = () => {
+ const { ready, sessions } = this.state;
+ if (!ready) return null;
+
+ return (
+
+
+ {sessions.length ? this.renderSessions() : this.renderEmpty()}
+
+
+
+ );
+ };
+}
diff --git a/app/components/Views/WalletConnectSessions/index.test.js b/app/components/Views/WalletConnectSessions/index.test.js
new file mode 100644
index 00000000000..5df63ba5303
--- /dev/null
+++ b/app/components/Views/WalletConnectSessions/index.test.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import WalletConnectSessions from './';
+
+describe('WalletConnectSessions', () => {
+ it('should render correctly', () => {
+ const wrapper = shallow();
+
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/app/core/Engine.js b/app/core/Engine.js
index cd2754b24c4..3a527f2801e 100644
--- a/app/core/Engine.js
+++ b/app/core/Engine.js
@@ -77,60 +77,6 @@ class Engine {
} catch (error) {
end(error);
}
- },
- eth_sign: async (payload, next, end) => {
- const { PersonalMessageManager } = this.datamodel.context;
- try {
- const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({
- data: payload.params[1],
- from: payload.params[0]
- });
- end(undefined, rawSig);
- } catch (error) {
- end(error);
- }
- },
- personal_sign: async (payload, next, end) => {
- const { PersonalMessageManager } = this.datamodel.context;
- try {
- const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({
- data: payload.params[0],
- from: payload.params[1]
- });
- end(undefined, rawSig);
- } catch (error) {
- end(error);
- }
- },
- eth_signTypedData: async (payload, next, end) => {
- const { TypedMessageManager } = this.datamodel.context;
- try {
- const rawSig = await TypedMessageManager.addUnapprovedMessageAsync(
- {
- data: payload.params[0],
- from: payload.params[1]
- },
- 'V1'
- );
- end(undefined, rawSig);
- } catch (error) {
- end(error);
- }
- },
- eth_signTypedData_v3: async (payload, next, end) => {
- const { TypedMessageManager } = this.datamodel.context;
- try {
- const rawSig = await TypedMessageManager.addUnapprovedMessageAsync(
- {
- data: payload.params[1],
- from: payload.params[0]
- },
- 'V3'
- );
- end(undefined, rawSig);
- } catch (error) {
- end(error);
- }
}
},
getAccounts: (end, payload) => {
diff --git a/app/core/WalletConnect.js b/app/core/WalletConnect.js
new file mode 100644
index 00000000000..8bd54c999c9
--- /dev/null
+++ b/app/core/WalletConnect.js
@@ -0,0 +1,273 @@
+import RNWalletConnect from '@walletconnect/react-native';
+import Engine from './Engine';
+import Logger from '../util/Logger';
+// eslint-disable-next-line import/no-nodejs-modules
+import { EventEmitter } from 'events';
+import AsyncStorage from '@react-native-community/async-storage';
+
+const hub = new EventEmitter();
+let connectors = [];
+const CLIENT_OPTIONS = {
+ clientMeta: {
+ // Required
+ description: 'MetaMask Mobile app',
+ url: 'https://metamask.io',
+ icons: ['https://raw.githubusercontent.com/MetaMask/brand-resources/master/SVG/metamask-fox.svg'],
+ name: 'MetaMask',
+ ssl: true
+ }
+};
+
+const persistSessions = async () => {
+ const sessions = connectors
+ .filter(connector => connector && connector.walletConnector && connector && connector.walletConnector.connected)
+ .map(connector => connector.walletConnector.session);
+
+ await AsyncStorage.setItem('@MetaMask:walletconnectSessions', JSON.stringify(sessions));
+};
+
+class WalletConnect {
+ selectedAddress = null;
+ chainId = null;
+
+ constructor(options) {
+ this.walletConnector = new RNWalletConnect(options, CLIENT_OPTIONS);
+ /**
+ * Subscribe to session requests
+ */
+ this.walletConnector.on('session_request', async (error, payload) => {
+ if (error) {
+ throw error;
+ }
+
+ try {
+ await this.sessionRequest(payload.params[0]);
+
+ const { network } = Engine.context.NetworkController.state;
+ this.selectedAddress = Engine.context.PreferencesController.state.selectedAddress;
+ const approveData = {
+ chainId: parseInt(network, 10),
+ accounts: [this.selectedAddress]
+ };
+ this.walletConnector.approveSession(approveData);
+ persistSessions();
+ } catch (e) {
+ this.walletConnector.rejectSession();
+ }
+ });
+
+ /**
+ * Subscribe to call requests
+ */
+ this.walletConnector.on('call_request', async (error, payload) => {
+ if (error) {
+ throw error;
+ }
+
+ const meta = this.walletConnector.session.peerMeta;
+
+ if (payload.method) {
+ if (payload.method === 'eth_sendTransaction') {
+ const { TransactionController } = Engine.context;
+ try {
+ const txParams = {};
+ txParams.to = payload.params[0].to;
+ txParams.from = payload.params[0].from;
+ txParams.value = payload.params[0].value;
+ txParams.gasLimit = payload.params[0].gasLimit;
+ txParams.gasPrice = payload.params[0].gasPrice;
+ if (payload.params[0].data && payload.params[0].data.toString() !== '0x') {
+ txParams.data = payload.params[0].data;
+ }
+
+ const hash = await (await TransactionController.addTransaction(txParams)).result;
+ this.walletConnector.approveRequest({
+ id: payload.id,
+ result: hash
+ });
+ } catch (error) {
+ this.walletConnector.rejectRequest({
+ id: payload.id,
+ error
+ });
+ }
+ } else if (payload.method === 'eth_sign' || payload.method === 'personal_sign') {
+ const { PersonalMessageManager } = Engine.context;
+
+ try {
+ const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({
+ data: payload.params[1],
+ from: payload.params[0],
+ meta: {
+ title: meta && meta.name,
+ url: meta && meta.url,
+ icon: meta && meta.icons && meta.icons[0]
+ }
+ });
+ this.walletConnector.approveRequest({
+ id: payload.id,
+ result: rawSig
+ });
+ } catch (error) {
+ this.walletConnector.rejectRequest({
+ id: payload.id,
+ error
+ });
+ }
+ } else if (payload.method && payload.method === 'eth_signTypedData') {
+ const { TypedMessageManager } = Engine.context;
+ try {
+ const rawSig = await TypedMessageManager.addUnapprovedMessageAsync(
+ {
+ data: payload.params[1],
+ from: payload.params[0],
+ meta: {
+ title: meta && meta.name,
+ url: meta && meta.url,
+ icon: meta && meta.icons && meta.icons[0]
+ }
+ },
+ 'V3'
+ );
+
+ this.walletConnector.approveRequest({
+ id: payload.id,
+ result: rawSig
+ });
+ } catch (error) {
+ this.walletConnector.rejectRequest({
+ id: payload.id,
+ error
+ });
+ }
+ }
+ }
+ });
+
+ this.walletConnector.on('disconnect', error => {
+ if (error) {
+ throw error;
+ }
+
+ // delete walletConnector
+ this.walletConnector = null;
+ persistSessions();
+ });
+
+ this.walletConnector.on('session_update', (error, payload) => {
+ if (error) {
+ throw error;
+ }
+ Logger.log('session_update FROM WC:', error, payload);
+ });
+
+ Engine.context.TransactionController.hub.on('networkChange', this.onNetworkChange);
+ Engine.context.PreferencesController.subscribe(this.onAccountChange);
+ const { selectedAddress } = Engine.context.PreferencesController.state;
+ const { network } = Engine.context.NetworkController.state;
+
+ this.selectedAddress = selectedAddress;
+ this.chainId = network;
+ }
+
+ onAccountChange = () => {
+ const { selectedAddress } = Engine.context.PreferencesController.state;
+
+ if (selectedAddress !== this.selectedAddress) {
+ this.selectedAddress = selectedAddress;
+ this.updateSession();
+ }
+ };
+
+ onNetworkChange = () => {
+ const { network } = Engine.context.NetworkController.state;
+ // Wait while the network is set
+ if (network !== 'loading' && network !== this.chainId) {
+ this.chainId = network;
+ this.updateSession();
+ }
+ };
+
+ updateSession = () => {
+ const { network } = Engine.context.NetworkController.state;
+ const { selectedAddress } = Engine.context.PreferencesController.state;
+ const sessionData = {
+ chainId: parseInt(network, 10),
+ accounts: [selectedAddress]
+ };
+ this.walletConnector.updateSession(sessionData);
+ };
+
+ killSession = () => {
+ this.walletConnector && this.walletConnector.killSession();
+ this.walletConnector = null;
+ };
+
+ sessionRequest = peerInfo =>
+ new Promise((resolve, reject) => {
+ hub.emit('walletconnectSessionRequest', peerInfo);
+
+ hub.on('walletconnectSessionRequest::approved', peerId => {
+ if (peerInfo.peerId === peerId) {
+ resolve(true);
+ }
+ });
+ hub.on('walletconnectSessionRequest::rejected', peerId => {
+ if (peerInfo.peerId === peerId) {
+ reject(false);
+ }
+ });
+ });
+}
+
+const instance = {
+ async init() {
+ const sessionData = await AsyncStorage.getItem('@MetaMask:walletconnectSessions');
+ if (sessionData) {
+ const sessions = JSON.parse(sessionData);
+ sessions.forEach(session => {
+ connectors.push(new WalletConnect({ session }));
+ });
+ }
+ },
+ connectors() {
+ return connectors;
+ },
+ newSession(uri) {
+ connectors.push(new WalletConnect({ uri }));
+ },
+ getSessions: async () => {
+ let sessions = [];
+ const sessionData = await AsyncStorage.getItem('@MetaMask:walletconnectSessions');
+ if (sessionData) {
+ sessions = JSON.parse(sessionData);
+ }
+ return sessions;
+ },
+ killSession: async id => {
+ // 1) First kill the session
+ const connectorToKill = connectors.find(
+ connector => connector && connector.walletConnector && connector.walletConnector.session.peerId === id
+ );
+ if (connectorToKill) {
+ await connectorToKill.killSession();
+ }
+ // 2) Remove from the list of connectors
+ connectors = connectors.filter(
+ connector =>
+ connector &&
+ connector.walletConnector &&
+ connector.walletConnector.connected &&
+ connector.walletConnector.session.peerId !== id
+ );
+ // 3) Persist the list
+ await persistSessions();
+ },
+ hub,
+ shutdown() {
+ Engine.context.TransactionController.hub.removeAllListeners();
+ Engine.context.PreferencesController.unsubscribe();
+ }
+};
+
+export default instance;
diff --git a/app/util/networks.js b/app/util/networks.js
index dc2897c3426..717d8bca4d3 100644
--- a/app/util/networks.js
+++ b/app/util/networks.js
@@ -10,21 +10,25 @@ const NetworkList = {
mainnet: {
name: 'Ethereum Main Network',
networkId: 1,
+ chanId: 1,
color: '#3cc29e'
},
ropsten: {
name: 'Ropsten Test Network',
networkId: 3,
+ chainId: 3,
color: '#ff4a8d'
},
kovan: {
name: 'Kovan Test Network',
networkId: 42,
+ chainId: 42,
color: '#7057ff'
},
rinkeby: {
name: 'Rinkeby Test Network',
networkId: 4,
+ chainId: 4,
color: '#f6c343'
},
rpc: {
diff --git a/locales/en.json b/locales/en.json
index da237094f9f..c979619a536 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -297,6 +297,8 @@
"security_title": "Security & Privacy",
"security_desc": "Privacy settings, MetaMetrics, private key and wallet seed phrase",
"info_title": "About MetaMask",
+ "experimental_title": "Experimental",
+ "experimental_desc": "WalletConnect & more...",
"legal_title": "Legal",
"conversion_title": "Currency conversion",
"conversion_desc": "Display fiat values in using a specific currency throughout the application.",
@@ -577,6 +579,7 @@
},
"accountApproval": {
"title": "CONNECT REQUEST",
+ "walletconnect_title": "WALLETCONNECT REQUEST",
"action": "would like to",
"connect": "CONNECT",
"cancel": "CANCEL",
@@ -766,5 +769,18 @@
"public_address": "Public Address",
"public_address_qr_code": "Public Address QR Code",
"coming_soon": "Coming soon..."
- }
+ },
+ "experimental_settings": {
+ "wallet_connect_dapps": "WalletConnect Sessions",
+ "wallet_connect_dapps_desc": "View the list of active WalletConnect sessions",
+ "wallet_connect_dapps_cta": "VIEW SESSIONS"
+ },
+ "walletconnect_sessions": {
+ "no_active_sessions": "You have no active sessions",
+ "end_session_title": "End Session",
+ "end": "End",
+ "cancel": "Cancel",
+ "session_ended_title": "Session Ended",
+ "session_ended_desc": "The selected session has been terminated"
+ }
}
diff --git a/locales/es.json b/locales/es.json
index 253ff68edad..77114fcc6c3 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -296,6 +296,8 @@
"security_title": "Seguridad y privacidad",
"security_desc": "Opciones de privacidad, MetaMetrics y frase semilla de la billetera",
"info_title": "Acerca de MetaMask",
+ "experimental_title": "Experimental",
+ "experimental_desc": "WalletConnect & más...",
"legal_title": "Legal",
"conversion_title": "Conversión de moneda",
"conversion_desc": "Mostrar valores en fiat usando una moneda específica en toda la aplicación.",
@@ -567,6 +569,7 @@
},
"accountApproval": {
"title": "SOLICITUD DE CONEXIÓN ",
+ "walletconnect_title": "SOLICITUD DE CONEXIÓN VIA WALLETCONNECT ",
"action": "le gustaría",
"connect": "CONECTAR",
"cancel": "CANCELAR",
@@ -754,5 +757,18 @@
"public_address": "Dirección Pública",
"public_address_qr_code": "Código QR de Dirección Pública",
"coming_soon": "Pronto..."
- }
+ },
+ "experimental_settings": {
+ "wallet_connect_dapps": "Sesiones de WalletConnect",
+ "wallet_connect_dapps_desc": "Accede a la lista de sesiones activas de WalletConnect",
+ "wallet_connect_dapps_cta": "VER SESIONES"
+ },
+ "walletconnect_sessions": {
+ "no_active_sessions": "No tienes sesiones activas",
+ "end_session_title": "Finalizar Sesión",
+ "end": "Finalizar",
+ "cancel": "Cancelar",
+ "session_ended_title": "Sesión Finalizada",
+ "session_ended_desc": "La sesión seleccionada ha sido terminada"
+ }
}
diff --git a/package-lock.json b/package-lock.json
index 6145c37284a..e42cca827b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2532,6 +2532,47 @@
"integrity": "sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==",
"dev": true
},
+ "@walletconnect/core": {
+ "version": "1.0.0-beta.18",
+ "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.0.0-beta.18.tgz",
+ "integrity": "sha512-xWs9WkXFlTwSCkUzqXVF49BtCHwtzyAQL9x1MCFWTSGlqK8xapSH+2PEq4k2CHw/Zjh22oK/qBvuYgCdo+RFuw==",
+ "requires": {
+ "@walletconnect/types": "^1.0.0-beta.17",
+ "@walletconnect/utils": "^1.0.0-beta.18"
+ }
+ },
+ "@walletconnect/react-native": {
+ "version": "1.0.0-beta.18",
+ "resolved": "https://registry.npmjs.org/@walletconnect/react-native/-/react-native-1.0.0-beta.18.tgz",
+ "integrity": "sha512-VL4GcXi1WdnnfjnvSkrPR4lDKhF3hdc31tFo7iuu4bZa52qeMTE36NBxSix+Nr1PlINE375K+ErqtuiBMq0FkQ==",
+ "requires": {
+ "@walletconnect/core": "^1.0.0-beta.18",
+ "@walletconnect/types": "^1.0.0-beta.17",
+ "@walletconnect/utils": "^1.0.0-beta.18"
+ }
+ },
+ "@walletconnect/types": {
+ "version": "1.0.0-beta.18",
+ "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.0.0-beta.18.tgz",
+ "integrity": "sha512-cqhVcyNdXEDvnUUD8r7cWI6ZHeIsIOriJMIkOuxslWbaHadnJ0SGStJ9sSINibAMD/ZUEabQZRWA18BxL4jRvQ=="
+ },
+ "@walletconnect/utils": {
+ "version": "1.0.0-beta.18",
+ "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.0.0-beta.18.tgz",
+ "integrity": "sha512-IgpJyHmtgRmset5gZZ9gTGziZQ5fKWtTud41vosKtQhgvh8+HQkPVWPqj/KxQupLFWK3/ACo1CqlxoKAjctpOQ==",
+ "requires": {
+ "@walletconnect/types": "^1.0.0-beta.17",
+ "js-sha3": "^0.8.0",
+ "lodash.isnumber": "^3.0.3"
+ },
+ "dependencies": {
+ "js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
+ }
+ }
+ },
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@@ -13100,6 +13141,11 @@
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
diff --git a/package.json b/package.json
index 030b65536cf..83dcd0ff8d2 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
"dependencies": {
"@react-native-community/async-storage": "1.2.0",
"@tradle/react-native-http": "2.0.1",
+ "@walletconnect/react-native": "1.0.0-beta.22",
"babel-plugin-transform-inline-environment-variables": "0.4.3",
"base-64": "0.1.0",
"bignumber.js": "8.1.1",