Skip to content

Commit

Permalink
feature/approve eip 681 (#1847)
Browse files Browse the repository at this point in the history
* approve

* handle toggle

* handle parsiong errors

* ckeanup

* ckeanup

* locales

* fix

* add origin to deeplinks

* AppConstants

* header

* handle

* ui

* chain id

* generateApproveData

* hex
  • Loading branch information
estebanmino authored Oct 14, 2020
1 parent 143104b commit e14657a
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 117 deletions.
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';
import ErrorBoundary from '../ErrorBoundary';

const styles = StyleSheet.create({
Expand Down Expand Up @@ -110,7 +111,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

0 comments on commit e14657a

Please sign in to comment.