Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/approve eip 681 #1847

Merged
merged 18 commits into from
Oct 14, 2020
2 changes: 1 addition & 1 deletion app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ class Main extends PureComponent {
}

if (data && data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE) {
this.props.toggleApproveModal();
!this.props.approveModalVisible && this.props.toggleApproveModal();
} else {
this.props.toggleDappTransactionModal();
}
Expand Down
14 changes: 4 additions & 10 deletions app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,10 +687,8 @@ export function getOfflineModalNavbar(navigation) {
* @returns {Object} - Corresponding navbar options containing headerTitle, headerTitle and headerTitle
*/
export function getWalletNavbarOptions(title, navigation) {
const onScanSuccess = data => {
if (data.target_address) {
navigation.navigate('SendView', { txMeta: data });
} else if (data.private_key) {
const onScanSuccess = (data, content) => {
if (data.private_key) {
Alert.alert(
strings('wallet.private_key_detected'),
strings('wallet.do_you_want_to_import_this_account'),
Expand Down Expand Up @@ -723,13 +721,9 @@ export function getWalletNavbarOptions(title, navigation) {
}, 500);
} else if (data.seed) {
Alert.alert(strings('wallet.error'), strings('wallet.logout_to_import_seed'));
} else if (data && data.indexOf(AppConstants.MM_UNIVERSAL_LINK_HOST) !== -1) {
} else {
setTimeout(() => {
DeeplinkManager.parse(data);
}, 500);
} else if ((data && data.indexOf('https://') !== -1) || data.indexOf('http://')) {
setTimeout(() => {
DeeplinkManager.parse(data);
DeeplinkManager.parse(content, { origin: AppConstants.DEEPLINKS.ORIGIN_QR_CODE });
}, 500);
}
};
Expand Down
139 changes: 87 additions & 52 deletions app/components/UI/TransactionHeader/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, View, Text } from 'react-native';
import { colors, fontStyles } from '../../../styles/common';
Expand All @@ -7,6 +7,10 @@ import WebsiteIcon from '../WebsiteIcon';
import { getHost, getUrlObj } from '../../../util/browser';
import networkList from '../../../util/networks';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import AppConstants from '../../../core/AppConstants';
import { renderShortAddress } from '../../../util/address';

const { ORIGIN_DEEPLINK, ORIGIN_QR_CODE } = AppConstants.DEEPLINKS;

const styles = StyleSheet.create({
transactionHeader: {
Expand Down Expand Up @@ -55,31 +59,34 @@ const styles = StyleSheet.create({
padding: 5,
color: colors.black,
textTransform: 'capitalize'
},
deeplinkIconContainer: {
borderWidth: 1,
borderColor: colors.grey600,
width: 56,
height: 56,
borderRadius: 38
},
deeplinkIcon: {
alignSelf: 'center',
lineHeight: 56
}
});

/**
* PureComponent that renders the transaction header used for signing, granting permissions and sending
*/
class TransactionHeader extends PureComponent {
static propTypes = {
/**
* Object containing current page title and url
*/
currentPageInformation: PropTypes.object,
/**
* String representing the selected network
*/
networkType: PropTypes.string
};

const TransactionHeader = props => {
const originIsDeeplink =
props.currentPageInformation.origin === ORIGIN_DEEPLINK ||
props.currentPageInformation.origin === ORIGIN_QR_CODE;
/**
* Returns a small circular indicator, red if the current selected network is offline, green if it's online.
*=
*
* @return {element} - JSX view element
*/
renderNetworkStatusIndicator = () => {
const { networkType } = this.props;
const renderNetworkStatusIndicator = () => {
const { networkType } = props;
const networkStatusIndicatorColor = (networkList[networkType] && networkList[networkType].color) || colors.red;
const networkStatusIndicator = (
<View style={[styles.networkStatusIndicator, { backgroundColor: networkStatusIndicatorColor }]} />
Expand All @@ -89,49 +96,77 @@ class TransactionHeader extends PureComponent {

/**
* Returns a secure icon next to the dApp URL. Lock for https protocol, warning sign otherwise.
*=
*
* @return {element} - JSX image element
*/
renderSecureIcon = () => {
const { url } = this.props.currentPageInformation;
const secureIcon = (
<FontAwesome
name={getUrlObj(url).protocol === 'https:' ? 'lock' : 'warning'}
size={15}
style={styles.secureIcon}
const renderSecureIcon = () => {
if (originIsDeeplink) return null;
const { url } = props.currentPageInformation;
const name = getUrlObj(url).protocol === 'https:' ? 'lock' : 'warning';
return <FontAwesome name={name} size={15} style={styles.secureIcon} />;
};

const renderTopIcon = () => {
const { url, currentEnsName, icon, origin } = props.currentPageInformation;
if (originIsDeeplink) {
return (
<View style={styles.deeplinkIconContainer}>
<FontAwesome
style={styles.deeplinkIcon}
name={origin === ORIGIN_DEEPLINK ? 'link' : 'qrcode'}
size={32}
color={colors.grey600}
/>
</View>
);
}
return (
<WebsiteIcon
style={styles.domainLogo}
viewStyle={styles.assetLogo}
title={getHost(currentEnsName || url)}
url={currentEnsName || url}
icon={icon}
/>
);
return secureIcon;
};

render() {
const {
currentPageInformation: { url, currentEnsName, icon },
networkType
} = this.props;
const title = getHost(currentEnsName || url);
const networkName = networkList[networkType].shortName;
return (
<View style={styles.transactionHeader}>
<WebsiteIcon
style={styles.domainLogo}
viewStyle={styles.assetLogo}
title={title}
url={currentEnsName || url}
icon={icon}
/>
<View style={styles.domanUrlContainer}>
{this.renderSecureIcon()}
<Text style={styles.domainUrl}>{title}</Text>
</View>
<View style={styles.networkContainer}>
{this.renderNetworkStatusIndicator()}
<Text style={styles.network}>{networkName}</Text>
</View>
const renderTitle = () => {
const { url, currentEnsName, spenderAddress } = props.currentPageInformation;
let title = '';
if (originIsDeeplink) title = renderShortAddress(spenderAddress);
else title = getHost(currentEnsName || url);

return <Text style={styles.domainUrl}>{title}</Text>;
};

const networkName = networkList[props.networkType].shortName;

return (
<View style={styles.transactionHeader}>
{renderTopIcon()}
<View style={styles.domanUrlContainer}>
{renderSecureIcon()}
{renderTitle()}
</View>
);
}
}
<View style={styles.networkContainer}>
{renderNetworkStatusIndicator()}
<Text style={styles.network}>{networkName}</Text>
</View>
</View>
);
};

TransactionHeader.propTypes = {
/**
* Object containing current page title and url
*/
currentPageInformation: PropTypes.object,
/**
* String representing the selected network
*/
networkType: PropTypes.string
};

const mapStateToProps = state => ({
networkType: state.engine.backgroundState.NetworkController.provider.type,
Expand Down
50 changes: 38 additions & 12 deletions app/components/Views/ApproveView/Approve/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import TransactionReviewDetailsCard from '../../../UI/TransactionReview/Transact
import StyledButton from '../../../UI/StyledButton';
import currencySymbols from '../../../../util/currency-symbols.json';
import Logger from '../../../../util/Logger';
import AppConstants from '../../../../core/AppConstants';

const { BNToHex, hexToBN } = util;
const styles = StyleSheet.create({
Expand Down Expand Up @@ -213,6 +214,8 @@ const styles = StyleSheet.create({
}
});

const { ORIGIN_DEEPLINK, ORIGIN_QR_CODE } = AppConstants.DEEPLINKS;

/**
* PureComponent that manages ERC20 approve from the dapp browser
*/
Expand Down Expand Up @@ -298,7 +301,8 @@ class Approve extends PureComponent {
spendLimitCustomValue: undefined,
ticker: getTicker(this.props.ticker),
validSpendLimitCustomValue: true,
viewDetails: false
viewDetails: false,
spenderAddress: '0x...'
};

customSpendLimitInput = React.createRef();
Expand All @@ -313,13 +317,18 @@ class Approve extends PureComponent {
let tokenSymbol, tokenDecimals;
const contract = contractMap[safeToChecksumAddress(to)];
if (!contract) {
tokenSymbol = await AssetsContractController.getAssetSymbol(to);
tokenDecimals = await AssetsContractController.getTokenDecimals(to);
try {
tokenSymbol = await AssetsContractController.getAssetSymbol(to);
tokenDecimals = await AssetsContractController.getTokenDecimals(to);
} catch (e) {
tokenSymbol = 'ERC20 Token';
tokenDecimals = 18;
}
} else {
tokenSymbol = contract.symbol;
tokenDecimals = contract.decimals;
}
const originalApproveAmount = decodeTransferData('transfer', data)[2];
const [spenderAddress, , originalApproveAmount] = decodeTransferData('transfer', data);
const approveAmount = fromTokenMinimalUnit(hexToBN(originalApproveAmount), tokenDecimals);
const totalGas = gas.mul(gasPrice);
const { name: method } = await getMethodData(data);
Expand All @@ -331,7 +340,8 @@ class Approve extends PureComponent {
tokenDecimals,
tokenSymbol,
totalGas: renderFromWei(totalGas),
totalGasFiat: weiToFiatNumber(totalGas, conversionRate)
totalGasFiat: weiToFiatNumber(totalGas, conversionRate),
spenderAddress
});
};

Expand Down Expand Up @@ -439,7 +449,7 @@ class Approve extends PureComponent {
renderTransactionReview = () => {
const { host, method, viewData, tokenSymbol } = this.state;
const {
transaction: { to, data }
transaction: { to, data, origin }
} = this.props;
const amount = decodeTransferData('transfer', data)[1];

Expand All @@ -449,7 +459,7 @@ class Approve extends PureComponent {
toggleViewData={this.toggleViewData}
copyContractAddress={this.copyContractAddress}
address={renderShortAddress(to)}
host={host}
host={origin || host}
allowance={amount}
tokenSymbol={tokenSymbol}
data={data}
Expand Down Expand Up @@ -698,13 +708,18 @@ class Approve extends PureComponent {
customGasVisible,
editPermissionVisible,
ticker,
gasError
gasError,
spenderAddress
} = this.state;

const {
transaction: { origin }
} = this.props;

const isFiat = primaryCurrency.toLowerCase() === 'fiat';
const currencySymbol = currencySymbols[currentCurrency];
const totalGasFiatRounded = Math.round(totalGasFiat * 100) / 100;

const originIsDeeplink = origin === ORIGIN_DEEPLINK || origin === ORIGIN_QR_CODE;
return (
<ActionModal
modalVisible={this.props.modalVisible}
Expand Down Expand Up @@ -732,12 +747,23 @@ class Approve extends PureComponent {
) : (
<>
<View style={styles.section} testID={'approve-screen'}>
<TransactionHeader currentPageInformation={{ title: host, url: activeTabUrl }} />
<TransactionHeader
currentPageInformation={{ origin, spenderAddress, title: host, url: activeTabUrl }}
/>
<Text style={styles.title} testID={'allow-access'}>
{strings('spend_limit_edition.allow_to_access', { tokenSymbol })}
{strings(
`spend_limit_edition.${
originIsDeeplink ? 'allow_to_address_access' : 'allow_to_access'
}`,
{ tokenSymbol }
)}
</Text>
<Text style={styles.explanation}>
{strings('spend_limit_edition.you_trust_this_site')}
{`${strings(
`spend_limit_edition.${
originIsDeeplink ? 'you_trust_this_address' : 'you_trust_this_site'
}`
)}`}
</Text>
<TouchableOpacity style={styles.actionTouchable} onPress={this.toggleEditPermission}>
<Text style={styles.editPermissionText}>
Expand Down
5 changes: 4 additions & 1 deletion app/components/Views/Entry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Logger from '../../../util/Logger';
import Device from '../../../util/Device';
import { recreateVaultWithSamePassword } from '../../../core/Vault';
import { EXISTING_USER, ONBOARDING_WIZARD, METRICS_OPT_IN, ENCRYPTION_LIB, ORIGINAL } from '../../../constants/storage';
import AppConstants from '../../../core/AppConstants';

/**
* Entry Screen that decides which screen to show
Expand Down Expand Up @@ -112,7 +113,9 @@ class Entry extends PureComponent {
if (deeplink) {
const { KeyringController } = Engine.context;
const isUnlocked = KeyringController.isUnlocked();
isUnlocked ? DeeplinkManager.parse(deeplink) : DeeplinkManager.setDeeplink(deeplink);
isUnlocked
? DeeplinkManager.parse(deeplink, { origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK })
: DeeplinkManager.setDeeplink(deeplink);
}
};

Expand Down
2 changes: 1 addition & 1 deletion app/components/Views/QRScanner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default class QrScanner extends PureComponent {
}
this.mounted = false;
this.props.navigation.goBack();
this.props.navigation.state.params.onScanSuccess(data);
this.props.navigation.state.params.onScanSuccess(data, content);
}
};

Expand Down
3 changes: 2 additions & 1 deletion app/components/Views/Wallet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getTicker } from '../../../util/transactions';
import OnboardingWizard from '../../UI/OnboardingWizard';
import { showTransactionNotification, hideTransactionNotification } from '../../../actions/notification';
import DeeplinkManager from '../../../core/DeeplinkManager';
import AppConstants from '../../../core/AppConstants';

const styles = StyleSheet.create({
wrapper: {
Expand Down Expand Up @@ -109,7 +110,7 @@ class Wallet extends PureComponent {
const pendingDeeplink = DeeplinkManager.getPendingDeeplink();
if (pendingDeeplink) {
DeeplinkManager.expireDeeplink();
DeeplinkManager.parse(pendingDeeplink);
DeeplinkManager.parse(pendingDeeplink, { origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK });
}
this.mounted = true;
});
Expand Down
4 changes: 4 additions & 0 deletions app/core/AppConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,9 @@ export default {
WYRE_MERCHANT_ID: 'merchant.io.metamask.wyre',
WYRE_MERCHANT_ID_TEST: 'merchant.io.metamask.wyre.test',
POLLING_FREQUENCY: 10000
},
DEEPLINKS: {
ORIGIN_DEEPLINK: 'deeplink',
ORIGIN_QR_CODE: 'qr-code'
}
};
Loading