diff --git a/android/app/src/main/java/io/metamask/MainApplication.java b/android/app/src/main/java/io/metamask/MainApplication.java
index 8782cc9fca1..d5bf78bdca3 100644
--- a/android/app/src/main/java/io/metamask/MainApplication.java
+++ b/android/app/src/main/java/io/metamask/MainApplication.java
@@ -1,6 +1,7 @@
package io.metamask;
import com.facebook.react.ReactApplication;
+import com.reactnativecommunity.webviewforked.RNCWebViewForkedPackage;
import com.cmcewen.blurview.BlurViewPackage;
import android.content.Context;
import com.facebook.react.PackageList;
diff --git a/android/settings.gradle b/android/settings.gradle
index b5e08ef3e18..f5b78e3e1af 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'MetaMask'
+include ':react-native-webview-forked'
+project(':react-native-webview-forked').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-forked/android')
include ':@react-native-community_blur'
project(':@react-native-community_blur').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/blur/android')
include ':lottie-react-native'
diff --git a/app/__mocks__/@exodus/react-native-payments.js b/app/__mocks__/@exodus/react-native-payments.js
new file mode 100644
index 00000000000..b69bc6d5c9f
--- /dev/null
+++ b/app/__mocks__/@exodus/react-native-payments.js
@@ -0,0 +1 @@
+export default from '@exodus/react-native-payments/lib/js/__mocks__';
diff --git a/app/components/Base/DetailsModal.js b/app/components/Base/DetailsModal.js
new file mode 100644
index 00000000000..df042dff0bf
--- /dev/null
+++ b/app/components/Base/DetailsModal.js
@@ -0,0 +1,127 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, StyleSheet, TouchableOpacity } from 'react-native';
+import Ionicons from 'react-native-vector-icons/Ionicons';
+import { colors, fontStyles } from '../../styles/common';
+
+import Text from './Text';
+
+const styles = StyleSheet.create({
+ modalContainer: {
+ width: '100%',
+ backgroundColor: colors.white,
+ borderRadius: 10
+ },
+ modalView: {
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ header: {
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderColor: colors.grey100,
+ flexDirection: 'row'
+ },
+ title: {
+ flex: 1,
+ textAlign: 'center',
+ fontSize: 18,
+ marginVertical: 12,
+ marginHorizontal: 24,
+ color: colors.fontPrimary,
+ ...fontStyles.bold
+ },
+ closeIcon: { paddingTop: 4, position: 'absolute', right: 16 },
+ body: {
+ paddingHorizontal: 15
+ },
+ section: {
+ paddingVertical: 16,
+ flexDirection: 'row'
+ },
+ sectionBorderBottom: {
+ borderBottomColor: colors.grey100,
+ borderBottomWidth: 1
+ },
+ column: {
+ flex: 1
+ },
+ columnEnd: {
+ alignItems: 'flex-end'
+ },
+ sectionTitle: {
+ ...fontStyles.normal,
+ fontSize: 10,
+ color: colors.grey500,
+ marginBottom: 8
+ }
+});
+const DetailsModal = ({ children }) => (
+
+ {children}
+
+);
+
+const DetailsModalHeader = ({ style, ...props }) => ;
+const DetailsModalTitle = ({ style, ...props }) => ;
+const DetailsModalCloseIcon = ({ style, ...props }) => (
+
+
+
+);
+const DetailsModalBody = ({ style, ...props }) => ;
+const DetailsModalSection = ({ style, borderBottom, ...props }) => (
+
+);
+const DetailsModalSectionTitle = ({ style, ...props }) => ;
+const DetailsModalColumn = ({ style, end, ...props }) => (
+
+);
+
+DetailsModal.Header = DetailsModalHeader;
+DetailsModal.Title = DetailsModalTitle;
+DetailsModal.CloseIcon = DetailsModalCloseIcon;
+DetailsModal.Body = DetailsModalBody;
+DetailsModal.Section = DetailsModalSection;
+DetailsModal.SectionTitle = DetailsModalSectionTitle;
+DetailsModal.Column = DetailsModalColumn;
+
+/**
+ * Any other external style defined in props will be applied
+ */
+const stylePropType = PropTypes.oneOfType([PropTypes.object, PropTypes.array]);
+
+DetailsModal.propTypes = {
+ children: PropTypes.node
+};
+
+DetailsModalHeader.propTypes = {
+ style: stylePropType
+};
+DetailsModalTitle.propTypes = {
+ style: stylePropType
+};
+DetailsModalCloseIcon.propTypes = {
+ style: stylePropType
+};
+DetailsModalBody.propTypes = {
+ style: stylePropType
+};
+DetailsModalSection.propTypes = {
+ style: stylePropType,
+ /**
+ * Adds a border to the bottom of the section
+ */
+ borderBottom: PropTypes.bool
+};
+DetailsModalSectionTitle.propTypes = {
+ style: stylePropType
+};
+DetailsModalColumn.propTypes = {
+ style: stylePropType,
+ /**
+ * Aligns column content to flex-end
+ */
+ end: PropTypes.bool
+};
+export default DetailsModal;
diff --git a/app/components/Base/ListItem.js b/app/components/Base/ListItem.js
new file mode 100644
index 00000000000..a2294a45722
--- /dev/null
+++ b/app/components/Base/ListItem.js
@@ -0,0 +1,112 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet, View } from 'react-native';
+import Device from '../../util/Device';
+import { colors, fontStyles } from '../../styles/common';
+import Text from './Text';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ padding: 15,
+ minHeight: Device.isIos() ? 95 : 100
+ },
+ date: {
+ color: colors.fontSecondary,
+ fontSize: 12,
+ marginBottom: 10,
+ ...fontStyles.normal
+ },
+ content: {
+ flexDirection: 'row'
+ },
+ actions: {
+ flexDirection: 'row',
+ paddingTop: 10,
+ paddingLeft: 40
+ },
+ icon: {
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ body: {
+ flex: 1,
+ marginLeft: 15
+ },
+ amounts: {
+ flex: 0.6,
+ alignItems: 'flex-end'
+ },
+ title: {
+ fontSize: 15,
+ color: colors.fontPrimary
+ },
+ amount: {
+ fontSize: 15,
+ color: colors.fontPrimary
+ },
+ fiatAmount: {
+ fontSize: 12,
+ color: colors.fontSecondary,
+ textTransform: 'uppercase'
+ }
+});
+
+const ListItem = ({ style, ...props }) => ;
+
+const ListItemDate = ({ style, ...props }) => ;
+const ListItemContent = ({ style, ...props }) => ;
+const ListItemActions = ({ style, ...props }) => ;
+const ListItemIcon = ({ style, ...props }) => ;
+const ListItemBody = ({ style, ...props }) => ;
+const ListItemTitle = ({ style, ...props }) => ;
+const ListItemAmounts = ({ style, ...props }) => ;
+const ListItemAmount = ({ style, ...props }) => ;
+const ListItemFiatAmount = ({ style, ...props }) => ;
+
+ListItem.Date = ListItemDate;
+ListItem.Content = ListItemContent;
+ListItem.Actions = ListItemActions;
+ListItem.Icon = ListItemIcon;
+ListItem.Body = ListItemBody;
+ListItem.Title = ListItemTitle;
+ListItem.Amounts = ListItemAmounts;
+ListItem.Amount = ListItemAmount;
+ListItem.FiatAmount = ListItemFiatAmount;
+
+export default ListItem;
+
+/**
+ * Any other external style defined in props will be applied
+ */
+const stylePropType = PropTypes.oneOfType([PropTypes.object, PropTypes.array]);
+
+ListItem.propTypes = {
+ style: stylePropType
+};
+ListItemDate.propTypes = {
+ style: stylePropType
+};
+ListItemContent.propTypes = {
+ style: stylePropType
+};
+ListItemActions.propTypes = {
+ style: stylePropType
+};
+ListItemIcon.propTypes = {
+ style: stylePropType
+};
+ListItemBody.propTypes = {
+ style: stylePropType
+};
+ListItemTitle.propTypes = {
+ style: stylePropType
+};
+ListItemAmounts.propTypes = {
+ style: stylePropType
+};
+ListItemAmount.propTypes = {
+ style: stylePropType
+};
+ListItemFiatAmount.propTypes = {
+ style: stylePropType
+};
diff --git a/app/components/Base/ModalHandler.js b/app/components/Base/ModalHandler.js
new file mode 100644
index 00000000000..417697a090c
--- /dev/null
+++ b/app/components/Base/ModalHandler.js
@@ -0,0 +1,17 @@
+import { useState } from 'react';
+
+function ModalHandler({ children }) {
+ const [isVisible, setVisible] = useState(false);
+
+ const showModal = () => setVisible(true);
+ const hideModal = () => setVisible(true);
+ const toggleModal = () => setVisible(!isVisible);
+
+ if (typeof children === 'function') {
+ return children({ isVisible, toggleModal, showModal, hideModal });
+ }
+
+ return children;
+}
+
+export default ModalHandler;
diff --git a/app/components/Base/StatusText.js b/app/components/Base/StatusText.js
new file mode 100644
index 00000000000..53d2850fac8
--- /dev/null
+++ b/app/components/Base/StatusText.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Text from './Text';
+import { colors } from '../../styles/common';
+import { StyleSheet } from 'react-native';
+import { FIAT_ORDER_STATES } from '../../reducers/fiatOrders';
+import { strings } from '../../../locales/i18n';
+
+const styles = StyleSheet.create({
+ status: {
+ marginTop: 4,
+ fontSize: 12,
+ letterSpacing: 0.5
+ }
+});
+
+export const ConfirmedText = props => ;
+export const PendingText = props => ;
+export const FailedText = props => ;
+
+function StatusText({ status, context, ...props }) {
+ switch (status) {
+ case 'Confirmed':
+ case 'confirmed':
+ return {strings(`${context}.${status}`)};
+ case 'Pending':
+ case 'pending':
+ case 'Submitted':
+ case 'submitted':
+ return {strings(`${context}.${status}`)};
+ case 'Failed':
+ case 'Cancelled':
+ case 'failed':
+ case 'cancelled':
+ return {strings(`${context}.${status}`)};
+
+ case FIAT_ORDER_STATES.COMPLETED:
+ return {strings(`${context}.completed`)};
+ case FIAT_ORDER_STATES.PENDING:
+ return {strings(`${context}.pending`)};
+ case FIAT_ORDER_STATES.FAILED:
+ return {strings(`${context}.failed`)};
+ case FIAT_ORDER_STATES.CANCELLED:
+ return {strings(`${context}.cancelled`)};
+
+ default:
+ return (
+
+ {status}
+
+ );
+ }
+}
+
+StatusText.defaultProps = {
+ context: 'transaction'
+};
+
+StatusText.propTypes = {
+ status: PropTypes.string.isRequired,
+ context: PropTypes.string
+};
+
+export default StatusText;
diff --git a/app/components/Base/Summary.js b/app/components/Base/Summary.js
new file mode 100644
index 00000000000..9112ac081c7
--- /dev/null
+++ b/app/components/Base/Summary.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, StyleSheet } from 'react-native';
+import { colors } from '../../styles/common';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ borderWidth: 1,
+ borderColor: colors.grey050,
+ borderRadius: 8,
+ padding: 16
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginVertical: 6
+ },
+ rowEnd: {
+ justifyContent: 'flex-end'
+ },
+ rowLast: {
+ marginBottom: 0,
+ marginTop: 6
+ },
+ col: {
+ flexDirection: 'row',
+ flex: 1,
+ flexWrap: 'wrap'
+ },
+ separator: {
+ borderBottomWidth: 1,
+ borderBottomColor: colors.grey050,
+ marginVertical: 6
+ }
+});
+
+const Summary = ({ style, ...props }) => ;
+const SummaryRow = ({ style, end, last, ...props }) => (
+
+);
+const SummaryCol = ({ style, end, ...props }) => ;
+const SummarySeparator = ({ style, ...props }) => ;
+
+Summary.Row = SummaryRow;
+Summary.Col = SummaryCol;
+Summary.Separator = SummarySeparator;
+export default Summary;
+
+/**
+ * Any other external style defined in props will be applied
+ */
+const stylePropType = PropTypes.oneOfType([PropTypes.object, PropTypes.array]);
+
+Summary.propTypes = {
+ style: stylePropType
+};
+SummaryRow.propTypes = {
+ style: stylePropType,
+ /**
+ * Aligns content to the end of the row
+ */
+ end: PropTypes.bool,
+ /**
+ * Add style to the last row of the summary
+ */
+ last: PropTypes.bool
+};
+SummaryCol.propTypes = {
+ style: stylePropType,
+ /**
+ * Aligns content to the end of the row
+ */
+ end: PropTypes.bool
+};
+SummarySeparator.propTypes = {
+ style: stylePropType
+};
diff --git a/app/components/Base/TabBar.js b/app/components/Base/TabBar.js
new file mode 100644
index 00000000000..ef2884a379d
--- /dev/null
+++ b/app/components/Base/TabBar.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import DefaultTabBar from 'react-native-scrollable-tab-view/DefaultTabBar';
+
+import { colors, fontStyles } from '../../styles/common';
+
+const styles = StyleSheet.create({
+ tabUnderlineStyle: {
+ height: 2,
+ backgroundColor: colors.blue
+ },
+ tabStyle: {
+ paddingVertical: 8
+ },
+ textStyle: {
+ ...fontStyles.normal,
+ fontSize: 14
+ }
+});
+
+function TabBar({ ...props }) {
+ return (
+
+ );
+}
+
+export default TabBar;
diff --git a/app/components/Base/Text.js b/app/components/Base/Text.js
new file mode 100644
index 00000000000..43f26fdd610
--- /dev/null
+++ b/app/components/Base/Text.js
@@ -0,0 +1,158 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Text as RNText, StyleSheet } from 'react-native';
+import { fontStyles, colors } from '../../styles/common';
+
+const style = StyleSheet.create({
+ text: {
+ ...fontStyles.normal,
+ color: colors.grey600,
+ marginVertical: 2,
+ fontSize: 14
+ },
+ centered: {
+ textAlign: 'center'
+ },
+ right: {
+ textAlign: 'right'
+ },
+ bold: fontStyles.bold,
+ green: {
+ color: colors.green400
+ },
+ primary: {
+ color: colors.fontPrimary
+ },
+ small: {
+ fontSize: 12
+ },
+ upper: {
+ textTransform: 'uppercase'
+ },
+ disclaimer: {
+ fontStyle: 'italic',
+ letterSpacing: 0.15
+ },
+ modal: {
+ color: colors.fontPrimary,
+ fontSize: 16,
+ lineHeight: 30
+ },
+ link: {
+ color: colors.blue
+ },
+ strikethrough: {
+ textDecorationLine: 'line-through'
+ }
+});
+
+const Text = ({
+ reset,
+ centered,
+ right,
+ bold,
+ green,
+ primary,
+ small,
+ upper,
+ modal,
+ disclaimer,
+ link,
+ strikethrough,
+ style: externalStyle,
+ ...props
+}) => (
+
+);
+
+Text.defaultProps = {
+ reset: false,
+ centered: false,
+ right: false,
+ bold: false,
+ green: false,
+ primary: false,
+ disclaimer: false,
+ modal: false,
+ small: false,
+ upper: false,
+ link: false,
+ strikethrough: false,
+ style: undefined
+};
+
+Text.propTypes = {
+ /**
+ * Removes teh default style
+ */
+ reset: PropTypes.bool,
+ /**
+ * Align text to center
+ */
+ centered: PropTypes.bool,
+ /**
+ * Align text to right
+ */
+ right: PropTypes.bool,
+ /**
+ * Makes text bold
+ */
+ bold: PropTypes.bool,
+ /**
+ * Makes text green
+ */
+ green: PropTypes.bool,
+ /**
+ * Makes text fontPrimary color
+ */
+ primary: PropTypes.bool,
+ /**
+ * Makes text italic and tight
+ * used in disclaimers
+ */
+ disclaimer: PropTypes.bool,
+ /**
+ * Makes text black and bigger
+ * Used in modals
+ */
+ modal: PropTypes.bool,
+ /**
+ * Makes text small
+ */
+ small: PropTypes.bool,
+ /**
+ * Makes text uppercase
+ */
+ upper: PropTypes.bool,
+ /**
+ * Applies a link style
+ */
+ link: PropTypes.bool,
+ /**
+ * Applies a strikethrough decoration
+ */
+ strikethrough: PropTypes.bool,
+ /**
+ * Any other external style defined in props will be applied
+ */
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
+};
+
+export default Text;
diff --git a/app/components/Base/Title.js b/app/components/Base/Title.js
new file mode 100644
index 00000000000..8a015378fbf
--- /dev/null
+++ b/app/components/Base/Title.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet } from 'react-native';
+import { fontStyles, colors } from '../../styles/common';
+import Text from './Text.js';
+
+const style = StyleSheet.create({
+ text: {
+ fontSize: 18,
+ marginVertical: 3,
+ color: colors.fontPrimary,
+ ...fontStyles.bold
+ },
+ hero: {
+ fontSize: 22
+ },
+ centered: {
+ textAlign: 'center'
+ }
+});
+
+const Title = ({ centered, hero, style: externalStyle, ...props }) => (
+
+);
+
+Title.defaultProps = {
+ centered: false,
+ hero: false,
+ style: undefined
+};
+
+Title.propTypes = {
+ /**
+ * Aligns title to center
+ */
+ centered: PropTypes.bool,
+ /**
+ * Makes title bigger
+ */
+ hero: PropTypes.bool,
+ /**
+ * Any other external style defined in props will be applied
+ */
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
+};
+
+export default Title;
diff --git a/app/components/Nav/App/__snapshots__/index.test.js.snap b/app/components/Nav/App/__snapshots__/index.test.js.snap
index 2d67e59a81b..399463fa8a0 100644
--- a/app/components/Nav/App/__snapshots__/index.test.js.snap
+++ b/app/components/Nav/App/__snapshots__/index.test.js.snap
@@ -61,6 +61,20 @@ exports[`App should render correctly 1`] = `
"getScreenOptions": [Function],
"getStateForAction": [Function],
},
+ "FiatOnRamp": Object {
+ "childRouters": Object {
+ "PaymentMethodApplePay": null,
+ "PaymentMethodSelector": null,
+ "TransakFlow": null,
+ },
+ "getActionCreators": [Function],
+ "getActionForPathAndParams": [Function],
+ "getComponentForRouteName": [Function],
+ "getComponentForState": [Function],
+ "getPathAndParamsForState": [Function],
+ "getScreenOptions": [Function],
+ "getStateForAction": [Function],
+ },
"Home": Object {
"childRouters": Object {
"BrowserTabHome": Object {
@@ -516,6 +530,20 @@ exports[`App should render correctly 1`] = `
"getScreenOptions": [Function],
"getStateForAction": [Function],
},
+ "FiatOnRamp": Object {
+ "childRouters": Object {
+ "PaymentMethodApplePay": null,
+ "PaymentMethodSelector": null,
+ "TransakFlow": null,
+ },
+ "getActionCreators": [Function],
+ "getActionForPathAndParams": [Function],
+ "getComponentForRouteName": [Function],
+ "getComponentForState": [Function],
+ "getPathAndParamsForState": [Function],
+ "getScreenOptions": [Function],
+ "getStateForAction": [Function],
+ },
"Home": Object {
"childRouters": Object {
"BrowserTabHome": Object {
diff --git a/app/components/Nav/Main/__snapshots__/index.test.js.snap b/app/components/Nav/Main/__snapshots__/index.test.js.snap
index 77acb0ad3d9..eefb84996d2 100644
--- a/app/components/Nav/Main/__snapshots__/index.test.js.snap
+++ b/app/components/Nav/Main/__snapshots__/index.test.js.snap
@@ -69,6 +69,20 @@ exports[`Main should render correctly 1`] = `
"getScreenOptions": [Function],
"getStateForAction": [Function],
},
+ "FiatOnRamp": Object {
+ "childRouters": Object {
+ "PaymentMethodApplePay": null,
+ "PaymentMethodSelector": null,
+ "TransakFlow": null,
+ },
+ "getActionCreators": [Function],
+ "getActionForPathAndParams": [Function],
+ "getComponentForRouteName": [Function],
+ "getComponentForState": [Function],
+ "getPathAndParamsForState": [Function],
+ "getScreenOptions": [Function],
+ "getStateForAction": [Function],
+ },
"Home": Object {
"childRouters": Object {
"BrowserTabHome": Object {
@@ -400,6 +414,20 @@ exports[`Main should render correctly 1`] = `
"getScreenOptions": [Function],
"getStateForAction": [Function],
},
+ "FiatOnRamp": Object {
+ "childRouters": Object {
+ "PaymentMethodApplePay": null,
+ "PaymentMethodSelector": null,
+ "TransakFlow": null,
+ },
+ "getActionCreators": [Function],
+ "getActionForPathAndParams": [Function],
+ "getComponentForRouteName": [Function],
+ "getComponentForState": [Function],
+ "getPathAndParamsForState": [Function],
+ "getScreenOptions": [Function],
+ "getStateForAction": [Function],
+ },
"Home": Object {
"childRouters": Object {
"BrowserTabHome": Object {
diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js
index f8628997d75..a3749e25cef 100644
--- a/app/components/Nav/Main/index.js
+++ b/app/components/Nav/Main/index.js
@@ -31,7 +31,6 @@ import NetworkSettings from '../../Views/Settings/NetworksSettings/NetworkSettin
import AppInformation from '../../Views/Settings/AppInformation';
import Contacts from '../../Views/Settings/Contacts';
import Wallet from '../../Views/Wallet';
-import TransactionsView from '../../Views/TransactionsView';
import SyncWithExtension from '../../Views/SyncWithExtension';
import Asset from '../../Views/Asset';
import AddAsset from '../../Views/AddAsset';
@@ -96,6 +95,10 @@ import ContactForm from '../../Views/Settings/Contacts/ContactForm';
import TransactionTypes from '../../../core/TransactionTypes';
import BackupAlert from '../../UI/BackupAlert';
import Notification from '../../UI/Notification';
+import FiatOrders from '../../UI/FiatOrders';
+import PaymentMethodSelector from '../../UI/FiatOrders/PaymentMethodSelector';
+import PaymentMethodApplePay from '../../UI/FiatOrders/PaymentMethodApplePay';
+import TransakWebView from '../../UI/FiatOrders/TransakWebView';
import {
showTransactionNotification,
hideTransactionNotification,
@@ -103,6 +106,7 @@ import {
} from '../../../actions/notification';
import { toggleDappTransactionModal, toggleApproveModal } from '../../../actions/modals';
import AccountApproval from '../../UI/AccountApproval';
+import ActivityView from '../../Views/ActivityView';
import ProtectYourWalletModal from '../../UI/ProtectYourWalletModal';
const styles = StyleSheet.create({
@@ -170,7 +174,7 @@ const MainNavigator = createStackNavigator(
}),
TransactionsHome: createStackNavigator({
TransactionsView: {
- screen: TransactionsView
+ screen: ActivityView
}
}),
PaymentChannelHome: createStackNavigator({
@@ -339,6 +343,15 @@ const MainNavigator = createStackNavigator(
}
)
},
+
+ FiatOnRamp: {
+ screen: createStackNavigator({
+ PaymentMethodSelector: { screen: PaymentMethodSelector },
+ PaymentMethodApplePay: { screen: PaymentMethodApplePay },
+ TransakFlow: { screen: TransakWebView }
+ })
+ },
+
SetPasswordFlow: {
screen: createStackNavigator(
{
@@ -1097,6 +1110,7 @@ class Main extends PureComponent {
+
diff --git a/app/components/UI/FiatOrders/PaymentMethodApplePay/index.js b/app/components/UI/FiatOrders/PaymentMethodApplePay/index.js
new file mode 100644
index 00000000000..96eaa15752a
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodApplePay/index.js
@@ -0,0 +1,500 @@
+import React, { useContext, useState, useCallback } from 'react';
+import PropTypes from 'prop-types';
+import { View, StyleSheet, Image, TouchableOpacity, InteractionManager } from 'react-native';
+import { NavigationContext } from 'react-navigation';
+import { connect } from 'react-redux';
+import IonicIcon from 'react-native-vector-icons/Ionicons';
+import NotificationManager from '../../../../core/NotificationManager';
+import Device from '../../../../util/Device';
+import Logger from '../../../../util/Logger';
+import { setLockTime } from '../../../../actions/settings';
+import { strings } from '../../../../../locales/i18n';
+import { getNotificationDetails } from '..';
+
+import {
+ useWyreTerms,
+ useWyreRates,
+ useWyreApplePay,
+ WyreException,
+ WYRE_IS_PROMOTION,
+ WYRE_FEE_PERCENT
+} from '../orderProcessor/wyreApplePay';
+
+import ScreenView from '../components/ScreenView';
+import { getPaymentMethodApplePayNavbar } from '../../Navbar';
+import AccountBar from '../components/AccountBar';
+import Text from '../../../Base/Text';
+import StyledButton from '../../StyledButton';
+import { colors, fontStyles } from '../../../../styles/common';
+import { protectWalletModalVisible } from '../../../../actions/user';
+
+//* styles and components */
+
+const styles = StyleSheet.create({
+ screen: {
+ flexGrow: 1,
+ justifyContent: 'space-between'
+ },
+ amountContainer: {
+ margin: Device.isIphone5() ? 0 : 10,
+ padding: Device.isMediumDevice() ? (Device.isIphone5() ? 5 : 10) : 15,
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ amount: {
+ ...fontStyles.light,
+ color: colors.black,
+ fontSize: Device.isIphone5() ? 48 : 48,
+ height: Device.isIphone5() ? 50 : 60
+ },
+ amountError: {
+ color: colors.red
+ },
+ content: {
+ flexGrow: 1,
+ justifyContent: 'space-around'
+ },
+ quickAmounts: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-around',
+ marginHorizontal: 70
+ },
+ quickAmount: {
+ borderRadius: 18,
+ borderColor: colors.grey200,
+ borderWidth: 1,
+ paddingVertical: 5,
+ paddingHorizontal: 8,
+ alignItems: 'center',
+ minWidth: 49
+ },
+ quickAmountSelected: {
+ backgroundColor: colors.blue,
+ borderColor: colors.blue
+ },
+ quickAmountSelectedText: {
+ color: colors.white
+ },
+ keypad: {
+ paddingHorizontal: 25
+ },
+ keypadRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-around'
+ },
+ keypadButton: {
+ paddingHorizontal: 20,
+ paddingVertical: Device.isMediumDevice() ? (Device.isIphone5() ? 5 : 10) : 15,
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ keypadButtonText: {
+ color: colors.black,
+ textAlign: 'center',
+ fontSize: 30
+ },
+ deleteIcon: {
+ fontSize: 25,
+ marginTop: 5
+ },
+ buttonContainer: {
+ paddingBottom: 20
+ },
+ applePayButton: {
+ backgroundColor: colors.black,
+ padding: 10,
+ margin: Device.isIphone5() ? 5 : 10,
+ marginHorizontal: 25,
+ alignItems: 'center'
+ },
+ applePayButtonText: {
+ color: colors.white
+ },
+ applePayButtonContentDisabled: {
+ opacity: 0.6
+ },
+ applePayLogo: {
+ marginLeft: 4
+ }
+});
+
+/* eslint-disable import/no-commonjs */
+const ApplePayLogo = require('../../../../images/ApplePayLogo.png');
+const ApplePay = ({ disabled }) => (
+
+);
+
+ApplePay.propTypes = {
+ disabled: PropTypes.bool
+};
+
+const Keypad = props => ;
+Keypad.Row = function Row(props) {
+ return ;
+};
+Keypad.Button = function KeypadButton({ children, ...props }) {
+ return (
+
+ {children}
+
+ );
+};
+
+Keypad.Button.propTypes = {
+ children: PropTypes.node
+};
+
+Keypad.DeleteButton = function DeleteButton(props) {
+ return (
+
+
+
+ );
+};
+
+const QuickAmount = ({ amount, current, ...props }) => {
+ const selected = amount === current;
+ return (
+
+
+ ${amount}
+
+
+ );
+};
+
+QuickAmount.propTypes = {
+ amount: PropTypes.string,
+ current: PropTypes.string
+};
+
+//* Constants */
+
+const quickAmounts = ['50', '100', '250'];
+const minAmount = 5;
+const maxAmount = 250;
+
+const hasTwoDecimals = /^\d+\.\d{2}$/;
+const hasZeroAsFirstDecimal = /^\d+\.0$/;
+const hasZerosAsDecimals = /^\d+\.00$/;
+const hasOneDigit = /^\d$/;
+const hasPeriodWithoutDecimal = /^\d+\.$/;
+const avoidZerosAsDecimals = false;
+
+//* Handlers
+
+const handleNewAmountInput = (currentAmount, newInput) => {
+ switch (newInput) {
+ case 'PERIOD': {
+ if (currentAmount === '0') {
+ return `${currentAmount}.`;
+ }
+ if (currentAmount.includes('.')) {
+ // TODO: throw error for feedback?
+ return currentAmount;
+ }
+
+ return `${currentAmount}.`;
+ }
+ case 'BACK': {
+ if (currentAmount === '0') {
+ return currentAmount;
+ }
+ if (hasOneDigit.test(currentAmount)) {
+ return '0';
+ }
+
+ return currentAmount.slice(0, -1);
+ }
+ case '0': {
+ if (currentAmount === '0') {
+ return currentAmount;
+ }
+ if (hasTwoDecimals.test(currentAmount)) {
+ return currentAmount;
+ }
+ if (avoidZerosAsDecimals && hasZeroAsFirstDecimal.test(currentAmount)) {
+ return currentAmount;
+ }
+ return `${currentAmount}${newInput}`;
+ }
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9': {
+ if (currentAmount === '0') {
+ return newInput;
+ }
+ if (hasTwoDecimals.test(currentAmount)) {
+ return currentAmount;
+ }
+
+ return `${currentAmount}${newInput}`;
+ }
+ default: {
+ return currentAmount;
+ }
+ }
+};
+
+function PaymentMethodApplePay({
+ lockTime,
+ setLockTime,
+ selectedAddress,
+ network,
+ addOrder,
+ protectWalletModalVisible
+}) {
+ const navigation = useContext(NavigationContext);
+ const [amount, setAmount] = useState('0');
+ const roundAmount =
+ hasZerosAsDecimals.test(amount) || hasZeroAsFirstDecimal.test(amount) || hasPeriodWithoutDecimal.test(amount)
+ ? amount.split('.')[0]
+ : amount;
+ const isUnderMinimum = (amount !== '0' || Number(roundAmount) !== 0) && Number(roundAmount) < minAmount;
+
+ const isOverMaximum = Number(roundAmount) > maxAmount;
+ const disabledButton = amount === '0' || isUnderMinimum || isOverMaximum;
+
+ const handleWyreTerms = useWyreTerms(navigation);
+ const rates = useWyreRates(network, 'USDETH');
+ const [pay, ABORTED, percentFee, flatFee, , fee] = useWyreApplePay(roundAmount, selectedAddress, network);
+
+ const handlePressApplePay = useCallback(async () => {
+ const prevLockTime = lockTime;
+ setLockTime(-1);
+ try {
+ const order = await pay();
+ if (order !== ABORTED) {
+ if (order) {
+ addOrder(order);
+ navigation.dismiss();
+ protectWalletModalVisible();
+ InteractionManager.runAfterInteractions(() =>
+ NotificationManager.showSimpleNotification(getNotificationDetails(order))
+ );
+ } else {
+ Logger.error('FiatOrders::WyreApplePayProcessor empty order response', order);
+ }
+ }
+ } catch (error) {
+ NotificationManager.showSimpleNotification({
+ duration: 5000,
+ title: strings('fiat_on_ramp.notifications.purchase_failed_title', {
+ currency: 'ETH'
+ }),
+ description: `${error instanceof WyreException ? 'Wyre: ' : ''}${error.message}`,
+ status: 'error'
+ });
+ Logger.error(error, 'FiatOrders::WyreApplePayProcessor Error');
+ } finally {
+ setLockTime(prevLockTime);
+ }
+ }, [ABORTED, addOrder, lockTime, navigation, pay, setLockTime, protectWalletModalVisible]);
+
+ const handleQuickAmountPress = useCallback(amount => setAmount(amount), []);
+ const handleKeypadPress = useCallback(
+ newInput => {
+ if (isOverMaximum && newInput !== 'BACK') {
+ return;
+ }
+ const newAmount = handleNewAmountInput(amount, newInput);
+ if (newAmount === amount) {
+ return;
+ }
+
+ setAmount(newAmount);
+ },
+ [amount, isOverMaximum]
+ );
+ const handleKeypadPress1 = useCallback(() => handleKeypadPress('1'), [handleKeypadPress]);
+ const handleKeypadPress2 = useCallback(() => handleKeypadPress('2'), [handleKeypadPress]);
+ const handleKeypadPress3 = useCallback(() => handleKeypadPress('3'), [handleKeypadPress]);
+ const handleKeypadPress4 = useCallback(() => handleKeypadPress('4'), [handleKeypadPress]);
+ const handleKeypadPress5 = useCallback(() => handleKeypadPress('5'), [handleKeypadPress]);
+ const handleKeypadPress6 = useCallback(() => handleKeypadPress('6'), [handleKeypadPress]);
+ const handleKeypadPress7 = useCallback(() => handleKeypadPress('7'), [handleKeypadPress]);
+ const handleKeypadPress8 = useCallback(() => handleKeypadPress('8'), [handleKeypadPress]);
+ const handleKeypadPress9 = useCallback(() => handleKeypadPress('9'), [handleKeypadPress]);
+ const handleKeypadPress0 = useCallback(() => handleKeypadPress('0'), [handleKeypadPress]);
+ const handleKeypadPressPeriod = useCallback(() => handleKeypadPress('PERIOD'), [handleKeypadPress]);
+ const handleKeypadPressBack = useCallback(() => handleKeypadPress('BACK'), [handleKeypadPress]);
+
+ return (
+
+
+
+
+
+ ${amount}
+
+ {!(isUnderMinimum || isOverMaximum) &&
+ (rates ? (
+
+ {roundAmount === '0' ? (
+ `$${rates.USD.toFixed(2)} ≈ 1 ETH`
+ ) : (
+ <>
+ {strings('fiat_on_ramp.wyre_estimated', {
+ currency: 'ETH',
+ amount: (amount * rates.ETH).toFixed(5)
+ })}
+ >
+ )}
+
+ ) : (
+ {strings('fiat_on_ramp.wyre_loading_rates')}
+ ))}
+ {isUnderMinimum && (
+ {strings('fiat_on_ramp.wyre_minimum_deposit', { amount: `$${minAmount}` })}
+ )}
+ {isOverMaximum && (
+
+ {strings('fiat_on_ramp.wyre_maximum_deposit', { amount: `$${maxAmount}` })}
+
+ )}
+
+ {quickAmounts.length > 0 && (
+
+ {quickAmounts.map(quickAmount => (
+ handleQuickAmountPress(quickAmount)}
+ />
+ ))}
+
+ )}
+
+
+
+
+ 1
+ 2
+ 3
+
+
+ 4
+ 5
+ 6
+
+
+ 7
+ 8
+ 9
+
+
+ .
+ 0
+
+
+
+
+
+
+
+ {strings('fiat_on_ramp.buy_with')}
+
+
+
+
+ {WYRE_IS_PROMOTION && (
+
+ {WYRE_FEE_PERCENT}% {strings('fiat_on_ramp.fee')} (
+ {strings('fiat_on_ramp.limited_time')})
+
+ )}
+ {!WYRE_IS_PROMOTION && (
+ <>
+ {disabledButton ? (
+
+
+ {strings('fiat_on_ramp.Fee')} ~{percentFee}% + ${flatFee}
+
+
+ ) : (
+
+ {strings('fiat_on_ramp.plus_fee', { fee: `$${fee}` })}
+
+ )}
+ >
+ )}
+
+
+
+ {strings('fiat_on_ramp.wyre_terms_of_service')}
+
+
+
+
+
+ );
+}
+
+PaymentMethodApplePay.propTypes = {
+ /**
+ * Current time to lock screen set in settings
+ */
+ lockTime: PropTypes.number.isRequired,
+ /**
+ * Function to change lock screen time setting
+ */
+ setLockTime: PropTypes.func.isRequired,
+ /**
+ * Currently selected wallet address
+ */
+ selectedAddress: PropTypes.string.isRequired,
+ /**
+ * Currently selected network
+ */
+ network: PropTypes.string.isRequired,
+ /**
+ * Function to dispatch adding a new fiat order to the state
+ */
+ addOrder: PropTypes.func.isRequired,
+ /**
+ * Prompts protect wallet modal
+ */
+ protectWalletModalVisible: PropTypes.func
+};
+
+PaymentMethodApplePay.navigationOptions = ({ navigation }) => getPaymentMethodApplePayNavbar(navigation);
+
+const mapStateToProps = state => ({
+ lockTime: state.settings.lockTime,
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ network: state.engine.backgroundState.NetworkController.network
+});
+
+const mapDispatchToProps = dispatch => ({
+ setLockTime: time => dispatch(setLockTime(time)),
+ addOrder: order => dispatch({ type: 'FIAT_ADD_ORDER', payload: order }),
+ protectWalletModalVisible: () => dispatch(protectWalletModalVisible())
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(PaymentMethodApplePay);
diff --git a/app/components/UI/FiatOrders/PaymentMethodSelector/index.android.js b/app/components/UI/FiatOrders/PaymentMethodSelector/index.android.js
new file mode 100644
index 00000000000..dd8e8d5d90c
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodSelector/index.android.js
@@ -0,0 +1,49 @@
+import React, { useContext, useCallback } from 'react';
+import { InteractionManager } from 'react-native';
+import PropTypes from 'prop-types';
+import { NavigationContext } from 'react-navigation';
+import { connect } from 'react-redux';
+import { strings } from '../../../../../locales/i18n';
+import Analytics from '../../../../core/Analytics';
+import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
+
+import { useTransakFlowURL } from '../orderProcessor/transak';
+import { getPaymentSelectorMethodNavbar } from '../../Navbar';
+import ScreenView from '../components/ScreenView';
+import Title from '../components/Title';
+
+import TransakPaymentMethod from './transak';
+
+function PaymentMethodSelectorView({ selectedAddress, ...props }) {
+ const navigation = useContext(NavigationContext);
+ const transakURL = useTransakFlowURL(selectedAddress);
+
+ const onPressTransak = useCallback(() => {
+ navigation.navigate('TransakFlow', {
+ url: transakURL,
+ title: strings('fiat_on_ramp.transak_webview_title')
+ });
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.PAYMENTS_SELECTS_DEBIT_OR_ACH);
+ });
+ }, [navigation, transakURL]);
+
+ return (
+
+
+
+
+ );
+}
+
+PaymentMethodSelectorView.propTypes = {
+ selectedAddress: PropTypes.string.isRequired
+};
+
+PaymentMethodSelectorView.navigationOptions = ({ navigation }) => getPaymentSelectorMethodNavbar(navigation);
+
+const mapStateToProps = state => ({
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress
+});
+
+export default connect(mapStateToProps)(PaymentMethodSelectorView);
diff --git a/app/components/UI/FiatOrders/PaymentMethodSelector/index.ios.js b/app/components/UI/FiatOrders/PaymentMethodSelector/index.ios.js
new file mode 100644
index 00000000000..3f2c2d98aa1
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodSelector/index.ios.js
@@ -0,0 +1,86 @@
+import React, { useContext, useCallback } from 'react';
+import { InteractionManager } from 'react-native';
+import PropTypes from 'prop-types';
+import { NavigationContext } from 'react-navigation';
+import { connect } from 'react-redux';
+import { strings } from '../../../../../locales/i18n';
+import Analytics from '../../../../core/Analytics';
+import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
+
+import { useTransakFlowURL } from '../orderProcessor/transak';
+import { WYRE_IS_PROMOTION } from '../orderProcessor/wyreApplePay';
+import { getPaymentSelectorMethodNavbar } from '../../Navbar';
+
+import ScreenView from '../components/ScreenView';
+import Heading from '../components/Heading';
+
+import Text from '../../../Base/Text';
+import Title from '../components/Title';
+import SubHeader from '../components/SubHeader';
+
+import TransakPaymentMethod from './transak';
+import WyreApplePayPaymentMethod from './wyreApplePay';
+
+function PaymentMethodSelectorView({ selectedAddress, network, ...props }) {
+ const navigation = useContext(NavigationContext);
+ const transakURL = useTransakFlowURL(selectedAddress);
+
+ const onPressWyreApplePay = useCallback(() => {
+ navigation.navigate('PaymentMethodApplePay');
+
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.PAYMENTS_SELECTS_APPLE_PAY);
+ });
+ }, [navigation]);
+ const onPressTransak = useCallback(() => {
+ navigation.navigate('TransakFlow', {
+ url: transakURL,
+ title: strings('fiat_on_ramp.transak_webview_title')
+ });
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.PAYMENTS_SELECTS_DEBIT_OR_ACH);
+ });
+ }, [navigation, transakURL]);
+
+ return (
+
+
+
+ {WYRE_IS_PROMOTION ? (
+ <>
+ {strings('fiat_on_ramp.purchase_method_title.wyre_first_line')}
+ {'\n'}
+ {strings('fiat_on_ramp.purchase_method_title.wyre_second_line')}
+ >
+ ) : (
+ <>
+ {strings('fiat_on_ramp.purchase_method_title.first_line')}
+ {'\n'}
+ {strings('fiat_on_ramp.purchase_method_title.second_line')}
+ >
+ )}
+
+ {WYRE_IS_PROMOTION && (
+ {strings('fiat_on_ramp.purchase_method_title.wyre_sub_header')}
+ )}
+
+
+
+ {network === '1' && }
+
+ );
+}
+
+PaymentMethodSelectorView.propTypes = {
+ selectedAddress: PropTypes.string.isRequired,
+ network: PropTypes.string.isRequired
+};
+
+PaymentMethodSelectorView.navigationOptions = ({ navigation }) => getPaymentSelectorMethodNavbar(navigation);
+
+const mapStateToProps = state => ({
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ network: state.engine.backgroundState.NetworkController.network
+});
+
+export default connect(mapStateToProps)(PaymentMethodSelectorView);
diff --git a/app/components/UI/FiatOrders/PaymentMethodSelector/index.js b/app/components/UI/FiatOrders/PaymentMethodSelector/index.js
new file mode 100644
index 00000000000..99f858cbe76
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodSelector/index.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line import/no-unresolved
+import PaymentMethodSelector from './PaymentMethodSelector';
+
+export default PaymentMethodSelector;
diff --git a/app/components/UI/FiatOrders/PaymentMethodSelector/transak.js b/app/components/UI/FiatOrders/PaymentMethodSelector/transak.js
new file mode 100644
index 00000000000..50b96ead956
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodSelector/transak.js
@@ -0,0 +1,128 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { TouchableOpacity, Image, StyleSheet, ScrollView, View } from 'react-native';
+import { strings } from '../../../../../locales/i18n';
+
+import PaymentMethod from '../components/PaymentMethod';
+import Title from '../../../Base/Title';
+import Text from '../../../Base/Text';
+import ModalHandler from '../../../Base/ModalHandler';
+import StyledButton from '../../StyledButton';
+import Device from '../../../../util/Device';
+
+const style = StyleSheet.create({
+ logo: {
+ marginVertical: 5,
+ aspectRatio: 95 / 25,
+ width: Device.isIphone5() ? 80 : 95,
+ height: Device.isIphone5() ? 20 : 25
+ },
+ cta: {
+ marginTop: 25,
+ marginBottom: 5
+ },
+ countryList: {
+ flexDirection: 'row'
+ },
+ countryCol: {
+ width: '50%'
+ }
+});
+
+// eslint-disable-next-line import/no-commonjs
+const TransakLogoIcon = require('../../../../images/TransakLogo.png');
+
+const TransakLogo = () => ;
+
+const TransakPaymentMethod = ({ onPress }) => (
+
+
+
+ {strings('fiat_on_ramp.bank_transfer_debit')}
+ {strings('fiat_on_ramp.requires_registration')}
+ {strings('fiat_on_ramp.options_fees_vary')}
+
+
+
+
+ {({ isVisible, toggleModal }) => (
+ <>
+
+
+
+ 33 {strings('fiat_on_ramp.countries')}
+
+
+
+
+
+ {strings('fiat_on_ramp.transak_modal_text')}
+
+
+
+ Austria 🇦🇹
+ Australia 🇦🇺
+ Belgium 🇧🇪
+ Canada 🇨🇦
+ Cyprus 🇨🇾
+ Czechia 🇨🇿
+ Denmark 🇩🇰
+ Estonia 🇪🇪
+ Finland 🇫🇮
+ France 🇫🇷
+ Germany 🇩🇪
+ Greece 🇬🇷
+ Hong Kong 🇭🇰
+ Ireland 🇮🇪
+ Italy 🇮🇹
+ India 🇮🇳
+ Latvia 🇱🇻
+
+
+ Luxembourg 🇱🇺
+ Malta 🇲🇹
+ Mexico 🇲🇽
+ Romania 🇷🇴
+ Netherlands 🇳🇱
+ New Zealand 🇳🇿
+ Norway 🇳🇴
+ Poland 🇵🇱
+ Portugal 🇵🇹
+ Slovakia 🇸🇰
+ Slovenia 🇸🇮
+ Spain 🇪🇸
+ Sweden 🇸🇪
+ Switzerland 🇨🇭
+ United Kingdom 🇬🇧
+ USA 🇺🇸
+
+
+
+
+ >
+ )}
+
+
+
+ {Device.isAndroid() && (
+
+
+ {strings('fiat_on_ramp.transak_cta')}
+
+
+ )}
+
+);
+
+TransakPaymentMethod.propTypes = {
+ onPress: PropTypes.func
+};
+TransakPaymentMethod.defaultProps = {
+ onPress: undefined
+};
+
+export default TransakPaymentMethod;
diff --git a/app/components/UI/FiatOrders/PaymentMethodSelector/wyreApplePay.js b/app/components/UI/FiatOrders/PaymentMethodSelector/wyreApplePay.js
new file mode 100644
index 00000000000..5b8159d3cad
--- /dev/null
+++ b/app/components/UI/FiatOrders/PaymentMethodSelector/wyreApplePay.js
@@ -0,0 +1,124 @@
+import React, { useContext } from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet, Image, TouchableOpacity } from 'react-native';
+import { NavigationContext } from 'react-navigation';
+import { strings } from '../../../../../locales/i18n';
+import {
+ useWyreTerms,
+ WYRE_IS_PROMOTION,
+ WYRE_FEE_PERCENT,
+ WYRE_FEE_FLAT,
+ WYRE_REGULAR_FEE_PERCENT,
+ WYRE_REGULAR_FEE_FLAT
+} from '../orderProcessor/wyreApplePay';
+
+import PaymentMethod from '../components/PaymentMethod';
+
+import ModalHandler from '../../../Base/ModalHandler';
+import Text from '../../../Base/Text';
+import Title from '../components/Title';
+
+const logosStyle = StyleSheet.create({
+ applePay: {
+ marginVertical: 3
+ }
+});
+
+/* eslint-disable import/no-commonjs */
+const ApplePayMarkIcon = require('../../../../images/ApplePayMark.png');
+const WyreLogoIcon = require('../../../../images/WyreLogo.png');
+/* eslint-enable import/no-commonjs */
+
+const ApplePayMark = () => ;
+const WyreLogo = () => ;
+
+const WyreApplePayPaymentMethod = ({ onPress }) => {
+ const navigation = useContext(NavigationContext);
+ const handleWyreTerms = useWyreTerms(navigation);
+
+ return (
+
+ {strings('fiat_on_ramp.best_deal')}
+
+
+
+ {strings('fiat_on_ramp.apple_pay')} {strings('fiat_on_ramp.via')}{' '}
+
+
+
+ {WYRE_IS_PROMOTION ? (
+ <>
+
+ ${WYRE_REGULAR_FEE_PERCENT.toFixed(2)} + ${WYRE_REGULAR_FEE_FLAT.toFixed(2)}
+ {' '}
+
+ {WYRE_FEE_PERCENT}% {strings('fiat_on_ramp.fee')}
+
+ {'\n'}
+ {strings('fiat_on_ramp.limited_time')}
+ >
+ ) : (
+
+ {strings('fiat_on_ramp.Fee')} ~{WYRE_FEE_PERCENT.toFixed(2)}% + $
+ {WYRE_FEE_FLAT.toFixed(2)}
+
+ )}
+
+ {strings('fiat_on_ramp.wyre_minutes')}
+ {strings('fiat_on_ramp.wyre_max')}
+ {strings('fiat_on_ramp.wyre_requires_debit_card')}
+
+
+
+
+ {({ isVisible, toggleModal }) => (
+ <>
+
+
+
+ {strings('fiat_on_ramp.wyre_us_only')}
+
+
+
+
+
+ {strings('fiat_on_ramp.some_states_excluded')}
+
+
+
+
+
+ {strings('fiat_on_ramp.wyre_modal_text')}{' '}
+ {
+ toggleModal();
+ handleWyreTerms();
+ }}
+ >
+ {strings('fiat_on_ramp.wyre_modal_terms_of_service_apply')}
+
+
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+WyreApplePayPaymentMethod.propTypes = {
+ onPress: PropTypes.func
+};
+WyreApplePayPaymentMethod.defaultProps = {
+ onPress: undefined
+};
+export default WyreApplePayPaymentMethod;
diff --git a/app/components/UI/FiatOrders/TransakWebView/index.js b/app/components/UI/FiatOrders/TransakWebView/index.js
new file mode 100644
index 00000000000..74a8500c2cc
--- /dev/null
+++ b/app/components/UI/FiatOrders/TransakWebView/index.js
@@ -0,0 +1,70 @@
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { View, InteractionManager } from 'react-native';
+import { connect } from 'react-redux';
+import { WebView } from 'react-native-webview';
+import NotificationManager from '../../../../core/NotificationManager';
+import { handleTransakRedirect } from '../orderProcessor/transak';
+import AppConstants from '../../../../core/AppConstants';
+import { getNotificationDetails } from '..';
+
+import { getTransakWebviewNavbar } from '../../../UI/Navbar';
+import { baseStyles } from '../../../../styles/common';
+import { protectWalletModalVisible } from '../../../../actions/user';
+
+class TransakWebView extends PureComponent {
+ static navigationOptions = ({ navigation }) => getTransakWebviewNavbar(navigation);
+
+ static propTypes = {
+ navigation: PropTypes.object,
+ /**
+ * Currently selected network
+ */
+ network: PropTypes.string,
+ /**
+ * Function to dispatch adding a new fiat order to the state
+ */
+ addOrder: PropTypes.func,
+ /**
+ * Prompts protect wallet modal
+ */
+ protectWalletModalVisible: PropTypes.func
+ };
+
+ handleNavigationStateChange = async navState => {
+ if (navState.url.indexOf(AppConstants.FIAT_ORDERS.TRANSAK_REDIRECT_URL) > -1) {
+ const order = handleTransakRedirect(navState.url, this.props.network);
+ this.props.addOrder(order);
+ this.props.protectWalletModalVisible();
+ this.props.navigation.dismiss();
+ InteractionManager.runAfterInteractions(() =>
+ NotificationManager.showSimpleNotification(getNotificationDetails(order))
+ );
+ }
+ };
+
+ render() {
+ const uri = this.props.navigation.getParam('url', null);
+ if (uri) {
+ return (
+
+
+
+ );
+ }
+ }
+}
+
+const mapStateToProps = state => ({
+ network: state.engine.backgroundState.NetworkController.network
+});
+
+const mapDispatchToProps = dispatch => ({
+ addOrder: order => dispatch({ type: 'FIAT_ADD_ORDER', payload: order }),
+ protectWalletModalVisible: () => dispatch(protectWalletModalVisible())
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(TransakWebView);
diff --git a/app/components/UI/FiatOrders/components/AccountBar.js b/app/components/UI/FiatOrders/components/AccountBar.js
new file mode 100644
index 00000000000..e1f5557bd7c
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/AccountBar.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, TouchableOpacity, StyleSheet } from 'react-native';
+import Icon from 'react-native-vector-icons/FontAwesome';
+import { connect } from 'react-redux';
+import { strings } from '../../../../../locales/i18n';
+
+import { colors, fontStyles } from '../../../../styles/common';
+import { toggleAccountsModal } from '../../../../actions/modals';
+import EthereumAddress from '../../EthereumAddress';
+import Identicon from '../../Identicon';
+import Text from '../../../Base/Text';
+
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: colors.grey000,
+ padding: 15,
+ alignItems: 'center'
+ },
+ addressContainer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center'
+ },
+ depositingText: {
+ ...fontStyles.thin,
+ color: colors.fontPrimary
+ },
+ accountText: {
+ ...fontStyles.bold,
+ color: colors.fontPrimary,
+ marginVertical: 3,
+ marginHorizontal: 5
+ },
+ caretDown: {
+ textAlign: 'right',
+ color: colors.grey600
+ }
+});
+const AccountBar = ({ toggleAccountsModal, selectedAddress, identities }) => (
+
+ <>
+
+ {strings('account_bar.depositing_to')}
+
+
+
+
+ {identities[selectedAddress]?.name} ()
+
+
+
+ >
+
+);
+
+AccountBar.propTypes = {
+ toggleAccountsModal: PropTypes.func.isRequired,
+ selectedAddress: PropTypes.string.isRequired,
+ identities: PropTypes.object.isRequired
+};
+
+const mapStateToProps = state => ({
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ identities: state.engine.backgroundState.PreferencesController.identities
+});
+
+const mapDispatchToProps = dispatch => ({
+ toggleAccountsModal: () => dispatch(toggleAccountsModal())
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(AccountBar);
diff --git a/app/components/UI/FiatOrders/components/Heading.js b/app/components/UI/FiatOrders/components/Heading.js
new file mode 100644
index 00000000000..393b3c765f0
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/Heading.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { View, StyleSheet } from 'react-native';
+import Device from '../../../../util/Device';
+
+const style = StyleSheet.create({
+ view: {
+ margin: Device.isIphone5() ? 20 : 30
+ }
+});
+
+const Heading = ({ ...props }) => ;
+
+export default Heading;
diff --git a/app/components/UI/FiatOrders/components/InfoIcon.js b/app/components/UI/FiatOrders/components/InfoIcon.js
new file mode 100644
index 00000000000..12f34980575
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/InfoIcon.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import { StyleSheet } from 'react-native';
+import IonicIcon from 'react-native-vector-icons/Ionicons';
+import Device from '../../../../util/Device';
+import { colors } from '../../../../styles/common';
+
+const styles = StyleSheet.create({
+ icon: {
+ color: colors.grey200
+ }
+});
+
+const InfoIcon = props => (
+
+);
+
+export default InfoIcon;
diff --git a/app/components/UI/FiatOrders/components/PaymentMethod/Modal.js b/app/components/UI/FiatOrders/components/PaymentMethod/Modal.js
new file mode 100644
index 00000000000..558bb06414e
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/PaymentMethod/Modal.js
@@ -0,0 +1,109 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet, View, TouchableOpacity, ScrollView } from 'react-native';
+import { SafeAreaView } from 'react-navigation';
+import Modal from 'react-native-modal';
+import IonicIcon from 'react-native-vector-icons/Ionicons';
+import { strings } from '../../../../../../locales/i18n';
+
+import Title from '../Title';
+import { colors } from '../../../../../styles/common';
+import StyledButton from '../../../StyledButton';
+
+const styles = StyleSheet.create({
+ modalView: {
+ backgroundColor: colors.white,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginVertical: 50,
+ borderRadius: 10,
+ shadowColor: colors.black,
+ shadowOffset: {
+ width: 0,
+ height: 5
+ },
+ shadowOpacity: 0.36,
+ shadowRadius: 6.68,
+ elevation: 11
+ },
+ modal: {
+ margin: 0,
+ width: '100%',
+ padding: 25
+ },
+ title: {
+ width: '100%',
+ paddingVertical: 15,
+ paddingHorizontal: 20,
+ paddingBottom: 5,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between'
+ },
+ closeIcon: {
+ color: colors.black
+ },
+ body: {
+ width: '100%',
+ paddingVertical: 5,
+ paddingHorizontal: 20
+ },
+ action: {
+ width: '100%',
+ alignItems: 'center',
+ marginTop: 15,
+ paddingVertical: 15,
+ paddingHorizontal: 20,
+ borderTopWidth: 1,
+ borderTopColor: colors.grey100
+ },
+ button: {
+ width: '50%'
+ }
+});
+
+const CloseIcon = props => ;
+
+const PaymentMethodModal = ({ isVisible, title, dismiss, children }) => (
+
+
+
+ {title}
+
+
+
+
+
+ true}>{children}
+
+
+
+ {strings('fiat_on_ramp.purchase_method_modal_close')}
+
+
+
+
+);
+
+PaymentMethodModal.propTypes = {
+ isVisible: PropTypes.bool,
+ title: PropTypes.string.isRequired,
+ dismiss: PropTypes.func,
+ children: PropTypes.node
+};
+
+PaymentMethodModal.defaultProps = {
+ isVisible: false,
+ dismiss: undefined,
+ children: undefined
+};
+
+export default PaymentMethodModal;
diff --git a/app/components/UI/FiatOrders/components/PaymentMethod/index.js b/app/components/UI/FiatOrders/components/PaymentMethod/index.js
new file mode 100644
index 00000000000..46782c0e7b3
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/PaymentMethod/index.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { TouchableOpacity, View, StyleSheet } from 'react-native';
+import { colors, fontStyles } from '../../../../../styles/common';
+
+import Text from '../../../../Base/Text';
+import InfoIcon from '../InfoIcon';
+import Modal from './Modal';
+
+const style = StyleSheet.create({
+ container: {
+ borderWidth: 1,
+ borderRadius: 8,
+ borderColor: colors.blue,
+ paddingVertical: 15,
+ paddingHorizontal: 20,
+ marginHorizontal: 25,
+ marginVertical: 12
+ },
+ content: {
+ flexDirection: 'row'
+ },
+ badgeWrapper: {
+ position: 'absolute',
+ alignItems: 'center',
+ top: -14,
+ left: 0,
+ right: 0
+ },
+ badge: {
+ fontSize: 12,
+ paddingVertical: 4,
+ paddingHorizontal: 8,
+ backgroundColor: colors.blue,
+ color: colors.white,
+ margin: 0,
+ borderRadius: 12,
+ overflow: 'hidden',
+ ...fontStyles.bold
+ },
+ details: {
+ flex: 2
+ },
+ terms: {
+ flex: 1,
+ alignItems: 'flex-end',
+ justifyContent: 'space-between',
+ marginLeft: 20
+ },
+ infoIconLine: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: 'flex-end'
+ },
+ infoIcon: {
+ marginLeft: 2
+ }
+});
+
+const PaymentMethod = ({ onPress, ...props }) => (
+
+);
+
+PaymentMethod.propTypes = {
+ onPress: PropTypes.func,
+ children: PropTypes.node
+};
+
+PaymentMethod.defaultProps = {
+ onPress: undefined,
+ children: undefined
+};
+
+const Badge = props => (
+
+
+
+);
+
+const Content = props => ;
+const Details = props => ;
+const Terms = props => ;
+const InfoIconLine = props => ;
+
+const PaymentMethodInfoIcon = props => (
+
+
+
+);
+
+PaymentMethod.Badge = Badge;
+PaymentMethod.Content = Content;
+PaymentMethod.Details = Details;
+PaymentMethod.Terms = Terms;
+PaymentMethod.InfoIconLine = InfoIconLine;
+PaymentMethod.InfoIcon = PaymentMethodInfoIcon;
+PaymentMethod.Modal = Modal;
+export default PaymentMethod;
diff --git a/app/components/UI/FiatOrders/components/ScreenView.js b/app/components/UI/FiatOrders/components/ScreenView.js
new file mode 100644
index 00000000000..131b716ea7b
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/ScreenView.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { SafeAreaView, StyleSheet, ScrollView } from 'react-native';
+import { colors } from '../../../../styles/common';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ backgroundColor: colors.white,
+ flex: 1
+ }
+});
+
+const ScreenView = props => (
+
+
+
+);
+
+export default ScreenView;
diff --git a/app/components/UI/FiatOrders/components/SubHeader.js b/app/components/UI/FiatOrders/components/SubHeader.js
new file mode 100644
index 00000000000..532b19f32d7
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/SubHeader.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet } from 'react-native';
+import Text from '../../../Base/Text';
+
+const style = StyleSheet.create({
+ subHeader: {
+ margin: 5
+ }
+});
+
+const SubHeader = ({ style: externalStyle, ...props }) => ;
+
+SubHeader.defaultProps = {
+ style: undefined
+};
+SubHeader.propTypes = {
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
+};
+
+export default SubHeader;
diff --git a/app/components/UI/FiatOrders/components/Title.js b/app/components/UI/FiatOrders/components/Title.js
new file mode 100644
index 00000000000..ce54b4a5f39
--- /dev/null
+++ b/app/components/UI/FiatOrders/components/Title.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet } from 'react-native';
+import BaseTitle from '../../../Base/Title';
+
+const style = StyleSheet.create({
+ hero: {
+ margin: 5
+ }
+});
+
+const Title = ({ style: externalStyle, ...props }) => (
+
+);
+
+Title.defaultProps = {
+ hero: false,
+ style: undefined
+};
+
+Title.propTypes = {
+ hero: PropTypes.bool,
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
+};
+
+export default Title;
diff --git a/app/components/UI/FiatOrders/hooks/useInterval.js b/app/components/UI/FiatOrders/hooks/useInterval.js
new file mode 100644
index 00000000000..1f7c2cb475b
--- /dev/null
+++ b/app/components/UI/FiatOrders/hooks/useInterval.js
@@ -0,0 +1,24 @@
+import { useRef, useEffect } from 'react';
+
+function useInterval(callback, delay) {
+ const savedCallback = useRef();
+
+ // Remember the latest function.
+ useEffect(() => {
+ savedCallback.current = callback;
+ }, [callback]);
+
+ // Set up the interval.
+ useEffect(() => {
+ function tick() {
+ savedCallback.current();
+ }
+ if (delay !== null) {
+ const id = setInterval(tick, delay);
+
+ return () => clearInterval(id);
+ }
+ }, [delay]);
+}
+
+export default useInterval;
diff --git a/app/components/UI/FiatOrders/index.js b/app/components/UI/FiatOrders/index.js
new file mode 100644
index 00000000000..171f066732b
--- /dev/null
+++ b/app/components/UI/FiatOrders/index.js
@@ -0,0 +1,112 @@
+import PropTypes from 'prop-types';
+import { InteractionManager } from 'react-native';
+import { connect } from 'react-redux';
+import Device from '../../../util/Device';
+import AppConstants from '../../../core/AppConstants';
+import NotificationManager from '../../../core/NotificationManager';
+import { strings } from '../../../../locales/i18n';
+import { renderNumber } from '../../../util/number';
+
+import { FIAT_ORDER_STATES, getPendingOrders } from '../../../reducers/fiatOrders';
+import useInterval from './hooks/useInterval';
+import processOrder from './orderProcessor';
+
+/**
+ * @typedef {import('../../../reducers/fiatOrders').FiatOrder} FiatOrder
+ */
+
+const POLLING_FREQUENCY = AppConstants.FIAT_ORDERS.POLLING_FREQUENCY;
+const NOTIFICATION_DURATION = 5000;
+
+export const allowedToBuy = network => network === '1' || (network === '42' && Device.isIos());
+
+const baseNotificationDetails = {
+ duration: NOTIFICATION_DURATION
+};
+
+/**
+ * @param {FiatOrder} fiatOrder
+ */
+export const getNotificationDetails = fiatOrder => {
+ switch (fiatOrder.state) {
+ case FIAT_ORDER_STATES.FAILED: {
+ return {
+ ...baseNotificationDetails,
+ title: strings('fiat_on_ramp.notifications.purchase_failed_title', {
+ currency: fiatOrder.cryptocurrency
+ }),
+ status: 'error'
+ };
+ }
+ case FIAT_ORDER_STATES.CANCELLED: {
+ return {
+ ...baseNotificationDetails,
+ title: strings('fiat_on_ramp.notifications.purchase_cancelled_title'),
+ status: 'cancelled'
+ };
+ }
+ case FIAT_ORDER_STATES.COMPLETED: {
+ return {
+ ...baseNotificationDetails,
+ title: strings('fiat_on_ramp.notifications.purchase_completed_title', {
+ amount: renderNumber(String(fiatOrder.cryptoAmount)),
+ currency: fiatOrder.cryptocurrency
+ }),
+ description: strings('fiat_on_ramp.notifications.purchase_completed_description', {
+ currency: fiatOrder.cryptocurrency
+ }),
+ status: 'success'
+ };
+ }
+ case FIAT_ORDER_STATES.PENDING:
+ default: {
+ return {
+ ...baseNotificationDetails,
+ title: strings('fiat_on_ramp.notifications.purchase_pending_title', {
+ currency: fiatOrder.cryptocurrency
+ }),
+ description: strings('fiat_on_ramp.notifications.purchase_pending_description'),
+ status: 'pending'
+ };
+ }
+ }
+};
+
+function FiatOrders({ pendingOrders, dispatch }) {
+ useInterval(
+ async () => {
+ await Promise.all(
+ pendingOrders.map(async order => {
+ const updatedOrder = await processOrder(order);
+ dispatch({ type: 'FIAT_UPDATE_ORDER', payload: updatedOrder });
+ if (updatedOrder.state !== order.state) {
+ InteractionManager.runAfterInteractions(() =>
+ NotificationManager.showSimpleNotification(getNotificationDetails(updatedOrder))
+ );
+ }
+ })
+ );
+ },
+ pendingOrders.length ? POLLING_FREQUENCY : null
+ );
+
+ return null;
+}
+
+FiatOrders.propTypes = {
+ orders: PropTypes.array,
+ selectedAddress: PropTypes.string,
+ network: PropTypes.string,
+ dispatch: PropTypes.func
+};
+
+const mapStateToProps = state => {
+ const orders = state.fiatOrders.orders;
+ const selectedAddress = state.engine.backgroundState.PreferencesController.selectedAddress;
+ const network = state.engine.backgroundState.NetworkController.network;
+ return {
+ pendingOrders: getPendingOrders(orders, selectedAddress, network)
+ };
+};
+
+export default connect(mapStateToProps)(FiatOrders);
diff --git a/app/components/UI/FiatOrders/orderProcessor/index.js b/app/components/UI/FiatOrders/orderProcessor/index.js
new file mode 100644
index 00000000000..86c85b7435f
--- /dev/null
+++ b/app/components/UI/FiatOrders/orderProcessor/index.js
@@ -0,0 +1,21 @@
+import { FIAT_ORDER_PROVIDERS } from '../../../../reducers/fiatOrders';
+import { processWyreApplePayOrder } from './wyreApplePay';
+import { processTransakOrder } from './transak';
+import Logger from '../../../../util/Logger';
+
+function processOrder(order) {
+ switch (order.provider) {
+ case FIAT_ORDER_PROVIDERS.WYRE_APPLE_PAY: {
+ return processWyreApplePayOrder(order);
+ }
+ case FIAT_ORDER_PROVIDERS.TRANSAK: {
+ return processTransakOrder(order);
+ }
+ default: {
+ Logger.error('FiatOrders::ProcessOrder unrecognized provider', order);
+ return order;
+ }
+ }
+}
+
+export default processOrder;
diff --git a/app/components/UI/FiatOrders/orderProcessor/transak.js b/app/components/UI/FiatOrders/orderProcessor/transak.js
new file mode 100644
index 00000000000..0f3d54aad7e
--- /dev/null
+++ b/app/components/UI/FiatOrders/orderProcessor/transak.js
@@ -0,0 +1,243 @@
+import { useMemo } from 'react';
+import axios from 'axios';
+import qs from 'query-string';
+import AppConstants from '../../../../core/AppConstants';
+import { FIAT_ORDER_PROVIDERS, FIAT_ORDER_STATES } from '../../../../reducers/fiatOrders';
+import Logger from '../../../../util/Logger';
+
+//* env vars
+
+const TRANSAK_API_KEY_STAGING = process.env.TRANSAK_API_KEY_STAGING;
+const TRANSAK_API_KEY_SECRET_STAGING = process.env.TRANSAK_API_KEY_SECRET_STAGING;
+const TRANSAK_API_KEY_PRODUCTION = process.env.TRANSAK_API_KEY_PRODUCTION;
+const TRANSAK_API_KEY_SECRET_PRODUCTION = process.env.TRANSAK_API_KEY_SECRET_PRODUCTION;
+
+//* typedefs
+
+/**
+ * @typedef {import('../../../../reducers/fiatOrders').FiatOrder} FiatOrder
+ */
+
+/**
+ * @typedef TransakOrder
+ * @type {object}
+ * @property {string} id
+ * @property {string} createdAt
+ * @property {string} updatedAt
+ * @property {string} completedAt
+ * @property {string} fiatCurrency
+ * @property {string} cryptocurrency
+ * @property {number} fiatAmount
+ * @property {string} walletLink
+ * @property {string} paymentOptionId Paymenth method ID, see: https://integrate.transak.com/Coverage-Payment-Methods-Fees-Limits-30c0954fbdf04beca68622d9734c59f9
+ * @property {boolean} addressAdditionalData
+ * @property {string} network this is NOT ethernet networks id
+ * @property {string} amountPaid
+ * @property {number} referenceCode
+ * @property {string} redirectURL Our redirect URL
+ * @property {number} conversionPrice
+ * @property {number} cryptoAmount
+ * @property {number} totalFeeInCrypto
+ * @property {number} totalFeeInFiat
+ * @property {array} paymentOption
+ * @property {TRANSAK_ORDER_STATES} status
+ * @property {string} walletAddress
+ * @property {string} autoExpiresAt
+ * @property {string} fromWalletAddress
+ * @property {string} transactionHash
+ * @property {string} transactionLink
+ */
+
+/**
+ * Query params added by Transak when redirecting after completing flow
+ * https://integrate.transak.com/Query-Parameters-9ec523df3b874ec58cef4fa3a906f238?p=d3edbf3a682d403daceee3249e8aea49
+ * @typedef TransakRedirectOrder
+ * @type {object}
+ * @property {string} orderId
+ * @property {string} fiatCurrency
+ * @property {string} cryptocurrency
+ * @property {string} fiatAmount
+ * @property {string} cryptoAmount
+ * @property {string} isBuyOrSell
+ * @property {string} status
+ * @property {string} walletAddress
+ * @property {string} totalFee
+ * @property {string} partnerCustomerId
+ * @property {string} partnerOrderId
+ */
+
+//* Constants
+
+const {
+ TRANSAK_URL,
+ TRANSAK_URL_STAGING,
+ TRANSAK_API_URL_STAGING,
+ TRANSAK_API_URL_PRODUCTION,
+ TRANSAK_REDIRECT_URL
+} = AppConstants.FIAT_ORDERS;
+
+const isDevelopment = process.env.NODE_ENV !== 'production';
+
+const TRANSAK_API_BASE_URL = `${isDevelopment ? TRANSAK_API_URL_STAGING : TRANSAK_API_URL_PRODUCTION}api/v1/`;
+const TRANSAK_API_KEY = isDevelopment ? TRANSAK_API_KEY_STAGING : TRANSAK_API_KEY_PRODUCTION;
+const TRANSAK_API_KEY_SECRET = isDevelopment ? TRANSAK_API_KEY_SECRET_STAGING : TRANSAK_API_KEY_SECRET_PRODUCTION;
+
+/**
+ * https://integrate.transak.com/69a2474c8d8d40daa04bd5bbe804fb6d?v=48a0c9fd98854078a4eaf5ec9a0a4f65
+ * @enum {string}
+ */
+export const TRANSAK_ORDER_STATES = {
+ AWAITING_PAYMENT_FROM_USER: 'AWAITING_PAYMENT_FROM_USER',
+ PAYMENT_DONE_MARKED_BY_USER: 'PAYMENT_DONE_MARKED_BY_USER',
+ PROCESSING: 'PROCESSING',
+ PENDING_DELIVERY_FROM_TRANSAK: 'PENDING_DELIVERY_FROM_TRANSAK',
+ COMPLETED: 'COMPLETED',
+ EXPIRED: 'EXPIRED',
+ FAILED: 'FAILED',
+ CANCELLED: 'CANCELLED'
+};
+
+//* API
+
+const transakApi = axios.create({
+ baseURL: TRANSAK_API_BASE_URL
+});
+
+// const getPartnerStatus = () => transakApi.get(`partners/${TRANSAK_API_KEY}`);
+const getOrderStatus = orderId =>
+ transakApi.get(`partners/order/${orderId}`, { params: { partnerAPISecret: TRANSAK_API_KEY_SECRET } });
+
+//* Helpers
+
+/**
+ * Transforms a TransakOrder state into a FiatOrder state
+ * @param {TRANSAK_ORDER_STATES} transakOrderState
+ */
+const transakOrderStateToFiatOrderState = transakOrderState => {
+ switch (transakOrderState) {
+ case TRANSAK_ORDER_STATES.COMPLETED: {
+ return FIAT_ORDER_STATES.COMPLETED;
+ }
+ case TRANSAK_ORDER_STATES.EXPIRED:
+ case TRANSAK_ORDER_STATES.FAILED: {
+ return FIAT_ORDER_STATES.FAILED;
+ }
+ case TRANSAK_ORDER_STATES.CANCELLED: {
+ return FIAT_ORDER_STATES.CANCELLED;
+ }
+ case TRANSAK_ORDER_STATES.AWAITING_PAYMENT_FROM_USER:
+ case TRANSAK_ORDER_STATES.PAYMENT_DONE_MARKED_BY_USER:
+ case TRANSAK_ORDER_STATES.PROCESSING:
+ case TRANSAK_ORDER_STATES.PENDING_DELIVERY_FROM_TRANSAK:
+ default: {
+ return FIAT_ORDER_STATES.PENDING;
+ }
+ }
+};
+
+/**
+ * Transforms Transak order object into a Fiat order object used in the state.
+ * @param {TransakOrder} transakOrder Transak order object
+ * @returns {FiatOrder} Fiat order object to store in the state
+ */
+const transakOrderToFiatOrder = transakOrder => ({
+ id: transakOrder.id,
+ provider: FIAT_ORDER_PROVIDERS.TRANSAK,
+ createdAt: new Date(transakOrder.createdAt).getTime(),
+ amount: transakOrder.fiatAmount,
+ fee: transakOrder.totalFeeInFiat,
+ cryptoAmount: transakOrder.cryptoAmount,
+ cryptoFee: transakOrder.totalFeeInCrypto,
+ currency: transakOrder.fiatCurrency,
+ cryptocurrency: transakOrder.cryptocurrency,
+ state: transakOrderStateToFiatOrderState(transakOrder.status),
+ account: transakOrder.walletAddress,
+ txHash: transakOrder.transactionHash || null,
+ data: transakOrder
+});
+
+/**
+ * Transforms Transak order object into a Fiat order object used in the state.
+ * @param {TransakRedirectOrder} transakRedirectOrder Transak order object
+ * @returns {FiatOrder} Fiat order object to store in the state
+ */
+const transakCallbackOrderToFiatOrder = transakRedirectOrder => ({
+ id: transakRedirectOrder.orderId,
+ provider: FIAT_ORDER_PROVIDERS.TRANSAK,
+ createdAt: Date.now(),
+ amount: Number(transakRedirectOrder.fiatAmount),
+ fee: Number(transakRedirectOrder.totalFee),
+ currency: transakRedirectOrder.fiatCurrency,
+ cryptoAmount: transakRedirectOrder.cryptoAmount,
+ cryptocurrency: transakRedirectOrder.cryptocurrency,
+ state: transakOrderStateToFiatOrderState(transakRedirectOrder.status),
+ account: transakRedirectOrder.walletAddress,
+ data: transakRedirectOrder
+});
+
+//* Handlers
+
+/**
+ * Function to handle Transak flow redirect after order creation
+ * @param {String} url Custom URL with query params transak flow redirected to.
+ * Query parameters are: `orderId`, `fiatCurrency`, `cryptocurrency`, `fiatAmount`,
+ * `cryptoAmount`, `isBuyOrSell`, `status`, `walletAddress`,
+ * `totalFee`, `partnerCustomerId`, `partnerOrderId`.
+ * @param {String} network Current network selected in the app
+ * @returns {FiatOrder}
+ */
+export const handleTransakRedirect = (url, network) => {
+ /** @type {TransakRedirectOrder} */
+ const data = qs.parse(url.split(TRANSAK_REDIRECT_URL)[1]);
+ const order = { ...transakCallbackOrderToFiatOrder(data), network };
+ return order;
+};
+
+/**
+ * Function used to poll and update the order
+ * @param {FiatOrder} order Order coming from the state
+ * @param {TransakOrder} order.data Original Transak order
+ * @returns {FiatOrder} Fiat order to update in the state
+ */
+export async function processTransakOrder(order) {
+ try {
+ const {
+ data: { response }
+ } = await getOrderStatus(order.id);
+ if (!response) {
+ throw new Error('Payment Request Failed: empty transak response');
+ }
+ return {
+ ...order,
+ ...transakOrderToFiatOrder(response)
+ };
+ } catch (error) {
+ Logger.error(error, { message: 'FiatOrders::TransakProcessor error while processing order', order });
+ return order;
+ }
+}
+
+//* Hooks
+
+export const useTransakFlowURL = address => {
+ const params = useMemo(
+ () =>
+ qs.stringify({
+ apiKey: TRANSAK_API_KEY,
+ // cryptoCurrencyCode: 'ETH',
+ themeColor: '037dd6',
+ // fiatCurrency: 'USD',
+ walletAddressesData: JSON.stringify({
+ networks: {
+ erc20: { address }
+ },
+ coins: {
+ DAI: { address }
+ }
+ }),
+ redirectURL: TRANSAK_REDIRECT_URL
+ }),
+ [address]
+ );
+ return `${isDevelopment ? TRANSAK_URL_STAGING : TRANSAK_URL}?${params}`;
+};
diff --git a/app/components/UI/FiatOrders/orderProcessor/wyreApplePay.js b/app/components/UI/FiatOrders/orderProcessor/wyreApplePay.js
new file mode 100644
index 00000000000..fc47aee5359
--- /dev/null
+++ b/app/components/UI/FiatOrders/orderProcessor/wyreApplePay.js
@@ -0,0 +1,444 @@
+import { useCallback, useMemo, useState, useEffect } from 'react';
+import { PaymentRequest } from '@exodus/react-native-payments';
+import axios from 'axios';
+import AppConstants from '../../../../core/AppConstants';
+import Logger from '../../../../util/Logger';
+import { strings } from '../../../../../locales/i18n';
+import { FIAT_ORDER_PROVIDERS, FIAT_ORDER_STATES } from '../../../../reducers/fiatOrders';
+
+//* env vars
+
+const WYRE_ACCOUNT_ID = process.env.WYRE_ACCOUNT_ID;
+const WYRE_ACCOUNT_ID_TEST = process.env.WYRE_ACCOUNT_ID_TEST;
+
+//* typedefs
+
+/**
+ * @typedef {import('../../../../reducers/fiatOrders').FiatOrder} FiatOrder
+ */
+
+/**
+ * Wyre API errors.
+ * Source: https://docs.sendwyre.com/docs/errors
+ * @typedef WyreError
+ * @property {string} exceptionId A unique identifier for this exception. This is very helpful when contacting support
+ * @property {WYRE_ERROR_TYPE} type The category of the exception. See below
+ * @property {string} errorCode A more granular specification than type
+ * @property {string} message A human-friendly description of the problem
+ * @property {string} language Indicates the language of the exception message
+ * @property {boolean} transient In rare cases, an exception may signal true here to indicate a transient problem. This means the request can be safely re-attempted
+ *
+ */
+
+/**
+ * @enum {string}
+ */
+export const WYRE_ERROR_TYPE = {
+ ValidationException: 'ValidationException', // The action failed due to problems with the request. 400
+ InsufficientFundsException: 'InsufficientFundsException', // You requested the use of more funds in the specified currency than were available. 400
+ AccessDeniedException: 'AccessDeniedException', // You lack sufficient privilege to perform the requested. action 401
+ TransferException: 'TransferException', // There was a problem completing your transfer request. 400
+ MFARequiredException: 'MFARequiredException', // An MFA action is required to complete the request. In general you should not get this exception while using API keys. 400
+ CustomerSupportException: 'CustomerSupportException', // Please contact us at support@sendwyre.com to resolve this! 400
+ NotFoundException: 'NotFoundException', // You referenced something that could not be located. 404
+ RateLimitException: 'RateLimitException', // Your requests have exceeded your usage restrictions. Please contact us if you need this increased. 429
+ AccountLockedException: 'AccountLockedException', // The account has had a locked placed on it for potential fraud reasons. The customer should contact Wyre support for follow-up. 400
+ LockoutException: 'LockoutException', // The account or IP has been blocked due to detected malicious behavior. 403
+ UnknownException: 'UnknownException' // A problem with our services internally. This should rarely happen. 500
+};
+
+/**
+ * https://docs.sendwyre.com/docs/apple-pay-order-integration
+ *
+ * @typedef WyreOrder
+ * @property {string} id Wallet order id eg: "WO_ELTUVYCAFPG"
+ * @property {number} createdAt Timestamp in UTC eg: 1576263687643
+ * @property {string} owner Owner eg: "account:AC_RNWQNRAZFPC"
+ * @property {WYRE_ORDER_STATES} status Order status eg: "PROCESSING",
+ * @property {string?} transferId Transfer id or null eg: "TF_MDA6MAY848D",
+ * @property {number} sourceAmount Fiat amount of order eg: 1.84,
+ * @property {string} sourceCurrency Fiat currency of order eg: "USD",
+ * @property {string} destCurrency Crypto currency eg: "ETH",
+ * @property {string} dest Destination of transfer eg: "ethereum:0x9E01E0E60dF079136a7a1d4ed97d709D5Fe3e341",
+ * @property {string} walletType Wallet type eg: "APPLE_PAY",
+ * @property {string} email Customer email eg: "user@company.com",
+ * @property {string?} errorMessage Error message null,
+ * @property {string} accountId Account ID: "AC_RNWQNRAZFPC",
+ * @property {string} paymentMethodName Display "Visa 2942"
+ */
+
+/**
+ * https://docs.sendwyre.com/docs/wallet-order-processing
+ * @enum {string}
+ */
+export const WYRE_ORDER_STATES = {
+ RUNNING_CHECKS: 'RUNNING_CHECKS',
+ FAILED: 'FAILED',
+ PROCESSING: 'PROCESSING',
+ COMPLETE: 'COMPLETE'
+};
+
+/**
+ * @typedef WyreTransfer
+ *
+ * @property {string} transferId Transfer ID eg:"TF_MDA6MAY848D"
+ * @property {string} feeCurrency Fee currency "USD"
+ * @property {number} fee Fee
+ * @property {object} fees Fees object
+ * @property {number} fees.ETH Fees in ETH
+ * @property {number} fees.USD Fees in USD
+ * @property {string} sourceCurrency Source currency eg: "USD"
+ * @property {string} destCurrency eg: "ETH"
+ * @property {number} sourceAmount Source amount eg: 1.84
+ * @property {number} destAmount Dest amount eg: 0.001985533306564713
+ * @property {string} destSrn Destination address eg: "ethereum:0x9E01E0E60dF079136a7a1d4ed97d709D5Fe3e341"
+ * @property {string} from eg: "Walletorderholding WO_ELTUVYCAFPG"
+ * @property {string} to
+ * @property {number} rate rate eg: 0.0019760479041916164
+ * @property {string?} customId customId eg:null
+ * @property {string} status status eg:COMPLETED
+ * @property {string?} blockchainNetworkTx Transfer transaction hash
+ * @property {string?} message
+ * @property {string} transferHistoryEntryType eg: "OUTGOING"
+ * @property {Array.<{statusDetail: string, state: string, createdAt: number}>} successTimeline
+ * @property {Array.<{statusDetail: string, state: string, createdAt: number}>} failedTimeline
+ * @property {string?} failureReason
+ * @property {string?} reversalReason
+ */
+
+//* Constants */
+
+const { WYRE_MERCHANT_ID, WYRE_MERCHANT_ID_TEST, WYRE_API_ENDPOINT, WYRE_API_ENDPOINT_TEST } = AppConstants.FIAT_ORDERS;
+export const WYRE_IS_PROMOTION = false;
+export const WYRE_REGULAR_FEE_PERCENT = 2.9;
+export const WYRE_REGULAR_FEE_FLAT = 0.3;
+export const WYRE_FEE_PERCENT = WYRE_IS_PROMOTION ? 0 : WYRE_REGULAR_FEE_PERCENT;
+export const WYRE_FEE_FLAT = WYRE_IS_PROMOTION ? 0 : WYRE_REGULAR_FEE_FLAT;
+
+const getMerchantIdentifier = network => (network === '1' ? WYRE_MERCHANT_ID : WYRE_MERCHANT_ID_TEST);
+const getPartnerId = network => (network === '1' ? WYRE_ACCOUNT_ID : WYRE_ACCOUNT_ID_TEST);
+
+//* API */
+
+const wyreAPI = axios.create({
+ baseURL: WYRE_API_ENDPOINT
+});
+
+const wyreTestAPI = axios.create({
+ baseURL: WYRE_API_ENDPOINT_TEST
+});
+
+const getRates = network => (network === '1' ? wyreAPI : wyreTestAPI).get(`v3/rates`, { params: { as: 'PRICED' } });
+const createFiatOrder = (network, payload) =>
+ (network === '1' ? wyreAPI : wyreTestAPI).post('v3/apple-pay/process/partner', payload, {
+ // * This promise will always be resolved, use response.status to handle errors
+ validateStatus: status => status >= 200,
+ // * Apple Pay timeouts at ~30s without throwing error, we want to catch that before and throw
+ timeout: 25000
+ });
+const getOrderStatus = (network, orderId) => (network === '1' ? wyreAPI : wyreTestAPI).get(`v3/orders/${orderId}`);
+const getTransferStatus = (network, transferId) =>
+ (network === '1' ? wyreAPI : wyreTestAPI).get(`v2/transfer/${transferId}/track`);
+
+//* Helpers
+
+const destToAddress = dest => (dest.indexOf('ethereum:') === 0 ? dest.substring(9) : dest);
+
+export class WyreException extends Error {
+ /**
+ * Creates a WyreException based on a WyreError
+ * @param {string} message
+ * @param {WYRE_ERROR_TYPE} type
+ * @param {string} exceptionId
+ */
+ constructor(message, type, exceptionId) {
+ super(message);
+ this.type = type;
+ this.id = exceptionId;
+ }
+}
+
+/**
+ * Transforms a WyreOrder state into a FiatOrder state
+ * @param {WYRE_ORDER_STATES} wyreOrderState
+ */
+const wyreOrderStateToFiatState = wyreOrderState => {
+ switch (wyreOrderState) {
+ case WYRE_ORDER_STATES.COMPLETE: {
+ return FIAT_ORDER_STATES.COMPLETED;
+ }
+ case WYRE_ORDER_STATES.FAILED: {
+ return FIAT_ORDER_STATES.FAILED;
+ }
+ case WYRE_ORDER_STATES.RUNNING_CHECKS:
+ case WYRE_ORDER_STATES.PROCESSING:
+ default: {
+ return FIAT_ORDER_STATES.PENDING;
+ }
+ }
+};
+
+/**
+ * Transforms Wyre order object into a Fiat order object used in the state.
+ * @param {WyreOrder} wyreOrder Wyre order object
+ * @returns {FiatOrder} Fiat order object to store in the state
+ */
+const wyreOrderToFiatOrder = wyreOrder => ({
+ id: wyreOrder.id,
+ provider: FIAT_ORDER_PROVIDERS.WYRE_APPLE_PAY,
+ createdAt: wyreOrder.createdAt,
+ amount: wyreOrder.sourceAmount,
+ fee: null,
+ cryptoAmount: null,
+ cryptoFee: null,
+ currency: wyreOrder.sourceCurrency,
+ cryptocurrency: wyreOrder.destCurrency,
+ state: wyreOrderStateToFiatState(wyreOrder.status),
+ account: destToAddress(wyreOrder.dest),
+ txHash: null,
+ data: {
+ order: wyreOrder
+ }
+});
+
+/**
+ * Returns fields present in a WyreTransfer which are not
+ * present in a WyreOrder to be assigned in a FiatOrder
+ * @param {WyreTransfer} wyreTransfer Wyre transfer object
+ * @returns {FiatOrder} Partial fiat order object to store in the state
+ */
+const wyreTransferToFiatOrder = wyreTransfer => ({
+ fee: wyreTransfer.fee,
+ cryptoAmount: wyreTransfer.destAmount,
+ cryptoFee: wyreTransfer.fees ? wyreTransfer.fees[wyreTransfer.destCurrency] : null,
+ txHash: wyreTransfer.blockchainNetworkTx
+});
+
+//* Handlers
+
+export async function processWyreApplePayOrder(order) {
+ try {
+ const { data } = await getOrderStatus(order.network, order.id);
+ if (!data) {
+ Logger.error('FiatOrders::WyreApplePayProcessor empty data', order);
+ return order;
+ }
+
+ const { transferId } = data;
+
+ if (transferId) {
+ try {
+ const transferResponse = await getTransferStatus(order.network, transferId);
+ if (transferResponse.data) {
+ return {
+ ...order,
+ ...wyreOrderToFiatOrder(data),
+ ...wyreTransferToFiatOrder(transferResponse.data),
+ data: {
+ order: data,
+ transfer: transferResponse.data
+ }
+ };
+ }
+ } catch (error) {
+ Logger.error(error, {
+ message: 'FiatOrders::WyreApplePayProcessor error while processing transfer',
+ order
+ });
+ }
+ }
+
+ return {
+ ...order,
+ ...wyreOrderToFiatOrder(data)
+ };
+ } catch (error) {
+ Logger.error(error, { message: 'FiatOrders::WyreApplePayProcessor error while processing order', order });
+ return order;
+ }
+}
+
+//* Payment Request */
+
+const USD_CURRENCY_CODE = 'USD';
+const ETH_CURRENCY_CODE = 'ETH';
+
+const ABORTED = 'ABORTED';
+
+const PAYMENT_REQUEST_COMPLETE = {
+ SUCCESS: 'success',
+ UNKNOWN: 'unknown',
+ FAIL: 'fail'
+};
+
+const getMethodData = network => [
+ {
+ supportedMethods: ['apple-pay'],
+ data: {
+ countryCode: 'US',
+ currencyCode: USD_CURRENCY_CODE,
+ supportedNetworks: ['visa', 'mastercard', 'discover'],
+ merchantIdentifier: getMerchantIdentifier(network)
+ }
+ }
+];
+
+const getPaymentDetails = (cryptoCurrency, amount, fee, total) => ({
+ displayItems: [
+ {
+ amount: { currency: USD_CURRENCY_CODE, value: `${amount}` },
+ label: strings('fiat_on_ramp.wyre_purchase', { currency: cryptoCurrency })
+ },
+ {
+ amount: { currency: USD_CURRENCY_CODE, value: `${fee}` },
+ label: strings('fiat_on_ramp.Fee')
+ }
+ ],
+ total: {
+ amount: { currency: USD_CURRENCY_CODE, value: `${total}` },
+ label: 'Wyre'
+ }
+});
+
+const paymentOptions = {
+ requestPayerPhone: true,
+ requestPayerEmail: true,
+ requestBilling: true,
+ merchantCapabilities: ['debit']
+};
+
+const createPayload = (network, amount, address, paymentDetails) => {
+ const {
+ billingContact: { postalAddress, name },
+ paymentData,
+ paymentMethod,
+ shippingContact,
+ transactionIdentifier
+ } = paymentDetails;
+ const dest = `ethereum:${address}`;
+
+ const formattedBillingContact = {
+ addressLines: postalAddress.street.split('\n'),
+ administrativeArea: postalAddress.state,
+ country: postalAddress.country,
+ countryCode: postalAddress.ISOCountryCode,
+ familyName: name.familyName,
+ givenName: name.givenName,
+ locality: postalAddress.city,
+ postalCode: postalAddress.postalCode,
+ subAdministrativeArea: postalAddress.subAdministrativeArea,
+ subLocality: postalAddress.subLocality
+ };
+
+ const formattedShippingContact = {
+ ...formattedBillingContact,
+ emailAddress: shippingContact.emailAddress,
+ phoneNumber: shippingContact.phoneNumber
+ };
+
+ const partnerId = getPartnerId(network);
+
+ return {
+ partnerId,
+ payload: {
+ orderRequest: {
+ amount,
+ dest,
+ destCurrency: ETH_CURRENCY_CODE,
+ referrerAccountId: partnerId,
+ sourceCurrency: USD_CURRENCY_CODE
+ },
+ paymentObject: {
+ billingContact: formattedBillingContact,
+ shippingContact: formattedShippingContact,
+ token: {
+ paymentData,
+ paymentMethod: {
+ ...paymentMethod,
+ type: 'debit'
+ },
+ transactionIdentifier
+ }
+ }
+ }
+ };
+};
+
+// * Hooks */
+
+export function useWyreRates(network, currencies) {
+ const [rates, setRates] = useState(null);
+
+ useEffect(() => {
+ async function getWyreRates() {
+ try {
+ const { data } = await getRates(network);
+ const rates = data[currencies];
+ setRates(rates);
+ } catch (error) {
+ Logger.error(error, 'FiatOrders::WyreAppleyPay error while fetching wyre rates');
+ }
+ }
+ getWyreRates();
+ }, [currencies, network]);
+
+ return rates;
+}
+
+export function useWyreApplePay(amount, address, network) {
+ const flatFee = useMemo(() => WYRE_FEE_FLAT.toFixed(2), []);
+ const percentFee = useMemo(() => WYRE_FEE_PERCENT.toFixed(2), []);
+ const percentFeeAmount = useMemo(() => ((Number(amount) * Number(percentFee)) / 100).toFixed(2), [
+ amount,
+ percentFee
+ ]);
+ const fee = useMemo(() => (Number(percentFeeAmount) + Number(flatFee)).toFixed(2), [flatFee, percentFeeAmount]);
+ const total = useMemo(() => Number(amount) + Number(fee), [amount, fee]);
+ const methodData = useMemo(() => getMethodData(network), [network]);
+ const paymentDetails = useMemo(() => getPaymentDetails(ETH_CURRENCY_CODE, amount, fee, total), [
+ amount,
+ fee,
+ total
+ ]);
+
+ const showRequest = useCallback(async () => {
+ const paymentRequest = new PaymentRequest(methodData, paymentDetails, paymentOptions);
+ try {
+ const paymentResponse = await paymentRequest.show();
+ if (!paymentResponse) {
+ throw new Error('Payment Request Failed: empty apple pay response');
+ }
+ const payload = createPayload(network, total, address, paymentResponse.details);
+ const { data, status } = await createFiatOrder(network, payload);
+ if (status >= 200 && status < 300) {
+ paymentResponse.complete(PAYMENT_REQUEST_COMPLETE.SUCCESS);
+ return { ...wyreOrderToFiatOrder(data), network };
+ }
+ paymentResponse.complete(PAYMENT_REQUEST_COMPLETE.FAIL);
+ throw new WyreException(data.message, data.type, data.exceptionId);
+ } catch (error) {
+ if (error.message.includes('AbortError')) {
+ return ABORTED;
+ }
+ if (paymentRequest && paymentRequest.abort) {
+ paymentRequest.abort();
+ }
+ Logger.error(error, { message: 'FiatOrders::WyreApplePayPayment error while creating order' });
+ throw error;
+ }
+ }, [address, methodData, network, paymentDetails, total]);
+
+ return [showRequest, ABORTED, percentFee, flatFee, percentFeeAmount, fee, total];
+}
+
+export function useWyreTerms(navigation) {
+ const handleWyreTerms = useCallback(
+ () =>
+ navigation.navigate('Webview', {
+ url: 'https://www.sendwyre.com/user-agreement/',
+ title: strings('fiat_on_ramp.wyre_user_agreement')
+ }),
+ [navigation]
+ );
+ return handleWyreTerms;
+}
diff --git a/app/components/UI/Navbar/index.js b/app/components/UI/Navbar/index.js
index 6958f9b42ee..ab48d9816f8 100644
--- a/app/components/UI/Navbar/index.js
+++ b/app/components/UI/Navbar/index.js
@@ -133,7 +133,7 @@ const styles = StyleSheet.create({
metamaskNameWrapper: {
marginLeft: Device.isAndroid() ? 20 : 0
},
- webviewTitle: {
+ centeredTitle: {
fontSize: 20,
color: colors.fontPrimary,
textAlign: 'center',
@@ -797,7 +797,7 @@ export function getWebviewNavbar(navigation) {
'';
});
return {
- headerTitle: {title},
+ headerTitle: {title},
headerLeft: Device.isAndroid() ? (
// eslint-disable-next-line react/jsx-no-bind
navigation.pop()} style={styles.backButton}>
@@ -822,3 +822,69 @@ export function getWebviewNavbar(navigation) {
)
};
}
+
+export function getPaymentSelectorMethodNavbar(navigation) {
+ const rightAction = navigation.dismiss;
+
+ return {
+ headerTitle: {strings('fiat_on_ramp.purchase_method')},
+ headerLeft: ,
+ headerRight: (
+ // eslint-disable-next-line react/jsx-no-bind
+
+ {strings('navigation.cancel')}
+
+ )
+ };
+}
+
+export function getPaymentMethodApplePayNavbar(navigation) {
+ return {
+ title: strings('fiat_on_ramp.amount_to_buy'),
+ headerTitleStyle: {
+ fontSize: 20,
+ color: colors.fontPrimary,
+ ...fontStyles.normal
+ },
+ headerRight: (
+ // eslint-disable-next-line react/jsx-no-bind
+ navigation.dismiss()} style={styles.closeButton}>
+ {strings('navigation.cancel')}
+
+ ),
+ headerLeft: Device.isAndroid() ? (
+ // eslint-disable-next-line react/jsx-no-bind
+ navigation.pop()} style={styles.backButton}>
+
+
+ ) : (
+ // eslint-disable-next-line react/jsx-no-bind
+ navigation.pop()} style={styles.closeButton}>
+ {strings('navigation.back')}
+
+ )
+ };
+}
+
+export function getTransakWebviewNavbar(navigation) {
+ const title = navigation.getParam('title', '');
+ return {
+ title,
+ headerTitleStyle: {
+ fontSize: 20,
+ color: colors.fontPrimary,
+ ...fontStyles.normal
+ },
+ headerLeft: Device.isAndroid() ? (
+ // eslint-disable-next-line react/jsx-no-bind
+ navigation.pop()} style={styles.backButton}>
+
+
+ ) : (
+ // eslint-disable-next-line react/jsx-no-bind
+ navigation.pop()} style={styles.backButton}>
+
+
+ )
+ };
+}
diff --git a/app/components/UI/ReceiveRequest/ReceiveRequestAction/__snapshots__/index.test.js.snap b/app/components/UI/ReceiveRequest/ReceiveRequestAction/__snapshots__/index.test.js.snap
deleted file mode 100644
index ca0c22c7eb7..00000000000
--- a/app/components/UI/ReceiveRequest/ReceiveRequestAction/__snapshots__/index.test.js.snap
+++ /dev/null
@@ -1,77 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ReceiveRequestAction should render correctly 1`] = `
-
-
-
-
- Title
-
-
-
-
- Description
-
-
-
-`;
diff --git a/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.js b/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.js
deleted file mode 100644
index b8671e4ff0c..00000000000
--- a/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import React, { PureComponent } from 'react';
-import PropTypes from 'prop-types';
-import { TouchableOpacity, StyleSheet, View, Text } from 'react-native';
-import { fontStyles, colors } from '../../../../styles/common';
-import { connect } from 'react-redux';
-
-const styles = StyleSheet.create({
- wrapper: {
- margin: 8,
- borderWidth: 1,
- borderColor: colors.blue,
- borderRadius: 8,
- padding: 10,
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center'
- },
- title: {
- ...fontStyles.bold,
- fontSize: 14,
- padding: 5
- },
- description: {
- ...fontStyles.normal,
- fontSize: 12,
- padding: 5,
- textAlign: 'center',
- color: colors.grey500
- },
- row: {
- alignSelf: 'center'
- },
- icon: {
- marginBottom: 5
- }
-});
-
-/**
- * PureComponent that renders a receive action
- */
-class ReceiveRequestAction extends PureComponent {
- static propTypes = {
- /**
- * The navigator object
- */
- icon: PropTypes.object,
- /**
- * Action title
- */
- actionTitle: PropTypes.string,
- /**
- * Action description
- */
- actionDescription: PropTypes.string,
- /**
- * Custom style
- */
- style: PropTypes.object,
- /**
- * Callback on press action
- */
- onPress: PropTypes.func
- };
-
- render() {
- const { icon, actionTitle, actionDescription, style, onPress } = this.props;
- return (
-
- {icon}
-
- {actionTitle}
-
-
-
- {actionDescription}
-
-
-
- );
- }
-}
-
-export default connect()(ReceiveRequestAction);
diff --git a/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.test.js b/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.test.js
deleted file mode 100644
index c26149835b8..00000000000
--- a/app/components/UI/ReceiveRequest/ReceiveRequestAction/index.test.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import ReceiveRequestAction from './';
-import configureMockStore from 'redux-mock-store';
-
-const mockStore = configureMockStore();
-
-describe('ReceiveRequestAction', () => {
- it('should render correctly', () => {
- const initialState = {};
-
- const wrapper = shallow(, {
- context: { store: mockStore(initialState) }
- });
- expect(wrapper.dive()).toMatchSnapshot();
- });
-});
diff --git a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
index 445e3cc01a1..55478afeac0 100644
--- a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
+++ b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
@@ -37,14 +37,26 @@ exports[`ReceiveRequest should render correctly 1`] = `
Receive
@@ -59,243 +72,155 @@ exports[`ReceiveRequest should render correctly 1`] = `
-
+
+
+
+ Scan address to receive payment
+
+
-
- }
- onPress={[Function]}
+
+
+
+
-
- }
+ upper={false}
+ >
+ Copy
+
+
-
+ >
+
+
+
-
- }
- onPress={[Function]}
- style={
+
-
+ disabledContainerStyle={
+ Object {
+ "opacity": 0.6,
+ }
}
onPress={[Function]}
- style={
+ styleDisabled={
Object {
- "flex": 1,
- "height": 343,
- "width": 343,
+ "opacity": 0.6,
}
}
- />
-
-
-
-
-
-
-
+ Buy ETH
+
+
-
-
-
-
- Coming soon...
-
-
-
+ Request Payment
+
+
+
+
`;
diff --git a/app/components/UI/ReceiveRequest/index.js b/app/components/UI/ReceiveRequest/index.js
index 4e661aad874..70145817902 100644
--- a/app/components/UI/ReceiveRequest/index.js
+++ b/app/components/UI/ReceiveRequest/index.js
@@ -1,28 +1,31 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { InteractionManager, SafeAreaView, Dimensions, StyleSheet, View, Text } from 'react-native';
-import { colors, fontStyles } from '../../../styles/common';
-import ReceiveRequestAction from './ReceiveRequestAction';
-import Logger from '../../../util/Logger';
-import Share from 'react-native-share'; // eslint-disable-line import/default
-import { connect } from 'react-redux';
-import { toggleReceiveModal } from '../../../actions/modals';
+import { InteractionManager, TouchableOpacity, SafeAreaView, Dimensions, StyleSheet, View, Alert } from 'react-native';
import Modal from 'react-native-modal';
-import { strings } from '../../../../locales/i18n';
-import ElevatedView from 'react-native-elevated-view';
-import AntIcon from 'react-native-vector-icons/AntDesign';
-import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons';
-import FontAwesome from 'react-native-vector-icons/FontAwesome';
-import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
-import Device from '../../../util/Device';
-import { generateUniversalLinkAddress } from '../../../util/payment-link-generator';
-import AddressQRCode from '../../Views/AddressQRCode';
+import Share from 'react-native-share';
+import QRCode from 'react-native-qrcode-svg';
+import Clipboard from '@react-native-community/clipboard';
+import EvilIcons from 'react-native-vector-icons/EvilIcons';
+import { connect } from 'react-redux';
+
import Analytics from '../../../core/Analytics';
+import Logger from '../../../util/Logger';
+import Device from '../../../util/Device';
+import { strings } from '../../../../locales/i18n';
import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
+import { generateUniversalLinkAddress } from '../../../util/payment-link-generator';
+import { allowedToBuy } from '../FiatOrders';
+import { showAlert } from '../../../actions/alert';
+import { toggleReceiveModal } from '../../../actions/modals';
import { protectWalletModalVisible } from '../../../actions/user';
-const TOTAL_PADDING = 64;
-const ACTION_WIDTH = (Dimensions.get('window').width - TOTAL_PADDING) / 2;
+import { colors, fontStyles } from '../../../styles/common';
+import Text from '../../Base/Text';
+import ModalHandler from '../../Base/ModalHandler';
+import AddressQRCode from '../../Views/AddressQRCode';
+import EthereumAddress from '../EthereumAddress';
+import GlobalAlert from '../GlobalAlert';
+import StyledButton from '../StyledButton';
const styles = StyleSheet.create({
wrapper: {
@@ -45,50 +48,48 @@ const styles = StyleSheet.create({
backgroundColor: colors.grey400,
opacity: Device.isAndroid() ? 0.6 : 0.5
},
- actionsWrapper: {
- marginHorizontal: 16,
- paddingBottom: Device.isIphoneX() ? 16 : 8
+ body: {
+ alignItems: 'center',
+ paddingHorizontal: 15
+ },
+ qrWrapper: {
+ margin: 15
+ },
+ addressWrapper: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ margin: 15,
+ padding: 9,
+ paddingHorizontal: 15,
+ backgroundColor: colors.grey000,
+ borderRadius: 30
+ },
+ copyButton: {
+ backgroundColor: colors.grey050,
+ color: colors.fontPrimary,
+ borderRadius: 12,
+ overflow: 'hidden',
+ paddingVertical: 3,
+ paddingHorizontal: 6,
+ marginHorizontal: 6
},
- row: {
+ actionRow: {
flexDirection: 'row',
- alignItems: 'center'
+ marginBottom: 15
+ },
+ actionButton: {
+ flex: 1,
+ marginHorizontal: 8
},
title: {
...fontStyles.normal,
+ color: colors.fontPrimary,
fontSize: 18,
flexDirection: 'row',
alignSelf: 'center'
},
titleWrapper: {
- marginVertical: 8
- },
- modal: {
- margin: 0,
- width: '100%'
- },
- copyAlert: {
- width: 180,
- backgroundColor: colors.darkAlert,
- padding: 20,
- paddingTop: 30,
- alignSelf: 'center',
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: 8
- },
- copyAlertIcon: {
- marginBottom: 20
- },
- copyAlertText: {
- textAlign: 'center',
- color: colors.white,
- fontSize: 16,
- ...fontStyles.normal
- },
- receiveAction: {
- flex: 1,
- width: ACTION_WIDTH,
- height: ACTION_WIDTH
+ marginTop: 10
}
});
@@ -113,6 +114,14 @@ class ReceiveRequest extends PureComponent {
* Action that toggles the receive modal
*/
toggleReceiveModal: PropTypes.func,
+ /**
+ /* Triggers global alert
+ */
+ showAlert: PropTypes.func,
+ /**
+ * Network id
+ */
+ network: PropTypes.string,
/**
* Prompts protect wallet modal
*/
@@ -151,14 +160,29 @@ class ReceiveRequest extends PureComponent {
/**
* Shows an alert message with a coming soon message
*/
- onBuy = () => {
- InteractionManager.runAfterInteractions(() => {
- this.setState({ buyModalVisible: true });
- setTimeout(() => {
- this.setState({ buyModalVisible: false });
- }, 1500);
- Analytics.trackEvent(ANALYTICS_EVENT_OPTS.RECEIVE_OPTIONS_BUY);
+ onBuy = async () => {
+ const { navigation, toggleReceiveModal, network } = this.props;
+ if (!allowedToBuy(network)) {
+ Alert.alert(strings('fiat_on_ramp.network_not_supported'), strings('fiat_on_ramp.switch_network'));
+ } else {
+ toggleReceiveModal();
+ navigation.navigate('PaymentMethodSelector');
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.WALLET_BUY_ETH);
+ });
+ }
+ };
+
+ copyAccountToClipboard = async () => {
+ const { selectedAddress } = this.props;
+ Clipboard.setString(selectedAddress);
+ this.props.showAlert({
+ isVisible: true,
+ autodismiss: 1500,
+ content: 'clipboard-alert',
+ data: { msg: strings('account_details.account_copied_to_clipboard') }
});
+ setTimeout(() => this.props.protectWalletModalVisible(), 1500);
};
/**
@@ -189,36 +213,7 @@ class ReceiveRequest extends PureComponent {
});
};
- actions = [
- {
- icon: ,
- title: strings('receive_request.share_title'),
- description: strings('receive_request.share_description'),
- onPress: this.onShare
- },
- {
- icon: ,
- title: strings('receive_request.qr_code_title'),
- description: strings('receive_request.qr_code_description'),
- onPress: this.openQrModal
- },
- {
- icon: ,
- title: strings('receive_request.request_title'),
- description: strings('receive_request.request_description'),
- onPress: this.onReceive
- },
- {
- icon: ,
- title: strings('receive_request.buy_title'),
- description: strings('receive_request.buy_description'),
- onPress: this.onBuy
- }
- ];
-
render() {
- const { qrModalVisible, buyModalVisible } = this.state;
-
return (
@@ -229,81 +224,84 @@ class ReceiveRequest extends PureComponent {
{strings('receive_request.title')}
+
+
+ {({ isVisible, toggleModal }) => (
+ <>
+ {
+ toggleModal();
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.RECEIVE_OPTIONS_QR_CODE);
+ });
+ }}
+ >
+
+
+
+
+
+ >
+ )}
+
-
-
-
-
-
-
-
-
+ {strings('receive_request.scan_address')}
+
+
+
+
+
+
+ {strings('receive_request.copy')}
+
+
+
+
+
+
+ {allowedToBuy(this.props.network) && (
+
+ {strings('fiat_on_ramp.buy_eth')}
+
+ )}
+
+ {strings('receive_request.request_payment')}
+
-
-
-
-
-
-
-
-
- {strings('receive_request.coming_soon')}
-
-
+
+
);
}
}
const mapStateToProps = state => ({
+ network: state.engine.backgroundState.NetworkController.network,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
receiveAsset: state.modals.receiveAsset
});
const mapDispatchToProps = dispatch => ({
toggleReceiveModal: () => dispatch(toggleReceiveModal()),
+ showAlert: config => dispatch(showAlert(config)),
protectWalletModalVisible: () => dispatch(protectWalletModalVisible())
});
diff --git a/app/components/UI/ReceiveRequest/index.test.js b/app/components/UI/ReceiveRequest/index.test.js
index 0d70c59336c..7ce372372d7 100644
--- a/app/components/UI/ReceiveRequest/index.test.js
+++ b/app/components/UI/ReceiveRequest/index.test.js
@@ -10,7 +10,8 @@ describe('ReceiveRequest', () => {
const initialState = {
engine: {
backgroundState: {
- PreferencesController: { selectedAddress: '0x' }
+ PreferencesController: { selectedAddress: '0x' },
+ NetworkController: { network: '1' }
}
},
modals: {
diff --git a/app/components/UI/Tokens/index.js b/app/components/UI/Tokens/index.js
index a1731664e00..19056318748 100644
--- a/app/components/UI/Tokens/index.js
+++ b/app/components/UI/Tokens/index.js
@@ -15,6 +15,8 @@ import { connect } from 'react-redux';
import { safeToChecksumAddress } from '../../../util/address';
import Analytics from '../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
+import StyledButton from '../StyledButton';
+import { allowedToBuy } from '../FiatOrders';
const styles = StyleSheet.create({
wrapper: {
@@ -28,6 +30,23 @@ const styles = StyleSheet.create({
alignItems: 'center',
marginTop: 50
},
+ tokensHome: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 35,
+ marginHorizontal: 50
+ },
+ tokensHomeText: {
+ ...fontStyles.normal,
+ marginBottom: 15,
+ marginHorizontal: 15,
+ fontSize: 18,
+ color: colors.fontPrimary,
+ textAlign: 'center'
+ },
+ tokensHomeButton: {
+ width: '100%'
+ },
text: {
fontSize: 20,
color: colors.fontTertiary,
@@ -111,7 +130,11 @@ class Tokens extends PureComponent {
/**
* Primary currency, either ETH or Fiat
*/
- primaryCurrency: PropTypes.string
+ primaryCurrency: PropTypes.string,
+ /**
+ * Network id
+ */
+ network: PropTypes.string
};
actionSheet = null;
@@ -183,12 +206,46 @@ class Tokens extends PureComponent {
);
};
+ goToBuy = () => {
+ this.props.navigation.navigate('PaymentMethodSelector');
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.WALLET_BUY_ETH);
+ });
+ };
+
+ renderBuyEth() {
+ const { tokens, network, tokenBalances } = this.props;
+ if (!allowedToBuy(network)) {
+ return null;
+ }
+ const eth = tokens.find(token => token.isETH);
+ const ethBalance = eth && eth.balance !== '0';
+ const hasTokens = eth ? tokens.length > 1 : tokens.length > 0;
+ const hasTokensBalance =
+ hasTokens &&
+ tokens.some(
+ token => !token.isETH && tokenBalances[token.address] && !tokenBalances[token.address].isZero()
+ );
+
+ return (
+
+ {!ethBalance && !hasTokensBalance && (
+ {strings('wallet.ready_to_explore')}
+ )}
+
+ {strings('fiat_on_ramp.buy_eth')}
+
+
+ );
+ }
+
renderList() {
const { tokens } = this.props;
return (
{tokens.map(item => this.renderItem(item))}
+ {this.renderBuyEth()}
{this.renderFooter()}
);
@@ -237,6 +294,7 @@ class Tokens extends PureComponent {
}
const mapStateToProps = state => ({
+ network: state.engine.backgroundState.NetworkController.network,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
primaryCurrency: state.settings.primaryCurrency,
diff --git a/app/components/UI/Tokens/index.test.js b/app/components/UI/Tokens/index.test.js
index f3cf057e636..ec7ebe31773 100644
--- a/app/components/UI/Tokens/index.test.js
+++ b/app/components/UI/Tokens/index.test.js
@@ -22,6 +22,9 @@ describe('Tokens', () => {
},
TokenBalancesController: {
contractBalance: {}
+ },
+ NetworkController: {
+ network: '1'
}
}
},
diff --git a/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap b/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap
index 602c0e8d602..fc22c4e25c3 100644
--- a/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap
@@ -1,195 +1,97 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TransactionDetails should render correctly 1`] = `
-
-
+
-
+
+ Status
+
+
+
+
-
-
- Status
-
-
- Confirmed
-
-
-
+ Date
+
+
-
- Date
-
-
- [missing "en.date.months.NaN" translation] NaN at NaN:NaNam
-
-
-
-
-
+
+
+
-
-
-
- From
-
+
+
+ From
+
+
-
-
+
+
+
+ To
+
+
-
- To
-
-
-
-
+
+
+
-
+
`;
diff --git a/app/components/UI/TransactionElement/TransactionDetails/index.js b/app/components/UI/TransactionElement/TransactionDetails/index.js
index b42eb4f2595..080c039f87e 100644
--- a/app/components/UI/TransactionElement/TransactionDetails/index.js
+++ b/app/components/UI/TransactionElement/TransactionDetails/index.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { TouchableOpacity, StyleSheet, Text, View } from 'react-native';
-import { colors, fontStyles, baseStyles } from '../../../../styles/common';
+import { TouchableOpacity, StyleSheet, View } from 'react-native';
+import { colors, fontStyles } from '../../../../styles/common';
import { strings } from '../../../../../locales/i18n';
import NetworkList, {
getNetworkTypeById,
@@ -18,39 +18,11 @@ import { toDateFormat } from '../../../../util/date';
import StyledButton from '../../StyledButton';
import { safeToChecksumAddress } from '../../../../util/address';
import AppConstants from '../../../../core/AppConstants';
+import StatusText from '../../../Base/StatusText';
+import Text from '../../../Base/Text';
+import DetailsModal from '../../../Base/DetailsModal';
const styles = StyleSheet.create({
- detailRowWrapper: {
- paddingHorizontal: 15
- },
- detailRowTitle: {
- fontSize: 10,
- color: colors.grey500,
- marginBottom: 8,
- ...fontStyles.normal
- },
- flexRow: {
- flexDirection: 'row'
- },
- section: {
- paddingVertical: 16
- },
- sectionBorderBottom: {
- borderBottomColor: colors.grey100,
- borderBottomWidth: 1
- },
- flexEnd: {
- flex: 1,
- alignItems: 'flex-end'
- },
- textUppercase: {
- textTransform: 'uppercase'
- },
- detailRowText: {
- fontSize: 12,
- color: colors.fontPrimary,
- ...fontStyles.normal
- },
viewOnEtherscan: {
fontSize: 16,
color: colors.blue,
@@ -64,10 +36,6 @@ const styles = StyleSheet.create({
summaryWrapper: {
marginVertical: 8
},
- statusText: {
- fontSize: 12,
- ...fontStyles.normal
- },
actionContainerStyle: {
height: 25,
width: 70,
@@ -147,6 +115,7 @@ class TransactionDetails extends PureComponent {
viewOnEtherscan = () => {
const {
+ navigation,
transactionObject: { networkID },
transactionDetails: { transactionHash },
network: {
@@ -159,7 +128,7 @@ class TransactionDetails extends PureComponent {
if (type === 'rpc') {
const url = `${rpcBlockExplorer}/tx/${transactionHash}`;
const title = new URL(rpcBlockExplorer).hostname;
- this.props.navigation.push('Webview', {
+ navigation.push('Webview', {
url,
title
});
@@ -167,7 +136,7 @@ class TransactionDetails extends PureComponent {
const network = getNetworkTypeById(networkID);
const url = getEtherscanTransactionUrl(network, transactionHash);
const etherscan_url = getEtherscanBaseUrl(network).replace('https://', '');
- this.props.navigation.push('Webview', {
+ navigation.push('Webview', {
url,
title: etherscan_url
});
@@ -179,20 +148,6 @@ class TransactionDetails extends PureComponent {
}
};
- renderStatusText = status => {
- status = status && status.charAt(0).toUpperCase() + status.slice(1);
- switch (status) {
- case 'Confirmed':
- return {status};
- case 'Pending':
- case 'Submitted':
- return {status};
- case 'Failed':
- case 'Cancelled':
- return {status};
- }
- };
-
renderSpeedUpButton = () => (
{
const {
+ transactionDetails,
transactionObject,
transactionObject: {
status,
@@ -230,76 +186,70 @@ class TransactionDetails extends PureComponent {
const renderSpeedUpAction = safeToChecksumAddress(to) !== AppConstants.CONNEXT.CONTRACTS[networkId];
const { rpcBlockExplorer } = this.state;
return (
-
-
-
-
- {strings('transactions.status')}
- {this.renderStatusText(status)}
- {!!renderTxActions && (
-
- {renderSpeedUpAction && this.renderSpeedUpButton()}
- {this.renderCancelButton()}
-
- )}
-
-
- {strings('transactions.date')}
- {toDateFormat(time)}
-
-
-
-
-
-
- {strings('transactions.from')}
-
-
-
- {strings('transactions.to')}
-
-
-
-
- {!!nonce && (
-
-
- {strings('transactions.nonce')}
+
+
+
+ {strings('transactions.status')}
+
+ {!!renderTxActions && (
+
+ {renderSpeedUpAction && this.renderSpeedUpButton()}
+ {this.renderCancelButton()}
+
+ )}
+
+
+ {strings('transactions.date')}
+
+ {toDateFormat(time)}
+
+
+
+
+
+ {strings('transactions.from')}
+
+
- {`#${parseInt(nonce.replace(/^#/, ''), 16)}`}
-
+
+
+ {strings('transactions.to')}
+
+
+
+
+
+ {!!nonce && (
+
+
+ {strings('transactions.nonce')}
+ {`#${parseInt(nonce.replace(/^#/, ''), 16)}`}
+
+
)}
- {this.props.transactionDetails.transactionHash &&
+ {transactionDetails.transactionHash &&
transactionObject.status !== 'cancelled' &&
rpcBlockExplorer !== NO_RPC_BLOCK_EXPLORER && (
-
+
{(rpcBlockExplorer &&
`${strings('transactions.view_on')} ${getBlockExplorerName(rpcBlockExplorer)}`) ||
strings('transactions.view_on_etherscan')}
)}
-
+
);
};
}
diff --git a/app/components/UI/TransactionElement/__snapshots__/index.test.js.snap b/app/components/UI/TransactionElement/__snapshots__/index.test.js.snap
index 27335c29c4c..81355a1a054 100644
--- a/app/components/UI/TransactionElement/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionElement/__snapshots__/index.test.js.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`TransactionElement should render correctly 1`] = ``;
+exports[`TransactionElement should render correctly 1`] = `""`;
diff --git a/app/components/UI/TransactionElement/index.js b/app/components/UI/TransactionElement/index.js
index 4c30ee9ccf9..74f20521f2c 100644
--- a/app/components/UI/TransactionElement/index.js
+++ b/app/components/UI/TransactionElement/index.js
@@ -1,20 +1,21 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { TouchableHighlight, StyleSheet, Text, View, Image } from 'react-native';
-import { colors, fontStyles } from '../../../styles/common';
+import { TouchableHighlight, StyleSheet, Image } from 'react-native';
+import { colors } from '../../../styles/common';
import { strings } from '../../../../locales/i18n';
import { toDateFormat } from '../../../util/date';
import TransactionDetails from './TransactionDetails';
import { safeToChecksumAddress } from '../../../util/address';
import { connect } from 'react-redux';
import AppConstants from '../../../core/AppConstants';
-import Ionicons from 'react-native-vector-icons/Ionicons';
import StyledButton from '../StyledButton';
import Networks from '../../../util/networks';
-import Device from '../../../util/Device';
import Modal from 'react-native-modal';
import decodeTransaction from './utils';
import { TRANSACTION_TYPES } from '../../../util/transactions';
+import ListItem from '../../Base/ListItem';
+import StatusText from '../../Base/StatusText';
+import DetailsModal from '../../Base/DetailsModal';
const styles = StyleSheet.create({
row: {
@@ -23,53 +24,6 @@ const styles = StyleSheet.create({
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: colors.grey100
},
- rowContent: {
- padding: 0
- },
- rowOnly: {
- padding: 15,
- minHeight: Device.isIos() ? 95 : 100
- },
- date: {
- color: colors.fontSecondary,
- fontSize: 12,
- marginBottom: 10,
- ...fontStyles.normal
- },
- info: {
- flex: 1,
- marginLeft: 15
- },
- address: {
- fontSize: 15,
- color: colors.fontPrimary,
- ...fontStyles.normal
- },
- status: {
- marginTop: 4,
- fontSize: 12,
- letterSpacing: 0.5,
- ...fontStyles.bold
- },
- amount: {
- fontSize: 15,
- color: colors.fontPrimary,
- ...fontStyles.normal
- },
- amountFiat: {
- fontSize: 12,
- color: colors.fontSecondary,
- textTransform: 'uppercase',
- ...fontStyles.normal
- },
- amounts: {
- flex: 0.6,
- alignItems: 'flex-end'
- },
- subRow: {
- flexDirection: 'row'
- },
-
actionContainerStyle: {
height: 25,
width: 70,
@@ -83,63 +37,23 @@ const styles = StyleSheet.create({
padding: 0,
paddingHorizontal: 10
},
- transactionActionsContainer: {
- flexDirection: 'row',
- paddingTop: 10,
- paddingLeft: 40
- },
- modalContainer: {
- width: '90%',
- backgroundColor: colors.white,
- borderRadius: 10
- },
- modal: {
- margin: 0,
- width: '100%'
- },
- modalView: {
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center'
- },
- titleWrapper: {
- borderBottomWidth: StyleSheet.hairlineWidth,
- borderColor: colors.grey100,
- flexDirection: 'row'
- },
- title: {
- flex: 1,
- textAlign: 'center',
- fontSize: 18,
- marginVertical: 12,
- marginHorizontal: 24,
- color: colors.fontPrimary,
- ...fontStyles.bold
- },
- closeIcon: { paddingTop: 4, position: 'absolute', right: 16 },
- iconWrapper: {
- flexDirection: 'row',
- alignItems: 'center'
- },
icon: {
width: 28,
height: 28
- },
- statusText: {
- fontSize: 12,
- ...fontStyles.normal
}
});
-const transactionIconApprove = require('../../../images/transaction-icons/approve.png'); // eslint-disable-line
-const transactionIconInteraction = require('../../../images/transaction-icons/interaction.png'); // eslint-disable-line
-const transactionIconSent = require('../../../images/transaction-icons/send.png'); // eslint-disable-line
-const transactionIconReceived = require('../../../images/transaction-icons/receive.png'); // eslint-disable-line
+/* eslint-disable import/no-commonjs */
+const transactionIconApprove = require('../../../images/transaction-icons/approve.png');
+const transactionIconInteraction = require('../../../images/transaction-icons/interaction.png');
+const transactionIconSent = require('../../../images/transaction-icons/send.png');
+const transactionIconReceived = require('../../../images/transaction-icons/receive.png');
-const transactionIconApproveFailed = require('../../../images/transaction-icons/approve-failed.png'); // eslint-disable-line
-const transactionIconInteractionFailed = require('../../../images/transaction-icons/interaction-failed.png'); // eslint-disable-line
-const transactionIconSentFailed = require('../../../images/transaction-icons/send-failed.png'); // eslint-disable-line
-const transactionIconReceivedFailed = require('../../../images/transaction-icons/receive-failed.png'); // eslint-disable-line
+const transactionIconApproveFailed = require('../../../images/transaction-icons/approve-failed.png');
+const transactionIconInteractionFailed = require('../../../images/transaction-icons/interaction-failed.png');
+const transactionIconSentFailed = require('../../../images/transaction-icons/send-failed.png');
+const transactionIconReceivedFailed = require('../../../images/transaction-icons/receive-failed.png');
+/* eslint-enable import/no-commonjs */
/**
* View that renders a transaction item part of transactions list
@@ -242,20 +156,6 @@ class TransactionElement extends PureComponent {
this.mounted = false;
}
- getStatusStyle(status) {
- switch (status) {
- case 'confirmed':
- return [styles.statusText, { color: colors.green400 }];
- case 'pending':
- case 'submitted':
- return [styles.statusText, { color: colors.orange }];
- case 'failed':
- case 'cancelled':
- return [styles.statusText, { color: colors.red }];
- }
- return null;
- }
-
onPressItem = () => {
const { tx, i, onPressItem } = this.props;
onPressItem(tx.id, i);
@@ -270,12 +170,9 @@ class TransactionElement extends PureComponent {
const { tx, selectedAddress } = this.props;
const incoming = safeToChecksumAddress(tx.transaction.to) === selectedAddress;
const selfSent = incoming && safeToChecksumAddress(tx.transaction.from) === selectedAddress;
- return (
-
- {(!incoming || selfSent) && tx.transaction.nonce && `#${parseInt(tx.transaction.nonce, 16)} - `}
- {`${toDateFormat(tx.time)}`}
-
- );
+ return `${
+ (!incoming || selfSent) && tx.transaction.nonce ? `#${parseInt(tx.transaction.nonce, 16)} - ` : ''
+ }${toDateFormat(tx.time)}`;
};
renderTxElementIcon = (transactionElement, status) => {
@@ -304,25 +201,7 @@ class TransactionElement extends PureComponent {
icon = isFailedTransaction ? transactionIconApproveFailed : transactionIconApprove;
break;
}
- return (
-
-
-
- );
- };
-
- renderStatusText = status => {
- status = status && status.charAt(0).toUpperCase() + status.slice(1);
- switch (status) {
- case 'Confirmed':
- return {status};
- case 'Pending':
- case 'Submitted':
- return {status};
- case 'Failed':
- case 'Cancelled':
- return {status};
- }
+ return ;
};
/**
@@ -343,28 +222,26 @@ class TransactionElement extends PureComponent {
const renderTxActions = status === 'submitted' || status === 'approved';
const renderSpeedUpAction = safeToChecksumAddress(to) !== AppConstants.CONNEXT.CONTRACTS[networkId];
return (
-
- {this.renderTxTime()}
-
- {this.renderTxElementIcon(transactionElement, status)}
-
-
- {actionKey}
-
- {this.renderStatusText(status)}
-
-
- {value}
- {fiatValue}
-
-
+
+ {this.renderTxTime()}
+
+ {this.renderTxElementIcon(transactionElement, status)}
+
+ {actionKey}
+
+
+
+ {value}
+ {fiatValue}
+
+
{!!renderTxActions && (
-
+
{renderSpeedUpAction && this.renderSpeedUpButton()}
{this.renderCancelButton()}
-
+
)}
-
+
);
};
@@ -412,48 +289,40 @@ class TransactionElement extends PureComponent {
const { tx } = this.props;
const { detailsModalVisible, transactionElement, transactionDetails } = this.state;
- if (!transactionElement || !transactionDetails) return ;
+ if (!transactionElement || !transactionDetails) return null;
return (
-
+ <>
- {this.renderTxElement(transactionElement)}
+ {this.renderTxElement(transactionElement)}
-
-
-
-
- {transactionElement.actionKey}
-
-
-
-
-
-
+
+
+
+ {transactionElement.actionKey}
+
+
+
+
+
-
+ >
);
}
}
diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap
index c1e77c3228c..5b0b28f90af 100644
--- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/__snapshots__/index.test.js.snap
@@ -3,224 +3,147 @@
exports[`TransactionReviewFeeCard should render correctly 1`] = `
-
-
+
+ Amount
+
+
+
+
+
- Amount
+ Network fee
-
-
-
-
- Network fee
+
+ Edit
-
-
- Edit
-
-
-
-
-
-
-
-
-
+
+
-
- Total
-
- Amount
-
+
+
+
+
+
+ Total
+
+ Amount
+
+
+
-
-
-
+
-
+
`;
diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
index c8945b6a5e3..e37249045ed 100644
--- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
+++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
@@ -1,65 +1,15 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { StyleSheet, Text, View, TouchableOpacity, ActivityIndicator } from 'react-native';
-import { colors, fontStyles } from '../../../../styles/common';
+import { StyleSheet, View, TouchableOpacity, ActivityIndicator } from 'react-native';
+import { colors } from '../../../../styles/common';
import { strings } from '../../../../../locales/i18n';
+import Summary from '../../../Base/Summary';
+import Text from '../../../Base/Text';
const styles = StyleSheet.create({
overview: {
- borderWidth: 1,
- borderColor: colors.grey200,
- borderRadius: 10,
- padding: 16,
marginHorizontal: 24
},
- overviewAccent: {
- color: colors.blue
- },
- overviewCol: {
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'column'
- },
- topOverviewCol: {
- borderBottomWidth: 1,
- borderColor: colors.grey200,
- paddingBottom: 12
- },
- bottomOverviewCol: {
- paddingTop: 12
- },
- amountRow: {
- width: '100%',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center'
- },
- amountRowBottomSpace: {
- paddingBottom: 12
- },
- totalValueRow: {
- justifyContent: 'flex-end'
- },
- networkTextWrapper: {
- flexDirection: 'row',
- justifyContent: 'flex-start',
- alignItems: 'center'
- },
- overviewText: {
- ...fontStyles.bold,
- color: colors.fontPrimary,
- fontSize: 14
- },
- amountText: {
- textTransform: 'uppercase'
- },
- networkFeeText: {
- paddingRight: 5
- },
- totalValueText: {
- color: colors.fontSecondary,
- textTransform: 'uppercase'
- },
loader: {
backgroundColor: colors.white,
height: 10
@@ -148,40 +98,45 @@ class TransactionReviewFeeCard extends PureComponent {
equivalentTotalAmount = totalValue;
}
return (
-
-
-
- {strings('transaction.amount')}
- {amount}
-
-
-
-
- {strings('transaction.gas_fee')}
+
+
+
+ {strings('transaction.amount')}
+
+
+ {amount}
+
+
+
+
+
+ {strings('transaction.gas_fee')}
+
+
+
+ {' '}
+ {strings('transaction.edit')}
-
-
- {strings('transaction.edit')}
-
-
-
- {this.renderIfGasEstimationReady({networkFee})}
-
-
-
-
-
- {strings('transaction.total')} {strings('transaction.amount')}
+
+
+
+ {this.renderIfGasEstimationReady(
+
+ {networkFee}
- {!!totalFiat && this.renderIfGasEstimationReady(totalAmount)}
-
-
- {this.renderIfGasEstimationReady(
- {equivalentTotalAmount}
- )}
-
-
-
+ )}
+
+
+
+
+ {strings('transaction.total')} {strings('transaction.amount')}
+
+ {!!totalFiat && this.renderIfGasEstimationReady(totalAmount)}
+
+
+ {this.renderIfGasEstimationReady({equivalentTotalAmount})}
+
+
);
}
}
diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap
index d22a205becd..81b40efda3e 100644
--- a/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionReview/TransactionReviewInformation/__snapshots__/index.test.js.snap
@@ -11,8 +11,8 @@ exports[`TransactionReviewInformation should render correctly 1`] = `
diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
index adfc831dce7..7c3ddcb06e3 100644
--- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
+++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
@@ -70,8 +70,8 @@ const styles = StyleSheet.create({
maxWidth: '30%'
},
viewDataWrapper: {
- marginTop: 32,
- marginBottom: 16
+ flex: 1,
+ marginTop: 16
},
viewDataButton: {
alignSelf: 'center'
diff --git a/app/components/Views/ActivityView/index.js b/app/components/Views/ActivityView/index.js
new file mode 100644
index 00000000000..40472acbda2
--- /dev/null
+++ b/app/components/Views/ActivityView/index.js
@@ -0,0 +1,64 @@
+import React, { useEffect, useContext } from 'react';
+import PropTypes from 'prop-types';
+import { View, StyleSheet } from 'react-native';
+import ScrollableTabView from 'react-native-scrollable-tab-view';
+import { connect } from 'react-redux';
+import { NavigationContext } from 'react-navigation';
+import { getHasOrders } from '../../../reducers/fiatOrders';
+
+import getNavbarOptions from '../../UI/Navbar';
+import TransactionsView from '../TransactionsView';
+import TabBar from '../../Base/TabBar';
+import { strings } from '../../../../locales/i18n';
+import FiatOrdersView from '../FiatOrdersView';
+
+const styles = StyleSheet.create({
+ wrapper: {
+ flex: 1
+ }
+});
+
+function ActivityView({ hasOrders, ...props }) {
+ const navigation = useContext(NavigationContext);
+
+ useEffect(
+ () => {
+ navigation.setParams({ hasOrders });
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [hasOrders]
+ );
+
+ return (
+
+
+
+ {hasOrders && }
+
+
+ );
+}
+
+ActivityView.defaultProps = {
+ hasOrders: false
+};
+
+ActivityView.propTypes = {
+ hasOrders: PropTypes.bool
+};
+
+ActivityView.navigationOptions = ({ navigation }) => {
+ const title = navigation.getParam('hasOrders', false) ? 'activity_view.title' : 'transactions_view.title';
+ return getNavbarOptions(title, navigation);
+};
+
+const mapStateToProps = state => {
+ const orders = state.fiatOrders.orders;
+ const selectedAddress = state.engine.backgroundState.PreferencesController.selectedAddress;
+ const network = state.engine.backgroundState.NetworkController.network;
+ return {
+ hasOrders: getHasOrders(orders, selectedAddress, network)
+ };
+};
+
+export default connect(mapStateToProps)(ActivityView);
diff --git a/app/components/Views/AddressQRCode/index.js b/app/components/Views/AddressQRCode/index.js
index 2bbab541bde..c571a513a51 100644
--- a/app/components/Views/AddressQRCode/index.js
+++ b/app/components/Views/AddressQRCode/index.js
@@ -10,6 +10,7 @@ import IonicIcon from 'react-native-vector-icons/Ionicons';
import Device from '../../../util/Device';
import { showAlert } from '../../../actions/alert';
import GlobalAlert from '../../UI/GlobalAlert';
+import { protectWalletModalVisible } from '../../../actions/user';
const WIDTH = Dimensions.get('window').width - 88;
@@ -76,7 +77,11 @@ class AddressQRCode extends PureComponent {
/**
/* Callback to close the modal
*/
- closeQrModal: PropTypes.func
+ closeQrModal: PropTypes.func,
+ /**
+ * Prompts protect wallet modal
+ */
+ protectWalletModalVisible: PropTypes.func
};
/**
@@ -84,6 +89,7 @@ class AddressQRCode extends PureComponent {
*/
closeQrModal = () => {
this.props.closeQrModal();
+ setTimeout(() => this.props.protectWalletModalVisible(), 1000);
};
copyAccountToClipboard = async () => {
@@ -139,7 +145,8 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
- showAlert: config => dispatch(showAlert(config))
+ showAlert: config => dispatch(showAlert(config)),
+ protectWalletModalVisible: () => dispatch(protectWalletModalVisible())
});
export default connect(
diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js
index dba023bb1a3..15037ac9b13 100644
--- a/app/components/Views/BrowserTab/index.js
+++ b/app/components/Views/BrowserTab/index.js
@@ -15,7 +15,7 @@ import {
} from 'react-native';
// eslint-disable-next-line import/named
import { withNavigation } from 'react-navigation';
-import { WebView } from 'react-native-webview';
+import { WebView } from 'react-native-webview-forked';
import Icon from 'react-native-vector-icons/FontAwesome';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIcons';
diff --git a/app/components/Views/FiatOrdersView/OrderDetails.js b/app/components/Views/FiatOrdersView/OrderDetails.js
new file mode 100644
index 00000000000..567054f4fff
--- /dev/null
+++ b/app/components/Views/FiatOrdersView/OrderDetails.js
@@ -0,0 +1,107 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet } from 'react-native';
+import { toDateFormat } from '../../../util/date';
+import { strings } from '../../../../locales/i18n';
+
+import Text from '../../Base/Text';
+import StatusText from '../../Base/StatusText';
+import DetailsModal from '../../Base/DetailsModal';
+import EthereumAddress from '../../UI/EthereumAddress';
+import { getProviderName } from '../../../reducers/fiatOrders';
+import Summary from '../../Base/Summary';
+import { addCurrencySymbol, renderNumber } from '../../../util/number';
+
+const styles = StyleSheet.create({
+ summary: {
+ marginTop: 8,
+ marginBottom: 16
+ }
+});
+
+function OrderDetails({ order: { ...order }, closeModal }) {
+ return (
+
+
+
+ {strings('fiat_on_ramp.purchased_currency', { currency: order.cryptocurrency })}
+
+
+
+
+
+
+ {strings('fiat_on_ramp.status')}
+
+
+
+ {strings('fiat_on_ramp.date')}
+
+ {toDateFormat(order.createdAt)}
+
+
+
+
+
+ {strings('fiat_on_ramp.from')}
+
+ {getProviderName(order.provider)}
+
+
+
+ {strings('fiat_on_ramp.to')}
+
+
+
+
+
+ {!!order.cryptoAmount && (
+
+
+ {strings('fiat_on_ramp.amount')}
+
+ {renderNumber(String(order.cryptoAmount))} {order.cryptocurrency}
+
+
+
+ )}
+ {Number.isFinite(order.amount) && Number.isFinite(order.fee) && (
+
+
+
+ {strings('fiat_on_ramp.amount')}
+
+
+ {addCurrencySymbol((order.amount - order.fee).toLocaleString(), order.currency)}
+
+
+
+
+ {strings('fiat_on_ramp.Fee')}
+
+
+ {addCurrencySymbol(order.fee.toLocaleString(), order.currency)}
+
+
+
+
+
+ {strings('fiat_on_ramp.total_amount')}
+
+
+ {addCurrencySymbol(order.amount.toLocaleString(), order.currency)}
+
+
+
+ )}
+
+
+ );
+}
+
+OrderDetails.propTypes = {
+ order: PropTypes.object,
+ closeModal: PropTypes.func
+};
+
+export default OrderDetails;
diff --git a/app/components/Views/FiatOrdersView/OrderListItem.js b/app/components/Views/FiatOrdersView/OrderListItem.js
new file mode 100644
index 00000000000..b7c809ba835
--- /dev/null
+++ b/app/components/Views/FiatOrdersView/OrderListItem.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Image, StyleSheet } from 'react-native';
+import ListItem from '../../Base/ListItem';
+import { strings } from '../../../../locales/i18n';
+import { toDateFormat } from '../../../util/date';
+import { renderNumber, addCurrencySymbol } from '../../../util/number';
+import { getProviderName } from '../../../reducers/fiatOrders';
+import StatusText from '../../Base/StatusText';
+/**
+ * @typedef {import('../../../reducers/fiatOrders').FiatOrder} FiatOrder
+ */
+
+// eslint-disable-next-line import/no-commonjs
+const transactionIconReceived = require('../../../images/transaction-icons/receive.png');
+
+const styles = StyleSheet.create({
+ icon: {
+ width: 28,
+ height: 28
+ }
+});
+
+/**
+ *
+ * @param {object} props
+ * @param {FiatOrder} props.order
+ */
+function OrderListItem({ order }) {
+ return (
+
+ {order.createdAt && {toDateFormat(order.createdAt)}}
+
+
+
+
+
+
+
+ {getProviderName(order.provider)}:{' '}
+ {strings('fiat_on_ramp.purchased_currency', { currency: order.cryptocurrency })}
+
+
+
+
+
+ {order.cryptoAmount ? renderNumber(String(order.cryptoAmount)) : '...'} {order.cryptocurrency}
+
+ {addCurrencySymbol(order.amount, order.currency)}
+
+
+
+ );
+}
+
+OrderListItem.propTypes = {
+ order: PropTypes.object
+};
+
+export default OrderListItem;
diff --git a/app/components/Views/FiatOrdersView/index.js b/app/components/Views/FiatOrdersView/index.js
new file mode 100644
index 00000000000..fc19f47732d
--- /dev/null
+++ b/app/components/Views/FiatOrdersView/index.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, StyleSheet, FlatList, TouchableHighlight } from 'react-native';
+import Modal from 'react-native-modal';
+import { connect } from 'react-redux';
+import { getOrders } from '../../../reducers/fiatOrders';
+
+import { colors } from '../../../styles/common';
+import ModalHandler from '../../Base/ModalHandler';
+import OrderListItem from './OrderListItem';
+import OrderDetails from './OrderDetails';
+
+/**
+ * @typedef {import('../../../reducers/fiatOrders').FiatOrder} FiatOrder
+ */
+const styles = StyleSheet.create({
+ wrapper: {
+ flex: 1
+ },
+ row: {
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderColor: colors.grey100
+ }
+});
+
+const keyExtractor = item => item.id;
+
+/**
+ *
+ * @param {object} data
+ * @param {FiatOrder} data.item
+ */
+const renderItem = ({ item }) => (
+
+ {({ isVisible, toggleModal }) => (
+ <>
+
+
+
+
+
+
+
+ >
+ )}
+
+);
+
+renderItem.propTypes = {
+ item: PropTypes.object
+};
+
+function FiatOrdersView({ orders, ...props }) {
+ return (
+
+
+
+ );
+}
+
+FiatOrdersView.propTypes = {
+ orders: PropTypes.array
+};
+
+const mapStateToProps = state => {
+ const orders = state.fiatOrders.orders;
+ const selectedAddress = state.engine.backgroundState.PreferencesController.selectedAddress;
+ const network = state.engine.backgroundState.NetworkController.network;
+ return {
+ orders: getOrders(orders, selectedAddress, network)
+ };
+};
+
+export default connect(mapStateToProps)(FiatOrdersView);
diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js
index e9e62b0b69e..77c38c0a6e0 100644
--- a/app/components/Views/SendFlow/Confirm/index.js
+++ b/app/components/Views/SendFlow/Confirm/index.js
@@ -981,7 +981,11 @@ class Confirm extends PureComponent {
onPress={isPaymentChannelTransaction ? this.onPaymentChannelSend : this.onNext}
testID={'txn-confirm-send-button'}
>
- {transactionConfirmed ? : 'Send'}
+ {transactionConfirmed ? (
+
+ ) : (
+ strings('transaction.send')
+ )}
{this.renderFromAccountModal()}
diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js
index d03085962a4..7d7cb774387 100644
--- a/app/components/Views/SendFlow/SendTo/index.js
+++ b/app/components/Views/SendFlow/SendTo/index.js
@@ -23,6 +23,7 @@ import WarningMessage from '../WarningMessage';
import { util } from '@metamask/controllers';
import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
+import { allowedToBuy } from '../../../UI/FiatOrders';
const { hexToBN } = util;
const styles = StyleSheet.create({
@@ -111,6 +112,11 @@ const styles = StyleSheet.create({
warningContainer: {
marginHorizontal: 24,
marginBottom: 32
+ },
+ buyEth: {
+ ...fontStyles.bold,
+ color: colors.black,
+ textDecorationLine: 'underline'
}
});
@@ -448,6 +454,28 @@ class SendFlow extends PureComponent {
this.setState({ toInputHighlighted: !toInputHighlighted });
};
+ goToBuy = () => {
+ this.props.navigation.navigate('PaymentMethodSelector');
+ InteractionManager.runAfterInteractions(() => {
+ Analytics.trackEvent(ANALYTICS_EVENT_OPTS.WALLET_BUY_ETH);
+ });
+ };
+
+ renderBuyEth = () => {
+ if (!allowedToBuy(this.props.network)) {
+ return null;
+ }
+
+ return (
+ <>
+ {'\n'}
+
+ {strings('fiat_on_ramp.buy_eth')}
+
+ >
+ );
+ };
+
render = () => {
const { isPaymentChannelTransaction } = this.props;
const {
@@ -517,7 +545,15 @@ class SendFlow extends PureComponent {
{!isPaymentChannelTransaction && balanceIsZero && (
-
+
+ {strings('transaction.not_enough_for_gas')}
+
+ {this.renderBuyEth()}
+ >
+ }
+ />
)}
diff --git a/app/components/Views/TransactionSummary/index.js b/app/components/Views/TransactionSummary/index.js
index c3d69e56ced..8b315bd31de 100644
--- a/app/components/Views/TransactionSummary/index.js
+++ b/app/components/Views/TransactionSummary/index.js
@@ -1,66 +1,16 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { StyleSheet, View, ActivityIndicator, Text, TouchableOpacity } from 'react-native';
-import { colors, fontStyles } from '../../../styles/common';
+import { StyleSheet, View, ActivityIndicator, TouchableOpacity } from 'react-native';
+import { colors } from '../../../styles/common';
import { strings } from '../../../../locales/i18n';
import { TRANSACTION_TYPES } from '../../../util/transactions';
+import Summary from '../../Base/Summary';
+import Text from '../../Base/Text';
const styles = StyleSheet.create({
- summaryWrapper: {
- flexDirection: 'column',
- borderWidth: 1,
- borderColor: colors.grey050,
- borderRadius: 8,
- padding: 16
- },
- summaryRow: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- marginVertical: 6
- },
- totalCryptoRow: {
- alignItems: 'flex-end',
- marginTop: 8
- },
- textSummary: {
- ...fontStyles.normal,
- color: colors.black,
- fontSize: 12
- },
- textSummaryAmount: {
- textTransform: 'uppercase'
- },
- textFee: {
- fontStyle: 'italic'
- },
- textCrypto: {
- ...fontStyles.normal,
- textAlign: 'right',
- fontSize: 12,
- textTransform: 'uppercase',
- color: colors.grey500
- },
- textBold: {
- ...fontStyles.bold,
- alignSelf: 'flex-end'
- },
- separator: {
- borderBottomWidth: 1,
- borderBottomColor: colors.grey050,
- marginVertical: 6
- },
loader: {
backgroundColor: colors.white,
height: 10
- },
- transactionFeeLeft: {
- display: 'flex',
- flexDirection: 'row'
- },
- transactionEditText: {
- fontSize: 12,
- marginLeft: 8,
- color: colors.blue
}
});
@@ -93,28 +43,38 @@ export default class TransactionSummary extends PureComponent {
this.props.transactionType === TRANSACTION_TYPES.RECEIVED
) {
return (
-
-
- {strings('transaction.amount')}
- {amount}
-
+
+
+
+ {strings('transaction.amount')}
+
+
+ {amount}
+
+
{secondaryTotalAmount && (
-
- {secondaryTotalAmount}
-
+
+
+ {secondaryTotalAmount}
+
+
)}
-
+
);
}
return (
-
-
- {strings('transaction.amount')}
- {amount}
-
-
-
-
+
+
+
+ {strings('transaction.amount')}
+
+
+ {amount}
+
+
+
+
+
{!fee
? strings('transaction.transaction_fee_less')
: strings('transaction.transaction_fee')}
@@ -125,32 +85,39 @@ export default class TransactionSummary extends PureComponent {
onPress={onEditPress}
key="transactionFeeEdit"
>
-
+
+ {' '}
{strings('transaction.edit')}
)}
-
+
{!!fee &&
this.renderIfGastEstimationReady(
- {fee}
+
+ {fee}
+
)}
-
-
-
- {strings('transaction.total_amount')}
+
+
+
+
+ {strings('transaction.total_amount')}
+
{this.renderIfGastEstimationReady(
-
+
{totalAmount}
)}
-
-
+
+
{this.renderIfGastEstimationReady(
- {secondaryTotalAmount}
+
+ {secondaryTotalAmount}
+
)}
-
-
+
+
);
};
}
diff --git a/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap b/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap
index fb108b6e544..e3064207040 100644
--- a/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap
+++ b/app/components/Views/TransactionsView/__snapshots__/index.test.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TransactionsView should render correctly 1`] = `
- getNavbarOptions('transactions_view.title', navigation);
-
static propTypes = {
/**
* ETH to current currency conversion rate
@@ -214,7 +211,6 @@ class TransactionsView extends PureComponent {
render = () => {
const { conversionRate, currentCurrency, selectedAddress, navigation, networkType } = this.props;
-
return (
{this.state.loading ? (
@@ -254,4 +250,4 @@ const mapDispatchToProps = dispatch => ({
export default connect(
mapStateToProps,
mapDispatchToProps
-)(TransactionsView);
+)(withNavigation(TransactionsView));
diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js
index 3da33cac0a9..6efbe79bc9b 100644
--- a/app/core/AppConstants.js
+++ b/app/core/AppConstants.js
@@ -34,5 +34,17 @@ export default {
: 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/76.0.3809.123 Mobile/15E148 Safari/605.1',
NOTIFICATION_NAMES: {
accountsChanged: 'wallet_accountsChanged'
+ },
+ FIAT_ORDERS: {
+ TRANSAK_URL: 'https://global.transak.com/',
+ TRANSAK_URL_STAGING: 'https://staging-global.transak.com/',
+ TRANSAK_API_URL_PRODUCTION: 'https://api.transak.com/',
+ TRANSAK_API_URL_STAGING: 'https://staging-api.transak.com/',
+ TRANSAK_REDIRECT_URL: 'https://metamask.io/',
+ WYRE_API_ENDPOINT: 'https://api.sendwyre.com/',
+ WYRE_API_ENDPOINT_TEST: 'https://api.testwyre.com/',
+ WYRE_MERCHANT_ID: 'merchant.io.metamask.wyre',
+ WYRE_MERCHANT_ID_TEST: 'merchant.io.metamask.wyre.test',
+ POLLING_FREQUENCY: 10000
}
};
diff --git a/app/images/ApplePayLogo.png b/app/images/ApplePayLogo.png
new file mode 100644
index 00000000000..8bb4621b639
Binary files /dev/null and b/app/images/ApplePayLogo.png differ
diff --git a/app/images/ApplePayLogo@2x.png b/app/images/ApplePayLogo@2x.png
new file mode 100644
index 00000000000..2d93bbea3b2
Binary files /dev/null and b/app/images/ApplePayLogo@2x.png differ
diff --git a/app/images/ApplePayLogo@3x.png b/app/images/ApplePayLogo@3x.png
new file mode 100644
index 00000000000..1d77cb5b677
Binary files /dev/null and b/app/images/ApplePayLogo@3x.png differ
diff --git a/app/images/ApplePayMark.png b/app/images/ApplePayMark.png
new file mode 100644
index 00000000000..4a0f6c58311
Binary files /dev/null and b/app/images/ApplePayMark.png differ
diff --git a/app/images/ApplePayMark@2x.png b/app/images/ApplePayMark@2x.png
new file mode 100644
index 00000000000..3c0aa16c13d
Binary files /dev/null and b/app/images/ApplePayMark@2x.png differ
diff --git a/app/images/ApplePayMark@3x.png b/app/images/ApplePayMark@3x.png
new file mode 100644
index 00000000000..b3618ba58d8
Binary files /dev/null and b/app/images/ApplePayMark@3x.png differ
diff --git a/app/images/TransakLogo.png b/app/images/TransakLogo.png
new file mode 100644
index 00000000000..84242afac8b
Binary files /dev/null and b/app/images/TransakLogo.png differ
diff --git a/app/images/TransakLogo@2x.png b/app/images/TransakLogo@2x.png
new file mode 100644
index 00000000000..1f3519b4847
Binary files /dev/null and b/app/images/TransakLogo@2x.png differ
diff --git a/app/images/TransakLogo@3x.png b/app/images/TransakLogo@3x.png
new file mode 100644
index 00000000000..117606fd4a6
Binary files /dev/null and b/app/images/TransakLogo@3x.png differ
diff --git a/app/images/WyreLogo.png b/app/images/WyreLogo.png
new file mode 100644
index 00000000000..959537a4b7f
Binary files /dev/null and b/app/images/WyreLogo.png differ
diff --git a/app/images/WyreLogo@2x.png b/app/images/WyreLogo@2x.png
new file mode 100644
index 00000000000..fad71536ddd
Binary files /dev/null and b/app/images/WyreLogo@2x.png differ
diff --git a/app/images/WyreLogo@3x.png b/app/images/WyreLogo@3x.png
new file mode 100644
index 00000000000..40a125e2a1c
Binary files /dev/null and b/app/images/WyreLogo@3x.png differ
diff --git a/app/reducers/fiatOrders/index.js b/app/reducers/fiatOrders/index.js
new file mode 100644
index 00000000000..3e29333144d
--- /dev/null
+++ b/app/reducers/fiatOrders/index.js
@@ -0,0 +1,131 @@
+/**
+ * @typedef FiatOrder
+ * @type {object}
+ * @property {string} id - Original id given by Provider. Orders are identified by (provider, id)
+ * @property {FIAT_ORDER_PROVIDERS} provider Fiat Provider
+ * @property {number} createdAt Fiat amount
+ * @property {string} amount Fiat amount
+ * @property {string?} fee Fiat fee
+ * @property {string?} cryptoAmount Crypto currency amount
+ * @property {string?} cryptoFee Crypto currency fee
+ * @property {string} currency "USD"
+ * @property {string} cryptocurrency "ETH"
+ * @property {FIAT_ORDER_STATES} state Order state
+ * @property {string} account
+ * @property {string} network
+ * @property {?string} txHash
+ * @property {object} data original provider data
+ * @property {object} data.order : Wyre order response
+ * @property {object} data.transfer : Wyre transfer response
+ */
+
+/**
+ * @enum {string}
+ */
+export const FIAT_ORDER_PROVIDERS = {
+ WYRE: 'WYRE',
+ WYRE_APPLE_PAY: 'WYRE_APPLE_PAY',
+ TRANSAK: 'TRANSAK'
+};
+
+/**
+ * @enum {string}
+ */
+export const FIAT_ORDER_STATES = {
+ PENDING: 'PENDING',
+ FAILED: 'FAILED',
+ COMPLETED: 'COMPLETED',
+ CANCELLED: 'CANCELLED'
+};
+
+/**
+ * Selectors
+ */
+
+/**
+ * Get the provider display name
+ * @param {FIAT_ORDER_PROVIDERS} provider
+ */
+export const getProviderName = provider => {
+ switch (provider) {
+ case FIAT_ORDER_PROVIDERS.WYRE:
+ case FIAT_ORDER_PROVIDERS.WYRE_APPLE_PAY: {
+ return 'Wyre';
+ }
+ case FIAT_ORDER_PROVIDERS.TRANSAK: {
+ return 'Transak';
+ }
+ default: {
+ return provider;
+ }
+ }
+};
+
+export const getOrders = (orders, selectedAddress, network) =>
+ orders.filter(order => order.account === selectedAddress && order.network === network);
+
+export const getPendingOrders = (orders, selectedAddress, network) =>
+ orders.filter(
+ order =>
+ order.account === selectedAddress && order.network === network && order.state === FIAT_ORDER_STATES.PENDING
+ );
+
+export const getHasOrders = (orders, selectedAddress, network) =>
+ orders.some(order => order.account === selectedAddress && order.network === network);
+
+const initialState = {
+ orders: []
+};
+
+const findOrderIndex = (provider, id, orders) =>
+ orders.findIndex(order => order.id === id && order.provider === provider);
+
+const fiatOrderReducer = (state = initialState, action) => {
+ switch (action.type) {
+ case 'FIAT_ADD_ORDER': {
+ const orders = state.orders;
+ const order = action.payload;
+ const index = findOrderIndex(order.provider, order.id, orders);
+ if (index !== -1) {
+ return state;
+ }
+ return {
+ ...state,
+ orders: [action.payload, ...state.orders]
+ };
+ }
+ case 'FIAT_UPDATE_ORDER': {
+ const orders = state.orders;
+ const order = action.payload;
+ const index = findOrderIndex(order.provider, order.id, orders);
+ return {
+ ...state,
+ orders: [
+ ...orders.slice(0, index),
+ {
+ ...orders[index],
+ ...order
+ },
+ ...orders.slice(index + 1)
+ ]
+ };
+ }
+ case 'FIAT_REMOVE_ORDER': {
+ const orders = state.orders;
+ const order = action.payload;
+ const index = findOrderIndex(order.provider, order.id, state.orders);
+ return {
+ ...state,
+ orders: [...orders.slice(0, index), ...orders.slice(index + 1)]
+ };
+ }
+ case 'FIAT_RESET': {
+ return initialState;
+ }
+ default: {
+ return state;
+ }
+ }
+};
+
+export default fiatOrderReducer;
diff --git a/app/reducers/index.js b/app/reducers/index.js
index 6c6d64adde8..969d3ac20ea 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -10,6 +10,7 @@ import userReducer from './user';
import wizardReducer from './wizard';
import analyticsReducer from './analytics';
import onboardingReducer from './onboarding';
+import fiatOrders from './fiatOrders';
import notificationReducer from './notification';
import { combineReducers } from 'redux';
@@ -26,7 +27,8 @@ const rootReducer = combineReducers({
user: userReducer,
wizard: wizardReducer,
onboarding: onboardingReducer,
- notification: notificationReducer
+ notification: notificationReducer,
+ fiatOrders
});
export default rootReducer;
diff --git a/app/util/Logger.js b/app/util/Logger.js
index 3ad0bb39522..b7e0be72aea 100644
--- a/app/util/Logger.js
+++ b/app/util/Logger.js
@@ -1,6 +1,6 @@
'use strict';
-import { addBreadcrumb, captureException, withScope } from '@sentry/react-native';
+import { addBreadcrumb, captureException, captureMessage, withScope } from '@sentry/react-native';
import AsyncStorage from '@react-native-community/async-storage';
/**
@@ -55,4 +55,21 @@ export default class Logger {
}
}
}
+
+ /**
+ * captureMessage wrapper
+ *
+ * @param {object} args - data to be logged
+ * @returns - void
+ */
+ static async message(...args) {
+ // Check if user passed accepted opt-in to metrics
+ const metricsOptIn = await AsyncStorage.getItem('@MetaMask:metricsOptIn');
+ if (__DEV__) {
+ args.unshift('[MetaMask DEBUG]:');
+ console.log.apply(null, args); // eslint-disable-line no-console
+ } else if (metricsOptIn === 'agreed') {
+ captureMessage(JSON.stringify(args));
+ }
+ }
}
diff --git a/app/util/analytics.js b/app/util/analytics.js
index d35f4b1b98f..68daa1b2a84 100644
--- a/app/util/analytics.js
+++ b/app/util/analytics.js
@@ -105,7 +105,11 @@ const NAMES = {
DAPP_APPROVE_SCREEN_CANCEL: 'Cancel',
DAPP_APPROVE_SCREEN_EDIT_PERMISSION: 'Edit permission',
DAPP_APPROVE_SCREEN_EDIT_FEE: 'Edit tx fee',
- DAPP_APPROVE_SCREEN_VIEW_DETAILS: 'View tx details'
+ DAPP_APPROVE_SCREEN_VIEW_DETAILS: 'View tx details',
+ // Fiat Orders
+ WALLET_BUY_ETH: 'Buy ETH',
+ PAYMENTS_SELECTS_DEBIT_OR_ACH: 'Selects debit card or bank account as payment method',
+ PAYMENTS_SELECTS_APPLE_PAY: 'Selects Apple Pay as payment method'
};
const ACTIONS = {
@@ -144,7 +148,10 @@ const ACTIONS = {
// Send Flow
SEND_FLOW: 'Send Flow',
// Dapp Interactions
- APPROVE_REQUEST: 'Approve Request'
+ APPROVE_REQUEST: 'Approve Request',
+ BUY_ETH: 'Buy ETH',
+ SELECTS_DEBIT_OR_ACH: 'Selects Debit or ACH',
+ SELECTS_APPLE_PAY: 'Selects Apple Pay'
};
const CATEGORIES = {
@@ -161,7 +168,9 @@ const CATEGORIES = {
RECEIVE_OPTIONS: 'Receive Options',
INSTAPAY_VIEW: 'InstaPay View',
SEND_FLOW: 'Send Flow',
- DAPP_INTERACTIONS: 'Dapp Interactions'
+ DAPP_INTERACTIONS: 'Dapp Interactions',
+ WALLET: 'Wallet',
+ PAYMENTS: 'Payments'
};
export const ANALYTICS_EVENT_OPTS = {
@@ -471,5 +480,17 @@ export const ANALYTICS_EVENT_OPTS = {
CATEGORIES.DAPP_INTERACTIONS,
ACTIONS.APPROVE_REQUEST,
NAMES.DAPP_APPROVE_SCREEN_VIEW_DETAILS
+ ),
+ // Fiat Orders
+ WALLET_BUY_ETH: generateOpt(CATEGORIES.WALLET, ACTIONS.BUY_ETH, NAMES.WALLET_BUY_ETH),
+ PAYMENTS_SELECTS_DEBIT_OR_ACH: generateOpt(
+ CATEGORIES.PAYMENTS,
+ ACTIONS.SELECTS_DEBIT_OR_ACH,
+ NAMES.PAYMENTS_SELECTS_DEBIT_OR_ACH
+ ),
+ PAYMENTS_SELECTS_APPLE_PAY: generateOpt(
+ CATEGORIES.PAYMENTS,
+ ACTIONS.SELECTS_APPLE_PAY,
+ NAMES.PAYMENTS_SELECTS_APPLE_PAY
)
};
diff --git a/app/util/number.js b/app/util/number.js
index e7fd484c0f9..8232d470b8d 100644
--- a/app/util/number.js
+++ b/app/util/number.js
@@ -303,17 +303,24 @@ export function weiToFiat(wei, conversionRate, currencyCode, decimalsToShow = 5)
}
/**
- * Adds currency symbol to a value
+ * Renders fiat amount with currency symbol if exists
*
- * @param {number} wei - BN corresponding to an amount of wei
- * @param {string} currencyCode - Current currency code to display
+ * @param {number|string} amount Number corresponding to a currency amount
+ * @param {string} currencyCode Current currency code to display
* @returns {string} - Currency-formatted string
*/
-export function addCurrencySymbol(value, currencyCode) {
+export function addCurrencySymbol(amount, currencyCode) {
if (currencySymbols[currencyCode]) {
- return `${currencySymbols[currencyCode]}${value}`;
+ return `${currencySymbols[currencyCode]}${amount}`;
}
- return `${value} ${currencyCode}`;
+
+ const lowercaseCurrencyCode = currencyCode.toLowerCase();
+
+ if (currencySymbols[lowercaseCurrencyCode]) {
+ return `${currencySymbols[lowercaseCurrencyCode]}${amount}`;
+ }
+
+ return `${amount} ${currencyCode}`;
}
/**
@@ -377,10 +384,7 @@ export function balanceToFiat(balance, conversionRate, exchangeRate, currencyCod
return undefined;
}
const fiatFixed = balanceToFiatNumber(balance, conversionRate, exchangeRate);
- if (currencySymbols[currencyCode]) {
- return `${currencySymbols[currencyCode]}${fiatFixed}`;
- }
- return `${fiatFixed} ${currencyCode}`;
+ return addCurrencySymbol(fiatFixed, currencyCode);
}
/**
@@ -437,7 +441,7 @@ export function renderWei(value) {
return renderWei.toString();
}
/**
- * Formatc a string number in an string number with at most 5 decimal places
+ * Format a string number in an string number with at most 5 decimal places
*
* @param {string} number - String containing a number
* @returns {string} - String number with none or at most 5 decimal places
diff --git a/babel.config.js b/babel.config.js
index 599c51ce991..26781fed9ca 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line import/no-commonjs
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['transform-inline-environment-variables'],
diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj
index f08ff26f157..28d352e7f08 100644
--- a/ios/MetaMask.xcodeproj/project.pbxproj
+++ b/ios/MetaMask.xcodeproj/project.pbxproj
@@ -25,6 +25,7 @@
15F7795E22A1B7B500B1DF8C /* Mixpanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F7795722A1B79400B1DF8C /* Mixpanel.framework */; };
15F7795F22A1B7B500B1DF8C /* Mixpanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15F7795722A1B79400B1DF8C /* Mixpanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15F7796522A1BC8C00B1DF8C /* RCTAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 15F7796422A1BC8C00B1DF8C /* RCTAnalytics.m */; };
+ 18967E5A4221746AF1C1516F /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 909175ED86295C18A9811286 /* libPods-MetaMask.a */; };
2370F9A340CF4ADFBCFB0543 /* EuclidCircularB-RegularItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 58572D81B5D54ED79A16A16D /* EuclidCircularB-RegularItalic.otf */; };
298242C958524BB38FB44CAE /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C9FD3FB1258A41A5A0546C83 /* Roboto-BoldItalic.ttf */; };
2A27FC9EEF1F4FD18E658544 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = EF1C01B7F08047F9B8ADCFBA /* config.json */; };
@@ -47,7 +48,6 @@
CD13D926E1E84D9ABFE672C0 /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E2492C67CF345CABD7B8601 /* Roboto-BlackItalic.ttf */; };
D171C39A8BD44DBEB6B68480 /* EuclidCircularB-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 42CBA652072F4BE2A8B815C1 /* EuclidCircularB-MediumItalic.otf */; };
DC6A024F56DD43E1A83B47B1 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D5FF0FF1DFB74B3C8BB99E09 /* Roboto-MediumItalic.ttf */; };
- E2015246FC33F4C4B8E4155C /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B069442EBFA83EF178D30B2E /* libPods-MetaMask.a */; };
E34DE917F6FC4438A6E88402 /* EuclidCircularB-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 13EE4910D3BD408A8FCCA5D7 /* EuclidCircularB-BoldItalic.otf */; };
EF65C42EA15B4774B1947A12 /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C752564A28B44392AEE16BD5 /* Roboto-Medium.ttf */; };
FF0F3B13A5354C41913F766D /* EuclidCircularB-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 67FBD519E04742E0AF191782 /* EuclidCircularB-Bold.otf */; };
@@ -191,7 +191,6 @@
1F06D56A2D2F41FB9345D16F /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = Lottie.framework; path = System/Library/Frameworks/Lottie.framework; sourceTree = SDKROOT; };
278065D027394AD9B2906E38 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; };
2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
- 2E000D1F2B3B0387309934A2 /* Pods-MetaMask.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask.debug.xcconfig"; path = "Target Support Files/Pods-MetaMask/Pods-MetaMask.debug.xcconfig"; sourceTree = ""; };
3E2492C67CF345CABD7B8601 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BlackItalic.ttf"; path = "../app/fonts/Roboto-BlackItalic.ttf"; sourceTree = ""; };
42C239E9FAA64BD9A34B8D8A /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; };
42C6DDE3B80F47AFA9C9D4F5 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; };
@@ -209,8 +208,10 @@
654378AF243E2ADC00571B9C /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; };
67FBD519E04742E0AF191782 /* EuclidCircularB-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-Bold.otf"; path = "../app/fonts/EuclidCircularB-Bold.otf"; sourceTree = ""; };
684F2C84313849199863B5FE /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../app/fonts/Roboto-Black.ttf"; sourceTree = ""; };
+ 740D82312DA04CE122022365 /* Pods-MetaMask.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask.debug.xcconfig"; path = "Target Support Files/Pods-MetaMask/Pods-MetaMask.debug.xcconfig"; sourceTree = ""; };
7FF1597C0ACA4902B86140B2 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; };
8E369AC13A2049B6B21E5120 /* libRCTSearchApi.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTSearchApi.a; sourceTree = ""; };
+ 909175ED86295C18A9811286 /* libPods-MetaMask.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask.a"; sourceTree = BUILT_PRODUCTS_DIR; };
9499B01ECAC44DA29AC44E80 /* EuclidCircularB-SemiboldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-SemiboldItalic.otf"; path = "../app/fonts/EuclidCircularB-SemiboldItalic.otf"; sourceTree = ""; };
A498EA4CD2F8488DB666B94C /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; };
A783D1CD7D27456796FE2E1B /* Roboto-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Bold.ttf"; path = "../app/fonts/Roboto-Bold.ttf"; sourceTree = ""; };
@@ -218,8 +219,7 @@
A98029A3662F4C1391489A6B /* EuclidCircularB-Light.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-Light.otf"; path = "../app/fonts/EuclidCircularB-Light.otf"; sourceTree = ""; };
A98DB430A7DA47EFB97EDF8B /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; };
AA9EDF17249955C7005D89EE /* MetaMaskDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = MetaMaskDebug.entitlements; path = MetaMask/MetaMaskDebug.entitlements; sourceTree = ""; };
- B069442EBFA83EF178D30B2E /* libPods-MetaMask.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- B6684830E1789B9B249D040C /* Pods-MetaMask.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask.release.xcconfig"; path = "Target Support Files/Pods-MetaMask/Pods-MetaMask.release.xcconfig"; sourceTree = ""; };
+ B80AE1A0D2D7B86D04BCE696 /* Pods-MetaMask.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask.release.xcconfig"; path = "Target Support Files/Pods-MetaMask/Pods-MetaMask.release.xcconfig"; sourceTree = ""; };
BB8BA2D3C0354D6090B56A8A /* Roboto-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Light.ttf"; path = "../app/fonts/Roboto-Light.ttf"; sourceTree = ""; };
BF485CDA047B4D52852B87F5 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = ""; };
C752564A28B44392AEE16BD5 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Medium.ttf"; path = "../app/fonts/Roboto-Medium.ttf"; sourceTree = ""; };
@@ -250,7 +250,7 @@
15ACC9FC22655C3A0063978B /* Lottie.framework in Frameworks */,
15F7795E22A1B7B500B1DF8C /* Mixpanel.framework in Frameworks */,
153F84CA2319B8FD00C19B63 /* Branch.framework in Frameworks */,
- E2015246FC33F4C4B8E4155C /* libPods-MetaMask.a in Frameworks */,
+ 18967E5A4221746AF1C1516F /* libPods-MetaMask.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -358,7 +358,7 @@
children = (
153C1A742217BCDC0088EFE0 /* JavaScriptCore.framework */,
2D16E6891FA4F8E400B85C8A /* libReact.a */,
- B069442EBFA83EF178D30B2E /* libPods-MetaMask.a */,
+ 909175ED86295C18A9811286 /* libPods-MetaMask.a */,
);
name = Frameworks;
sourceTree = "";
@@ -460,8 +460,8 @@
AA342D524556DBBE26F5997C /* Pods */ = {
isa = PBXGroup;
children = (
- 2E000D1F2B3B0387309934A2 /* Pods-MetaMask.debug.xcconfig */,
- B6684830E1789B9B249D040C /* Pods-MetaMask.release.xcconfig */,
+ 740D82312DA04CE122022365 /* Pods-MetaMask.debug.xcconfig */,
+ B80AE1A0D2D7B86D04BCE696 /* Pods-MetaMask.release.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -473,7 +473,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "MetaMask" */;
buildPhases = (
- 6DA7FCA07245EB28ED1BEB65 /* [CP] Check Pods Manifest.lock */,
+ 1EFD70C7D753BD5DBF716A3E /* [CP] Check Pods Manifest.lock */,
65E00B0A247EA25400E5AC88 /* Start Packager */,
15FDD86321B76696006B7C35 /* Override xcconfig files */,
13B07F871A680F5B00A75B9A /* Sources */,
@@ -481,7 +481,7 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
15ACCA0022655C3A0063978B /* Embed Frameworks */,
- AB6DBF608FE351FFD3A85A0F /* [CP] Copy Pods Resources */,
+ 6EDA5DD4542EEC12BF192BB6 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -703,7 +703,7 @@
shellPath = /bin/sh;
shellScript = "if [ -e ../.ios.env ]\nthen\n cp -rf ../.ios.env debug.xcconfig\n cp -rf ../.ios.env release.xcconfig\nelse\n cp -rf ../.ios.env.example debug.xcconfig\n cp -rf ../.ios.env.example release.xcconfig\nfi\n\n";
};
- 65E00B0A247EA25400E5AC88 /* Start Packager */ = {
+ 1EFD70C7D753BD5DBF716A3E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -711,18 +711,21 @@
inputFileListPaths = (
);
inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
);
- name = "Start Packager";
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-MetaMask-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 6DA7FCA07245EB28ED1BEB65 /* [CP] Check Pods Manifest.lock */ = {
+ 65E00B0A247EA25400E5AC88 /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -730,21 +733,18 @@
inputFileListPaths = (
);
inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
);
- name = "[CP] Check Pods Manifest.lock";
+ name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-MetaMask-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
- AB6DBF608FE351FFD3A85A0F /* [CP] Copy Pods Resources */ = {
+ 6EDA5DD4542EEC12BF192BB6 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -839,7 +839,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 2E000D1F2B3B0387309934A2 /* Pods-MetaMask.debug.xcconfig */;
+ baseConfigurationReference = 740D82312DA04CE122022365 /* Pods-MetaMask.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
@@ -847,7 +847,7 @@
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 511;
+ CURRENT_PROJECT_VERSION = 518;
DEAD_CODE_STRIPPING = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 48XVW22RCG;
@@ -902,7 +902,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = B6684830E1789B9B249D040C /* Pods-MetaMask.release.xcconfig */;
+ baseConfigurationReference = B80AE1A0D2D7B86D04BCE696 /* Pods-MetaMask.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
@@ -910,7 +910,7 @@
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 511;
+ CURRENT_PROJECT_VERSION = 518;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 48XVW22RCG;
FRAMEWORK_SEARCH_PATHS = (
diff --git a/ios/MetaMask/MetaMask.entitlements b/ios/MetaMask/MetaMask.entitlements
index 0ee0d28c177..70d4d8f04a3 100644
--- a/ios/MetaMask/MetaMask.entitlements
+++ b/ios/MetaMask/MetaMask.entitlements
@@ -12,8 +12,8 @@
com.apple.developer.in-app-payments
- merchant.io.metamask
- merchant.io.metamask-test
+ merchant.io.metamask.wyre
+ merchant.io.metamask.wyre.test
diff --git a/ios/Podfile b/ios/Podfile
index 728a441cda1..dd039c0d771 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -104,6 +104,7 @@ target 'MetaMask' do
add_flipper_pods!
+
post_install do |installer|
flipper_post_install(installer)
end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 1d95a3f2a63..1c32727cacf 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -278,7 +278,9 @@ PODS:
- React
- react-native-viewpager (3.3.0):
- React
- - react-native-webview (7.0.5):
+ - react-native-webview (10.7.0):
+ - React
+ - react-native-webview-forked (7.0.5):
- React
- React-RCTActionSheet (0.62.2):
- React-Core/RCTActionSheetHeaders (= 0.62.2)
@@ -344,6 +346,8 @@ PODS:
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/callinvoker (= 0.62.2)
+ - ReactNativePayments (1.5.0):
+ - React
- RNCAsyncStorage (1.9.0):
- React
- RNCCheckbox (0.4.2):
@@ -436,6 +440,7 @@ DEPENDENCIES:
- react-native-view-shot (from `../node_modules/react-native-view-shot`)
- "react-native-viewpager (from `../node_modules/@react-native-community/viewpager`)"
- react-native-webview (from `../node_modules/react-native-webview`)
+ - react-native-webview-forked (from `../node_modules/react-native-webview-forked`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
- React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`)
@@ -448,6 +453,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - "ReactNativePayments (from `../node_modules/@exodus/react-native-payments/lib/ios`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCCheckbox (from `../node_modules/@react-native-community/checkbox`)"
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
@@ -540,6 +546,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/viewpager"
react-native-webview:
:path: "../node_modules/react-native-webview"
+ react-native-webview-forked:
+ :path: "../node_modules/react-native-webview-forked"
React-RCTActionSheet:
:path: "../node_modules/react-native/Libraries/ActionSheetIOS"
React-RCTAnimation:
@@ -562,6 +570,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Vibration"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ ReactNativePayments:
+ :path: "../node_modules/@exodus/react-native-payments/lib/ios"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNCCheckbox:
@@ -639,7 +649,8 @@ SPEC CHECKSUMS:
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-view-shot: 4475fde003fe8a210053d1f98fb9e06c1d834e1c
react-native-viewpager: a7b438ca32c57b2614ece2a123e7fe116f743131
- react-native-webview: 174e0f8f1bf547224a134215607c75c4bb0312b7
+ react-native-webview: 6edf4d6f71b9161fc3e96083726a538ee395304d
+ react-native-webview-forked: 30ecda2456675d54273c274a5d659faad44b17ad
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
@@ -651,6 +662,7 @@ SPEC CHECKSUMS:
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
+ ReactNativePayments: a4e3ac915256a4e759c8a04338b558494a63a0f5
RNCAsyncStorage: 453cd7c335ec9ba3b877e27d02238956b76f3268
RNCCheckbox: 357578d3b42652c78ee9a1bb9bcfc3195af6e161
RNCClipboard: 8148e21ac347c51fd6cd4b683389094c216bb543
@@ -672,6 +684,6 @@ SPEC CHECKSUMS:
Yoga: 3ebccbdd559724312790e7742142d062476b698e
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 17c3a229d2052b61aedf2b90c073f6a3342b048f
+PODFILE CHECKSUM: 038ce06ee40546e81f34f1057e015a7a12547d35
COCOAPODS: 1.9.3
diff --git a/locales/en.json b/locales/en.json
index 90c6579c6ef..2cc02583751 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -245,7 +245,11 @@
"private_key_detected": "Private key detected",
"do_you_want_to_import_this_account": "Do you want to import this account?",
"error": "Error",
- "logout_to_import_seed": "You need to log out first in order to import a seed phrase."
+ "logout_to_import_seed": "You need to log out first in order to import a seed phrase.",
+ "ready_to_explore": "Ready to start exploring blockchain applications?"
+ },
+ "activity_view": {
+ "title": "Activity"
},
"transactions_view": {
"title": "Transactions"
@@ -560,7 +564,13 @@
"could_not_resolve_ens": "Couldn't resolve ENS",
"asset": "Asset",
"balance": "Balance",
- "not_enough_for_gas": "You have 0 ETH in your account to pay for transaction fees. Buy some ETH or deposit from another account."
+ "not_enough_for_gas": "You have 0 ETH in your account to pay for transaction fees. Buy some ETH or deposit from another account.",
+ "send": "Send",
+ "confirmed": "Confirmed",
+ "pending": "Pending",
+ "submitted": "Submitted",
+ "failed": "Failed",
+ "cancelled": "Cancelled"
},
"custom_gas": {
"total": "Total",
@@ -1104,10 +1114,13 @@
"request_title": "Request",
"request_description": "Request assets from friends",
"buy_title": "Buy",
- "buy_description": "Buy Crypto with Credit Card",
+ "buy_description": "Buy crypto with debit card or bank transfer",
"public_address": "Public Address",
"public_address_qr_code": "Public Address",
- "coming_soon": "Coming soon..."
+ "coming_soon": "Coming soon...",
+ "request_payment": "Request Payment",
+ "copy": "Copy",
+ "scan_address": "Scan address to receive payment"
},
"experimental_settings": {
"payment_channels": "Payment Channels",
@@ -1225,6 +1238,76 @@
"title": "You're all set!",
"text": "You can now return to your browser"
},
+ "account_bar": {
+ "depositing_to": "Depositing to:"
+ },
+ "fiat_on_ramp": {
+ "buy_eth": "Buy ETH",
+ "purchased_currency": "Purchased {{currency}}",
+ "network_not_supported": "Current network not supported",
+ "switch_network": "Please switch to Mainnet",
+ "switch": "Switch",
+ "purchases": "Purchases",
+ "purchase_method": "Purchase Method",
+ "amount_to_buy": "Amount to buy",
+ "transak_webview_title": "Transak",
+ "wyre_user_agreement": "Wyre User Agreement",
+ "wyre_terms_of_service": "Wyre Terms of Service",
+ "best_deal": "Best deal",
+ "purchase_method_title": {
+ "wyre_first_line": "0% fee when you use",
+ "wyre_second_line": "Apple Pay.",
+ "wyre_sub_header": "Valid until July 1st, 2020",
+ "first_line": "How do you want to make",
+ "second_line": "your purchase?"
+ },
+ "bank_transfer_debit": "Bank transfer or debit card",
+ "requires_registration": "Requires registration",
+ "options_fees_vary": "Options and fees vary based on location",
+ "countries": "countries",
+ "some_states_excluded": "Some states excluded",
+ "purchase_method_modal_close": "Close",
+ "modal_transak_support": "Transak Support",
+ "modal_wyre_support": "Wyre Support",
+ "transak_cta": "Buy ETH with Transak",
+ "transak_modal_text": "Exact payment methods and fees vary depending on location. Supported countries are:",
+ "apple_pay": "Apple Pay",
+ "via": "via",
+ "fee": "fee",
+ "Fee": "Fee",
+ "limited_time": "limited time",
+ "wyre_loading_rates": " ",
+ "wyre_estimated": "Estimated {{amount}} {{currency}}",
+ "wyre_minutes": "1 - 2 minutes",
+ "wyre_max": "Max $450 weekly",
+ "wyre_requires_debit_card": "Requires debit card",
+ "wyre_us_only": "🇺🇸 U.S. only",
+ "wyre_modal_text": "Paying with Apple Pay, powered by Wyre is supported in the United Sates 🇺🇸 except for CT, HI, NC, NH, NY, VA and VT.",
+ "wyre_modal_terms_of_service_apply": "Wyre terms of service apply.",
+ "wyre_minimum_deposit": "Minimum deposit is {{amount}}",
+ "wyre_maximum_deposit": "Maximum deposit is {{amount}}",
+ "wyre_purchase": "{{currency}} Purchase",
+ "buy_with": "Buy with",
+ "plus_fee": "Plus a {{fee}} fee",
+ "notifications": {
+ "purchase_failed_title": "Purchase of {{currency}} has failed! Please try again, sorry for the inconvenience!",
+ "purchase_cancelled_title": "Your purchase was cancelled",
+ "purchase_completed_title": "Your purchase of {{amount}} {{currency}} was successful!",
+ "purchase_completed_description": "Your {{currency}} is now available",
+ "purchase_pending_title": "Processing your purchase of {{currency}}",
+ "purchase_pending_description": "Your deposit is in progress"
+ },
+ "date": "Date",
+ "from": "From",
+ "to": "To",
+ "status": "Status",
+ "completed": "Completed",
+ "pending": "Pending",
+ "failed": "Failed",
+ "cancelled": "Canceled",
+ "amount": "Amount",
+ "total_amount": "Total amount"
+ },
"protect_wallet_modal": {
"title": "Protect your wallet",
"top_button": "Protect wallet",
diff --git a/locales/es.json b/locales/es.json
index 38f4dfcbd6b..bff134c17a8 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -172,7 +172,10 @@
"send": {
"title": "Enviar",
"deeplink_failure": "Ooops! Algo ha salido mal, por favor intėntalo de nuevo",
- "warn_network_change": "La red ha sido cambiada a "
+ "warn_network_change": "La red ha sido cambiada a ",
+ "send_to": "Enviar a",
+ "confirm": "Confirmar",
+ "amount": "Monto"
},
"receive": {
"title": "Recibir"
@@ -203,7 +206,11 @@
"private_key_detected": "Clave privada detectada",
"do_you_want_to_import_this_account": "Deseas importar esta cuenta?",
"error": "Error",
- "logout_to_import_seed": "Primero debes cerrar la sesión para poder importar una nueva frase semilla."
+ "logout_to_import_seed": "Primero debes cerrar la sesión para poder importar una nueva frase semilla.",
+ "ready_to_explore": "¿Listo para explorar aplicaciones blockchain?"
+ },
+ "activity_view": {
+ "title": "Actividad"
},
"transactions_view": {
"title": "Transacciones"
@@ -508,7 +515,13 @@
"could_not_resolve_ens": "No se pudo resolver nombre ENS",
"asset": "Activo",
"balance": "Balance",
- "not_enough_for_gas": "Tienes 0 ETH en tu cuenta para pagar por la tarifa de transacción. Compra ETH o depositalo desde otra cuenta."
+ "not_enough_for_gas": "Tienes 0 ETH en tu cuenta para pagar por la tarifa de transacción. Compra ETH o deposítalo desde otra cuenta.",
+ "send": "Enviar",
+ "confirmed": "Confirmada",
+ "pending": "Pendiente",
+ "submitted": "Enviada",
+ "failed": "Fallida",
+ "cancelled": "Cancelada"
},
"custom_gas": {
"advanced_options": "Mostrar opciones avanzadas",
@@ -639,7 +652,10 @@
"address_copied_to_clipboard": "La dirección fue copiada al portapapeles",
"transaction_error": "Error de transacción",
"address_to_placeholder": "Buscar, dirección pública (0x), o ENS",
- "address_from_balance": "Balance:"
+ "address_from_balance": "Balance:",
+ "status": "Estado",
+ "date": "Fecha",
+ "nonce": "Nonce"
},
"address_book": {
"recents": "Recientes",
@@ -971,10 +987,13 @@
"request_title": "Solicitar",
"request_description": "Solicitar activos de amigos",
"buy_title": "Comprar",
- "buy_description": "Compre con tarjeta de crédito",
+ "buy_description": "Compre con tarjeta de dédito o transferencia bancaria",
"public_address": "Dirección Pública",
"public_address_qr_code": "Dirección Pública",
- "coming_soon": "Pronto..."
+ "coming_soon": "Pronto...",
+ "request_payment": "Solicitar Pago",
+ "copy": "Copiar",
+ "scan_address": "Escanee dirección para recibir pago"
},
"experimental_settings": {
"payment_channels": "Canales de Pago",
@@ -1088,6 +1107,76 @@
"title": "Listo!",
"text": "Ahora puedes volver a tu navegador"
},
+ "account_bar": {
+ "depositing_to": "Depositando a:"
+ },
+ "fiat_on_ramp": {
+ "buy_eth": "Comprar ETH",
+ "purchased_currency": "Compra de {{currency}}",
+ "network_not_supported": "La red actual no esta soportada",
+ "switch_network": "Por favor cambiar a Mainnet",
+ "switch": "Cambiar",
+ "purchases": "Compras",
+ "purchase_method": "Método de pago",
+ "amount_to_buy": "Monto a comprar",
+ "transak_webview_title": "Transak",
+ "wyre_user_agreement": "Acuerdo de Usuario de Wyre",
+ "wyre_terms_of_service": "Términos de Uso de Wyre",
+ "best_deal": "Mejor oferta",
+ "purchase_method_title": {
+ "wyre_first_line": "0% de comisión al",
+ "wyre_second_line": "pagar con Apple Pay.",
+ "wyre_sub_header": "Válido hasta 1 Jul, 2020",
+ "first_line": "¿Cómo quiere pagar",
+ "second_line": "su compra?"
+ },
+ "bank_transfer_debit": "Transferencia bancaria o tarjeta de débito",
+ "requires_registration": "Requiere registro",
+ "options_fees_vary": "Opciones y comisiones varían según ubicación",
+ "countries": "países",
+ "some_states_excluded": "Algunos estados excluídos",
+ "purchase_method_modal_close": "Cerrar",
+ "modal_transak_support": "Disponibilidad de Transak",
+ "modal_wyre_support": "Disponibilidad de Wyre",
+ "transak_cta": "Comprar ETH con Transak",
+ "transak_modal_text": "Métodos de pago y comisiones varían dependiendo de la ubicación. Países disponibles:",
+ "apple_pay": "Apple Pay",
+ "via": "via",
+ "fee": "comisión",
+ "Fee": "Comisión",
+ "limited_time": "tiempo limitado",
+ "wyre_loading_rates": " ",
+ "wyre_estimated": "Estimado {{amount}} {{currency}}",
+ "wyre_minutes": "1 - 2 minutos",
+ "wyre_max": "Máx. $450 semanal",
+ "wyre_requires_debit_card": "Requiere tarjeta de débito",
+ "wyre_us_only": "🇺🇸 solo EUA",
+ "wyre_modal_text": "El pago con Apple Pay, provisto por Wyre es aceptado en los Estados Unidos 🇺🇸 a excepción de CT, HI, NC, NH, NY, VA y VT.",
+ "wyre_modal_terms_of_service_apply": "Rige Acuerdo de Usuario de Wyre.",
+ "wyre_minimum_deposit": "El monto mínimo es {{amount}}",
+ "wyre_maximum_deposit": "El monto máximo es {{amount}}",
+ "wyre_purchase": "Comprar {{currency}}",
+ "buy_with": "Comprar con",
+ "plus_fee": "Más {{fee}} de comisión",
+ "notifications": {
+ "purchase_failed_title": "¡Su compra de {{currency}} ha fallado! Lo sentimos, por favor intente de nuevo",
+ "purchase_cancelled_title": "Su compra fue cancelada",
+ "purchase_completed_title": "¡Su compra de {{amount}} {{currency}} fue exitosa!",
+ "purchase_completed_description": "Su {{currency}} está disponible",
+ "purchase_pending_title": "Procesando su compra de {{currency}}",
+ "purchase_pending_description": "Su depósito está en progreso"
+ },
+ "date": "Fecha",
+ "from": "Desde",
+ "to": "Para",
+ "status": "Estado",
+ "completed": "Realizada",
+ "pending": "Pendiente",
+ "failed": "Fallida",
+ "cancelled": "Cancelada",
+ "amount": "Cantidad",
+ "total_amount": "Cantidad total"
+ },
"protect_wallet_modal": {
"title": "Protege tu billetera",
"top_button": "Proteger billetera",
diff --git a/package.json b/package.json
index ec192fbba47..237282a3b18 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"react-native-level-fs/**/bl": "^0.9.5"
},
"dependencies": {
+ "@exodus/react-native-payments": "https://github.com/wachunei/react-native-payments.git#package-json-hack",
"@metamask/controllers": "2.0.3",
"@react-native-community/async-storage": "1.9.0",
"@react-native-community/blur": "^3.6.0",
@@ -83,6 +84,7 @@
"@walletconnect/client": "1.0.0-rc.3",
"@walletconnect/utils": "1.0.0-rc.3",
"asyncstorage-down": "4.2.0",
+ "axios": "^0.19.2",
"babel-plugin-transform-inline-environment-variables": "0.4.3",
"babel-plugin-transform-remove-console": "6.9.4",
"base-64": "0.1.0",
@@ -121,6 +123,7 @@
"pubnub": "4.27.3",
"pump": "3.0.0",
"qs": "6.7.0",
+ "query-string": "^6.12.1",
"react": "16.11.0",
"react-native": "0.62.2",
"react-native-actionsheet": "beefe/react-native-actionsheet#107/head",
@@ -167,7 +170,8 @@
"react-native-v8": "^0.62.2-patch.1",
"react-native-vector-icons": "6.4.2",
"react-native-view-shot": "^3.1.2",
- "react-native-webview": "git+https://github.com/MetaMask/react-native-webview.git#931ae99a0f80a650c958e9d2e39e4ed0e68d95a7",
+ "react-native-webview": "10.7.0",
+ "react-native-webview-forked": "git+https://github.com/MetaMask/react-native-webview#8c1942b4d3887a80d5d88128a5e55f3944f7fe21",
"react-navigation": "4.0.10",
"react-navigation-drawer": "1.4.0",
"react-navigation-stack": "1.7.3",
@@ -207,6 +211,7 @@
"husky": "1.3.1",
"jest": "^25.2.7",
"jest-serializer": "24.4.0",
+ "jetifier": "^1.6.6",
"lint-staged": "8.1.5",
"metro": "^0.59.0",
"metro-react-native-babel-preset": "^0.59.0",
diff --git a/scripts/postinstall.sh b/scripts/postinstall.sh
index 85d4739beda..7c1b96f0164 100755
--- a/scripts/postinstall.sh
+++ b/scripts/postinstall.sh
@@ -4,13 +4,16 @@ echo "PostInstall script:"
echo "1. React Native nodeify..."
node_modules/.bin/rn-nodeify --install 'crypto,buffer,react-native-randombytes,vm,stream,http,https,os,url,net,fs' --hack
-echo "2. Patch npm packages"
+echo "2. jetify"
+npx jetify
+
+echo "3. Patch npm packages"
npx patch-package
-echo "2. Create xcconfig files..."
+echo "4. Create xcconfig files..."
echo "" > ios/debug.xcconfig
echo "" > ios/release.xcconfig
-echo "3. Init git submodules"
+echo "5. Init git submodules"
echo "This may take a while..."
git submodule update --init
diff --git a/yarn.lock b/yarn.lock
index f13172361d2..c35ad4af556 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -735,6 +735,14 @@
dependencies:
"@types/hammerjs" "^2.0.36"
+"@exodus/react-native-payments@https://github.com/wachunei/react-native-payments.git#package-json-hack":
+ version "1.5.0"
+ resolved "https://github.com/wachunei/react-native-payments.git#dbc8cbbed570892d2fea5e3d183bf243e062c1e5"
+ dependencies:
+ es6-error "^4.0.2"
+ uuid "3.3.2"
+ validator "^7.0.0"
+
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -2096,6 +2104,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
+axios@^0.19.2:
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
+ integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
+ dependencies:
+ follow-redirects "1.5.10"
+
babel-core@7.0.0-bridge.0:
version "7.0.0-bridge.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
@@ -3367,7 +3382,7 @@ debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
-debug@3.1.0:
+debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -3979,6 +3994,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
+es6-error@^4.0.2:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
+ integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
+
es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -4001,6 +4021,11 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escape-string-regexp@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
+ integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
+
escodegen@1.x.x, escodegen@^1.11.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
@@ -5440,6 +5465,13 @@ fn-name@~2.0.1:
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
for-each@~0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -7150,6 +7182,11 @@ jetifier@^1.6.2:
resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.5.tgz#ea87324a4230bef20a9651178ecab978ee54a8cb"
integrity sha512-T7yzBSu9PR+DqjYt+I0KVO1XTb1QhAfHnXV5Nd3xpbXM6Xg4e3vP60Q4qkNU8Fh6PHC2PivPUNN3rY7G2MxcDQ==
+jetifier@^1.6.6:
+ version "1.6.6"
+ resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.6.tgz#fec8bff76121444c12dc38d2dad6767c421dab68"
+ integrity sha512-JNAkmPeB/GS2tCRqUzRPsTOHpGDah7xP18vGJfIjZC+W2sxEHbxgJxetIjIqhjQ3yYbYNEELkM/spKLtwoOSUQ==
+
js-sha3@0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a"
@@ -9684,6 +9721,15 @@ qs@~6.5.2:
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
query-string@^6.11.1:
+ version "6.13.1"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad"
+ integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA==
+ dependencies:
+ decode-uri-component "^0.2.0"
+ split-on-first "^1.0.0"
+ strict-uri-encode "^2.0.0"
+
+query-string@^6.12.1:
version "6.12.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.12.1.tgz#2ae4d272db4fba267141665374e49a1de09e8a7c"
integrity sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA==
@@ -10139,13 +10185,21 @@ react-native-view-shot@^3.1.2:
resolved "https://registry.yarnpkg.com/react-native-view-shot/-/react-native-view-shot-3.1.2.tgz#8c8e84c67a4bc8b603e697dbbd59dbc9b4f84825"
integrity sha512-9u9fPtp6a52UMoZ/UCPrCjKZk8tnkI9To0Eh6yYnLKFEGkRZ7Chm6DqwDJbYJHeZrheCCopaD5oEOnRqhF4L2Q==
-"react-native-webview@git+https://github.com/MetaMask/react-native-webview.git#931ae99a0f80a650c958e9d2e39e4ed0e68d95a7":
+"react-native-webview-forked@git+https://github.com/MetaMask/react-native-webview#8c1942b4d3887a80d5d88128a5e55f3944f7fe21":
version "7.0.5"
- resolved "git+https://github.com/MetaMask/react-native-webview.git#931ae99a0f80a650c958e9d2e39e4ed0e68d95a7"
+ resolved "git+https://github.com/MetaMask/react-native-webview#8c1942b4d3887a80d5d88128a5e55f3944f7fe21"
dependencies:
escape-string-regexp "1.0.5"
invariant "2.2.4"
+react-native-webview@10.7.0:
+ version "10.7.0"
+ resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-10.7.0.tgz#b96e152ffdae4eeffaa74f43671a35733885b52d"
+ integrity sha512-4TSYwJqMBUTKB9+xqGbPwx+eLXbp6RRD7lQ2BumT8eSTfuuqr2rXNqcrlKU1VRla7QGGYowmYmxl2aXIx5k9wA==
+ dependencies:
+ escape-string-regexp "2.0.0"
+ invariant "2.2.4"
+
react-native@0.62.2:
version "0.62.2"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.62.2.tgz#d831e11a3178705449142df19a70ac2ca16bad10"
@@ -12193,6 +12247,11 @@ uuid@2.0.1:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac"
integrity sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=
+uuid@3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
+ integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
+
uuid@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
@@ -12235,6 +12294,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+validator@^7.0.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/validator/-/validator-7.2.0.tgz#a63dcbaba51d4350bf8df20988e0d5a54d711791"
+ integrity sha512-c8NGTUYeBEcUIGeMppmNVKHE7wwfm3mYbNZxV+c5mlv9fDHI7Ad3p07qfNrn/CvpdkK2k61fOLRO2sTEhgQXmg==
+
varint@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf"