diff --git a/.circleci/config.yml b/.circleci/config.yml
index 29ca80eddf2..2b607f1bd02 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -20,7 +20,7 @@ jobs:
prep-deps:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- run:
@@ -33,7 +33,7 @@ jobs:
prep-node-deps:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- restore_cache: *restore-node-cache
@@ -49,7 +49,7 @@ jobs:
lint:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- attach_workspace:
@@ -60,7 +60,7 @@ jobs:
<<: *defaults
parallelism: 3
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- attach_workspace:
@@ -74,7 +74,7 @@ jobs:
test-deps:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- attach_workspace:
@@ -106,7 +106,7 @@ jobs:
upload-coverage:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- checkout
- attach_workspace:
@@ -117,7 +117,7 @@ jobs:
all-tests-pass:
<<: *defaults
docker:
- - image: circleci/node:10
+ - image: circleci/node:14
steps:
- run:
name: All Tests Passed
@@ -199,13 +199,13 @@ workflows:
filters:
branches:
only:
- - master
+ - main
- develop
- prep-node-deps:
filters:
branches:
ignore:
- - master
+ - main
- develop
- lint:
requires:
diff --git a/.eslintrc.js b/.eslintrc.js
index bcf2b8df859..2b876e8b391 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line import/no-commonjs
module.exports = {
root: true,
parser: 'babel-eslint',
diff --git a/.nvmrc b/.nvmrc
index e338b86593f..958b5a36e1f 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v10
+v14
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90310404a41..fb592ef25a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,61 @@
# Changelog
+## v2.2.0 - Apr 21 2021
+- [#2547](https://github.com/MetaMask/metamask-mobile/pull/2547): Include decimalsToShow in balanceToFiatNumber
+- [#2554](https://github.com/MetaMask/metamask-mobile/pull/2554): Bug fix/sync import time
+- [#2546](https://github.com/MetaMask/metamask-mobile/pull/2546): Fix analytics try catch
+- [#2543](https://github.com/MetaMask/metamask-mobile/pull/2543): Only get nonce from the network if the feature is enabled
+- [#2460](https://github.com/MetaMask/metamask-mobile/pull/2460): Feature/tx local state logs
+- [#2540](https://github.com/MetaMask/metamask-mobile/pull/2540): bump v2.1.2
+- [#2538](https://github.com/MetaMask/metamask-mobile/pull/2538): fix/connection change handler
+- [#2375](https://github.com/MetaMask/metamask-mobile/pull/2375): Style updates
+- [#2536](https://github.com/MetaMask/metamask-mobile/pull/2536): Change Send Feedback to Request a Feature and update link
+- [#2485](https://github.com/MetaMask/metamask-mobile/pull/2485): Fix notification so it doesn't block terms + conditions
+- [#2469](https://github.com/MetaMask/metamask-mobile/pull/2469): Bug/persists old account names
+- [#2534](https://github.com/MetaMask/metamask-mobile/pull/2534): Fix typo
+- [#2373](https://github.com/MetaMask/metamask-mobile/pull/2373): use contract metadata version from package
+- [#2520](https://github.com/MetaMask/metamask-mobile/pull/2520): Check infura availability
+- [#2371](https://github.com/MetaMask/metamask-mobile/pull/2371): Feature/custom nonce
+- [#2521](https://github.com/MetaMask/metamask-mobile/pull/2521): Bump v2.1.1
+- [#2493](https://github.com/MetaMask/metamask-mobile/pull/2493): rename master to main
+- [#2447](https://github.com/MetaMask/metamask-mobile/pull/2447): Bump vm-browserify from 0.0.4 to 1.1.2
+- [#2501](https://github.com/MetaMask/metamask-mobile/pull/2501): Bump jest-serializer from 24.4.0 to 26.6.2
+- [#2499](https://github.com/MetaMask/metamask-mobile/pull/2499): Bump react-native-share from 3.3.2 to 5.2.2
+- [#2411](https://github.com/MetaMask/metamask-mobile/pull/2411): Bump json-rpc-middleware-stream from 2.1.1 to 3.0.0
+- [#2406](https://github.com/MetaMask/metamask-mobile/pull/2406): Bump eslint-plugin-prettier from 3.3.0 to 3.3.1
+- [#2403](https://github.com/MetaMask/metamask-mobile/pull/2403): Bump babel-eslint from 10.0.3 to 10.1.0
+- [#2381](https://github.com/MetaMask/metamask-mobile/pull/2381): Display correct number of decimals for 'usd' fiat
+
+## v2.1.3 - Apr 19 2021
+- [#2548](https://github.com/MetaMask/metamask-mobile/pull/2548): Hotfix analytics try catch
+
+## v2.1.2 - Apr 16 2021
+- [#2538](https://github.com/MetaMask/metamask-mobile/pull/2538): fix/connection change handler
+
+## v2.1.1 - Apr 14 2021
+- [#2520](https://github.com/MetaMask/metamask-mobile/pull/2520): Check provider status
+
+## v2.1.0 - Apr 12 2021
+- [#2487](https://github.com/MetaMask/metamask-mobile/pull/2487): Fix/analytics v1 priority1
+- [#2456](https://github.com/MetaMask/metamask-mobile/pull/2456): Analytics v2 (priority 1)
+- [#2408](https://github.com/MetaMask/metamask-mobile/pull/2408): Fix/gas estimations
+- [#2479](https://github.com/MetaMask/metamask-mobile/pull/2479): remove controllers tgz
+- [#2441](https://github.com/MetaMask/metamask-mobile/pull/2441): Improvement/assets by chainid
+- [#2442](https://github.com/MetaMask/metamask-mobile/pull/2442): Improvement/chain ticker
+- [#2372](https://github.com/MetaMask/metamask-mobile/pull/2372): Remove instapay
+- [#2467](https://github.com/MetaMask/metamask-mobile/pull/2467): Fix iOS build
+- [#2084](https://github.com/MetaMask/metamask-mobile/pull/2084): Migrate from AsyncStorage to FileStorage
+- [#2443](https://github.com/MetaMask/metamask-mobile/pull/2443): Update terms and privacy links
+- [#2318](https://github.com/MetaMask/metamask-mobile/pull/2318): Add custom network rpc API
+- [#2306](https://github.com/MetaMask/metamask-mobile/pull/2306): Feature/high gas warn
+- [#2463](https://github.com/MetaMask/metamask-mobile/pull/2463): update pods
+- [#2448](https://github.com/MetaMask/metamask-mobile/pull/2448): Add resolution for netmask
+- [#2445](https://github.com/MetaMask/metamask-mobile/pull/2445): Add resolution for y18n
+- [#2404](https://github.com/MetaMask/metamask-mobile/pull/2404): Bump react-native-branch from 5.0.0 to 5.0.1
+- [#2439](https://github.com/MetaMask/metamask-mobile/pull/2439): json-rpc-engine@6.1.0
+- [#2413](https://github.com/MetaMask/metamask-mobile/pull/2413): remove "git add" per husky warning
+- [#2431](https://github.com/MetaMask/metamask-mobile/pull/2431): Update BN import
+
## v2.0.1 - Mar 24 2021
- [#2430](https://github.com/MetaMask/metamask-mobile/pull/2430): Fix/send to style
- [#2426](https://github.com/MetaMask/metamask-mobile/pull/2426): bugfix/allow seedphrases when locked
diff --git a/RELEASE.MD b/RELEASE.MD
index 409647923fb..b878ee3631b 100644
--- a/RELEASE.MD
+++ b/RELEASE.MD
@@ -47,6 +47,6 @@
### Once you're done with both stores:
- Submit a PR with the changes
-- Once it's merged create a tag on master for that version
+- Once it's merged create a tag on main for that version
- Go to the release pages and create a new release for that tag, including the changelog
diff --git a/android/app/build.gradle b/android/app/build.gradle
index bd75d063563..87caea76f7b 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -166,8 +166,8 @@ android {
applicationId "io.metamask"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 49
- versionName "2.0.1"
+ versionCode 54
+ versionName "2.2.0"
multiDexEnabled true
testBuildType System.getProperty('testBuildType', 'debug')
missingDimensionStrategy "minReactNative", "minReactNative46"
diff --git a/app/__mocks__/rn-fetch-blob.js b/app/__mocks__/rn-fetch-blob.js
index 2e08193f590..efdd761092c 100644
--- a/app/__mocks__/rn-fetch-blob.js
+++ b/app/__mocks__/rn-fetch-blob.js
@@ -9,6 +9,7 @@ export default {
config: noop,
session: noop,
fs: {
+ writeFile: () => Promise.resolve(),
exists: () => Promise.resolve(),
dirs: {
CacheDir: noop,
diff --git a/app/actions/infuraAvailability/index.js b/app/actions/infuraAvailability/index.js
new file mode 100644
index 00000000000..7fd1b8ec23d
--- /dev/null
+++ b/app/actions/infuraAvailability/index.js
@@ -0,0 +1,13 @@
+import { INFURA_AVAILABILITY_BLOCKED, INFURA_AVAILABILITY_NOT_BLOCKED } from '../../reducers/infuraAvailability';
+
+export function setInfuraAvailabilityBlocked() {
+ return {
+ type: INFURA_AVAILABILITY_BLOCKED
+ };
+}
+
+export function setInfuraAvailabilityNotBlocked() {
+ return {
+ type: INFURA_AVAILABILITY_NOT_BLOCKED
+ };
+}
diff --git a/app/actions/settings/index.js b/app/actions/settings/index.js
index 9f61e001997..8bec8eda1f1 100644
--- a/app/actions/settings/index.js
+++ b/app/actions/settings/index.js
@@ -12,6 +12,13 @@ export function setShowHexData(showHexData) {
};
}
+export function setShowCustomNonce(showCustomNonce) {
+ return {
+ type: 'SET_SHOW_CUSTOM_NONCE',
+ showCustomNonce
+ };
+}
+
export function setLockTime(lockTime) {
return {
type: 'SET_LOCK_TIME',
diff --git a/app/actions/transaction/index.js b/app/actions/transaction/index.js
index 449aea8d81f..ee514d97cc7 100644
--- a/app/actions/transaction/index.js
+++ b/app/actions/transaction/index.js
@@ -172,3 +172,17 @@ export function setCollectibleContractTransaction(collectible) {
collectible
};
}
+
+export function setNonce(nonce) {
+ return {
+ type: 'SET_NONCE',
+ nonce
+ };
+}
+
+export function setProposedNonce(proposedNonce) {
+ return {
+ type: 'SET_PROPOSED_NONCE',
+ proposedNonce
+ };
+}
diff --git a/app/components/Base/Text.js b/app/components/Base/Text.js
index d79aea64d18..1478cb60198 100644
--- a/app/components/Base/Text.js
+++ b/app/components/Base/Text.js
@@ -16,7 +16,16 @@ const style = StyleSheet.create({
right: {
textAlign: 'right'
},
+ red: {
+ color: colors.red
+ },
+ black: {
+ color: colors.black
+ },
bold: fontStyles.bold,
+ blue: {
+ color: colors.blue
+ },
green: {
color: colors.green400
},
@@ -58,6 +67,9 @@ const Text = ({
right,
bold,
green,
+ black,
+ blue,
+ red,
primary,
small,
upper,
@@ -77,6 +89,10 @@ const Text = ({
right && style.right,
bold && style.bold,
green && style.green,
+ black && style.black,
+ blue && style.blue,
+ red && style.red,
+ black && style.black,
primary && style.primary,
disclaimer && [style.small, style.disclaimer],
small && style.small,
@@ -98,6 +114,9 @@ Text.defaultProps = {
right: false,
bold: false,
green: false,
+ black: false,
+ blue: false,
+ red: false,
primary: false,
disclaimer: false,
modal: false,
@@ -130,6 +149,18 @@ Text.propTypes = {
* Makes text green
*/
green: PropTypes.bool,
+ /**
+ * Makes text black
+ */
+ black: PropTypes.bool,
+ /**
+ * Makes text blue
+ */
+ blue: PropTypes.bool,
+ /**
+ * Makes text red
+ */
+ red: PropTypes.bool,
/**
* Makes text fontPrimary color
*/
diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js
index ff7a77f8bde..27c3ed21e2b 100644
--- a/app/components/Nav/Main/index.js
+++ b/app/components/Nav/Main/index.js
@@ -38,7 +38,6 @@ import {
decodeApproveData
} from '../../../util/transactions';
import { BN } from 'ethereumjs-util';
-import { safeToChecksumAddress } from '../../../util/address';
import Logger from '../../../util/Logger';
import contractMap from '@metamask/contract-metadata';
import MessageSign from '../../UI/MessageSign';
@@ -58,11 +57,12 @@ import AccountApproval from '../../UI/AccountApproval';
import ProtectYourWalletModal from '../../UI/ProtectYourWalletModal';
import MainNavigator from './MainNavigator';
import SkipAccountSecurityModal from '../../UI/SkipAccountSecurityModal';
-import { swapsUtils, util } from '@estebanmino/controllers';
+import { swapsUtils, util } from '@metamask/swaps-controller';
import SwapsLiveness from '../../UI/Swaps/SwapsLiveness';
import Analytics from '../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
import BigNumber from 'bignumber.js';
+import { setInfuraAvailabilityBlocked, setInfuraAvailabilityNotBlocked } from '../../../actions/infuraAvailability';
const styles = StyleSheet.create({
flex: {
@@ -79,9 +79,8 @@ const styles = StyleSheet.create({
margin: 0
}
});
-
const Main = props => {
- const [connected, setConnected] = useState(false);
+ const [connected, setConnected] = useState(true);
const [forceReload, setForceReload] = useState(false);
const [signMessage, setSignMessage] = useState(false);
const [signMessageParams, setSignMessageParams] = useState({ data: '' });
@@ -128,24 +127,51 @@ const Main = props => {
const onUnapprovedMessage = (messageParams, type) => {
const { title: currentPageTitle, url: currentPageUrl } = messageParams.meta;
delete messageParams.meta;
- setSignMessage(true);
setSignMessageParams(messageParams);
setSignType(type);
setCurrentPageTitle(currentPageTitle);
setCurrentPageUrl(currentPageUrl);
+ setSignMessage(true);
};
const connectionChangeHandler = useCallback(
state => {
+ if (!state) return;
+ const { isConnected } = state;
// Show the modal once the status changes to offline
- if (connected && !state.isConnected) {
+ if (connected && isConnected === false) {
props.navigation.navigate('OfflineModeView');
}
- setConnected(state.isConnected);
+ if (connected !== isConnected && isConnected !== null) {
+ setConnected(isConnected);
+ }
},
- [connected, props.navigation]
+ [connected, setConnected, props.navigation]
);
+ const checkInfuraAvailability = useCallback(async () => {
+ if (props.providerType !== 'rpc') {
+ try {
+ const { TransactionController } = Engine.context;
+ await util.query(TransactionController.ethQuery, 'blockNumber', []);
+ props.setInfuraAvailabilityNotBlocked();
+ } catch (e) {
+ if (e.message === AppConstants.ERRORS.INFURA_BLOCKED_MESSAGE) {
+ props.navigation.navigate('OfflineModeView');
+ props.setInfuraAvailabilityBlocked();
+ }
+ }
+ } else {
+ props.setInfuraAvailabilityNotBlocked();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ props.navigation,
+ props.providerType,
+ props.setInfuraAvailabilityBlocked,
+ props.setInfuraAvailabilityNotBlocked
+ ]);
+
const initializeWalletConnect = () => {
WalletConnect.hub.on('walletconnectSessionRequest', peerInfo => {
setWalletConnectRequest(true);
@@ -283,16 +309,17 @@ const Main = props => {
async transactionMeta => {
if (transactionMeta.origin === TransactionTypes.MMM) return;
- const to = safeToChecksumAddress(transactionMeta.transaction.to);
+ const to = transactionMeta.transaction.to?.toLowerCase();
const { data } = transactionMeta.transaction;
// if approval data includes metaswap contract
// if destination address is metaswap contract
if (
- to === safeToChecksumAddress(swapsUtils.SWAPS_CONTRACT_ADDRESS) ||
+ to === swapsUtils.getSwapsContractAddress(props.chainId) ||
(data &&
data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE &&
- decodeApproveData(data).spenderAddress === swapsUtils.SWAPS_CONTRACT_ADDRESS)
+ decodeApproveData(data).spenderAddress?.toLowerCase() ===
+ swapsUtils.getSwapsContractAddress(props.chainId))
) {
if (transactionMeta.origin === process.env.MM_FOX_CODE) {
autoSign(transactionMeta);
@@ -361,6 +388,7 @@ const Main = props => {
},
[
props.tokens,
+ props.chainId,
setEtherTransaction,
setTransactionObject,
toggleApproveModal,
@@ -541,6 +569,14 @@ const Main = props => {
}
});
+ // unapprovedTransaction effect
+ useEffect(() => {
+ Engine.context.TransactionController.hub.on('unapprovedTransaction', onUnapprovedTransaction);
+ return () => {
+ Engine.context.TransactionController.hub.removeListener('unapprovedTransaction', onUnapprovedTransaction);
+ };
+ }, [onUnapprovedTransaction]);
+
useEffect(() => {
initializeWalletConnect();
AppState.addEventListener('change', handleAppStateChange);
@@ -569,8 +605,6 @@ const Main = props => {
}
});
- Engine.context.TransactionController.hub.on('unapprovedTransaction', onUnapprovedTransaction);
-
Engine.context.MessageManager.hub.on('unapprovedMessage', messageParams =>
onUnapprovedMessage(messageParams, 'eth')
);
@@ -592,7 +626,7 @@ const Main = props => {
removeNotificationById: props.removeNotificationById
});
pollForIncomingTransactions();
-
+ checkInfuraAvailability();
removeConnectionStatusListener.current = NetInfo.addEventListener(connectionChangeHandler);
}, 1000);
@@ -601,7 +635,6 @@ const Main = props => {
lockManager.current.stopListening();
Engine.context.PersonalMessageManager.hub.removeAllListeners();
Engine.context.TypedMessageManager.hub.removeAllListeners();
- Engine.context.TransactionController.hub.removeListener('unapprovedTransaction', onUnapprovedTransaction);
WalletConnect.hub.removeAllListeners();
removeConnectionStatusListener.current && removeConnectionStatusListener.current();
};
@@ -709,18 +742,36 @@ Main.propTypes = {
/**
* Selected address
*/
- selectedAddress: PropTypes.string
+ selectedAddress: PropTypes.string,
+ /**
+ * Chain id
+ */
+ chainId: PropTypes.string,
+ /**
+ * Network provider type
+ */
+ providerType: PropTypes.string,
+ /**
+ * Dispatch infura availability blocked
+ */
+ setInfuraAvailabilityBlocked: PropTypes.func,
+ /**
+ * Dispatch infura availability not blocked
+ */
+ setInfuraAvailabilityNotBlocked: PropTypes.func
};
const mapStateToProps = state => ({
lockTime: state.settings.lockTime,
thirdPartyApiMode: state.privacy.thirdPartyApiMode,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
tokens: state.engine.backgroundState.AssetsController.tokens,
isPaymentRequest: state.transaction.paymentRequest,
dappTransactionModalVisible: state.modals.dappTransactionModalVisible,
approveModalVisible: state.modals.approveModalVisible,
- swapsTransactions: state.engine.backgroundState.TransactionController.swapsTransactions || {}
+ swapsTransactions: state.engine.backgroundState.TransactionController.swapsTransactions || {},
+ providerType: state.engine.backgroundState.NetworkController.provider.type
});
const mapDispatchToProps = dispatch => ({
@@ -731,7 +782,9 @@ const mapDispatchToProps = dispatch => ({
hideCurrentNotification: () => dispatch(hideCurrentNotification()),
removeNotificationById: id => dispatch(removeNotificationById(id)),
toggleDappTransactionModal: (show = null) => dispatch(toggleDappTransactionModal(show)),
- toggleApproveModal: show => dispatch(toggleApproveModal(show))
+ toggleApproveModal: show => dispatch(toggleApproveModal(show)),
+ setInfuraAvailabilityBlocked: () => dispatch(setInfuraAvailabilityBlocked()),
+ setInfuraAvailabilityNotBlocked: () => dispatch(setInfuraAvailabilityNotBlocked())
});
export default connect(
diff --git a/app/components/UI/AccountApproval/index.js b/app/components/UI/AccountApproval/index.js
index 3b644ba7c6b..a53493d3daf 100644
--- a/app/components/UI/AccountApproval/index.js
+++ b/app/components/UI/AccountApproval/index.js
@@ -8,10 +8,9 @@ import AccountInfoCard from '../AccountInfoCard';
import { strings } from '../../../../locales/i18n';
import { colors, fontStyles } from '../../../styles/common';
import Device from '../../../util/Device';
-import Analytics from '../../../core/Analytics';
-import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
import NotificationManager from '../../../core/NotificationManager';
-
+import AnalyticsV2 from '../../../util/analyticsV2';
+import URL from 'url-parse';
const styles = StyleSheet.create({
root: {
backgroundColor: colors.white,
@@ -90,18 +89,35 @@ class AccountApproval extends PureComponent {
/**
* Whether it was a request coming through wallet connect
*/
- walletConnectRequest: PropTypes.bool
+ walletConnectRequest: PropTypes.bool,
+ /**
+ * A string representing the network chainId
+ */
+ chainId: PropTypes.string
};
state = {
start: Date.now()
};
+ getAnalyticsParams = () => {
+ try {
+ const { currentPageInformation, chainId, networkType } = this.props;
+ const url = new URL(currentPageInformation?.url);
+ return {
+ dapp_host_name: url?.host,
+ dapp_url: currentPageInformation?.url,
+ network_name: networkType,
+ chain_id: chainId
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
componentDidMount = () => {
- const params = this.getTrackingParams();
- delete params.timeOpen;
InteractionManager.runAfterInteractions(() => {
- Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.AUTHENTICATION_CONNECT, params);
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.CONNECT_REQUEST_STARTED, this.getAnalyticsParams());
});
};
@@ -126,10 +142,7 @@ class AccountApproval extends PureComponent {
*/
onConfirm = () => {
this.props.onConfirm();
- Analytics.trackEventWithParameters(
- ANALYTICS_EVENT_OPTS.AUTHENTICATION_CONNECT_CONFIRMED,
- this.getTrackingParams()
- );
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.CONNECT_REQUEST_COMPLETED, this.getAnalyticsParams());
this.showWalletConnectNotification(true);
};
@@ -137,10 +150,8 @@ class AccountApproval extends PureComponent {
* Calls onConfirm callback and analytics to track connect canceled event
*/
onCancel = () => {
- Analytics.trackEventWithParameters(
- ANALYTICS_EVENT_OPTS.AUTHENTICATION_CONNECT_CANCELED,
- this.getTrackingParams()
- );
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.CONNECT_REQUEST_CANCELLED, this.getAnalyticsParams());
+
this.props.onCancel();
this.showWalletConnectNotification();
};
@@ -202,7 +213,8 @@ class AccountApproval extends PureComponent {
const mapStateToProps = state => ({
accountsLength: Object.keys(state.engine.backgroundState.AccountTrackerController.accounts || {}).length,
tokensLength: state.engine.backgroundState.AssetsController.tokens.length,
- networkType: state.engine.backgroundState.NetworkController.provider.type
+ networkType: state.engine.backgroundState.NetworkController.provider.type,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId
});
export default connect(mapStateToProps)(AccountApproval);
diff --git a/app/components/UI/AccountInfoCard/__snapshots__/index.test.js.snap b/app/components/UI/AccountInfoCard/__snapshots__/index.test.js.snap
index 042bc064d6b..3b4ced5f45c 100644
--- a/app/components/UI/AccountInfoCard/__snapshots__/index.test.js.snap
+++ b/app/components/UI/AccountInfoCard/__snapshots__/index.test.js.snap
@@ -45,7 +45,7 @@ exports[`AccountInfoCard should render correctly 1`] = `
numberOfLines={1}
style={
Object {
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Bold",
"fontSize": 16,
"fontWeight": "600",
@@ -60,7 +60,7 @@ exports[`AccountInfoCard should render correctly 1`] = `
numberOfLines={1}
style={
Object {
- "color": "#000000",
+ "color": "#24292E",
"flexGrow": 1,
"fontFamily": "EuclidCircularB-Bold",
"fontSize": 16,
@@ -78,7 +78,7 @@ exports[`AccountInfoCard should render correctly 1`] = `
style={
Object {
"alignSelf": "flex-start",
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 14,
"fontWeight": "100",
diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js
index 9b11b04316f..1cbfdc57956 100644
--- a/app/components/UI/AccountOverview/index.js
+++ b/app/components/UI/AccountOverview/index.js
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { ScrollView, TextInput, StyleSheet, Text, View, TouchableOpacity, InteractionManager } from 'react-native';
import Clipboard from '@react-native-community/clipboard';
-import { swapsUtils } from '@estebanmino/controllers';
+import { swapsUtils } from '@metamask/swaps-controller';
import { connect } from 'react-redux';
import Engine from '../../../core/Engine';
import Analytics from '../../../core/Analytics';
@@ -21,6 +21,7 @@ import { renderFiat } from '../../../util/number';
import { renderAccountName } from '../../../util/address';
import { isMainNet } from '../../../util/networks';
import { getEther } from '../../../util/transactions';
+import { isSwapsAllowed } from '../Swaps/utils';
import Identicon from '../Identicon';
import AssetActionButton from '../AssetActionButton';
@@ -257,7 +258,7 @@ class AccountOverview extends PureComponent {
goToSwaps = () =>
this.props.navigation.navigate('Swaps', {
- sourceToken: swapsUtils.ETH_SWAPS_TOKEN_ADDRESS
+ sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS
});
render() {
@@ -331,7 +332,7 @@ class AccountOverview extends PureComponent {
)}
- {fiatBalance}
+ {isMainNet(chainId) && {fiatBalance}}
@@ -358,7 +359,7 @@ class AccountOverview extends PureComponent {
{AppConstants.SWAPS.ACTIVE && (
diff --git a/app/components/UI/AddCustomCollectible/index.js b/app/components/UI/AddCustomCollectible/index.js
index fb538ef8435..5983e6fd1e8 100644
--- a/app/components/UI/AddCustomCollectible/index.js
+++ b/app/components/UI/AddCustomCollectible/index.js
@@ -9,6 +9,7 @@ import ActionView from '../ActionView';
import { isSmartContractAddress } from '../../../util/transactions';
import Device from '../../../util/Device';
import { connect } from 'react-redux';
+import AnalyticsV2 from '../../../util/analyticsV2';
const styles = StyleSheet.create({
wrapper: {
@@ -72,6 +73,19 @@ class AddCustomCollectible extends PureComponent {
this.mounted = false;
};
+ getAnalyticsParams = () => {
+ try {
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ return {
+ network_name: type,
+ chain_id: chainId
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
addCollectible = async () => {
if (!(await this.validateCustomCollectible())) return;
const isOwner = await this.validateCollectibleOwnership();
@@ -82,6 +96,9 @@ class AddCustomCollectible extends PureComponent {
const { AssetsController } = Engine.context;
const { address, tokenId } = this.state;
AssetsController.addCollectible(address, tokenId);
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.COLLECTIBLE_ADDED, this.getAnalyticsParams());
+
this.props.navigation.goBack();
};
diff --git a/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap b/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap
index 95fd845dbd9..7d82f7e0e3d 100644
--- a/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap
+++ b/app/components/UI/AddCustomNetwork/__snapshots__/index.test.js.snap
@@ -1,7 +1,448 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddCustomNetwork should render correctly 1`] = `
-
-
-
+
+
+
+
+ Allow this site to add a network?
+
+
+ This allows this network to be used within MetaMask.
+
+
+
+ MetaMask does not verify custom networks or their security.
+
+
+ Learn about
+
+
+ scams and network security risks
+
+ .
+
+
+
+
+ Display name
+
+
+
+
+
+ Chain ID
+
+
+
+
+
+ Network URL
+
+
+
+
+
+
+
+ View details
+
+
+
+
+
+ Cancel
+
+
+ Approve
+
+
+
+
`;
diff --git a/app/components/UI/AddCustomNetwork/index.js b/app/components/UI/AddCustomNetwork/index.js
index 2d27dfea06a..29f9dcb0117 100644
--- a/app/components/UI/AddCustomNetwork/index.js
+++ b/app/components/UI/AddCustomNetwork/index.js
@@ -9,7 +9,6 @@ import Device from '../../../util/Device';
import Icon from 'react-native-vector-icons/FontAwesome';
import Alert from '../../Base/Alert';
import EvilIcons from 'react-native-vector-icons/EvilIcons';
-import { withNavigation } from 'react-navigation';
import Text from '../../Base/Text';
const styles = StyleSheet.create({
@@ -122,7 +121,7 @@ const styles = StyleSheet.create({
/**
* Account access approval component
*/
-const AddCustomNetwork = ({ customNetworkInformation, currentPageInformation, navigation, onCancel, onConfirm }) => {
+const AddCustomNetwork = ({ customNetworkInformation, currentPageInformation, onCancel, onConfirm }) => {
const [viewDetails, setViewDetails] = useState(false);
/**
@@ -326,11 +325,7 @@ AddCustomNetwork.propTypes = {
/**
* Object containing info of the network to add
*/
- customNetworkInformation: PropTypes.object,
- /**
- * Object that represents the navigator
- */
- navigation: PropTypes.object
+ customNetworkInformation: PropTypes.object
};
-export default withNavigation(AddCustomNetwork);
+export default AddCustomNetwork;
diff --git a/app/components/UI/AddCustomNetwork/index.test.js b/app/components/UI/AddCustomNetwork/index.test.js
index e308830dfb4..117e29f8e90 100644
--- a/app/components/UI/AddCustomNetwork/index.test.js
+++ b/app/components/UI/AddCustomNetwork/index.test.js
@@ -4,7 +4,7 @@ import { shallow } from 'enzyme';
describe('AddCustomNetwork', () => {
it('should render correctly', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
});
diff --git a/app/components/UI/AddCustomToken/index.js b/app/components/UI/AddCustomToken/index.js
index 365b1d03791..d1e01408d02 100644
--- a/app/components/UI/AddCustomToken/index.js
+++ b/app/components/UI/AddCustomToken/index.js
@@ -7,6 +7,7 @@ import { strings } from '../../../../locales/i18n';
import { isValidAddress } from 'ethereumjs-util';
import ActionView from '../ActionView';
import { isSmartContractAddress } from '../../../util/transactions';
+import AnalyticsV2 from '../../../util/analyticsV2';
const styles = StyleSheet.create({
wrapper: {
@@ -50,11 +51,31 @@ export default class AddCustomToken extends PureComponent {
navigation: PropTypes.object
};
+ getAnalyticsParams = () => {
+ try {
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const { address, symbol } = this.state;
+ return {
+ token_address: address,
+ token_symbol: symbol,
+ network_name: type,
+ chain_id: chainId,
+ source: 'Custom token'
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
addToken = async () => {
if (!(await this.validateCustomToken())) return;
const { AssetsController } = Engine.context;
const { address, symbol, decimals } = this.state;
await AssetsController.addToken(address, symbol, decimals);
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.TOKEN_ADDED, this.getAnalyticsParams());
+
// Clear state before closing
this.setState(
{
@@ -104,7 +125,9 @@ export default class AddCustomToken extends PureComponent {
let validated = true;
const address = this.state.address;
const isValidTokenAddress = isValidAddress(address);
- const toSmartContract = isValidTokenAddress && (await isSmartContractAddress(address));
+ const { NetworkController } = Engine.context;
+ const { chainId } = NetworkController?.state?.provider || {};
+ const toSmartContract = isValidTokenAddress && (await isSmartContractAddress(address, chainId));
if (address.length === 0) {
this.setState({ warningAddress: strings('token.address_cant_be_empty') });
validated = false;
diff --git a/app/components/UI/ApproveTransactionReview/index.js b/app/components/UI/ApproveTransactionReview/index.js
index 37831fc34f0..35873b19479 100644
--- a/app/components/UI/ApproveTransactionReview/index.js
+++ b/app/components/UI/ApproveTransactionReview/index.js
@@ -26,6 +26,7 @@ import {
import { showAlert } from '../../../actions/alert';
import Analytics from '../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
+import AnalyticsV2 from '../../../util/analyticsV2';
import TransactionHeader from '../../UI/TransactionHeader';
import AccountInfoCard from '../../UI/AccountInfoCard';
import IonicIcon from 'react-native-vector-icons/Ionicons';
@@ -35,8 +36,8 @@ import AppConstants from '../../../core/AppConstants';
import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect';
import { withNavigation } from 'react-navigation';
import { getNetworkName, isMainNet } from '../../../util/networks';
-import { capitalize } from '../../../util/format';
import scaling from '../../../util/scaling';
+import { capitalize } from '../../../util/general';
import EditPermission from './EditPermission';
const { hexToBN } = util;
@@ -227,7 +228,11 @@ class ApproveTransactionReview extends PureComponent {
/**
* True if transaction is over the available funds
*/
- over: PropTypes.bool
+ over: PropTypes.bool,
+ /**
+ * Function to set analytics params
+ */
+ onSetAnalyticsParams: PropTypes.func
};
state = {
@@ -276,16 +281,22 @@ class ApproveTransactionReview extends PureComponent {
const totalGas = gas?.mul(gasPrice);
const { name: method } = await getMethodData(data);
- this.setState({
- host,
- method,
- originalApproveAmount: approveAmount,
- tokenSymbol,
- token: { symbol: tokenSymbol, decimals: tokenDecimals },
- totalGas: renderFromWei(totalGas),
- totalGasFiat: weiToFiatNumber(totalGas, conversionRate),
- spenderAddress
- });
+ this.setState(
+ {
+ host,
+ method,
+ originalApproveAmount: approveAmount,
+ tokenSymbol,
+ token: { symbol: tokenSymbol, decimals: tokenDecimals },
+ totalGas: renderFromWei(totalGas),
+ totalGasFiat: weiToFiatNumber(totalGas, conversionRate),
+ spenderAddress,
+ encodedAmount
+ },
+ () => {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.APPROVAL_STARTED, this.getAnalyticsParams());
+ }
+ );
};
componentDidUpdate(previousProps) {
@@ -306,6 +317,33 @@ class ApproveTransactionReview extends PureComponent {
}
}
+ getAnalyticsParams = () => {
+ try {
+ const { activeTabUrl, transaction, onSetAnalyticsParams } = this.props;
+ const { tokenSymbol, originalApproveAmount, encodedAmount } = this.state;
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const isDapp = !Object.values(AppConstants.DEEPLINKS).includes(transaction?.origin);
+ const unlimited = encodedAmount === 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
+ const params = {
+ dapp_host_name: transaction?.origin,
+ dapp_url: isDapp ? activeTabUrl : undefined,
+ network_name: type,
+ chain_id: chainId,
+ active_currency: { value: tokenSymbol, anonymous: true },
+ number_tokens_requested: { value: originalApproveAmount, anonymous: true },
+ unlimited_permission_requested: unlimited,
+ referral_type: isDapp ? 'dapp' : transaction?.origin
+ };
+ // Send analytics params to parent component so it's available when cancelling and confirming
+ onSetAnalyticsParams && onSetAnalyticsParams(params);
+
+ return params;
+ } catch (error) {
+ return {};
+ }
+ };
+
trackApproveEvent = event => {
const { transaction, tokensLength, accountsLength, providerType } = this.props;
InteractionManager.runAfterInteractions(() => {
@@ -392,6 +430,7 @@ class ApproveTransactionReview extends PureComponent {
const newApprovalTransaction = { ...transaction, data: approvalData };
setTransactionObject(newApprovalTransaction);
this.toggleEditPermission();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.APPROVAL_PERMISSION_UPDATED, this.getAnalyticsParams());
};
renderEditPermission = () => {
@@ -402,6 +441,7 @@ class ApproveTransactionReview extends PureComponent {
spendLimitCustomValue,
originalApproveAmount
} = this.state;
+
return (
{
+ const { onConfirm } = this.props;
+ onConfirm && onConfirm();
+ };
+
gotoFaucet = () => {
const mmFaucetUrl = 'https://faucet.metamask.io/';
InteractionManager.runAfterInteractions(() => {
@@ -534,8 +578,8 @@ class ApproveTransactionReview extends PureComponent {
confirmButtonMode="confirm"
cancelText={strings('transaction.reject')}
confirmText={strings('transactions.approve')}
- onCancelPress={this.props.onCancel}
- onConfirmPress={this.props.onConfirm}
+ onCancelPress={this.onCancelPress}
+ onConfirmPress={this.onConfirmPress}
>
{
this.props.navigation.navigate('Swaps', {
- sourceToken: this.props.asset.isETH ? swapsUtils.ETH_SWAPS_TOKEN_ADDRESS : this.props.asset.address
+ sourceToken: this.props.asset.isETH ? swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS : this.props.asset.address
});
};
@@ -249,12 +250,16 @@ class AssetOverview extends PureComponent {
let balance, balanceFiat;
if (isETH) {
balance = renderFromWei(accounts[selectedAddress] && accounts[selectedAddress].balance);
- balanceFiat = weiToFiat(hexToBN(accounts[selectedAddress].balance), conversionRate, currentCurrency);
+ balanceFiat = isMainNet(chainId)
+ ? weiToFiat(hexToBN(accounts[selectedAddress].balance), conversionRate, currentCurrency)
+ : null;
} else {
const exchangeRate = itemAddress in tokenExchangeRates ? tokenExchangeRates[itemAddress] : undefined;
balance =
itemAddress in tokenBalances ? renderFromTokenMinimalUnit(tokenBalances[itemAddress], decimals) : 0;
- balanceFiat = balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency);
+ balanceFiat = isMainNet(chainId)
+ ? balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency)
+ : null;
}
// choose balances depending on 'primaryCurrency'
if (primaryCurrency === 'ETH') {
@@ -275,7 +280,7 @@ class AssetOverview extends PureComponent {
{mainBalance}
- {secondaryBalance}
+ {secondaryBalance && {secondaryBalance}}
>
)}
@@ -303,7 +308,7 @@ class AssetOverview extends PureComponent {
{AppConstants.SWAPS.ACTIVE && (
diff --git a/app/components/UI/BiometryButton/__snapshots__/index.test.js.snap b/app/components/UI/BiometryButton/__snapshots__/index.test.js.snap
index 47166de2fc3..a7a712f4332 100644
--- a/app/components/UI/BiometryButton/__snapshots__/index.test.js.snap
+++ b/app/components/UI/BiometryButton/__snapshots__/index.test.js.snap
@@ -13,7 +13,7 @@ exports[`BiometryButton should render correctly 1`] = `
>
@@ -36,7 +36,7 @@ exports[`CustomGas should render correctly 1`] = `
style={
Object {
"alignSelf": "center",
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Bold",
"fontSize": 14,
"fontWeight": "600",
@@ -85,7 +85,7 @@ exports[`CustomGas should render correctly 1`] = `
parseInt(this.props.basicGasEstimates.fastGwei) * 1.5) {
- const currentGasPrice = getRenderableFiatGasFee(
- gasPrice,
- this.props.conversionRate,
- this.props.currentCurrency,
- customGasLimitBN
- );
- warningGasPriceHigh = strings('transaction.high_gas_price', { currentGasPrice });
+ if (this.onlyAdvanced() && this.props.minimumGasPrice) {
+ if (parseInt(gasPrice) < parseInt(fromWei(this.props.minimumGasPrice, 'gwei'))) {
+ warningGasPrice = strings('transaction.low_gas_price');
+ }
+ }
+ if (this.props.basicGasEstimates) {
+ if (parseInt(gasPrice) < parseInt(this.props.basicGasEstimates.safeLowGwei))
+ warningGasPrice = strings('transaction.low_gas_price');
+ //Warning should be displayed when the gas fee is 1.5 times higher than the fast rate
+ if (parseInt(gasPrice) > parseInt(this.props.basicGasEstimates.fastGwei) * 1.5) {
+ const currentGasPrice = getRenderableFiatGasFee(
+ gasPrice,
+ this.props.conversionRate,
+ this.props.currentCurrency,
+ customGasLimitBN
+ );
+ warningGasPriceHigh = strings('transaction.high_gas_price', { currentGasPrice });
+ }
}
if (!value || value === '' || !isDecimal(value) || value <= 0)
warningGasPrice = strings('transaction.invalid_gas_price');
@@ -488,16 +524,27 @@ class CustomGas extends PureComponent {
});
};
+ getAnalyticsParams = () => {
+ try {
+ const { advancedCustomGas, chainId, networkType, view, analyticsParams } = this.props;
+ const { gasSpeedSelected } = this.state;
+ return {
+ ...(analyticsParams || {}),
+ network_name: networkType,
+ chain_id: chainId,
+ function_type: view,
+ gas_mode: advancedCustomGas ? 'Advanced' : 'Basic',
+ speed_set: advancedCustomGas ? undefined : gasSpeedSelected
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
//Handle gas fee selection when save button is pressed instead of everytime a change is made, otherwise cannot switch back to review mode if there is an error
saveCustomGasSelection = () => {
const { gasSpeedSelected, customGasLimit, customGasPrice } = this.state;
- const {
- review,
- gas,
- handleGasFeeSelection,
- advancedCustomGas,
- basicGasEstimates: { fastGwei, averageGwei, safeLowGwei }
- } = this.props;
+ const { review, gas, handleGasFeeSelection, advancedCustomGas } = this.props;
if (advancedCustomGas) {
handleGasFeeSelection(
new BN(customGasLimit),
@@ -509,6 +556,9 @@ class CustomGas extends PureComponent {
);
} else {
const mode = { mode: gasSpeedSelected };
+ const {
+ basicGasEstimates: { fastGwei, averageGwei, safeLowGwei }
+ } = this.props;
const noGasWarning = '';
if (gasSpeedSelected === 'slow')
handleGasFeeSelection(gas, apiEstimateModifiedToWEI(safeLowGwei), noGasWarning, mode);
@@ -517,7 +567,9 @@ class CustomGas extends PureComponent {
if (gasSpeedSelected === 'fast')
handleGasFeeSelection(gas, apiEstimateModifiedToWEI(fastGwei), noGasWarning, mode);
}
+
review();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.GAS_FEE_CHANGED, this.getAnalyticsParams());
};
renderCustomGasSelector = () => {
@@ -636,7 +688,7 @@ class CustomGas extends PureComponent {
@@ -693,6 +745,14 @@ class CustomGas extends PureComponent {
!this.state.gasInputHeight && this.setState({ gasInputHeight: event.nativeEvent.layout.height });
};
+ onlyAdvanced = () => {
+ const { chainId, basicGasEstimates } = this.props;
+ const isNotMainnet = !isMainnetByChainId(chainId);
+ // Check if either no basicGasEstimates were provided or less than 3 options were provided (for example, only the average gas price)
+ const noBasicGasEstimates = !basicGasEstimates || Object.keys(basicGasEstimates).length < 3;
+ return isNotMainnet || noBasicGasEstimates;
+ };
+
render = () => {
const { warningGasLimit, warningGasPrice, warningSufficientFunds } = this.state;
const {
@@ -722,23 +782,26 @@ class CustomGas extends PureComponent {
{strings('transaction.edit_network_fee')}
-
-
- {strings('custom_gas.basic_options')}
-
-
- {strings('custom_gas.advanced_options')}
-
-
+ {this.onlyAdvanced() ? null : (
+
+
+ {strings('custom_gas.basic_options')}
+
+
+
+ {strings('custom_gas.advanced_options')}
+
+
+ )}
- {this.renderCustomGasSelector()}
+ {this.onlyAdvanced() ? null : this.renderCustomGasSelector()}
{this.renderCustomGasInput()}
@@ -768,7 +831,9 @@ const mapStateToProps = (state, props) => ({
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
- transaction: props.customTransaction || getNormalizedTxState(state)
+ transaction: props.customTransaction || getNormalizedTxState(state),
+ networkType: state.engine.backgroundState.NetworkController.provider.type,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId
});
export default connect(mapStateToProps)(CustomGas);
diff --git a/app/components/UI/CustomGas/index.test.js b/app/components/UI/CustomGas/index.test.js
index 067677ec8d3..600b8329106 100644
--- a/app/components/UI/CustomGas/index.test.js
+++ b/app/components/UI/CustomGas/index.test.js
@@ -25,7 +25,8 @@ describe('CustomGas', () => {
},
NetworkController: {
provider: {
- ticker: 'ETH'
+ ticker: 'ETH',
+ chainId: '1'
}
}
}
diff --git a/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap b/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap
new file mode 100644
index 00000000000..79962ceefa0
--- /dev/null
+++ b/app/components/UI/CustomNonceModal/__snapshots__/index.test.js.snap
@@ -0,0 +1,419 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CustomNonceModal should render correctly 1`] = `
+
+
+
+
+
+
+ Edit transaction nonce
+
+
+
+
+
+ Current suggested nonce:
+
+
+ 26
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Warning: You may encounter issues with future transactions if you continue. Use with caution.
+
+
+
+ This is an advanced feature used to cancel or speed up any pending transactions.
+
+
+ Think of the nonce as the transaction number of an account. Every account's nonce begins with 0 for the first transaction and continues in sequential order.
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+
+`;
diff --git a/app/components/UI/CustomNonceModal/index.js b/app/components/UI/CustomNonceModal/index.js
new file mode 100644
index 00000000000..e4a9a12a7b7
--- /dev/null
+++ b/app/components/UI/CustomNonceModal/index.js
@@ -0,0 +1,224 @@
+import React from 'react';
+import { colors, fontStyles } from '../../../styles/common';
+import { strings } from '../../../../locales/i18n';
+import { StyleSheet, View, TextInput, SafeAreaView, TouchableOpacity } from 'react-native';
+import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
+import ModalDragger from '../../Base/ModalDragger';
+import Text from '../../Base/Text';
+import StyledButton from '../../UI/StyledButton';
+import Modal from 'react-native-modal';
+import PropTypes from 'prop-types';
+import Icon from 'react-native-vector-icons/FontAwesome';
+import EvilIcons from 'react-native-vector-icons/EvilIcons';
+
+const styles = StyleSheet.create({
+ bottomModal: {
+ justifyContent: 'flex-end',
+ margin: 0
+ },
+ keyboardAwareWrapper: {
+ flex: 1,
+ justifyContent: 'flex-end'
+ },
+ modal: {
+ minHeight: 200,
+ backgroundColor: colors.white,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20
+ },
+ modalContainer: {
+ margin: 24
+ },
+ title: {
+ fontSize: 14,
+ color: colors.black
+ },
+ nonceInput: {
+ width: 80,
+ fontSize: 36,
+ ...fontStyles.bold,
+ color: colors.black,
+ textAlign: 'center',
+ marginHorizontal: 24
+ },
+ desc: {
+ color: colors.black,
+ fontSize: 12,
+ lineHeight: 16,
+ marginVertical: 10
+ },
+ nonceInputContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ alignSelf: 'center',
+ marginVertical: 10
+ },
+ incrementDecrementNonceContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ alignSelf: 'center'
+ },
+ currentSuggested: {
+ fontSize: 14,
+ color: colors.grey500,
+ marginBottom: 10
+ },
+ nonceWarning: {
+ borderWidth: 1,
+ borderColor: colors.yellow,
+ backgroundColor: colors.yellow100,
+ padding: 16,
+ display: 'flex',
+ flexDirection: 'row',
+ borderRadius: 8,
+ marginTop: 10,
+ marginBottom: 16
+ },
+ nonceWarningText: {
+ color: colors.black,
+ fontSize: 12,
+ lineHeight: 16,
+ width: '100%',
+ flex: 1
+ },
+ descWarningContainer: {
+ height: 240
+ },
+ actionRow: {
+ flexDirection: 'row',
+ marginBottom: 15
+ },
+ actionButton: {
+ flex: 1,
+ marginHorizontal: 8
+ },
+ incrementHit: {
+ padding: 4
+ },
+ icon: {
+ flex: 0,
+ marginTop: 6,
+ paddingRight: 14
+ },
+ incrementDecrementIcon: {
+ color: colors.blue
+ }
+});
+
+const CustomModalNonce = ({ proposedNonce, nonceValue, close, save }) => {
+ const [nonce, onChangeText] = React.useState(nonceValue);
+
+ const incrementDecrementNonce = decrement => {
+ let newValue = nonce;
+ newValue = decrement ? --newValue : ++newValue;
+ onChangeText(newValue > 1 ? newValue : 1);
+ };
+
+ const saveAndClose = () => {
+ save(nonce);
+ close();
+ };
+
+ const displayWarning = String(proposedNonce) !== String(nonce);
+
+ return (
+
+
+
+
+
+
+ {strings('transaction.edit_transaction_nonce')}
+
+
+
+
+
+ {strings('transaction.current_suggested_nonce')} {proposedNonce}
+
+
+ incrementDecrementNonce(true)}>
+
+
+ incrementDecrementNonce(false)}
+ >
+
+
+
+
+ {displayWarning ? (
+
+
+ {strings('transaction.nonce_warning')}
+
+ ) : null}
+
+ {strings('transaction.this_is_an_advanced')}
+
+ {strings('transaction.think_of_the_nonce')}
+
+
+
+
+ {strings('transaction.cancel')}
+
+ saveAndClose(nonce)}
+ containerStyle={styles.actionButton}
+ >
+ {strings('transaction.save')}
+
+
+
+
+
+ );
+};
+
+CustomModalNonce.propTypes = {
+ proposedNonce: PropTypes.number.isRequired,
+ nonceValue: PropTypes.number.isRequired,
+ save: PropTypes.func.isRequired,
+ close: PropTypes.func.isRequired
+};
+
+export default CustomModalNonce;
diff --git a/app/components/UI/CustomNonceModal/index.test.js b/app/components/UI/CustomNonceModal/index.test.js
new file mode 100644
index 00000000000..6ec667ee64b
--- /dev/null
+++ b/app/components/UI/CustomNonceModal/index.test.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import CustomNonceModal from './';
+
+describe('CustomNonceModal', () => {
+ it('should render correctly', () => {
+ const proposedNonce = 26;
+ const customNonce = 28;
+ const noop = () => ({});
+ const wrapper = shallow(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/app/components/UI/DrawerView/index.js b/app/components/UI/DrawerView/index.js
index 4b22602b96f..ebf002880fc 100644
--- a/app/components/UI/DrawerView/index.js
+++ b/app/components/UI/DrawerView/index.js
@@ -8,7 +8,7 @@ import Icon from 'react-native-vector-icons/FontAwesome';
import FeatherIcon from 'react-native-vector-icons/Feather';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import { colors, fontStyles } from '../../../styles/common';
-import { hasBlockExplorer, findBlockExplorerForRpc, getBlockExplorerName } from '../../../util/networks';
+import { hasBlockExplorer, findBlockExplorerForRpc, getBlockExplorerName, isMainNet } from '../../../util/networks';
import Identicon from '../Identicon';
import StyledButton from '../StyledButton';
import AccountList from '../AccountList';
@@ -41,6 +41,7 @@ import WhatsNewModal from '../WhatsNewModal';
import InvalidCustomNetworkAlert from '../InvalidCustomNetworkAlert';
import { RPC } from '../../../constants/network';
import { findBottomTabRouteNameFromNavigatorState, findRouteNameFromNavigatorState } from '../../../util/general';
+import { ANALYTICS_EVENTS_V2 } from '../../../util/analyticsV2';
const styles = StyleSheet.create({
wrapper: {
@@ -337,6 +338,10 @@ class DrawerView extends PureComponent {
* Wizard onboarding state
*/
wizard: PropTypes.object,
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string,
/**
* Current provider ticker
*/
@@ -497,7 +502,7 @@ class DrawerView extends PureComponent {
showWallet = () => {
this.props.navigation.navigate('WalletTabHome');
this.hideDrawer();
- this.trackEvent(ANALYTICS_EVENT_OPTS.NAVIGATION_TAPS_WALLET);
+ this.trackEvent(ANALYTICS_EVENTS_V2.WALLET_OPENED);
};
goToTransactionHistory = () => {
@@ -572,7 +577,10 @@ class DrawerView extends PureComponent {
submitFeedback = () => {
this.trackEvent(ANALYTICS_EVENT_OPTS.NAVIGATION_TAPS_SEND_FEEDBACK);
- this.goToBrowserUrl('https://metamask.zendesk.com/hc/en-us/requests/new', strings('drawer.metamask_support'));
+ this.goToBrowserUrl(
+ 'https://community.metamask.io/c/feature-requests-ideas/',
+ strings('drawer.request_feature')
+ );
};
showHelp = () => {
@@ -721,7 +729,7 @@ class DrawerView extends PureComponent {
action: this.showHelp
},
{
- name: strings('drawer.submit_feedback'),
+ name: strings('drawer.request_feature'),
icon: this.getFeatherIcon('message-square'),
action: this.submitFeedback
},
@@ -826,6 +834,7 @@ class DrawerView extends PureComponent {
selectedAddress,
keyrings,
currentCurrency,
+ chainId,
ticker,
seedphraseBackedUp
} = this.props;
@@ -872,7 +881,7 @@ class DrawerView extends PureComponent {
- {fiatBalanceStr}
+ {isMainNet(chainId) && {fiatBalanceStr}}
({
receiveModalVisible: state.modals.receiveModalVisible,
passwordSet: state.user.passwordSet,
wizard: state.wizard,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
tokens: state.engine.backgroundState.AssetsController.tokens,
tokenBalances: state.engine.backgroundState.TokenBalancesController.contractBalances,
diff --git a/app/components/UI/MessageSign/index.js b/app/components/UI/MessageSign/index.js
index d8d4a6c7f2c..09e15d7c66d 100644
--- a/app/components/UI/MessageSign/index.js
+++ b/app/components/UI/MessageSign/index.js
@@ -8,6 +8,8 @@ import ExpandedMessage from '../SignatureRequest/ExpandedMessage';
import NotificationManager from '../../../core/NotificationManager';
import { strings } from '../../../../locales/i18n';
import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect';
+import URL from 'url-parse';
+import AnalyticsV2 from '../../../util/analyticsV2';
const styles = StyleSheet.create({
expandedMessage: {
@@ -59,6 +61,28 @@ export default class MessageSign extends PureComponent {
truncateMessage: false
};
+ getAnalyticsParams = () => {
+ try {
+ const { currentPageInformation } = this.props;
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const url = new URL(currentPageInformation?.url);
+ return {
+ dapp_host_name: url?.host,
+ dapp_url: currentPageInformation?.url,
+ network_name: type,
+ chain_id: chainId,
+ sign_type: 'eth'
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
+ componentDidMount = () => {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_STARTED, this.getAnalyticsParams());
+ };
+
showWalletConnectNotification = (messageParams = {}, confirmation = false) => {
InteractionManager.runAfterInteractions(() => {
messageParams.origin &&
@@ -94,11 +118,13 @@ export default class MessageSign extends PureComponent {
cancelSignature = () => {
this.rejectMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_CANCELLED, this.getAnalyticsParams());
this.props.onCancel();
};
confirmSignature = () => {
this.signMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_COMPLETED, this.getAnalyticsParams());
this.props.onConfirm();
};
diff --git a/app/components/UI/NetworkList/index.js b/app/components/UI/NetworkList/index.js
index 6bbc6d000a7..142e11087d9 100644
--- a/app/components/UI/NetworkList/index.js
+++ b/app/components/UI/NetworkList/index.js
@@ -7,8 +7,7 @@ import { colors, fontStyles } from '../../../styles/common';
import { strings } from '../../../../locales/i18n';
import Networks, { getAllNetworks, isSafeChainId } from '../../../util/networks';
import { connect } from 'react-redux';
-import Analytics from '../../../core/Analytics';
-import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
+import AnalyticsV2 from '../../../util/analyticsV2';
import { MAINNET, RPC } from '../../../constants/network';
const styles = StyleSheet.create({
@@ -142,7 +141,6 @@ export class NetworkList extends PureComponent {
getOtherNetworks = () => getAllNetworks().slice(1);
onNetworkChange = type => {
- const { provider } = this.props;
requestAnimationFrame(() => {
this.props.onClose(false);
InteractionManager.runAfterInteractions(() => {
@@ -153,9 +151,11 @@ export class NetworkList extends PureComponent {
setTimeout(() => {
Engine.refreshTransactionHistory();
}, 1000);
- Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.COMMON_SWITCHED_NETWORKS, {
- 'From Network': provider.type,
- 'To Network': type
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_SWITCHED, {
+ network_name: type,
+ chain_id: String(Networks[type].chainId),
+ source: 'Settings'
});
});
});
@@ -169,7 +169,13 @@ export class NetworkList extends PureComponent {
const { frequentRpcList } = this.props;
const { NetworkController, CurrencyRateController } = Engine.context;
const rpc = frequentRpcList.find(({ rpcUrl }) => rpcUrl === rpcTarget);
- const { rpcUrl, chainId, ticker, nickname } = rpc;
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ nickname,
+ rpcPrefs: { blockExplorerUrl }
+ } = rpc;
// If the network does not have chainId then show invalid custom network alert
const chainIdNumber = parseInt(chainId, 10);
@@ -181,6 +187,16 @@ export class NetworkList extends PureComponent {
CurrencyRateController.configure({ nativeCurrency: ticker });
NetworkController.setRpcTarget(rpcUrl, chainId, ticker, nickname);
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_SWITCHED, {
+ rpc_url: rpcUrl,
+ chain_id: chainId,
+ source: 'Settings',
+ symbol: ticker,
+ block_explorer_url: blockExplorerUrl,
+ network_name: 'rpc'
+ });
+
this.props.onClose(false);
};
diff --git a/app/components/UI/Notification/BaseNotification/index.js b/app/components/UI/Notification/BaseNotification/index.js
index 97a54f6ef29..f4b90393ac4 100644
--- a/app/components/UI/Notification/BaseNotification/index.js
+++ b/app/components/UI/Notification/BaseNotification/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { TouchableOpacity, StyleSheet, View, Text } from 'react-native';
+import { TouchableOpacity, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import { colors, fontStyles, baseStyles } from '../../../../styles/common';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
@@ -7,6 +7,7 @@ import AnimatedSpinner from '../../AnimatedSpinner';
import { strings } from '../../../../../locales/i18n';
import IonicIcon from 'react-native-vector-icons/Ionicons';
import AntIcon from 'react-native-vector-icons/AntDesign';
+import Text from '../../../Base/Text';
const styles = StyleSheet.create({
defaultFlashFloating: {
diff --git a/app/components/UI/Notification/SimpleNotification/index.js b/app/components/UI/Notification/SimpleNotification/index.js
index 22735c06d21..bde4f6bfe56 100644
--- a/app/components/UI/Notification/SimpleNotification/index.js
+++ b/app/components/UI/Notification/SimpleNotification/index.js
@@ -28,17 +28,18 @@ const styles = StyleSheet.create({
function SimpleNotification({ isInBrowserView, notificationAnimated, hideCurrentNotification, currentNotification }) {
return (
-
-
+
-
-
+
+
);
}
diff --git a/app/components/UI/Notification/TransactionNotification/index.js b/app/components/UI/Notification/TransactionNotification/index.js
index 0cff2f48f06..995928086c0 100644
--- a/app/components/UI/Notification/TransactionNotification/index.js
+++ b/app/components/UI/Notification/TransactionNotification/index.js
@@ -179,6 +179,7 @@ function TransactionNotification(props) {
const {
selectedAddress,
ticker,
+ chainId,
conversionRate,
currentCurrency,
exchangeRate,
@@ -194,6 +195,7 @@ function TransactionNotification(props) {
tx,
selectedAddress,
ticker,
+ chainId,
conversionRate,
currentCurrency,
exchangeRate,
@@ -338,6 +340,10 @@ TransactionNotification.propTypes = {
* Current provider ticker
*/
ticker: PropTypes.string,
+ /**
+ * Current provider chainId
+ */
+ chainId: PropTypes.string,
/**
* ETH to current currency conversion rate
*/
@@ -374,6 +380,7 @@ const mapStateToProps = state => ({
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
transactions: state.engine.backgroundState.TransactionController.transactions,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
tokens: state.engine.backgroundState.AssetsController.tokens.reduce((tokens, token) => {
tokens[token.address] = token;
return tokens;
diff --git a/app/components/UI/OptinMetrics/index.js b/app/components/UI/OptinMetrics/index.js
index 6461ce64200..0b9dc548b92 100644
--- a/app/components/UI/OptinMetrics/index.js
+++ b/app/components/UI/OptinMetrics/index.js
@@ -121,8 +121,8 @@ class OptinMetrics extends PureComponent {
clearOnboardingEvents: PropTypes.func
};
- actionsList = [1, 2, 3, 4, 5].map(value => ({
- action: value <= 2 ? 0 : 1,
+ actionsList = [1, 2, 3, 4, 5, 6].map(value => ({
+ action: value <= 3 ? 0 : 1,
description: strings(`privacy_policy.action_description_${value}`)
}));
diff --git a/app/components/UI/PersonalSign/index.js b/app/components/UI/PersonalSign/index.js
index e47027b5414..91e21679a4b 100644
--- a/app/components/UI/PersonalSign/index.js
+++ b/app/components/UI/PersonalSign/index.js
@@ -9,6 +9,8 @@ import { util } from '@metamask/controllers';
import NotificationManager from '../../../core/NotificationManager';
import { strings } from '../../../../locales/i18n';
import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect';
+import URL from 'url-parse';
+import AnalyticsV2 from '../../../util/analyticsV2';
const styles = StyleSheet.create({
messageText: {
@@ -64,6 +66,29 @@ export default class PersonalSign extends PureComponent {
truncateMessage: false
};
+ getAnalyticsParams = () => {
+ try {
+ const { currentPageInformation } = this.props;
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const url = new URL(currentPageInformation?.url);
+
+ return {
+ dapp_host_name: url?.host,
+ dapp_url: currentPageInformation?.url,
+ network_name: type,
+ chain_id: chainId,
+ sign_type: 'personal'
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
+ componentDidMount = () => {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_STARTED, this.getAnalyticsParams());
+ };
+
showWalletConnectNotification = (messageParams = {}, confirmation = false) => {
InteractionManager.runAfterInteractions(() => {
messageParams.origin &&
@@ -99,11 +124,13 @@ export default class PersonalSign extends PureComponent {
cancelSignature = () => {
this.rejectMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_CANCELLED, this.getAnalyticsParams());
this.props.onCancel();
};
confirmSignature = () => {
this.signMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_COMPLETED, this.getAnalyticsParams());
this.props.onConfirm();
};
diff --git a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
index 76937393466..84e57f358d0 100644
--- a/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
+++ b/app/components/UI/ReceiveRequest/__snapshots__/index.test.js.snap
@@ -19,6 +19,8 @@ exports[`ReceiveRequest should render correctly 1`] = `
}
>
{
+ this.getAnalyticsParams();
+ };
+
+ getAnalyticsParams = () => {
+ try {
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const { address, symbol } = this.state.selectedAsset || {};
+ return {
+ token_address: address,
+ token_symbol: symbol,
+ network_name: type,
+ chain_id: chainId,
+ source: 'Add token dropdown'
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
addToken = async () => {
const { AssetsController } = Engine.context;
const { address, symbol, decimals } = this.state.selectedAsset;
await AssetsController.addToken(address, symbol, decimals);
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.TOKEN_ADDED, this.getAnalyticsParams());
+
// Clear state before closing
this.setState(
{
diff --git a/app/components/UI/SignatureRequest/index.js b/app/components/UI/SignatureRequest/index.js
index fcf6b7551c1..c9b77ff7edc 100644
--- a/app/components/UI/SignatureRequest/index.js
+++ b/app/components/UI/SignatureRequest/index.js
@@ -232,8 +232,9 @@ class SignatureRequest extends PureComponent {
let expandedHeight;
if (Device.isMediumDevice()) {
expandedHeight = styles.expandedHeight2;
- } else if (type === 'ethSign' && Device.isMediumDevice()) {
- expandedHeight = styles.expandedHeight1;
+ if (type === 'ethSign') {
+ expandedHeight = styles.expandedHeight1;
+ }
}
return (
diff --git a/app/components/UI/SlippageSlider/__snapshots__/index.test.js.snap b/app/components/UI/SlippageSlider/__snapshots__/index.test.js.snap
index 1f421b08bdc..1307399c9d6 100644
--- a/app/components/UI/SlippageSlider/__snapshots__/index.test.js.snap
+++ b/app/components/UI/SlippageSlider/__snapshots__/index.test.js.snap
@@ -215,7 +215,7 @@ exports[`SlippageSlider should render correctly 1`] = `
"bottom": 0,
"height": 30,
"position": "absolute",
- "shadowColor": "#000000",
+ "shadowColor": "#24292E",
"shadowOffset": Object {
"height": 0,
"width": 0,
diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js
index 124c82db1b8..66e66ce4e8e 100644
--- a/app/components/UI/Swaps/QuotesView.js
+++ b/app/components/UI/Swaps/QuotesView.js
@@ -7,7 +7,8 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
import FAIcon from 'react-native-vector-icons/FontAwesome';
import BigNumber from 'bignumber.js';
import { NavigationContext } from 'react-navigation';
-import { swapsUtils, util } from '@estebanmino/controllers';
+import { swapsUtils, util } from '@metamask/swaps-controller';
+import { WalletDevice } from '@metamask/controllers/';
import {
BNToHex,
@@ -19,8 +20,9 @@ import {
toWei,
weiToFiat
} from '../../../util/number';
+import { isMainNet } from '../../../util/networks';
import { safeToChecksumAddress } from '../../../util/address';
-import { getErrorMessage, getFetchParams, getQuotesNavigationsParams, isSwapsETH } from './utils';
+import { getErrorMessage, getFetchParams, getQuotesNavigationsParams, isSwapsNativeAsset } from './utils';
import { colors } from '../../../styles/common';
import { strings } from '../../../../locales/i18n';
@@ -48,7 +50,7 @@ import InfoModal from './components/InfoModal';
import useModalHandler from '../../Base/hooks/useModalHandler';
import useBalance from './utils/useBalance';
import useGasPrice from './utils/useGasPrice';
-import { decodeApproveData } from '../../../util/transactions';
+import { decodeApproveData, getTicker } from '../../../util/transactions';
import Logger from '../../../util/Logger';
const POLLING_INTERVAL = AppConstants.SWAPS.POLLING_INTERVAL;
@@ -197,6 +199,9 @@ const styles = StyleSheet.create({
termsButton: {
marginTop: 10,
marginBottom: 6
+ },
+ text: {
+ lineHeight: 20
}
});
@@ -208,7 +213,7 @@ async function resetAndStartPolling({ slippage, sourceToken, destinationToken, s
const contractExchangeRates = TokenRatesController.state.contractExchangeRates;
// ff the token is not in the wallet, we'll add it
if (
- destinationToken.address !== swapsUtils.ETH_SWAPS_TOKEN_ADDRESS &&
+ !isSwapsNativeAsset(destinationToken) &&
!contractExchangeRates[safeToChecksumAddress(destinationToken.address)]
) {
const { address, symbol, decimals } = destinationToken;
@@ -252,6 +257,8 @@ function SwapsQuotesView({
selectedAddress,
currentCurrency,
conversionRate,
+ chainId,
+ ticker,
isInPolling,
quotesLastFetched,
pollingCyclesLeft,
@@ -279,7 +286,7 @@ function SwapsQuotesView({
const hasConversionRate =
Boolean(destinationToken) &&
- (isSwapsETH(destinationToken) ||
+ (isSwapsNativeAsset(destinationToken) ||
Boolean(
Engine.context.TokenRatesController.state.contractExchangeRates?.[
safeToChecksumAddress(destinationToken.address)
@@ -372,7 +379,7 @@ function SwapsQuotesView({
const hasEnoughTokenBalance = tokenBalanceBN.gte(sourceBN);
const missingTokenBalance = hasEnoughTokenBalance ? null : sourceBN.minus(tokenBalanceBN);
- const ethAmountBN = sourceToken.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS ? sourceBN : new BigNumber(0);
+ const ethAmountBN = isSwapsNativeAsset(sourceToken) ? sourceBN : new BigNumber(0);
const ethBalanceBN = new BigNumber(accounts[selectedAddress].balance);
const gasBN = toWei(selectedQuoteValue?.maxEthFee || '0');
const hasEnoughEthBalance = ethBalanceBN.gte(ethAmountBN.plus(gasBN));
@@ -552,7 +559,8 @@ function SwapsQuotesView({
try {
const { transactionMeta } = await TransactionController.addTransaction(
approvalTransaction,
- process.env.MM_FOX_CODE
+ process.env.MM_FOX_CODE,
+ WalletDevice.MM_MOBILE
);
approvalTransactionMetaId = transactionMeta.id;
newSwapsTransactions[transactionMeta.id] = {
@@ -571,7 +579,8 @@ function SwapsQuotesView({
try {
const { transactionMeta } = await TransactionController.addTransaction(
selectedQuote.trade,
- process.env.MM_FOX_CODE
+ process.env.MM_FOX_CODE,
+ WalletDevice.MM_MOBILE
);
updateSwapsTransactions(transactionMeta, approvalTransactionMetaId, newSwapsTransactions);
} catch (e) {
@@ -1019,22 +1028,23 @@ function SwapsQuotesView({
{`${strings('swaps.you_need')} `}
- {!hasEnoughTokenBalance && sourceToken.address !== swapsUtils.ETH_SWAPS_TOKEN_ADDRESS
+ {!hasEnoughTokenBalance && !isSwapsNativeAsset(sourceToken)
? `${renderFromTokenMinimalUnit(missingTokenBalance, sourceToken.decimals)} ${
sourceToken.symbol
// eslint-disable-next-line no-mixed-spaces-and-tabs
} `
- : `${renderFromWei(missingEthBalance)} ETH `}
+ : `${renderFromWei(missingEthBalance)} ${getTicker(ticker)} `}
{!hasEnoughTokenBalance
? `${strings('swaps.more_to_complete')} `
: `${strings('swaps.more_gas_to_complete')} `}
- {(sourceToken.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS ||
- (hasEnoughTokenBalance && !hasEnoughEthBalance)) && (
-
- {strings('swaps.buy_more_eth')}
-
- )}
+ {isMainNet(chainId) &&
+ (isSwapsNativeAsset(sourceToken) ||
+ (hasEnoughTokenBalance && !hasEnoughEthBalance)) && (
+
+ {strings('swaps.buy_more_eth')}
+
+ )}
)}
@@ -1206,7 +1216,7 @@ function SwapsQuotesView({
- {renderFromWei(toWei(selectedQuoteValue?.ethFee))} ETH
+ {renderFromWei(toWei(selectedQuoteValue?.ethFee))} {getTicker(ticker)}
{` ${weiToFiat(
@@ -1230,7 +1240,10 @@ function SwapsQuotesView({
- {renderFromWei(toWei(selectedQuoteValue?.maxEthFee || '0x0'))} ETH
+
+ {renderFromWei(toWei(selectedQuoteValue?.maxEthFee || '0x0'))}{' '}
+ {getTicker(ticker)}
+
{` ${weiToFiat(
toWei(selectedQuoteValue?.maxEthFee),
@@ -1293,20 +1306,20 @@ function SwapsQuotesView({
isVisible={isUpdateModalVisible}
toggleModal={toggleUpdateModal}
title={strings('swaps.quotes_update_often')}
- body={{strings('swaps.quotes_update_often_text')}}
+ body={{strings('swaps.quotes_update_often_text')}}
/>
{strings('swaps.price_difference_body')}}
+ body={{strings('swaps.price_difference_body')}}
/>
+
{strings('swaps.fee_text.get_the')} {strings('swaps.fee_text.best_price')}{' '}
{strings('swaps.fee_text.from_the')} {strings('swaps.fee_text.top_liquidity')}{' '}
{strings('swaps.fee_text.fee_is_applied', {
@@ -1323,6 +1336,7 @@ function SwapsQuotesView({
destinationToken={destinationToken}
selectedQuote={selectedQuoteId}
showOverallValue={hasConversionRate}
+ ticker={getTicker(ticker)}
/>
);
@@ -1371,6 +1386,14 @@ SwapsQuotesView.propTypes = {
* A string that represents the selected address
*/
selectedAddress: PropTypes.string,
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string,
+ /**
+ * Native asset ticker
+ */
+ ticker: PropTypes.string,
isInPolling: PropTypes.bool,
quotesLastFetched: PropTypes.number,
topAggId: PropTypes.string,
@@ -1389,6 +1412,8 @@ SwapsQuotesView.propTypes = {
const mapStateToProps = state => ({
accounts: state.engine.backgroundState.AccountTrackerController.accounts,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
+ ticker: state.engine.backgroundState.NetworkController.provider.ticker,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
balances: state.engine.backgroundState.TokenBalancesController.contractBalances,
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
diff --git a/app/components/UI/Swaps/SwapsLiveness.js b/app/components/UI/Swaps/SwapsLiveness.js
index 27ba0a6fb07..16d3324a1d8 100644
--- a/app/components/UI/Swaps/SwapsLiveness.js
+++ b/app/components/UI/Swaps/SwapsLiveness.js
@@ -1,4 +1,4 @@
-import { swapsUtils } from '@estebanmino/controllers';
+import { swapsUtils } from '@metamask/swaps-controller';
import { useCallback, useEffect, useState } from 'react';
import { AppState } from 'react-native';
import { connect } from 'react-redux';
@@ -10,18 +10,18 @@ import useInterval from '../../hooks/useInterval';
const SWAPS_ACTIVE = AppConstants.SWAPS.ACTIVE;
const POLLING_FREQUENCY = AppConstants.SWAPS.LIVENESS_POLLING_FREQUENCY;
-function SwapLiveness({ isLive, setLiveness }) {
+function SwapLiveness({ isLive, chainId, setLiveness }) {
const [hasMountChecked, setHasMountChecked] = useState(false);
const checkLiveness = useCallback(async () => {
try {
- const { mobile_active: liveness } = await swapsUtils.fetchSwapsFeatureLiveness();
- setLiveness(liveness);
+ const { mobile_active: liveness } = await swapsUtils.fetchSwapsFeatureLiveness(chainId);
+ setLiveness(liveness, chainId);
} catch (error) {
Logger.error(error, 'Swaps: error while fetching swaps liveness');
- setLiveness(false);
+ setLiveness(false, chainId);
}
- }, [setLiveness]);
+ }, [setLiveness, chainId]);
// Check on mount
useEffect(() => {
@@ -62,11 +62,12 @@ function SwapLiveness({ isLive, setLiveness }) {
}
const mapStateToProps = state => ({
- isLive: swapsLivenessSelector(state)
+ isLive: swapsLivenessSelector(state),
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId
});
const mapDispatchToProps = dispatch => ({
- setLiveness: liveness => dispatch(setSwapsLiveness(liveness))
+ setLiveness: (liveness, chainId) => dispatch(setSwapsLiveness(liveness, chainId))
});
export default connect(
diff --git a/app/components/UI/Swaps/components/QuotesModal.js b/app/components/UI/Swaps/components/QuotesModal.js
index 9078cd8ca13..514245d3497 100644
--- a/app/components/UI/Swaps/components/QuotesModal.js
+++ b/app/components/UI/Swaps/components/QuotesModal.js
@@ -137,7 +137,8 @@ function QuotesModal({
conversionRate,
currentCurrency,
quoteValues,
- showOverallValue
+ showOverallValue,
+ ticker
}) {
const bestOverallValue = quoteValues[quotes[0].aggregator].overallValueOfQuote;
const [displayDetails, setDisplayDetails] = useState(false);
@@ -290,7 +291,7 @@ function QuotesModal({
{renderFromWei(toWei(selectedDetailsQuoteValues.ethFee))}{' '}
- ETH
+ {ticker}
{' '}
(~
@@ -466,6 +467,10 @@ QuotesModal.propTypes = {
* Currency code of the currently-active currency
*/
currentCurrency: PropTypes.string,
+ /**
+ * Native asset ticker
+ */
+ ticker: PropTypes.string,
quoteValues: PropTypes.object,
showOverallValue: PropTypes.bool
};
diff --git a/app/components/UI/Swaps/components/TokenIcon.js b/app/components/UI/Swaps/components/TokenIcon.js
index d0ef90f26b9..b857ef30c93 100644
--- a/app/components/UI/Swaps/components/TokenIcon.js
+++ b/app/components/UI/Swaps/components/TokenIcon.js
@@ -6,8 +6,10 @@ import RemoteImage from '../../../Base/RemoteImage';
import Text from '../../../Base/Text';
import { colors } from '../../../../styles/common';
-// eslint-disable-next-line import/no-commonjs
+/* eslint-disable import/no-commonjs */
const ethLogo = require('../../../../images/eth-logo.png');
+const bnbLogo = require('../../../../images/bnb-logo.png');
+/* eslint-enable import/no-commonjs */
const REGULAR_SIZE = 24;
const REGULAR_RADIUS = 12;
@@ -79,11 +81,11 @@ EmptyIcon.propTypes = {
};
function TokenIcon({ symbol, icon, medium, big, biggest, style }) {
- if (symbol === 'ETH') {
+ if (symbol === 'ETH' || symbol === 'BNB') {
return (
)}
@@ -157,7 +160,8 @@ TransactionsEditionModal.propTypes = {
onHandleGasFeeSelection: PropTypes.func,
setApprovalTransaction: PropTypes.func,
selectedQuote: PropTypes.object,
- sourceToken: PropTypes.object
+ sourceToken: PropTypes.object,
+ chainId: PropTypes.string
};
const mapStateToProps = state => ({
diff --git a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap
index ed438b8f218..2e5a01e3d1d 100644
--- a/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap
+++ b/app/components/UI/Swaps/components/__snapshots__/TokenIcon.test.js.snap
@@ -29,6 +29,8 @@ exports[`TokenIcon component should Render correctly 2`] = `
exports[`TokenIcon component should Render correctly 3`] = `
{
(async () => {
try {
- const { mobile_active: liveness } = await swapsUtils.fetchSwapsFeatureLiveness();
- setLiveness(liveness);
+ const { mobile_active: liveness } = await swapsUtils.fetchSwapsFeatureLiveness(chainId);
+ setLiveness(liveness, chainId);
if (liveness) {
// Triggered when a user enters the MetaMask Swap feature
InteractionManager.runAfterInteractions(() => {
const parameters = {
- source: initialSource === SWAPS_ETH_ADDRESS ? 'MainView' : 'TokenView',
+ source: initialSource === SWAPS_NATIVE_ADDRESS ? 'MainView' : 'TokenView',
activeCurrency: swapsTokens?.find(
token => token.address?.toLowerCase() === initialSource.toLowerCase()
)?.symbol
@@ -198,12 +202,12 @@ function SwapsAmountView({
}
} catch (error) {
Logger.error(error, 'Swaps: error while fetching swaps liveness');
- setLiveness(false);
+ setLiveness(false, chainId);
navigation.pop();
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [initialSource, navigation, setLiveness]);
+ }, [initialSource, chainId, navigation, setLiveness]);
const keypadViewRef = useRef(null);
@@ -251,11 +255,13 @@ function SwapsAmountView({
}, [destinationToken]);
const isTokenInBalances =
- sourceToken && !isSwapsETH(sourceToken) ? safeToChecksumAddress(sourceToken.address) in balances : false;
+ sourceToken && !isSwapsNativeAsset(sourceToken)
+ ? safeToChecksumAddress(sourceToken.address) in balances
+ : false;
useEffect(() => {
(async () => {
- if (sourceToken && !isSwapsETH(sourceToken) && !isTokenInBalances) {
+ if (sourceToken && !isSwapsNativeAsset(sourceToken) && !isTokenInBalances) {
setContractBalance(null);
setContractBalanceAsUnits(numberToBN(0));
const { AssetsContractController } = Engine.context;
@@ -285,9 +291,9 @@ function SwapsAmountView({
const controllerBalance = useBalance(accounts, balances, selectedAddress, sourceToken);
const controllerBalanceAsUnits = useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits: true });
- const balance = isSwapsETH(sourceToken) || isTokenInBalances ? controllerBalance : contractBalance;
+ const balance = isSwapsNativeAsset(sourceToken) || isTokenInBalances ? controllerBalance : contractBalance;
const balanceAsUnits =
- isSwapsETH(sourceToken) || isTokenInBalances ? controllerBalanceAsUnits : contractBalanceAsUnits;
+ isSwapsNativeAsset(sourceToken) || isTokenInBalances ? controllerBalanceAsUnits : contractBalanceAsUnits;
const hasBalance = useMemo(() => {
if (!balanceAsUnits || !sourceToken) {
return false;
@@ -308,7 +314,7 @@ function SwapsAmountView({
return undefined;
}
let balanceFiat;
- if (isSwapsETH(sourceToken)) {
+ if (isSwapsNativeAsset(sourceToken)) {
balanceFiat = weiToFiat(toTokenMinimalUnit(amount, sourceToken?.decimals), conversionRate, currentCurrency);
} else {
const sourceAddress = safeToChecksumAddress(sourceToken.address);
@@ -319,7 +325,7 @@ function SwapsAmountView({
}, [amount, conversionRate, currentCurrency, hasInvalidDecimals, sourceToken, tokenExchangeRates]);
const destinationTokenHasEnoughOcurrances = useMemo(() => {
- if (!destinationToken || isSwapsETH(destinationToken)) {
+ if (!destinationToken || isSwapsNativeAsset(destinationToken)) {
return true;
}
return destinationToken?.occurances > TOKEN_MINIMUM_SOURCES;
@@ -330,7 +336,7 @@ function SwapsAmountView({
if (hasInvalidDecimals) {
return;
}
- if (!isSwapsETH(sourceToken) && !isTokenInBalances && !balanceAsUnits?.isZero()) {
+ if (!isSwapsNativeAsset(sourceToken) && !isTokenInBalances && !balanceAsUnits?.isZero()) {
const { AssetsController } = Engine.context;
const { address, symbol, decimals } = sourceToken;
await AssetsController.addToken(address, symbol, decimals);
@@ -404,10 +410,10 @@ function SwapsAmountView({
}
hideTokenVerificationModal();
navigation.navigate('Webview', {
- url: getEtherscanAddressUrl('mainnet', destinationToken.address),
+ url: explorer.token(destinationToken.address),
title: strings('swaps.verify')
});
- }, [destinationToken, hideTokenVerificationModal, navigation]);
+ }, [explorer, destinationToken, hideTokenVerificationModal, navigation]);
const handleAmountPress = useCallback(() => keypadViewRef?.current?.shake?.(), []);
@@ -481,7 +487,7 @@ function SwapsAmountView({
strings('swaps.available_to_swap', {
asset: `${balance} ${sourceToken.symbol}`
})}
- {!isSwapsETH(sourceToken) && hasBalance && (
+ {!isSwapsNativeAsset(sourceToken) && hasBalance && (
{' '}
{strings('swaps.use_max')}
@@ -519,23 +525,33 @@ function SwapsAmountView({
dismiss={toggleDestinationModal}
title={strings('swaps.convert_to')}
tokens={swapsTokens}
- initialTokens={[swapsUtils.ETH_SWAPS_TOKEN_OBJECT, ...tokensTopAssets.slice(0, MAX_TOP_ASSETS)]}
+ initialTokens={[
+ swapsUtils.getNativeSwapsToken(chainId),
+ ...tokensTopAssets.slice(0, MAX_TOP_ASSETS)
+ ]}
onItemPress={handleDestinationTokenPress}
excludeAddresses={[sourceToken?.address]}
/>
- {Boolean(destinationToken) && !isSwapsETH(destinationToken) ? (
+ {Boolean(destinationToken) && !isSwapsNativeAsset(destinationToken) ? (
destinationTokenHasEnoughOcurrances ? (
-
+
{strings('swaps.verified_on_sources', { sources: destinationToken.occurances })}
{` ${strings('swaps.verify_on')} `}
-
- Etherscan
-
+ {explorer.isValid ? (
+
+ {explorer.name}
+
+ ) : (
+ strings('swaps.a_block_explorer')
+ )}
.
@@ -548,7 +564,7 @@ function SwapsAmountView({
onInfoPress={toggleTokenVerificationModal}
>
{textStyle => (
-
+
{strings('swaps.only_verified_on', {
symbol: destinationToken.symbol,
@@ -557,9 +573,13 @@ function SwapsAmountView({
{`${strings('swaps.verify_address_on')} `}
-
- Etherscan
-
+ {explorer.isValid ? (
+
+ {explorer.name}
+
+ ) : (
+ strings('swaps.a_block_explorer')
+ )}
.
@@ -617,9 +637,13 @@ function SwapsAmountView({
{strings('swaps.token_multiple')}
{` ${strings('swaps.token_check')} `}
-
- Etherscan
-
+ {explorer.isValid ? (
+
+ {explorer.name}
+
+ ) : (
+ strings('swaps.a_block_explorer')
+ )}
{` ${strings('swaps.token_to_verify')}`}
}
@@ -672,6 +696,18 @@ SwapsAmountView.propTypes = {
* Function to set hasOnboarded
*/
setHasOnboarded: PropTypes.func,
+ /**
+ * Current Network provider
+ */
+ provider: PropTypes.object,
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string,
+ /**
+ * Frequent RPC list from PreferencesController
+ */
+ frequentRpcList: PropTypes.array,
/**
* Function to set liveness
*/
@@ -686,6 +722,9 @@ const mapStateToProps = state => ({
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
tokenExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
+ provider: state.engine.backgroundState.NetworkController.provider,
+ frequentRpcList: state.engine.backgroundState.PreferencesController.frequentRpcList,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
tokensWithBalance: swapsTokensWithBalanceSelector(state),
tokensTopAssets: swapsTopAssetsSelector(state),
userHasOnboarded: swapsHasOnboardedSelector(state)
@@ -693,7 +732,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
setHasOnboarded: hasOnboarded => dispatch(setSwapsHasOnboarded(hasOnboarded)),
- setLiveness: liveness => dispatch(setSwapsLiveness(liveness))
+ setLiveness: (liveness, chainId) => dispatch(setSwapsLiveness(liveness, chainId))
});
export default connect(
diff --git a/app/components/UI/Swaps/utils/index.js b/app/components/UI/Swaps/utils/index.js
index 31bf2a6f10c..46baab4f9fe 100644
--- a/app/components/UI/Swaps/utils/index.js
+++ b/app/components/UI/Swaps/utils/index.js
@@ -1,10 +1,25 @@
import { useMemo } from 'react';
import BigNumber from 'bignumber.js';
-import { swapsUtils } from '@estebanmino/controllers';
+import { swapsUtils } from '@metamask/swaps-controller';
import { strings } from '../../../../../locales/i18n';
+import AppConstants from '../../../../core/AppConstants';
-export function isSwapsETH(token) {
- return Boolean(token) && token?.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS;
+const { ETH_CHAIN_ID, BSC_CHAIN_ID, SWAPS_TESTNET_CHAIN_ID } = swapsUtils;
+
+const allowedChainIds = [ETH_CHAIN_ID, BSC_CHAIN_ID];
+
+export function isSwapsAllowed(chainId) {
+ if (!AppConstants.SWAPS.ACTIVE) {
+ return false;
+ }
+ if (!AppConstants.SWAPS.ONLY_MAINNET) {
+ allowedChainIds.push(SWAPS_TESTNET_CHAIN_ID);
+ }
+ return allowedChainIds.includes(chainId);
+}
+
+export function isSwapsNativeAsset(token) {
+ return Boolean(token) && token?.address === swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS;
}
/**
diff --git a/app/components/UI/Swaps/utils/useBalance.js b/app/components/UI/Swaps/utils/useBalance.js
index 4d4e09bfee1..d006be26a8f 100644
--- a/app/components/UI/Swaps/utils/useBalance.js
+++ b/app/components/UI/Swaps/utils/useBalance.js
@@ -1,6 +1,6 @@
-import { swapsUtils } from '@estebanmino/controllers';
import { useMemo } from 'react';
import numberToBN from 'number-to-bn';
+import { isSwapsNativeAsset } from '.';
import { renderFromTokenMinimalUnit, renderFromWei } from '../../../../util/number';
import { safeToChecksumAddress } from '../../../../util/address';
@@ -9,7 +9,7 @@ function useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits
if (!sourceToken) {
return null;
}
- if (sourceToken.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS) {
+ if (isSwapsNativeAsset(sourceToken)) {
if (asUnits) {
// Controller stores balances in hex for ETH
return numberToBN((accounts[selectedAddress] && accounts[selectedAddress].balance) || 0);
diff --git a/app/components/UI/Swaps/utils/useBlockExplorer.js b/app/components/UI/Swaps/utils/useBlockExplorer.js
new file mode 100644
index 00000000000..193c7fb1a8b
--- /dev/null
+++ b/app/components/UI/Swaps/utils/useBlockExplorer.js
@@ -0,0 +1,76 @@
+import { useCallback, useEffect, useState } from 'react';
+import etherscanLink from '@metamask/etherscan-link';
+import { RPC } from '../../../../constants/network';
+import { findBlockExplorerForRpc, getBlockExplorerName } from '../../../../util/networks';
+import { strings } from '../../../../../locales/i18n';
+
+function useBlockExplorer(provider, frequentRpcList) {
+ const [explorer, setExplorer] = useState({ name: '', value: null, isValid: false, isRPC: false });
+
+ useEffect(() => {
+ if (provider.type === RPC) {
+ try {
+ const blockExplorer = findBlockExplorerForRpc(provider.rpcTarget, frequentRpcList);
+ if (!blockExplorer) {
+ throw new Error('No block explorer url');
+ }
+ const url = new URL(blockExplorer);
+ if (!['http:', 'https:'].includes(url.protocol)) {
+ throw new Error('Block explorer URL is not a valid http(s) protocol');
+ }
+
+ const name = getBlockExplorerName(blockExplorer) || strings('swaps.block_explorer');
+ setExplorer({ name, value: blockExplorer, isValid: true, isRPC: true });
+ } catch {
+ setExplorer({ name: '', value: null, isValid: false, isRPC: false });
+ }
+ } else {
+ setExplorer({ name: 'Etherscan', value: provider.chainId, isValid: true, isRPC: false });
+ }
+ }, [frequentRpcList, provider]);
+
+ const tx = useCallback(
+ hash => {
+ if (!explorer.isValid) {
+ return '';
+ }
+
+ const create = explorer.isRPC ? etherscanLink.createCustomExplorerLink : etherscanLink.createExplorerLink;
+ return create(hash, explorer.value);
+ },
+ [explorer]
+ );
+ const account = useCallback(
+ address => {
+ if (!explorer.isValid) {
+ return '';
+ }
+
+ const create = explorer.isRPC ? etherscanLink.createCustomAccountLink : etherscanLink.createAccountLink;
+ return create(address, explorer.value);
+ },
+ [explorer]
+ );
+ const token = useCallback(
+ address => {
+ if (!explorer.isValid) {
+ return '';
+ }
+
+ const create = explorer.isRPC
+ ? etherscanLink.createCustomTokenTrackerLink
+ : etherscanLink.createTokenTrackerLink;
+ return create(address, explorer.value);
+ },
+ [explorer]
+ );
+
+ return {
+ ...explorer,
+ tx,
+ account,
+ token
+ };
+}
+
+export default useBlockExplorer;
diff --git a/app/components/UI/Swaps/utils/useGasPrice.js b/app/components/UI/Swaps/utils/useGasPrice.js
index f4fde19514d..9db789b12ea 100644
--- a/app/components/UI/Swaps/utils/useGasPrice.js
+++ b/app/components/UI/Swaps/utils/useGasPrice.js
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
-import { getBasicGasEstimates } from '../../../../util/custom-gas';
+import { getBasicGasEstimatesByChainId } from '../../../../util/custom-gas';
import Logger from '../../../../util/Logger';
function useGasPrice() {
@@ -7,7 +7,7 @@ function useGasPrice() {
const getGasPrice = useCallback(async () => {
try {
- const gasEstimates = await getBasicGasEstimates();
+ const gasEstimates = await getBasicGasEstimatesByChainId();
setGasPrice(gasEstimates);
} catch (error) {
Logger.log('Swaps: Error while trying to get gas estimates', error);
diff --git a/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap b/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap
index f61fb102fa0..b1c7d1a29d4 100644
--- a/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap
+++ b/app/components/UI/SwitchCustomNetwork/__snapshots__/index.test.js.snap
@@ -14,6 +14,8 @@ exports[`SwitchCustomNetwork should render correctly 1`] = `
}
>
- "undefined"
+ ""
{
it('should render correctly', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper).toMatchSnapshot();
});
});
diff --git a/app/components/UI/Tabs/__snapshots__/index.test.js.snap b/app/components/UI/Tabs/__snapshots__/index.test.js.snap
index 3595f187e79..07f81d00944 100644
--- a/app/components/UI/Tabs/__snapshots__/index.test.js.snap
+++ b/app/components/UI/Tabs/__snapshots__/index.test.js.snap
@@ -50,7 +50,7 @@ exports[`Tabs should render correctly 1`] = `
"marginBottom": 0,
"paddingHorizontal": 20,
"paddingTop": 17,
- "shadowColor": "#000000",
+ "shadowColor": "#24292E",
"shadowOffset": Object {
"height": 12,
"width": 0,
diff --git a/app/components/UI/Tokens/index.js b/app/components/UI/Tokens/index.js
index cd1f46b10d5..886a2469f3d 100644
--- a/app/components/UI/Tokens/index.js
+++ b/app/components/UI/Tokens/index.js
@@ -17,6 +17,7 @@ import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
import StyledButton from '../StyledButton';
import { allowedToBuy } from '../FiatOrders';
import NetworkMainAssetLogo from '../NetworkMainAssetLogo';
+import { isMainNet } from '../../../util/networks';
const styles = StyleSheet.create({
wrapper: {
@@ -162,14 +163,23 @@ class Tokens extends PureComponent {
);
renderItem = asset => {
- const { conversionRate, currentCurrency, tokenBalances, tokenExchangeRates, primaryCurrency } = this.props;
+ const {
+ chainId,
+ conversionRate,
+ currentCurrency,
+ tokenBalances,
+ tokenExchangeRates,
+ primaryCurrency
+ } = this.props;
const itemAddress = safeToChecksumAddress(asset.address);
const logo = asset.logo || ((contractMap[itemAddress] && contractMap[itemAddress].logo) || undefined);
const exchangeRate = itemAddress in tokenExchangeRates ? tokenExchangeRates[itemAddress] : undefined;
const balance =
asset.balance ||
(itemAddress in tokenBalances ? renderFromTokenMinimalUnit(tokenBalances[itemAddress], asset.decimals) : 0);
- const balanceFiat = asset.balanceFiat || balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency);
+ const balanceFiat = isMainNet(chainId)
+ ? asset.balanceFiat || balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency)
+ : null;
const balanceValue = `${balance} ${asset.symbol}`;
// render balances according to primary currency
diff --git a/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap b/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap
index 5653c3a8212..249be5f27d6 100644
--- a/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionEditor/__snapshots__/index.test.js.snap
@@ -29,9 +29,20 @@ exports[`TransactionEditor should render correctly 1`] = `
validate={[Function]}
/>
diff --git a/app/components/UI/TransactionEditor/index.js b/app/components/UI/TransactionEditor/index.js
index 1c1fb135d69..dfdb3fa2a1c 100644
--- a/app/components/UI/TransactionEditor/index.js
+++ b/app/components/UI/TransactionEditor/index.js
@@ -9,8 +9,8 @@ import { isValidAddress, toChecksumAddress, BN, addHexPrefix } from 'ethereumjs-
import { strings } from '../../../../locales/i18n';
import { connect } from 'react-redux';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
-import { generateTransferData, getNormalizedTxState, getTicker } from '../../../util/transactions';
-import { getBasicGasEstimates, apiEstimateModifiedToWEI } from '../../../util/custom-gas';
+import { generateTransferData, getNormalizedTxState, getTicker, getActiveTabUrl } from '../../../util/transactions';
+import { getBasicGasEstimatesByChainId, apiEstimateModifiedToWEI } from '../../../util/custom-gas';
import { setTransactionObject } from '../../../actions/transaction';
import Engine from '../../../core/Engine';
import collectiblesTransferInformation from '../../../util/collectibles-transfer';
@@ -93,7 +93,11 @@ class TransactionEditor extends PureComponent {
/**
* Current selected ticker
*/
- ticker: PropTypes.string
+ ticker: PropTypes.string,
+ /**
+ * Active tab URL, the currently active tab url
+ */
+ activeTabUrl: PropTypes.string
};
state = {
@@ -596,9 +600,28 @@ class TransactionEditor extends PureComponent {
handleFetchBasicEstimates = async () => {
this.setState({ ready: false });
- const basicGasEstimates = await getBasicGasEstimates();
- this.handleGasFeeSelection(this.props.transaction.gas, apiEstimateModifiedToWEI(basicGasEstimates.averageGwei));
- this.setState({ basicGasEstimates, ready: true });
+ const basicGasEstimates = await getBasicGasEstimatesByChainId();
+ if (basicGasEstimates) {
+ this.handleGasFeeSelection(
+ this.props.transaction.gas,
+ apiEstimateModifiedToWEI(basicGasEstimates.averageGwei)
+ );
+ }
+ return this.setState({ basicGasEstimates, ready: true });
+ };
+
+ getGasAnalyticsParams = () => {
+ try {
+ const { transaction, activeTabUrl } = this.props;
+ const { selectedAsset } = transaction;
+ return {
+ dapp_host_name: transaction?.origin,
+ dapp_url: activeTabUrl,
+ active_currency: { value: selectedAsset?.symbol, anonymous: true }
+ };
+ } catch (error) {
+ return {};
+ }
};
render = () => {
@@ -623,6 +646,8 @@ class TransactionEditor extends PureComponent {
gasPrice={transaction.gasPrice}
gasError={gasError}
mode={mode}
+ view={'Transaction'}
+ analyticsParams={this.getGasAnalyticsParams()}
/>
@@ -639,7 +664,8 @@ const mapStateToProps = state => ({
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
tokens: state.engine.backgroundState.AssetsController.tokens,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
- transaction: getNormalizedTxState(state)
+ transaction: getNormalizedTxState(state),
+ activeTabUrl: getActiveTabUrl(state)
});
const mapDispatchToProps = dispatch => ({
diff --git a/app/components/UI/TransactionEditor/index.test.js b/app/components/UI/TransactionEditor/index.test.js
index 063baf0db9f..7cd96248ac5 100644
--- a/app/components/UI/TransactionEditor/index.test.js
+++ b/app/components/UI/TransactionEditor/index.test.js
@@ -29,7 +29,8 @@ describe('TransactionEditor', () => {
},
NetworkController: {
provider: {
- type: 'mainnet'
+ type: 'mainnet',
+ chainId: '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 e3639c236a7..bd9bdbcb3fe 100644
--- a/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionElement/TransactionDetails/__snapshots__/index.test.js.snap
@@ -21,6 +21,8 @@ exports[`TransactionDetails should render correctly 1`] = `
Date
{
const {
+ chainId,
transactionDetails,
transactionObject,
transactionObject: {
@@ -223,7 +233,9 @@ class TransactionDetails extends PureComponent {
amount={transactionDetails.summaryAmount}
fee={transactionDetails.summaryFee}
totalAmount={transactionDetails.summaryTotalAmount}
- secondaryTotalAmount={transactionDetails.summarySecondaryTotalAmount}
+ secondaryTotalAmount={
+ isMainNet(chainId) ? transactionDetails.summarySecondaryTotalAmount : undefined
+ }
gasEstimationReady
transactionType={transactionDetails.transactionType}
/>
@@ -247,6 +259,7 @@ class TransactionDetails extends PureComponent {
const mapStateToProps = state => ({
network: state.engine.backgroundState.NetworkController,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
frequentRpcList: state.engine.backgroundState.PreferencesController.frequentRpcList
});
export default connect(mapStateToProps)(TransactionDetails);
diff --git a/app/components/UI/TransactionElement/index.js b/app/components/UI/TransactionElement/index.js
index 79e61ac7e16..b49481973b6 100644
--- a/app/components/UI/TransactionElement/index.js
+++ b/app/components/UI/TransactionElement/index.js
@@ -1,7 +1,8 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import { TouchableHighlight, StyleSheet, Image } from 'react-native';
-import { colors } from '../../../styles/common';
+import { TouchableOpacity, TouchableHighlight, StyleSheet, Image, Text, View } from 'react-native';
+import { colors, fontStyles } from '../../../styles/common';
+import FAIcon from 'react-native-vector-icons/FontAwesome';
import { strings } from '../../../../locales/i18n';
import { toDateFormat } from '../../../util/date';
import TransactionDetails from './TransactionDetails';
@@ -14,6 +15,8 @@ import { TRANSACTION_TYPES } from '../../../util/transactions';
import ListItem from '../../Base/ListItem';
import StatusText from '../../Base/StatusText';
import DetailsModal from '../../Base/DetailsModal';
+import { isMainNet } from '../../../util/networks';
+import { WalletDevice } from '@metamask/controllers/';
const styles = StyleSheet.create({
row: {
@@ -38,6 +41,26 @@ const styles = StyleSheet.create({
icon: {
width: 28,
height: 28
+ },
+ summaryWrapper: {
+ padding: 15
+ },
+ fromDeviceText: {
+ color: colors.fontSecondary,
+ fontSize: 14,
+ marginBottom: 10,
+ ...fontStyles.normal
+ },
+ importText: {
+ color: colors.fontSecondary,
+ fontSize: 14,
+ ...fontStyles.bold,
+ alignContent: 'center'
+ },
+ importRowBody: {
+ alignItems: 'center',
+ backgroundColor: colors.grey000,
+ paddingTop: 10
}
});
@@ -71,6 +94,10 @@ class TransactionElement extends PureComponent {
* String of selected address
*/
selectedAddress: PropTypes.string,
+ /**
+ /* Identities object required to get import time name
+ */
+ identities: PropTypes.object,
/**
* Current element of the list index
*/
@@ -88,7 +115,11 @@ class TransactionElement extends PureComponent {
*/
onCancelAction: PropTypes.func,
swapsTransactions: PropTypes.object,
- swapsTokens: PropTypes.arrayOf(PropTypes.object)
+ swapsTokens: PropTypes.arrayOf(PropTypes.object),
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string
};
state = {
@@ -96,6 +127,7 @@ class TransactionElement extends PureComponent {
cancelIsOpen: false,
speedUpIsOpen: false,
detailsModalVisible: false,
+ importModalVisible: false,
transactionGas: { gasBN: undefined, gasPriceBN: undefined, gasTotal: undefined },
transactionElement: undefined,
transactionDetails: undefined
@@ -124,6 +156,14 @@ class TransactionElement extends PureComponent {
this.setState({ detailsModalVisible: true });
};
+ onPressImportWalletTip = () => {
+ this.setState({ importModalVisible: true });
+ };
+
+ onCloseImportWalletModal = () => {
+ this.setState({ importModalVisible: false });
+ };
+
onCloseDetailsModal = () => {
this.setState({ detailsModalVisible: false });
};
@@ -133,8 +173,36 @@ class TransactionElement extends PureComponent {
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)}`;
+ (!incoming || selfSent) && tx.deviceConfirmedOn === WalletDevice.MM_MOBILE
+ ? `#${parseInt(tx.transaction.nonce, 16)} - ${toDateFormat(tx.time)} ${strings(
+ 'transactions.from_device_label'
+ // eslint-disable-next-line no-mixed-spaces-and-tabs
+ )}`
+ : `${toDateFormat(tx.time)}
+ `
+ }`;
+ };
+
+ /**
+ * Function that evaluates tx to see if the Added Wallet label should be rendered.
+ * @returns Account added to wallet view
+ */
+ renderImportTime = () => {
+ const { tx, identities, selectedAddress } = this.props;
+ if (tx.insertImportTime && identities[selectedAddress].importTime) {
+ return (
+ <>
+
+
+ {`${strings('transactions.import_wallet_row')} `}
+
+
+ {toDateFormat(identities[selectedAddress].importTime)}
+
+ >
+ );
+ }
+ return null;
};
renderTxElementIcon = (transactionElement, status) => {
@@ -169,33 +237,41 @@ class TransactionElement extends PureComponent {
*/
renderTxElement = transactionElement => {
const {
- tx: { status }
+ identities,
+ chainId,
+ selectedAddress,
+ tx: { time, status }
} = this.props;
const { value, fiatValue = false, actionKey } = transactionElement;
const renderTxActions = status === 'submitted' || status === 'approved';
+ const accountImportTime = identities[selectedAddress].importTime;
return (
-
- {this.renderTxTime()}
-
- {this.renderTxElementIcon(transactionElement, status)}
-
- {actionKey}
-
-
- {Boolean(value) && (
-
- {value}
- {fiatValue}
-
+ <>
+ {accountImportTime > time && this.renderImportTime()}
+
+ {this.renderTxTime()}
+
+ {this.renderTxElementIcon(transactionElement, status)}
+
+ {actionKey}
+
+
+ {Boolean(value) && (
+
+ {value}
+ {isMainNet(chainId) && {fiatValue}}
+
+ )}
+
+ {!!renderTxActions && (
+
+ {this.renderSpeedUpButton()}
+ {this.renderCancelButton()}
+
)}
-
- {!!renderTxActions && (
-
- {this.renderSpeedUpButton()}
- {this.renderCancelButton()}
-
- )}
-
+
+ {accountImportTime <= time && this.renderImportTime()}
+ >
);
};
@@ -241,7 +317,7 @@ class TransactionElement extends PureComponent {
render() {
const { tx } = this.props;
- const { detailsModalVisible, transactionElement, transactionDetails } = this.state;
+ const { detailsModalVisible, importModalVisible, transactionElement, transactionDetails } = this.state;
if (!transactionElement || !transactionDetails) return null;
return (
@@ -276,6 +352,25 @@ class TransactionElement extends PureComponent {
/>
+
+
+
+
+ {strings('transactions.import_wallet_label')}
+
+
+
+
+ {strings('transactions.import_wallet_tip')}
+
+
+
>
);
}
@@ -283,7 +378,10 @@ class TransactionElement extends PureComponent {
const mapStateToProps = state => ({
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
+ identities: state.engine.backgroundState.PreferencesController.identities,
primaryCurrency: state.settings.primaryCurrency,
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
swapsTransactions: state.engine.backgroundState.TransactionController.swapsTransactions || {},
swapsTokens: state.engine.backgroundState.SwapsController.tokens
});
diff --git a/app/components/UI/TransactionElement/index.test.js b/app/components/UI/TransactionElement/index.test.js
index 6f84fc1146a..a14320b5952 100644
--- a/app/components/UI/TransactionElement/index.test.js
+++ b/app/components/UI/TransactionElement/index.test.js
@@ -10,6 +10,10 @@ describe('TransactionElement', () => {
const initialState = {
engine: {
backgroundState: {
+ PreferencesController: {
+ selectedAddress: '0x0',
+ identities: { '0xbar': { name: 'Account 1', address: '0x0', importTime: Date.now() } }
+ },
CurrencyRateController: {
currentCurrency: 'usd',
conversionRate: 0.1
diff --git a/app/components/UI/TransactionElement/utils.js b/app/components/UI/TransactionElement/utils.js
index 88a08654d9f..8c7753326cc 100644
--- a/app/components/UI/TransactionElement/utils.js
+++ b/app/components/UI/TransactionElement/utils.js
@@ -23,9 +23,10 @@ import {
} from '../../../util/transactions';
import contractMap from '@metamask/contract-metadata';
import { toChecksumAddress } from 'ethereumjs-util';
-import { swapsUtils } from '@estebanmino/controllers';
+import { swapsUtils } from '@metamask/swaps-controller';
+import { isSwapsNativeAsset } from '../Swaps/utils';
-const { ETH_SWAPS_TOKEN_ADDRESS, SWAPS_CONTRACT_ADDRESS } = swapsUtils;
+const { getSwapsContractAddress } = swapsUtils;
function calculateTotalGas(gas, gasPrice) {
const gasBN = hexToBN(gas);
@@ -604,16 +605,14 @@ function decodeSwapsTx(args) {
);
}
- const sourceExchangeRate =
- sourceToken.address === ETH_SWAPS_TOKEN_ADDRESS
- ? 1
- : contractExchangeRates[safeToChecksumAddress(sourceToken.address)];
+ const sourceExchangeRate = isSwapsNativeAsset(sourceToken)
+ ? 1
+ : contractExchangeRates[safeToChecksumAddress(sourceToken.address)];
const renderSourceTokenFiatNumber = balanceToFiatNumber(decimalSourceAmount, conversionRate, sourceExchangeRate);
- const destinationExchangeRate =
- destinationToken.address === ETH_SWAPS_TOKEN_ADDRESS
- ? 1
- : contractExchangeRates[safeToChecksumAddress(destinationToken.address)];
+ const destinationExchangeRate = isSwapsNativeAsset(destinationToken)
+ ? 1
+ : contractExchangeRates[safeToChecksumAddress(destinationToken.address)];
const renderDestinationTokenFiatNumber = balanceToFiatNumber(
decimalDestinationAmount,
conversionRate,
@@ -682,13 +681,13 @@ function decodeSwapsTx(args) {
* currentCurrency, exchangeRate, contractExchangeRates, collectibleContracts, tokens
*/
export default async function decodeTransaction(args) {
- const { tx, selectedAddress, ticker, swapsTransactions = {} } = args;
+ const { tx, selectedAddress, ticker, chainId, swapsTransactions = {} } = args;
const { isTransfer } = tx || {};
- const actionKey = await getActionKey(tx, selectedAddress, ticker);
+ const actionKey = await getActionKey(tx, selectedAddress, ticker, chainId);
let transactionElement, transactionDetails;
- if (tx.transaction.to === SWAPS_CONTRACT_ADDRESS || swapsTransactions[tx.id]) {
+ if (tx.transaction.to?.toLowerCase() === getSwapsContractAddress(chainId) || swapsTransactions[tx.id]) {
const [transactionElement, transactionDetails] = decodeSwapsTx({ ...args, actionKey });
if (transactionElement && transactionDetails) return [transactionElement, transactionDetails];
}
diff --git a/app/components/UI/TransactionHeader/__snapshots__/index.test.js.snap b/app/components/UI/TransactionHeader/__snapshots__/index.test.js.snap
index ee3ebce1772..b60c6a06a7a 100644
--- a/app/components/UI/TransactionHeader/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionHeader/__snapshots__/index.test.js.snap
@@ -50,7 +50,7 @@ exports[`TransactionHeader should render correctly 1`] = `
@@ -38,7 +38,7 @@ exports[`TransactionReviewData should render correctly 1`] = `
style={
Object {
"alignSelf": "center",
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Bold",
"fontSize": 14,
"fontWeight": "600",
@@ -57,7 +57,7 @@ exports[`TransactionReviewData should render correctly 1`] = `
+
-
-
- Amount
-
-
-
-
-
+ }
+ }
+ >
+
- Network fee
+ Amount
-
+
+
+
+
-
- Edit
+ Network fee
-
-
-
+
+
+ Edit
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+ Total
+
+ Amount
+
+
+
- Total
-
- Amount
-
-
-
-
-
-
-
+ >
+
+
+
+
`;
diff --git a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
index a3dab58bdf6..4a2eec462be 100644
--- a/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
+++ b/app/components/UI/TransactionReview/TransactionReviewFeeCard/index.js
@@ -16,6 +16,20 @@ const styles = StyleSheet.create({
},
over: {
color: colors.red
+ },
+ customNonce: {
+ marginTop: 10,
+ marginHorizontal: 24,
+ borderWidth: 1,
+ borderColor: colors.grey050,
+ borderRadius: 8,
+ paddingVertical: 14,
+ paddingHorizontal: 16,
+ display: 'flex',
+ flexDirection: 'row'
+ },
+ nonceNumber: {
+ marginLeft: 'auto'
}
});
@@ -67,7 +81,19 @@ class TransactionReviewFeeCard extends PureComponent {
/**
* True if transaction is gas price is higher than the "FAST" value
*/
- warningGasPriceHigh: PropTypes.string
+ warningGasPriceHigh: PropTypes.string,
+ /**
+ * Indicates whether custom nonce should be shown in transaction editor
+ */
+ showCustomNonce: PropTypes.bool,
+ /**
+ * Current nonce
+ */
+ nonceValue: PropTypes.number,
+ /**
+ * Function called when editing nonce
+ */
+ onNonceEdit: PropTypes.func
};
renderIfGasEstimationReady = children => {
@@ -93,8 +119,12 @@ class TransactionReviewFeeCard extends PureComponent {
gasEstimationReady,
edit,
over,
- warningGasPriceHigh
+ warningGasPriceHigh,
+ showCustomNonce,
+ nonceValue,
+ onNonceEdit
} = this.props;
+
let amount;
let networkFee;
let totalAmount;
@@ -111,44 +141,60 @@ class TransactionReviewFeeCard extends PureComponent {
equivalentTotalAmount = totalValue;
}
return (
-
-
-
- {strings('transaction.amount')}
-
-
- {amount}
-
-
-
-
+
+
+
- {strings('transaction.gas_fee')}
+ {strings('transaction.amount')}
+
+
+ {amount}
-
-
- {' '}
- {strings('transaction.edit')}
+
+
+
+
+ {strings('transaction.gas_fee')}
-
-
- {this.renderIfGasEstimationReady(
-
- {networkFee}
+
+
+ {' '}
+ {strings('transaction.edit')}
+
+
+
+ {this.renderIfGasEstimationReady(
+
+ {networkFee}
+
+ )}
+
+
+
+
+ {strings('transaction.total')} {strings('transaction.amount')}
+
+ {!!totalFiat && this.renderIfGasEstimationReady(totalAmount)}
+
+
+ {this.renderIfGasEstimationReady({equivalentTotalAmount})}
+
+
+ {showCustomNonce && (
+
+
+ {strings('transaction.custom_nonce')}
- )}
-
-
-
-
- {strings('transaction.total')} {strings('transaction.amount')}
-
- {!!totalFiat && this.renderIfGasEstimationReady(totalAmount)}
-
-
- {this.renderIfGasEstimationReady({equivalentTotalAmount})}
-
-
+
+ {' '}
+ {strings('transaction.edit')}
+
+
+ {nonceValue}
+
+
+ )}
+
);
}
}
diff --git a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
index 16de211749c..500e0393fe3 100644
--- a/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
+++ b/app/components/UI/TransactionReview/TransactionReviewInformation/index.js
@@ -19,8 +19,10 @@ import TransactionReviewFeeCard from '../TransactionReviewFeeCard';
import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
import { withNavigation } from 'react-navigation';
-import { getNetworkName, isMainNet } from '../../../../util/networks';
-import { capitalize } from '../../../../util/format';
+import { getNetworkName, getNetworkNonce, isMainNet } from '../../../../util/networks';
+import { capitalize } from '../../../../util/general';
+import CustomNonceModal from '../../../UI/CustomNonceModal';
+import { setNonce, setProposedNonce } from '../../../../actions/transaction';
const styles = StyleSheet.create({
overviewAlert: {
@@ -184,13 +186,53 @@ class TransactionReviewInformation extends PureComponent {
/**
* Network id
*/
- network: PropTypes.string
+ network: PropTypes.string,
+ /**
+ * Indicates whether custom nonce should be shown in transaction editor
+ */
+ showCustomNonce: PropTypes.bool,
+ /**
+ * Set transaction nonce
+ */
+ setNonce: PropTypes.func,
+ /**
+ * Set proposed nonce (from network)
+ */
+ setProposedNonce: PropTypes.func
};
state = {
toFocused: false,
amountError: '',
- actionKey: strings('transactions.tx_review_confirm')
+ actionKey: strings('transactions.tx_review_confirm'),
+ nonceModalVisible: false
+ };
+
+ componentDidMount = async () => {
+ const { showCustomNonce } = this.props;
+ showCustomNonce && (await this.setNetworkNonce());
+ };
+
+ setNetworkNonce = async () => {
+ const { setNonce, setProposedNonce, transaction } = this.props;
+ const proposedNonce = await getNetworkNonce(transaction);
+ setNonce(proposedNonce);
+ setProposedNonce(proposedNonce);
+ };
+
+ toggleNonceModal = () => this.setState(state => ({ nonceModalVisible: !state.nonceModalVisible }));
+
+ renderCustomNonceModal = () => {
+ const { setNonce } = this.props;
+ const { proposedNonce, nonce } = this.props.transaction;
+ return (
+
+ );
};
getTotalFiat = (asset, totalGas, conversionRate, exchangeRate, currentCurrency, amountToken) => {
@@ -305,7 +347,8 @@ class TransactionReviewInformation extends PureComponent {
};
render() {
- const { amountError } = this.state;
+ const { amountError, nonceModalVisible } = this.state;
+ const { nonce } = this.props.transaction;
const {
fiatValue,
assetAmount,
@@ -318,7 +361,8 @@ class TransactionReviewInformation extends PureComponent {
ticker,
error,
over,
- network
+ network,
+ showCustomNonce
} = this.props;
const is_main_net = isMainNet(network);
const totalGas = isBN(gas) && isBN(gasPrice) ? gas.mul(gasPrice) : toBN('0x0');
@@ -333,6 +377,7 @@ class TransactionReviewInformation extends PureComponent {
return (
+ {nonceModalVisible && this.renderCustomNonceModal()}
{!!amountError && (
@@ -370,7 +418,7 @@ class TransactionReviewInformation extends PureComponent {
{warningGasPriceHigh}
)}
- {!over && (
+ {!over && !showCustomNonce && (
{strings('transaction.view_data')}
@@ -389,7 +437,16 @@ const mapStateToProps = state => ({
contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates,
transaction: getNormalizedTxState(state),
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
- primaryCurrency: state.settings.primaryCurrency
+ primaryCurrency: state.settings.primaryCurrency,
+ showCustomNonce: state.settings.showCustomNonce
+});
+
+const mapDispatchToProps = dispatch => ({
+ setNonce: nonce => dispatch(setNonce(nonce)),
+ setProposedNonce: nonce => dispatch(setProposedNonce(nonce))
});
-export default connect(mapStateToProps)(withNavigation(TransactionReviewInformation));
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(withNavigation(TransactionReviewInformation));
diff --git a/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap b/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap
index 4d6170c0902..b0fe994646c 100644
--- a/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap
+++ b/app/components/UI/TransactionReview/TransactionReviewSummary/__snapshots__/index.test.js.snap
@@ -21,7 +21,7 @@ exports[`TransactionReviewSummary should render correctly 1`] = `
"borderColor": "#848c96",
"borderRadius": 12,
"borderWidth": 1,
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 10,
"fontWeight": "400",
diff --git a/app/components/UI/TransactionReview/index.js b/app/components/UI/TransactionReview/index.js
index c6d6461ef81..e8c7520ee74 100644
--- a/app/components/UI/TransactionReview/index.js
+++ b/app/components/UI/TransactionReview/index.js
@@ -124,6 +124,10 @@ class TransactionReview extends PureComponent {
* Current provider ticker
*/
ticker: PropTypes.string,
+ /**
+ * Chain id
+ */
+ chainId: PropTypes.string,
/**
* ETH or fiat, depending on user setting
*/
@@ -174,14 +178,15 @@ class TransactionReview extends PureComponent {
validate,
transaction,
transaction: { data, to },
- tokens
+ tokens,
+ chainId
} = this.props;
let { showHexData } = this.props;
let assetAmount, conversionRate, fiatValue;
showHexData = showHexData || data;
const approveTransaction = data && data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE;
const error = validate && (await validate());
- const actionKey = await getTransactionReviewActionKey(transaction);
+ const actionKey = await getTransactionReviewActionKey(transaction, chainId);
if (approveTransaction) {
let contract = contractMap[safeToChecksumAddress(to)];
if (!contract) {
@@ -358,6 +363,7 @@ const mapStateToProps = state => ({
contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates,
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
showHexData: state.settings.showHexData,
transaction: getNormalizedTxState(state),
browser: state.browser,
diff --git a/app/components/UI/Transactions/index.js b/app/components/UI/Transactions/index.js
index ab01fbb17f7..4597f431f0f 100644
--- a/app/components/UI/Transactions/index.js
+++ b/app/components/UI/Transactions/index.js
@@ -306,13 +306,13 @@ class Transactions extends PureComponent {
if (!this.props.transactions.length) {
return this.renderEmpty();
}
-
const { submittedTransactions, confirmedTransactions, header } = this.props;
const { cancelConfirmDisabled, speedUpConfirmDisabled } = this.state;
const transactions =
submittedTransactions && submittedTransactions.length
? submittedTransactions.concat(confirmedTransactions)
: this.props.transactions;
+
return (
({
accounts: state.engine.backgroundState.AccountTrackerController.accounts,
- tokens: state.engine.backgroundState.AssetsController.tokens.reduce((tokens, token) => {
- tokens[token.address] = token;
- return tokens;
- }, {}),
collectibleContracts: state.engine.backgroundState.AssetsController.collectibleContracts,
contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates,
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
- thirdPartyApiMode: state.privacy.thirdPartyApiMode
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ thirdPartyApiMode: state.privacy.thirdPartyApiMode,
+ tokens: state.engine.backgroundState.AssetsController.tokens.reduce((tokens, token) => {
+ tokens[token.address] = token;
+ return tokens;
+ }, {})
});
const mapDispatchToProps = dispatch => ({
diff --git a/app/components/UI/Transactions/index.test.js b/app/components/UI/Transactions/index.test.js
index f863c5bd064..cd5c80ff4fd 100644
--- a/app/components/UI/Transactions/index.test.js
+++ b/app/components/UI/Transactions/index.test.js
@@ -12,6 +12,10 @@ describe('Transactions', () => {
const initialState = {
engine: {
backgroundState: {
+ PreferencesController: {
+ selectedAddress: '0x0',
+ identities: { '0xbar': { name: 'Account 1', address: '0x0', importTime: Date.now() } }
+ },
AccountTrackerController: {
accounts: {}
},
@@ -24,6 +28,11 @@ describe('Transactions', () => {
CurrencyRateController: {
currentCurrency: 'USD',
conversionRate: 1
+ },
+ NetworkController: {
+ provider: {
+ chainId: '1'
+ }
}
}
},
diff --git a/app/components/UI/TypedSign/index.js b/app/components/UI/TypedSign/index.js
index aa09e788723..09e9b5e1425 100644
--- a/app/components/UI/TypedSign/index.js
+++ b/app/components/UI/TypedSign/index.js
@@ -9,6 +9,8 @@ import Device from '../../../util/Device';
import NotificationManager from '../../../core/NotificationManager';
import { strings } from '../../../../locales/i18n';
import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect';
+import AnalyticsV2 from '../../../util/analyticsV2';
+import URL from 'url-parse';
const styles = StyleSheet.create({
messageText: {
@@ -73,6 +75,29 @@ export default class TypedSign extends PureComponent {
truncateMessage: false
};
+ getAnalyticsParams = () => {
+ try {
+ const { currentPageInformation, messageParams } = this.props;
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ const url = new URL(currentPageInformation?.url);
+ return {
+ dapp_host_name: url?.host,
+ dapp_url: currentPageInformation?.url,
+ network_name: type,
+ chain_id: chainId,
+ sign_type: 'typed',
+ version: messageParams?.version
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
+ componentDidMount = () => {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_STARTED, this.getAnalyticsParams());
+ };
+
showWalletConnectNotification = (messageParams = {}, confirmation = false) => {
InteractionManager.runAfterInteractions(() => {
messageParams.origin &&
@@ -109,11 +134,13 @@ export default class TypedSign extends PureComponent {
cancelSignature = () => {
this.rejectMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_CANCELLED, this.getAnalyticsParams());
this.props.onCancel();
};
confirmSignature = () => {
this.signMessage();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SIGN_REQUEST_COMPLETED, this.getAnalyticsParams());
this.props.onConfirm();
};
diff --git a/app/components/UI/WatchAssetRequest/index.js b/app/components/UI/WatchAssetRequest/index.js
index c8283859300..2651f41c88a 100644
--- a/app/components/UI/WatchAssetRequest/index.js
+++ b/app/components/UI/WatchAssetRequest/index.js
@@ -9,6 +9,8 @@ import { renderFromTokenMinimalUnit } from '../../../util/number';
import TokenImage from '../../UI/TokenImage';
import Device from '../../../util/Device';
import Engine from '../../../core/Engine';
+import URL from 'url-parse';
+import AnalyticsV2 from '../../../util/analyticsV2';
const styles = StyleSheet.create({
root: {
@@ -96,7 +98,36 @@ class WatchAssetRequest extends PureComponent {
/**
* Object containing token balances in the format address => balance
*/
- contractBalances: PropTypes.object
+ contractBalances: PropTypes.object,
+ /**
+ * Object containing current page title, url, and icon href
+ */
+ currentPageInformation: PropTypes.object
+ };
+
+ getAnalyticsParams = () => {
+ try {
+ const {
+ suggestedAssetMeta: { asset },
+ currentPageInformation
+ } = this.props;
+
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+
+ const url = new URL(currentPageInformation?.url);
+ return {
+ token_address: asset?.address,
+ token_symbol: asset?.symbol,
+ dapp_host_name: url?.host,
+ dapp_url: currentPageInformation?.url,
+ network_name: type,
+ chain_id: chainId,
+ source: 'Dapp suggested (watchAsset)'
+ };
+ } catch (error) {
+ return {};
+ }
};
componentWillUnmount = async () => {
@@ -109,6 +140,7 @@ class WatchAssetRequest extends PureComponent {
const { onConfirm, suggestedAssetMeta } = this.props;
const { AssetsController } = Engine.context;
await AssetsController.acceptWatchAsset(suggestedAssetMeta.id);
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.TOKEN_ADDED, this.getAnalyticsParams());
onConfirm && onConfirm();
};
diff --git a/app/components/UI/WhatsNewModal/__snapshots__/index.test.js.snap b/app/components/UI/WhatsNewModal/__snapshots__/index.test.js.snap
index c612138ea05..2ae9617eab7 100644
--- a/app/components/UI/WhatsNewModal/__snapshots__/index.test.js.snap
+++ b/app/components/UI/WhatsNewModal/__snapshots__/index.test.js.snap
@@ -48,7 +48,7 @@ exports[`WhatsNewModal should render correctly 1`] = `
getNetworkNavbarOptions('add_asset.title', true, navigation);
state = {
@@ -45,7 +47,11 @@ export default class AddAsset extends PureComponent {
/**
/* navigation object required to push new views
*/
- navigation: PropTypes.object
+ navigation: PropTypes.object,
+ /**
+ * Chain id
+ */
+ chainId: PropTypes.string
};
renderTabBar() {
@@ -74,11 +80,13 @@ export default class AddAsset extends PureComponent {
{assetType === 'token' ? (
-
+ {NetworksChainId.mainnet === this.props.chainId && (
+
+ )}
({
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId
+});
+
+export default connect(mapStateToProps)(AddAsset);
diff --git a/app/components/Views/AddAsset/index.test.js b/app/components/Views/AddAsset/index.test.js
index 310a5389180..a3e33019bdd 100644
--- a/app/components/Views/AddAsset/index.test.js
+++ b/app/components/Views/AddAsset/index.test.js
@@ -1,10 +1,27 @@
import React from 'react';
+import configureMockStore from 'redux-mock-store';
import { shallow } from 'enzyme';
import AddAsset from './';
+const mockStore = configureMockStore();
+
describe('AddAsset', () => {
it('should render correctly', () => {
- const wrapper = shallow();
- expect(wrapper).toMatchSnapshot();
+ const initialState = {
+ engine: {
+ backgroundState: {
+ NetworkController: {
+ provider: {
+ chainId: '1'
+ }
+ }
+ }
+ }
+ };
+
+ const wrapper = shallow(, {
+ context: { store: mockStore(initialState) }
+ });
+ expect(wrapper.dive()).toMatchSnapshot();
});
});
diff --git a/app/components/Views/Approval/index.js b/app/components/Views/Approval/index.js
index 73bceb4ebcd..a72d4d62c4e 100644
--- a/app/components/Views/Approval/index.js
+++ b/app/components/Views/Approval/index.js
@@ -11,11 +11,12 @@ import { connect } from 'react-redux';
import NotificationManager from '../../../core/NotificationManager';
import Analytics from '../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics';
-import { getTransactionReviewActionKey, getNormalizedTxState } from '../../../util/transactions';
+import { getTransactionReviewActionKey, getNormalizedTxState, getActiveTabUrl } from '../../../util/transactions';
import { strings } from '../../../../locales/i18n';
import { safeToChecksumAddress } from '../../../util/address';
import { WALLET_CONNECT_ORIGIN } from '../../../util/walletconnect';
import Logger from '../../../util/Logger';
+import AnalyticsV2 from '../../../util/analyticsV2';
const REVIEW = 'review';
const EDIT = 'edit';
@@ -62,7 +63,20 @@ class Approval extends PureComponent {
/**
* Tells whether or not dApp transaction modal is visible
*/
- dappTransactionModalVisible: PropTypes.bool
+ dappTransactionModalVisible: PropTypes.bool,
+ /**
+ * Indicates whether custom nonce should be shown in transaction editor
+ */
+ showCustomNonce: PropTypes.bool,
+ nonce: PropTypes.number,
+ /**
+ * Active tab URL, the currently active tab url
+ */
+ activeTabUrl: PropTypes.string,
+ /**
+ * A string representing the network chainId
+ */
+ chainId: PropTypes.string
};
state = {
@@ -93,7 +107,8 @@ class Approval extends PureComponent {
const { navigation } = this.props;
AppState.addEventListener('change', this.handleAppStateChange);
navigation && navigation.setParams({ mode: REVIEW, dispatch: this.onModeChange });
- this.trackConfirmScreen();
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.DAPP_TRANSACTION_STARTED, this.getAnalyticsParams());
};
/**
@@ -153,6 +168,23 @@ class Approval extends PureComponent {
};
};
+ getAnalyticsParams = () => {
+ try {
+ const { activeTabUrl, chainId, transaction, networkType } = this.props;
+ const { selectedAsset } = transaction;
+ return {
+ dapp_host_name: transaction?.origin,
+ dapp_url: activeTabUrl,
+ network_name: networkType,
+ chain_id: chainId,
+ active_currency: { value: selectedAsset?.symbol, anonymous: true },
+ asset_type: { value: transaction?.assetType, anonymous: true }
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
/**
* Transaction state is erased, ready to create a new clean transaction
*/
@@ -180,6 +212,7 @@ class Approval extends PureComponent {
this.props.toggleDappTransactionModal();
this.state.mode === REVIEW && this.trackOnCancel();
this.showWalletConnectNotification();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.DAPP_TRANSACTION_CANCELLED, this.getAnalyticsParams());
};
/**
@@ -189,9 +222,12 @@ class Approval extends PureComponent {
const { TransactionController } = Engine.context;
const {
transactions,
- transaction: { assetType, selectedAsset }
+ transaction: { assetType, selectedAsset },
+ showCustomNonce
} = this.props;
let { transaction } = this.props;
+ const { nonce } = transaction;
+ if (showCustomNonce && nonce) transaction.nonce = BNToHex(nonce);
try {
if (assetType === 'ETH') {
transaction = this.prepareTransaction(transaction);
@@ -224,7 +260,7 @@ class Approval extends PureComponent {
Logger.error(error, 'error while trying to send transaction (Approval)');
this.setState({ transactionHandled: false });
}
- this.trackOnConfirm();
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.DAPP_TRANSACTION_COMPLETED, this.getAnalyticsParams());
};
/**
@@ -313,7 +349,10 @@ class Approval extends PureComponent {
const mapStateToProps = state => ({
transaction: getNormalizedTxState(state),
transactions: state.engine.backgroundState.TransactionController.transactions,
- networkType: state.engine.backgroundState.NetworkController.provider.type
+ networkType: state.engine.backgroundState.NetworkController.provider.type,
+ showCustomNonce: state.settings.showCustomNonce,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
+ activeTabUrl: getActiveTabUrl(state)
});
const mapDispatchToProps = dispatch => ({
diff --git a/app/components/Views/Approval/index.test.js b/app/components/Views/Approval/index.test.js
index 4ef3bb31fdb..cd9beef6aba 100644
--- a/app/components/Views/Approval/index.test.js
+++ b/app/components/Views/Approval/index.test.js
@@ -9,6 +9,9 @@ const mockStore = configureMockStore();
describe('Approval', () => {
it('should render correctly', () => {
const initialState = {
+ settings: {
+ showCustomNonce: false
+ },
transaction: {
value: '',
data: '',
diff --git a/app/components/Views/ApproveView/Approve/index.js b/app/components/Views/ApproveView/Approve/index.js
index 879111b5e25..727c3758d4b 100644
--- a/app/components/Views/ApproveView/Approve/index.js
+++ b/app/components/Views/ApproveView/Approve/index.js
@@ -14,12 +14,13 @@ import { setTransactionObject } from '../../../../actions/transaction';
import { util } from '@metamask/controllers';
import { isBN, renderFromWei } from '../../../../util/number';
import { getNormalizedTxState, getTicker } from '../../../../util/transactions';
-import { getBasicGasEstimates, apiEstimateModifiedToWEI } from '../../../../util/custom-gas';
+import { apiEstimateModifiedToWEI, getBasicGasEstimatesByChainId } from '../../../../util/custom-gas';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import NotificationManager from '../../../../core/NotificationManager';
import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
import Logger from '../../../../util/Logger';
+import AnalyticsV2 from '../../../../util/analyticsV2';
const { BNToHex, hexToBN } = util;
@@ -92,7 +93,8 @@ class Approve extends PureComponent {
warningGasPriceHigh: undefined,
ready: false,
mode: REVIEW,
- over: false
+ over: false,
+ analyticsParams: {}
};
componentDidMount = () => {
@@ -121,9 +123,11 @@ class Approve extends PureComponent {
handleFetchBasicEstimates = async () => {
this.setState({ ready: false });
- const basicGasEstimates = await getBasicGasEstimates();
- this.handleSetGasFee(this.props.transaction.gas, apiEstimateModifiedToWEI(basicGasEstimates.averageGwei));
- this.setState({ basicGasEstimates, ready: true });
+ const basicGasEstimates = await getBasicGasEstimatesByChainId();
+ if (basicGasEstimates) {
+ this.handleSetGasFee(this.props.transaction.gas, apiEstimateModifiedToWEI(basicGasEstimates.averageGwei));
+ }
+ return this.setState({ basicGasEstimates, ready: true });
};
trackApproveEvent = event => {
@@ -205,7 +209,7 @@ class Approve extends PureComponent {
const updatedTx = { ...fullTx, transaction };
await TransactionController.updateTransaction(updatedTx);
await TransactionController.approveTransaction(transaction.id);
- this.trackApproveEvent(ANALYTICS_EVENT_OPTS.DAPP_APPROVE_SCREEN_APPROVE);
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.APPROVAL_COMPLETED, this.state.analyticsParams);
} catch (error) {
Alert.alert(strings('transactions.transaction_error'), error && error.message, [{ text: 'OK' }]);
Logger.error(error, 'error while trying to send transaction (Approve)');
@@ -214,7 +218,7 @@ class Approve extends PureComponent {
};
onCancel = () => {
- this.trackApproveEvent(ANALYTICS_EVENT_OPTS.DAPP_APPROVE_SCREEN_CANCEL);
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.APPROVAL_CANCELLED, this.state.analyticsParams);
this.props.toggleApproveModal(false);
};
@@ -231,10 +235,29 @@ class Approve extends PureComponent {
}
};
+ setAnalyticsParams = analyticsParams => {
+ this.setState({ analyticsParams });
+ };
+
+ getGasAnalyticsParams = () => {
+ try {
+ const { analyticsParams } = this.state;
+
+ return {
+ dapp_host_name: analyticsParams?.dapp_host_name,
+ dapp_url: analyticsParams?.dapp_url,
+ active_currency: { value: analyticsParams?.active_currency, anonymous: true }
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
render = () => {
const { gasError, basicGasEstimates, mode, ready, over, warningGasPriceHigh } = this.state;
const { transaction } = this.props;
if (!transaction.id) return null;
+
return (
@@ -280,8 +306,7 @@ const mapStateToProps = state => ({
transaction: getNormalizedTxState(state),
transactions: state.engine.backgroundState.TransactionController.transactions,
accountsLength: Object.keys(state.engine.backgroundState.AccountTrackerController.accounts || {}).length,
- tokensLength: state.engine.backgroundState.AssetsController.tokens.length,
- providerType: state.engine.backgroundState.NetworkController.provider.type
+ tokensLength: state.engine.backgroundState.AssetsController.tokens.length
});
const mapDispatchToProps = dispatch => ({
diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js
index 0ed4e3cd97d..7fed702bfcf 100644
--- a/app/components/Views/Asset/index.js
+++ b/app/components/Views/Asset/index.js
@@ -2,13 +2,15 @@ import React, { PureComponent } from 'react';
import { ActivityIndicator, InteractionManager, View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
+import { swapsUtils } from '@metamask/swaps-controller/';
+
import { colors } from '../../../styles/common';
import AssetOverview from '../../UI/AssetOverview';
import Transactions from '../../UI/Transactions';
import { getNetworkNavbarOptions } from '../../UI/Navbar';
import Engine from '../../../core/Engine';
import { safeToChecksumAddress } from '../../../util/address';
-import { SWAPS_CONTRACT_ADDRESS } from '@estebanmino/controllers/dist/swaps/SwapsUtil';
+import { addAccountTimeFlagFilter } from '../../../util/transactions';
const styles = StyleSheet.create({
wrapper: {
@@ -46,6 +48,10 @@ class Asset extends PureComponent {
/* Selected currency
*/
currentCurrency: PropTypes.string,
+ /**
+ /* Identities object required to get account name
+ */
+ identities: PropTypes.object,
/**
* A string that represents the selected address
*/
@@ -163,7 +169,8 @@ class Asset extends PureComponent {
if (isTransfer) return this.navAddress === transferInformation.contractAddress.toLowerCase();
if (
swapsTransactions[tx.id] &&
- (to?.toLowerCase() === SWAPS_CONTRACT_ADDRESS || to?.toLowerCase() === this.navAddress)
+ (to?.toLowerCase() === swapsUtils.getSwapsContractAddress(chainId) ||
+ to?.toLowerCase() === this.navAddress)
) {
const { destinationToken, sourceToken } = swapsTransactions[tx.id];
return destinationToken.address === this.navAddress || sourceToken.address === this.navAddress;
@@ -174,15 +181,24 @@ class Asset extends PureComponent {
normalizeTransactions() {
if (this.isNormalizing) return;
+ let accountAddedTimeInsertPointFound = false;
+ const addedAccountTime = this.props.identities[this.props.selectedAddress]?.importTime;
this.isNormalizing = true;
let submittedTxs = [];
const newPendingTxs = [];
const confirmedTxs = [];
const { chainId, transactions } = this.props;
if (transactions.length) {
+ transactions.sort((a, b) => (a.time > b.time ? -1 : b.time > a.time ? 1 : 0));
const txs = transactions.filter(tx => {
- const filerResult = this.filter(tx);
- if (filerResult) {
+ const filterResult = this.filter(tx);
+ if (filterResult) {
+ tx.insertImportTime = addAccountTimeFlagFilter(
+ tx,
+ addedAccountTime,
+ accountAddedTimeInsertPointFound
+ );
+ if (tx.insertImportTime) accountAddedTimeInsertPointFound = true;
switch (tx.status) {
case 'submitted':
case 'signed':
@@ -197,13 +213,9 @@ class Asset extends PureComponent {
break;
}
}
- return filerResult;
+ return filterResult;
});
- txs.sort((a, b) => (a.time > b.time ? -1 : b.time > a.time ? 1 : 0));
- submittedTxs.sort((a, b) => (a.time > b.time ? -1 : b.time > a.time ? 1 : 0));
- confirmedTxs.sort((a, b) => (a.time > b.time ? -1 : b.time > a.time ? 1 : 0));
-
const submittedNonces = [];
submittedTxs = submittedTxs.filter(transaction => {
const alreadySubmitted = submittedNonces.includes(transaction.transaction.nonce);
@@ -211,6 +223,10 @@ class Asset extends PureComponent {
return !alreadySubmitted;
});
+ //if the account added insertpoint is not found add it to the last transaction
+ if (!accountAddedTimeInsertPointFound && txs && txs.length) {
+ txs[txs.length - 1].insertImportTime = true;
+ }
// To avoid extra re-renders we want to set the new txs only when
// there's a new tx in the history or the status of one of the existing txs changed
if (
@@ -295,6 +311,7 @@ const mapStateToProps = state => ({
conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ identities: state.engine.backgroundState.PreferencesController.identities,
chainId: state.engine.backgroundState.NetworkController.provider.chainId,
tokens: state.engine.backgroundState.AssetsController.tokens,
transactions: state.engine.backgroundState.TransactionController.transactions,
diff --git a/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap b/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
index 8d031f91958..20e5dcf04a1 100644
--- a/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
+++ b/app/components/Views/BrowserTab/__snapshots__/index.test.js.snap
@@ -306,6 +306,13 @@ exports[`Browser should render correctly 1`] = `
useNativeDriver={false}
>
@@ -364,7 +371,7 @@ exports[`Browser should render correctly 1`] = `
swipeThreshold={100}
useNativeDriver={false}
>
- {
const { privacyMode, selectedAddress } = props;
const isEnabled = !privacyMode || approvedHosts[hostname];
- return isEnabled ? [selectedAddress.toLowerCase()] : [];
+ return isEnabled && selectedAddress ? [selectedAddress] : [];
};
const rpcMethods = {
@@ -464,7 +464,7 @@ export const BrowserTab = props => {
});
if (approved) {
- res.result = [selectedAddress.toLowerCase()];
+ res.result = selectedAddress ? [selectedAddress] : [];
} else {
throw ethErrors.provider.userRejectedRequest('User denied account authorization.');
}
@@ -1757,6 +1757,11 @@ export const BrowserTab = props => {
onCancel={onCancelWatchAsset}
onConfirm={onCancelWatchAsset}
suggestedAssetMeta={suggestedAssetMeta}
+ currentPageInformation={{
+ title: title.current,
+ url: getMaskedUrl(url.current),
+ icon: icon.current
+ }}
/>
);
@@ -1956,7 +1961,7 @@ const mapStateToProps = state => ({
networkProvider: state.engine.backgroundState.NetworkController.provider,
networkType: state.engine.backgroundState.NetworkController.provider.type,
network: state.engine.backgroundState.NetworkController.network,
- selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress.toLowerCase(),
+ selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress?.toLowerCase(),
privacyMode: state.privacy.privacyMode,
searchEngine: state.settings.searchEngine,
whitelist: state.browser.whitelist,
diff --git a/app/components/Views/ChoosePassword/__snapshots__/index.test.js.snap b/app/components/Views/ChoosePassword/__snapshots__/index.test.js.snap
index 7a05bd3f229..9e928630fda 100644
--- a/app/components/Views/ChoosePassword/__snapshots__/index.test.js.snap
+++ b/app/components/Views/ChoosePassword/__snapshots__/index.test.js.snap
@@ -69,9 +69,9 @@ exports[`ChoosePassword should render correctly 1`] = `
style={
Object {
"color": "#000000",
- "fontFamily": "EuclidCircularB-Regular",
- "fontSize": 24,
- "fontWeight": "400",
+ "fontFamily": "EuclidCircularB-Bold",
+ "fontSize": 25,
+ "fontWeight": "600",
"justifyContent": "center",
"marginBottom": 20,
"marginTop": 20,
@@ -110,6 +110,7 @@ exports[`ChoosePassword should render correctly 1`] = `
@@ -134,13 +133,11 @@ exports[`ChoosePassword should render correctly 1`] = `
style={
Array [
Object {
- "color": "#8E8E93",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
- "fontSize": 12,
+ "fontSize": 16,
"fontWeight": "400",
- "height": 20,
- "marginTop": 14,
- "textAlign": "left",
+ "marginBottom": 12,
},
Object {
"position": "absolute",
@@ -182,13 +179,12 @@ exports[`ChoosePassword should render correctly 1`] = `
@@ -196,6 +192,7 @@ exports[`ChoosePassword should render correctly 1`] = `
@@ -249,20 +244,19 @@ exports[`ChoosePassword should render correctly 1`] = `
"alignSelf": "flex-end",
"position": "absolute",
"right": 17,
- "top": 50,
+ "top": 52,
}
}
/>
@@ -282,11 +276,11 @@ exports[`ChoosePassword should render correctly 1`] = `
@@ -347,7 +341,7 @@ exports[`ChoosePassword should render correctly 1`] = `
onPress={[Function]}
style={
Object {
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 14,
"fontWeight": "400",
diff --git a/app/components/Views/ChoosePassword/index.js b/app/components/Views/ChoosePassword/index.js
index bc412367a4b..5601e44c747 100644
--- a/app/components/Views/ChoosePassword/index.js
+++ b/app/components/Views/ChoosePassword/index.js
@@ -29,7 +29,7 @@ import {
SEED_PHRASE_HINTS,
BIOMETRY_CHOICE_DISABLED
} from '../../../constants/storage';
-import { getPasswordStrengthWord, passwordRequirementsMet } from '../../../util/password';
+import { getPasswordStrengthWord, passwordRequirementsMet, MIN_PASSWORD_LENGTH } from '../../../util/password';
import { CHOOSE_PASSWORD_STEPS } from '../../../constants/onboarding';
@@ -71,13 +71,13 @@ const styles = StyleSheet.create({
alignItems: 'center'
},
title: {
- fontSize: 24,
+ fontSize: Device.isAndroid() ? 20 : 25,
marginTop: 20,
marginBottom: 20,
color: colors.fontPrimary,
justifyContent: 'center',
textAlign: 'center',
- ...fontStyles.normal
+ ...fontStyles.bold
},
subtitle: {
fontSize: 16,
@@ -118,6 +118,7 @@ const styles = StyleSheet.create({
textDecorationColor: colors.blue
},
field: {
+ marginVertical: 5,
position: 'relative'
},
input: {
@@ -144,11 +145,10 @@ const styles = StyleSheet.create({
marginBottom: 30
},
biometryLabel: {
- fontSize: 14,
- color: colors.fontPrimary,
- position: 'absolute',
- top: 0,
- left: 0
+ flex: 1,
+ fontSize: 16,
+ color: colors.black,
+ ...fontStyles.normal
},
biometrySwitch: {
position: 'absolute',
@@ -156,11 +156,16 @@ const styles = StyleSheet.create({
right: 0
},
hintLabel: {
+ color: colors.black,
+ fontSize: 16,
+ marginBottom: 12,
+ ...fontStyles.normal
+ },
+ passwordStrengthLabel: {
height: 20,
- marginTop: 14,
- fontSize: 12,
- color: colors.grey450,
- textAlign: 'left',
+ marginTop: 10,
+ fontSize: 15,
+ color: colors.black,
...fontStyles.normal
},
showPassword: {
@@ -182,7 +187,7 @@ const styles = StyleSheet.create({
},
showMatchingPasswords: {
position: 'absolute',
- top: 50,
+ top: 52,
right: 17,
alignSelf: 'flex-end'
}
@@ -571,14 +576,14 @@ class ChoosePassword extends PureComponent {
autoCapitalize="none"
/>
{(password !== '' && (
-
+
{strings('choose_password.password_strength')}
{' '}
{strings(`choose_password.strength_${passwordStrengthWord}`)}
- )) || }
+ )) || }
{strings('choose_password.confirm_password')}
@@ -600,8 +605,8 @@ class ChoosePassword extends PureComponent {
) : null}
-
- {strings('choose_password.must_be_at_least', { number: 8 })}
+
+ {strings('choose_password.must_be_at_least', { number: MIN_PASSWORD_LENGTH })}
{this.renderSwitch()}
diff --git a/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.js.snap b/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.js.snap
index 3284eee2aac..0ff57e33882 100644
--- a/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.js.snap
+++ b/app/components/Views/EnterPasswordSimple/__snapshots__/index.test.js.snap
@@ -51,7 +51,7 @@ exports[`EnterPasswordSimple should render correctly 1`] = `
{
const animation = useRef(null);
const animationName = useRef(null);
- const opacity = new Animated.Value(1);
+ const opacity = useRef(new Animated.Value(1)).current;
const onAnimationFinished = useCallback(() => {
Animated.timing(opacity, {
diff --git a/app/components/Views/ImportFromSeed/__snapshots__/index.test.js.snap b/app/components/Views/ImportFromSeed/__snapshots__/index.test.js.snap
index 78a1ae10949..25e9974b730 100644
--- a/app/components/Views/ImportFromSeed/__snapshots__/index.test.js.snap
+++ b/app/components/Views/ImportFromSeed/__snapshots__/index.test.js.snap
@@ -67,8 +67,9 @@ exports[`ImportFromSeed should render correctly 1`] = `
@@ -191,8 +202,9 @@ exports[`ImportFromSeed should render correctly 1`] = `
@@ -281,14 +299,16 @@ exports[`ImportFromSeed should render correctly 1`] = `
style={
Object {
"marginVertical": 5,
+ "position": "relative",
}
}
>
@@ -364,6 +390,7 @@ exports[`ImportFromSeed should render correctly 1`] = `
{hideSeedPhraseInput ? (
{strings('import_from_seed.confirm_password')}
- {strings('choose_password.must_be_at_least', { number: 8 })}
+ {strings('choose_password.must_be_at_least', { number: MIN_PASSWORD_LENGTH })}
diff --git a/app/components/Views/Login/index.js b/app/components/Views/Login/index.js
index 9ea55a275af..28901625909 100644
--- a/app/components/Views/Login/index.js
+++ b/app/components/Views/Login/index.js
@@ -85,7 +85,8 @@ const styles = StyleSheet.create({
flexDirection: 'column'
},
label: {
- fontSize: 14,
+ color: colors.black,
+ fontSize: 16,
marginBottom: 12,
...fontStyles.normal
},
@@ -114,11 +115,17 @@ const styles = StyleSheet.create({
biometryLabel: {
flex: 1,
fontSize: 16,
+ color: colors.black,
...fontStyles.normal
},
biometrySwitch: {
flex: 0
},
+ input: {
+ ...fontStyles.normal,
+ fontSize: 16,
+ paddingTop: 2
+ },
cant: {
width: 280,
alignSelf: 'center',
@@ -278,7 +285,8 @@ class Login extends PureComponent {
// Restore vault with user entered password
await KeyringController.submitPassword(this.state.password);
const encryptionLib = await AsyncStorage.getItem(ENCRYPTION_LIB);
- if (encryptionLib !== ORIGINAL) {
+ const existingUser = await AsyncStorage.getItem(EXISTING_USER);
+ if (encryptionLib !== ORIGINAL && existingUser) {
await recreateVaultWithSamePassword(this.state.password, this.props.selectedAddress);
await AsyncStorage.setItem(ENCRYPTION_LIB, ORIGINAL);
}
@@ -477,6 +485,7 @@ class Login extends PureComponent {
{strings('login.type_delete')}
{strings('login.password')}
-
+
-
You're offline
- Check your internet connection and try again
+ Unable to connect to the blockchain host.
-
+
+
Try again
-
-
+
+
`;
diff --git a/app/components/Views/OfflineMode/index.js b/app/components/Views/OfflineMode/index.js
index 6b9596e240e..b3d747bbcd7 100644
--- a/app/components/Views/OfflineMode/index.js
+++ b/app/components/Views/OfflineMode/index.js
@@ -1,100 +1,112 @@
'use strict';
-import React, { PureComponent } from 'react';
-import { SafeAreaView, Image, Text, View, StyleSheet } from 'react-native';
+import React from 'react';
+import { SafeAreaView, Image, View, StyleSheet } from 'react-native';
+import Text from '../../Base/Text';
import NetInfo from '@react-native-community/netinfo';
-import { colors } from '../../../styles/common';
+import { baseStyles, colors, fontStyles } from '../../../styles/common';
import PropTypes from 'prop-types';
import { strings } from '../../../../locales/i18n';
import StyledButton from '../../UI/StyledButton';
import { getOfflineModalNavbar } from '../../UI/Navbar';
import AndroidBackHandler from '../AndroidBackHandler';
import Device from '../../../util/Device';
+import AppConstants from '../../../core/AppConstants';
+import { connect } from 'react-redux';
+import { getInfuraBlockedSelector } from '../../../reducers/infuraAvailability';
const styles = StyleSheet.create({
container: {
- flex: 1,
- backgroundColor: colors.white
- },
- innerView: {
flex: 1
},
frame: {
width: 200,
height: 200,
alignSelf: 'center',
- justifyContent: 'center',
- marginTop: 80,
- marginBottom: 10
+ marginTop: 60
},
content: {
- width: 300,
- height: 125,
- alignSelf: 'center',
- justifyContent: 'center'
- },
- text: {
flex: 1,
- fontSize: 12,
- color: colors.fontPrimary,
- textAlign: 'center',
- justifyContent: 'center'
+ marginHorizontal: 18,
+ justifyContent: 'center',
+ marginVertical: 30
},
title: {
- fontSize: 17,
+ fontSize: 18,
color: colors.fontPrimary,
- textAlign: 'center',
- justifyContent: 'center',
- marginBottom: 10
+ marginBottom: 10,
+ ...fontStyles.bold
},
- button: {
- alignSelf: 'center',
- width: 150,
- height: 50
+ text: {
+ fontSize: 12,
+ color: colors.fontPrimary,
+ ...fontStyles.normal
+ },
+ buttonContainer: {
+ marginHorizontal: 18
}
});
const astronautImage = require('../../../images/astronaut.png'); // eslint-disable-line import/no-commonjs
-/**
- * View that wraps the Offline mode screen
- */
-export default class OfflineMode extends PureComponent {
- static navigationOptions = ({ navigation }) => getOfflineModalNavbar(navigation);
+const OfflineMode = ({ navigation, infuraBlocked }) => {
+ const netinfo = NetInfo.useNetInfo();
- static propTypes = {
- /**
- * Object that represents the navigator
- */
- navigation: PropTypes.object
+ const tryAgain = () => {
+ if (netinfo?.isConnected) {
+ navigation.pop();
+ }
};
- goBack = () => {
- this.props.navigation.goBack();
+ const learnMore = () => {
+ navigation.navigate('SimpleWebview', { url: AppConstants.URLS.CONNECTIVITY_ISSUES });
};
- tryAgain = () => {
- NetInfo.isConnected.fetch().then(isConnected => {
- if (isConnected) {
- this.props.navigation.pop();
- }
- });
+ const action = () => {
+ if (infuraBlocked) {
+ learnMore();
+ } else {
+ tryAgain();
+ }
};
- render() {
- return (
-
-
-
-
- {strings('offline_mode.title')}
- {strings('offline_mode.text')}
-
- {strings('offline_mode.try_again')}
-
-
-
- {Device.isAndroid() && }
+ return (
+
+
+
+
+
+ {strings('offline_mode.title')}
+
+
+ {strings(`offline_mode.text`)}
+
+
+
+
+ {strings(`offline_mode.${infuraBlocked ? 'learn_more' : 'try_again'}`)}
+
+
- );
- }
-}
+ {Device.isAndroid() && }
+
+ );
+};
+
+OfflineMode.navigationOptions = ({ navigation }) => getOfflineModalNavbar(navigation);
+
+OfflineMode.propTypes = {
+ /**
+ * Object that represents the navigator
+ */
+ navigation: PropTypes.object,
+ /**
+ * Whether infura was blocked or not
+ */
+ infuraBlocked: PropTypes.bool
+};
+
+const mapStateToProps = state => ({
+ infuraBlocked: getInfuraBlockedSelector(state)
+});
+
+export default connect(mapStateToProps)(OfflineMode);
diff --git a/app/components/Views/OfflineMode/index.test.js b/app/components/Views/OfflineMode/index.test.js
index 53b1297aa0f..ab24f9fbf8c 100644
--- a/app/components/Views/OfflineMode/index.test.js
+++ b/app/components/Views/OfflineMode/index.test.js
@@ -1,10 +1,20 @@
import React from 'react';
import { shallow } from 'enzyme';
+import configureMockStore from 'redux-mock-store';
import OfflineMode from './';
+const mockStore = configureMockStore();
+
describe('OfflineMode', () => {
it('should render correctly', () => {
- const wrapper = shallow( false }} />);
- expect(wrapper).toMatchSnapshot();
+ const initialState = {
+ infuraAvailability: {
+ isBlocked: false
+ }
+ };
+ const wrapper = shallow( false }} />, {
+ context: { store: mockStore(initialState) }
+ });
+ expect(wrapper.dive()).toMatchSnapshot();
});
});
diff --git a/app/components/Views/Onboarding/index.js b/app/components/Views/Onboarding/index.js
index d0b8964a164..d83e83674dc 100644
--- a/app/components/Views/Onboarding/index.js
+++ b/app/components/Views/Onboarding/index.js
@@ -624,17 +624,17 @@ class Onboarding extends PureComponent {
handleSimpleNotification = () => {
if (!this.props.navigation.getParam('delete', false)) return;
return (
-
-
+
+
-
-
+
+
);
};
diff --git a/app/components/Views/QRScanner/__snapshots__/index.test.js.snap b/app/components/Views/QRScanner/__snapshots__/index.test.js.snap
index 4332e2d6208..8846c353cd8 100644
--- a/app/components/Views/QRScanner/__snapshots__/index.test.js.snap
+++ b/app/components/Views/QRScanner/__snapshots__/index.test.js.snap
@@ -4,7 +4,7 @@ exports[`QrScanner should render correctly 1`] = `
getOnboardingNavbarOptions(navigation);
+ static navigationOptions = ({ navigation }) =>
+ getNavigationOptionsTitle(strings('password_reset.change_password'), navigation);
static propTypes = {
/**
@@ -400,6 +402,7 @@ class ResetPassword extends PureComponent {
const { originalPassword, password: newPassword } = this.state;
const { KeyringController, PreferencesController } = Engine.context;
const seedPhrase = await this.getSeedPhrase();
+ const oldPrefs = PreferencesController.state;
let importedAccounts = [];
try {
@@ -426,7 +429,6 @@ class ResetPassword extends PureComponent {
const hdKeyring = KeyringController.state.keyrings[0];
const existingAccountCount = hdKeyring.accounts.length;
const selectedAddress = this.props.selectedAddress;
- let preferencesControllerState = PreferencesController.state;
// Create previous accounts again
for (let i = 0; i < existingAccountCount - 1; i++) {
@@ -442,16 +444,17 @@ class ResetPassword extends PureComponent {
Logger.error(e, 'error while trying to import accounts on recreate vault');
}
- // Reset preferencesControllerState
- preferencesControllerState = PreferencesController.state;
+ //Persist old account/identities names
+ const preferencesControllerState = PreferencesController.state;
+ const prefUpdates = syncPrefs(oldPrefs, preferencesControllerState);
// Set preferencesControllerState again
- await PreferencesController.update(preferencesControllerState);
+ await PreferencesController.update(prefUpdates);
// Reselect previous selected account if still available
if (hdKeyring.accounts.includes(selectedAddress)) {
PreferencesController.setSelectedAddress(selectedAddress);
} else {
- PreferencesController.setSelectedAddress(hdKeyring[0]);
+ PreferencesController.setSelectedAddress(hdKeyring.accounts[0]);
}
};
diff --git a/app/components/Views/Send/index.js b/app/components/Views/Send/index.js
index 5e1daf16aff..f29b80de929 100644
--- a/app/components/Views/Send/index.js
+++ b/app/components/Views/Send/index.js
@@ -28,6 +28,7 @@ import { isENS } from '../../../util/address';
import TransactionTypes from '../../../core/TransactionTypes';
import { MAINNET } from '../../../constants/network';
import BigNumber from 'bignumber.js';
+import { WalletDevice } from '@metamask/controllers/';
const REVIEW = 'review';
const EDIT = 'edit';
@@ -317,7 +318,6 @@ class Send extends PureComponent {
if (gasPrice) {
newTxMeta.gasPrice = toBN(gas);
}
-
// TODO: We should add here support for sending tokens
// or calling smart contract functions
}
@@ -333,7 +333,6 @@ class Send extends PureComponent {
newTxMeta.from = selectedAddress;
newTxMeta.transactionFromName = identities[selectedAddress].name;
-
this.props.setTransactionObject(newTxMeta);
this.mounted && this.setState({ ready: true, transactionKey: Date.now() });
};
@@ -487,7 +486,8 @@ class Send extends PureComponent {
}
const { result, transactionMeta } = await TransactionController.addTransaction(
transaction,
- TransactionTypes.MMM
+ TransactionTypes.MMM,
+ WalletDevice.MM_MOBILE
);
await TransactionController.approveTransaction(transactionMeta.id);
diff --git a/app/components/Views/SendFlow/AddressElement/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/AddressElement/__snapshots__/index.test.js.snap
index ea86e74defe..fe1a33ccc96 100644
--- a/app/components/Views/SendFlow/AddressElement/__snapshots__/index.test.js.snap
+++ b/app/components/Views/SendFlow/AddressElement/__snapshots__/index.test.js.snap
@@ -37,7 +37,7 @@ exports[`AddressElement should render correctly 1`] = `
numberOfLines={1}
style={
Object {
- "color": "#000000",
+ "color": "#24292E",
"flex": 1,
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 14,
diff --git a/app/components/Views/SendFlow/AddressInputs/index.js b/app/components/Views/SendFlow/AddressInputs/index.js
index 24e7ccf9cf7..7a59d533301 100644
--- a/app/components/Views/SendFlow/AddressInputs/index.js
+++ b/app/components/Views/SendFlow/AddressInputs/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { StyleSheet, View, Text, TextInput, TouchableOpacity } from 'react-native';
+import { StyleSheet, View, TextInput, TouchableOpacity } from 'react-native';
import { colors, fontStyles, baseStyles } from '../../../../styles/common';
import AntIcon from 'react-native-vector-icons/AntDesign';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
@@ -7,6 +7,8 @@ import PropTypes from 'prop-types';
import Identicon from '../../../UI/Identicon';
import { renderShortAddress } from '../../../../util/address';
import { strings } from '../../../../../locales/i18n';
+import Text from '../../../Base/Text';
+import { hasZeroWidthPoints } from '../../../../util/validators';
const styles = StyleSheet.create({
wrapper: {
@@ -45,7 +47,15 @@ const styles = StyleSheet.create({
addressToInformation: {
flex: 1,
flexDirection: 'row',
- alignItems: 'center'
+ alignItems: 'center',
+ position: 'relative'
+ },
+ exclamation: {
+ backgroundColor: colors.white,
+ borderRadius: 12,
+ position: 'absolute',
+ bottom: 8,
+ left: 20
},
address: {
flexDirection: 'column',
@@ -119,6 +129,43 @@ const styles = StyleSheet.create({
}
});
+const AddressName = ({ toAddressName, confusableCollection = [] }) => {
+ if (confusableCollection.length) {
+ const texts = toAddressName.split('').map((char, index) => {
+ // if text has a confusable highlight it red
+ if (confusableCollection.includes(char)) {
+ // if the confusable is zero width, replace it with `?`
+ const replacement = hasZeroWidthPoints(char) ? '?' : char;
+ return (
+
+ {replacement}
+
+ );
+ }
+ return (
+
+ {char}
+
+ );
+ });
+ return (
+
+ {texts}
+
+ );
+ }
+ return (
+
+ {toAddressName}
+
+ );
+};
+
+AddressName.propTypes = {
+ toAddressName: PropTypes.string,
+ confusableCollection: PropTypes.array
+};
+
export const AddressTo = props => {
const {
addressToReady,
@@ -132,7 +179,9 @@ export const AddressTo = props => {
onInputFocus,
onSubmit,
onInputBlur,
- inputWidth
+ inputWidth,
+ confusableCollection,
+ displayExclamation
} = props;
return (
@@ -173,12 +222,18 @@ export const AddressTo = props => {
+ {displayExclamation && (
+
+
+
+ )}
{toAddressName && (
-
- {toAddressName}
-
+
)}
{
diff --git a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.js.snap
index 63bca618303..b51c9b1fe36 100644
--- a/app/components/Views/SendFlow/Amount/__snapshots__/index.test.js.snap
+++ b/app/components/Views/SendFlow/Amount/__snapshots__/index.test.js.snap
@@ -56,6 +56,8 @@ exports[`Amount should render correctly 1`] = `
"backgroundColor": "#037dd6",
"borderRadius": 100,
"flexDirection": "row",
+ "fontFamily": "EuclidCircularB-Regular",
+ "fontWeight": "400",
"paddingHorizontal": 16,
"paddingVertical": 2,
}
@@ -147,7 +149,8 @@ exports[`Amount should render correctly 1`] = `
placeholder="0"
style={
Object {
- "fontFamily": "Roboto-Light",
+ "color": "#24292E",
+ "fontFamily": "EuclidCircularB-Regular",
"fontSize": 44,
"fontWeight": "300",
"textAlign": "center",
diff --git a/app/components/Views/SendFlow/Amount/index.js b/app/components/Views/SendFlow/Amount/index.js
index b8b98cbebfd..6f64eb6bf7d 100644
--- a/app/components/Views/SendFlow/Amount/index.js
+++ b/app/components/Views/SendFlow/Amount/index.js
@@ -41,18 +41,18 @@ import {
import { getTicker, generateTransferData, getEther } from '../../../../util/transactions';
import { util } from '@metamask/controllers';
import ErrorMessage from '../ErrorMessage';
-import { fetchBasicGasEstimates, convertApiValueToGWEI } from '../../../../util/custom-gas';
+import { getGasPriceByChainId } from '../../../../util/custom-gas';
import Engine from '../../../../core/Engine';
import CollectibleImage from '../../../UI/CollectibleImage';
import collectiblesTransferInformation from '../../../../util/collectibles-transfer';
import { strings } from '../../../../../locales/i18n';
-import TransactionTypes from '../../../../core/TransactionTypes';
import Device from '../../../../util/Device';
import { BN } from 'ethereumjs-util';
import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard';
import NetworkMainAssetLogo from '../../../UI/NetworkMainAssetLogo';
+import { isMainNet } from '../../../../util/networks';
const { hexToBN, BNToHex } = util;
@@ -91,6 +91,7 @@ const styles = StyleSheet.create({
flex: 0.8
},
actionDropdown: {
+ ...fontStyles.normal,
backgroundColor: colors.blue,
paddingHorizontal: 16,
paddingVertical: 2,
@@ -128,7 +129,7 @@ const styles = StyleSheet.create({
flexDirection: 'row'
},
inputCurrencyText: {
- fontFamily: 'Roboto-Light',
+ ...fontStyles.normal,
fontWeight: fontStyles.light.fontWeight,
color: colors.black,
fontSize: 44,
@@ -139,10 +140,11 @@ const styles = StyleSheet.create({
textTransform: 'uppercase'
},
textInput: {
- fontFamily: 'Roboto-Light',
+ ...fontStyles.normal,
fontWeight: fontStyles.light.fontWeight,
fontSize: 44,
- textAlign: 'center'
+ textAlign: 'center',
+ color: colors.black
},
switch: {
flex: 1,
@@ -329,6 +331,10 @@ class Amount extends PureComponent {
* An array that represents the user tokens
*/
tokens: PropTypes.array,
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string,
/**
* Current provider ticker
*/
@@ -398,7 +404,7 @@ class Amount extends PureComponent {
this.collectibles = this.processCollectibles();
this.amountInput && this.amountInput.current && this.amountInput.current.focus();
this.onInputChange(readableValue);
- this.handleSelectedAssetBalance(selectedAsset);
+ !selectedAsset.tokenId && this.handleSelectedAssetBalance(selectedAsset);
const estimatedTotalGas = await this.estimateTransactionTotalGas();
this.setState({
@@ -614,27 +620,15 @@ class Amount extends PureComponent {
* Estimate transaction gas with information available
*/
estimateTransactionTotalGas = async () => {
- const { TransactionController } = Engine.context;
const {
transaction: { from },
transactionTo
} = this.props.transactionState;
- let estimation, basicGasEstimates;
- try {
- estimation = await TransactionController.estimateGas({
- from,
- to: transactionTo
- });
- } catch (e) {
- estimation = { gas: TransactionTypes.CUSTOM_GAS.DEFAULT_GAS_LIMIT };
- }
- try {
- basicGasEstimates = await fetchBasicGasEstimates();
- } catch (error) {
- basicGasEstimates = { average: 20 };
- }
- const gas = hexToBN(estimation.gas);
- const gasPrice = toWei(convertApiValueToGWEI(basicGasEstimates.average), 'gwei');
+ const { gas, gasPrice } = await getGasPriceByChainId({
+ from,
+ to: transactionTo
+ });
+
return gas.mul(gasPrice);
};
@@ -675,7 +669,7 @@ class Amount extends PureComponent {
};
onInputChange = (inputValue, selectedAsset, useMax) => {
- const { contractExchangeRates, conversionRate, currentCurrency, ticker } = this.props;
+ const { contractExchangeRates, conversionRate, currentCurrency, chainId, ticker } = this.props;
const { internalPrimaryCurrencyIsCrypto } = this.state;
let inputValueConversion, renderableInputValueConversion, hasExchangeRate, comma;
// Remove spaces from input
@@ -689,7 +683,7 @@ class Amount extends PureComponent {
const processedInputValue = isDecimal(inputValue) ? handleWeiNumber(inputValue) : '0';
selectedAsset = selectedAsset || this.props.selectedAsset;
if (selectedAsset.isETH) {
- hasExchangeRate = !!conversionRate;
+ hasExchangeRate = isMainNet(chainId) ? !!conversionRate : false;
if (internalPrimaryCurrencyIsCrypto) {
inputValueConversion = `${weiToFiatNumber(toWei(processedInputValue), conversionRate)}`;
renderableInputValueConversion = `${weiToFiat(
@@ -703,7 +697,7 @@ class Amount extends PureComponent {
}
} else {
const exchangeRate = contractExchangeRates[selectedAsset.address];
- hasExchangeRate = !!exchangeRate;
+ hasExchangeRate = isMainNet(chainId) ? !!exchangeRate : false;
// If !hasExchangeRate we have to handle crypto amount
if (internalPrimaryCurrencyIsCrypto || !hasExchangeRate) {
inputValueConversion = `${balanceToFiatNumber(processedInputValue, conversionRate, exchangeRate)}`;
@@ -746,7 +740,6 @@ class Amount extends PureComponent {
handleSelectedAssetBalance = ({ address, decimals, symbol, isETH }, renderableBalance) => {
const { accounts, selectedAddress, contractBalances } = this.props;
let currentBalance;
-
if (renderableBalance) {
currentBalance = `${renderableBalance} ${symbol}`;
} else if (isETH) {
@@ -778,6 +771,7 @@ class Amount extends PureComponent {
renderToken = (token, index) => {
const {
accounts,
+ chainId,
selectedAddress,
conversionRate,
currentCurrency,
@@ -788,11 +782,15 @@ class Amount extends PureComponent {
const { address, decimals, symbol } = token;
if (token.isETH) {
balance = renderFromWei(accounts[selectedAddress].balance);
- balanceFiat = weiToFiat(hexToBN(accounts[selectedAddress].balance), conversionRate, currentCurrency);
+ balanceFiat = isMainNet(chainId)
+ ? weiToFiat(hexToBN(accounts[selectedAddress].balance), conversionRate, currentCurrency)
+ : null;
} else {
balance = renderFromTokenMinimalUnit(contractBalances[address], decimals);
const exchangeRate = contractExchangeRates[address];
- balanceFiat = balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency);
+ balanceFiat = isMainNet(chainId)
+ ? balanceToFiat(balance, conversionRate, exchangeRate, currentCurrency)
+ : null;
}
return (
({
providerType: state.engine.backgroundState.NetworkController.provider.type,
primaryCurrency: state.settings.primaryCurrency,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
tokens: state.engine.backgroundState.AssetsController.tokens,
transactionState: ownProps.transaction || state.transaction,
diff --git a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap
index 4ef738915b4..9ef917d8156 100644
--- a/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap
+++ b/app/components/Views/SendFlow/Confirm/__snapshots__/index.test.js.snap
@@ -24,11 +24,40 @@ exports[`Confirm should render correctly 1`] = `
fromAccountAddress="0x1"
onPressIcon={null}
/>
-
+
+
+ We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam.
+
+ }
+ isVisible={false}
+ title="Check the recipient address"
+ toggleModal={[Function]}
+ />
Amount
-
}
+ totalFiat={
+
+ }
totalGasFiat=""
- totalValue={}
+ totalValue={
+
+ }
transactionValue=""
/>
Hex Data
@@ -285,7 +388,7 @@ exports[`Confirm should render correctly 1`] = `
>
@@ -304,10 +407,24 @@ exports[`Confirm should render correctly 1`] = `
}
>
Hex Data
0x
diff --git a/app/components/Views/SendFlow/Confirm/index.js b/app/components/Views/SendFlow/Confirm/index.js
index 3f42d50dbd8..ba9afcd178e 100644
--- a/app/components/Views/SendFlow/Confirm/index.js
+++ b/app/components/Views/SendFlow/Confirm/index.js
@@ -6,7 +6,6 @@ import {
SafeAreaView,
View,
Alert,
- Text,
ScrollView,
TouchableOpacity,
ActivityIndicator
@@ -24,23 +23,22 @@ import {
weiToFiatNumber,
balanceToFiatNumber,
renderFiatAddition,
- toWei,
isDecimal,
toBN
} from '../../../../util/number';
import { getTicker, decodeTransferData, getNormalizedTxState } from '../../../../util/transactions';
import StyledButton from '../../../UI/StyledButton';
-import { util } from '@metamask/controllers';
-import { prepareTransaction, resetTransaction } from '../../../../actions/transaction';
+import { util, WalletDevice } from '@metamask/controllers';
+import { prepareTransaction, resetTransaction, setNonce, setProposedNonce } from '../../../../actions/transaction';
import {
- fetchBasicGasEstimates,
- convertApiValueToGWEI,
apiEstimateModifiedToWEI,
- getBasicGasEstimates
+ getGasPriceByChainId,
+ getBasicGasEstimatesByChainId
} from '../../../../util/custom-gas';
import Engine from '../../../../core/Engine';
import Logger from '../../../../util/Logger';
import AccountList from '../../../UI/AccountList';
+import CustomNonceModal from '../../../UI/CustomNonceModal';
import AnimatedTransactionModal from '../../../UI/AnimatedTransactionModal';
import TransactionReviewFeeCard from '../../../UI/TransactionReview/TransactionReviewFeeCard';
import CustomGas from '../../../UI/CustomGas';
@@ -54,16 +52,19 @@ import IonicIcon from 'react-native-vector-icons/Ionicons';
import TransactionTypes from '../../../../core/TransactionTypes';
import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
-import { capitalize } from '../../../../util/format';
-import { isMainNet, getNetworkName } from '../../../../util/networks';
+import { capitalize } from '../../../../util/general';
+import { isMainNet, getNetworkName, getNetworkNonce } from '../../../../util/networks';
+import Text from '../../../Base/Text';
+import AnalyticsV2 from '../../../../util/analyticsV2';
+import { collectConfusables } from '../../../../util/validators';
+import InfoModal from '../../../UI/Swaps/components/InfoModal';
+import { toChecksumAddress } from 'ethereumjs-util';
const EDIT = 'edit';
+const EDIT_NONCE = 'edit_nonce';
const REVIEW = 'review';
const { hexToBN, BNToHex } = util;
-const {
- CUSTOM_GAS: { AVERAGE_GAS, FAST_GAS, LOW_GAS }
-} = TransactionTypes;
const styles = StyleSheet.create({
wrapper: {
@@ -89,7 +90,7 @@ const styles = StyleSheet.create({
marginVertical: 3
},
textAmount: {
- fontFamily: 'Roboto-Light',
+ ...fontStyles.normal,
fontWeight: fontStyles.light.fontWeight,
color: colors.black,
fontSize: 44,
@@ -213,6 +214,9 @@ const styles = StyleSheet.create({
over: {
color: colors.red,
...fontStyles.bold
+ },
+ text: {
+ lineHeight: 20
}
});
@@ -232,6 +236,10 @@ class Confirm extends PureComponent {
* Map of accounts to information objects including balances
*/
accounts: PropTypes.object,
+ /**
+ * Map representing the address book
+ */
+ addressBook: PropTypes.object,
/**
* Object containing token balances in the format address => balance
*/
@@ -264,6 +272,10 @@ class Confirm extends PureComponent {
* Set transaction object to be sent
*/
prepareTransaction: PropTypes.func,
+ /**
+ * Chain Id
+ */
+ chainId: PropTypes.string,
/**
* Network id
*/
@@ -272,6 +284,10 @@ class Confirm extends PureComponent {
* Indicates whether hex data should be shown in transaction editor
*/
showHexData: PropTypes.bool,
+ /**
+ * Indicates whether custom nonce should be shown in transaction editor
+ */
+ showCustomNonce: PropTypes.bool,
/**
* Network provider type as mainnet
*/
@@ -295,10 +311,19 @@ class Confirm extends PureComponent {
/**
* ETH or fiat, depending on user setting
*/
- primaryCurrency: PropTypes.string
+ primaryCurrency: PropTypes.string,
+ /**
+ * Set transaction nonce
+ */
+ setNonce: PropTypes.func,
+ /**
+ * Set proposed nonce (from network)
+ */
+ setProposedNonce: PropTypes.func
};
state = {
+ confusableCollection: [],
gasSpeedSelected: 'average',
gasEstimationReady: false,
customGas: undefined,
@@ -317,14 +342,59 @@ class Confirm extends PureComponent {
transactionTotalAmountFiat: undefined,
errorMessage: undefined,
fromAccountModalVisible: false,
+ warningModalVisible: false,
mode: REVIEW,
over: false
};
+ setNetworkNonce = async () => {
+ const { setNonce, setProposedNonce, transaction } = this.props;
+ const proposedNonce = await getNetworkNonce(transaction);
+ setNonce(proposedNonce);
+ setProposedNonce(proposedNonce);
+ };
+
+ getAnalyticsParams = () => {
+ try {
+ const { selectedAsset } = this.props;
+ const { NetworkController } = Engine.context;
+ const { chainId, type } = NetworkController?.state?.provider || {};
+ return {
+ active_currency: { value: selectedAsset?.symbol, anonymous: true },
+ network_name: type,
+ chain_id: chainId
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
+ getGasAnalyticsParams = () => {
+ try {
+ const { selectedAsset } = this.props;
+ return {
+ active_currency: { value: selectedAsset.symbol, anonymous: true }
+ };
+ } catch (error) {
+ return {};
+ }
+ };
+
+ handleConfusables = async () => {
+ const { transactionToName } = this.props.transactionState;
+ await this.setState({ confusableCollection: collectConfusables(transactionToName) });
+ };
+
+ toggleWarningModal = () => this.setState(state => ({ warningModalVisible: !state.warningModalVisible }));
+
componentDidMount = async () => {
// For analytics
- const { navigation, providerType } = this.props;
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.SEND_TRANSACTION_STARTED, this.getAnalyticsParams());
+
+ const { showCustomNonce, navigation, providerType } = this.props;
await this.handleFetchBasicEstimates();
+ showCustomNonce && (await this.setNetworkNonce());
+ await this.handleConfusables();
navigation.setParams({ providerType });
this.parseTransactionData();
this.prepareTransaction();
@@ -362,8 +432,8 @@ class Confirm extends PureComponent {
this.onModeChange(REVIEW);
};
- edit = () => {
- this.onModeChange(EDIT);
+ edit = MODE => {
+ this.onModeChange(MODE);
};
onModeChange = mode => {
@@ -377,9 +447,11 @@ class Confirm extends PureComponent {
handleFetchBasicEstimates = async () => {
this.setState({ ready: false });
- const basicGasEstimates = await getBasicGasEstimates();
- this.handleSetGasFee(this.props.transaction.gas, apiEstimateModifiedToWEI(basicGasEstimates.averageGwei));
- this.setState({ basicGasEstimates, ready: true });
+ const basicGasEstimates = await getBasicGasEstimatesByChainId();
+ if (basicGasEstimates) {
+ this.handleSetGasFee(this.props.transaction.gas, apiEstimateModifiedToWEI(basicGasEstimates.averageGwei));
+ }
+ return this.setState({ basicGasEstimates, ready: true });
};
prepareTransaction = async () => {
@@ -394,30 +466,14 @@ class Confirm extends PureComponent {
};
estimateGas = async transaction => {
- const { TransactionController } = Engine.context;
const { value, data, to, from } = transaction;
- let estimation;
- try {
- estimation = await TransactionController.estimateGas({
- value,
- from,
- data,
- to
- });
- } catch (e) {
- estimation = { gas: TransactionTypes.CUSTOM_GAS.DEFAULT_GAS_LIMIT };
- }
- let basicGasEstimates;
- try {
- basicGasEstimates = await fetchBasicGasEstimates();
- } catch (error) {
- Logger.log('Error while trying to get gas limit estimates', error);
- basicGasEstimates = { average: AVERAGE_GAS, safeLow: LOW_GAS, fast: FAST_GAS };
- }
- return {
- gas: hexToBN(estimation.gas),
- gasPrice: toWei(convertApiValueToGWEI(basicGasEstimates.average), 'gwei')
- };
+
+ return await getGasPriceByChainId({
+ value,
+ from,
+ data,
+ to
+ });
};
parseTransactionData = () => {
@@ -576,13 +632,16 @@ class Confirm extends PureComponent {
prepareTransactionToSend = () => {
const {
- transactionState: { transaction }
+ transactionState: { transaction },
+ showCustomNonce
} = this.props;
const { fromSelectedAddress } = this.state;
+ const { nonce } = this.props.transaction;
const transactionToSend = { ...transaction };
transactionToSend.gas = BNToHex(transaction.gas);
transactionToSend.gasPrice = BNToHex(transaction.gasPrice);
transactionToSend.from = fromSelectedAddress;
+ if (showCustomNonce && nonce) transactionToSend.nonce = BNToHex(nonce);
return transactionToSend;
};
@@ -657,7 +716,6 @@ class Confirm extends PureComponent {
const {
transactionState: { assetType },
navigation,
- providerType,
resetTransaction
} = this.props;
this.setState({ transactionConfirmed: true });
@@ -673,9 +731,9 @@ class Confirm extends PureComponent {
}
const { result, transactionMeta } = await TransactionController.addTransaction(
transaction,
- TransactionTypes.MMM
+ TransactionTypes.MMM,
+ WalletDevice.MM_MOBILE
);
-
await TransactionController.approveTransaction(transactionMeta.id);
await new Promise(resolve => resolve(result));
@@ -689,9 +747,10 @@ class Confirm extends PureComponent {
assetType
});
this.checkRemoveCollectible();
- Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SEND_FLOW_CONFIRM_SEND, {
- network: providerType
- });
+ AnalyticsV2.trackEvent(
+ AnalyticsV2.ANALYTICS_EVENTS.SEND_TRANSACTION_COMPLETED,
+ this.getAnalyticsParams()
+ );
resetTransaction();
navigation && navigation.dismiss();
});
@@ -779,6 +838,8 @@ class Confirm extends PureComponent {
mode={mode}
onPress={this.handleSetGasSpeed}
gasSpeedSelected={gasSpeedSelected}
+ view={'SendTo (Confirm)'}
+ analyticsParams={this.getGasAnalyticsParams()}
/>
@@ -786,6 +847,19 @@ class Confirm extends PureComponent {
);
};
+ renderCustomNonceModal = () => {
+ const { setNonce } = this.props;
+ const { proposedNonce, nonce } = this.props.transaction;
+ return (
+ this.review()}
+ save={setNonce}
+ />
+ );
+ };
+
renderHexDataModal = () => {
const { hexDataModalVisible } = this.state;
const { data } = this.props.transactionState.transaction;
@@ -857,7 +931,8 @@ class Confirm extends PureComponent {
render = () => {
const { transactionToName, selectedAsset, paymentRequest } = this.props.transactionState;
- const { showHexData, primaryCurrency, network } = this.props;
+ const { addressBook, showHexData, showCustomNonce, primaryCurrency, network, chainId } = this.props;
+ const { nonce } = this.props.transaction;
const {
gasEstimationReady,
fromAccountBalance,
@@ -873,10 +948,36 @@ class Confirm extends PureComponent {
errorMessage,
transactionConfirmed,
warningGasPriceHigh,
+ confusableCollection,
mode,
- over
+ over,
+ warningModalVisible
} = this.state;
+ const checksummedAddress = transactionTo && toChecksumAddress(transactionTo);
+ const existingContact = checksummedAddress && addressBook[network] && addressBook[network][checksummedAddress];
+ const displayExclamation = !existingContact && !!confusableCollection.length;
+
+ const AdressToComponent = () => (
+
+ );
+
+ const AdressToComponentWrap = () =>
+ !existingContact && confusableCollection.length ? (
+
+
+
+ ) : (
+
+ );
+
const is_main_net = isMainNet(network);
const errorPress = is_main_net ? this.buyEth : this.gotoFaucet;
const networkName = capitalize(getNetworkName(network));
@@ -892,14 +993,16 @@ class Confirm extends PureComponent {
fromAccountName={fromAccountName}
fromAccountBalance={fromAccountBalance}
/>
-
+
+ {strings('transaction.confusable_msg')}}
+ />
+
{!selectedAsset.tokenId ? (
@@ -907,7 +1010,7 @@ class Confirm extends PureComponent {
{transactionValue}
- {transactionValueFiat}
+ {isMainNet(chainId) && {transactionValueFiat}}
) : (
@@ -928,15 +1031,18 @@ class Confirm extends PureComponent {
}
fiat={transactionValueFiat}
totalValue={transactionTotalAmount}
transactionValue={transactionValue}
primaryCurrency={primaryCurrency}
gasEstimationReady={gasEstimationReady}
- edit={this.edit}
+ edit={() => this.edit(EDIT)}
over={over}
warningGasPriceHigh={warningGasPriceHigh}
+ showCustomNonce={showCustomNonce}
+ nonceValue={nonce}
+ onNonceEdit={() => this.edit(EDIT_NONCE)}
/>
{errorMessage && (
@@ -979,6 +1085,7 @@ class Confirm extends PureComponent {
{this.renderFromAccountModal()}
{mode === EDIT && this.renderCustomGasModal()}
+ {mode === EDIT_NONCE && this.renderCustomNonceModal()}
{this.renderHexDataModal()}
);
@@ -987,6 +1094,7 @@ class Confirm extends PureComponent {
const mapStateToProps = state => ({
accounts: state.engine.backgroundState.AccountTrackerController.accounts,
+ addressBook: state.engine.backgroundState.AddressBookController?.addressBook,
contractBalances: state.engine.backgroundState.TokenBalancesController.contractBalances,
contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates,
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
@@ -995,6 +1103,8 @@ const mapStateToProps = state => ({
identities: state.engine.backgroundState.PreferencesController.identities,
providerType: state.engine.backgroundState.NetworkController.provider.type,
showHexData: state.settings.showHexData,
+ showCustomNonce: state.settings.showCustomNonce,
+ chainId: state.engine.backgroundState.NetworkController.provider.chainId,
ticker: state.engine.backgroundState.NetworkController.provider.ticker,
keyrings: state.engine.backgroundState.KeyringController.keyrings,
transaction: getNormalizedTxState(state),
@@ -1005,7 +1115,9 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
prepareTransaction: transaction => dispatch(prepareTransaction(transaction)),
- resetTransaction: () => dispatch(resetTransaction())
+ resetTransaction: () => dispatch(resetTransaction()),
+ setNonce: nonce => dispatch(setNonce(nonce)),
+ setProposedNonce: nonce => dispatch(setProposedNonce(nonce))
});
export default connect(
diff --git a/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap b/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap
index 2d3d098a235..621c6cfde6f 100644
--- a/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap
+++ b/app/components/Views/SendFlow/ErrorMessage/__snapshots__/index.test.js.snap
@@ -23,6 +23,8 @@ exports[`ErrorMessage should render correctly 1`] = `
>
Add to address book
Enter an alias
@@ -219,7 +252,7 @@ exports[`SendTo should render correctly 1`] = `
spellCheck={false}
style={
Object {
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 20,
"fontWeight": "400",
diff --git a/app/components/Views/SendFlow/SendTo/index.js b/app/components/Views/SendFlow/SendTo/index.js
index 54a4e6adda7..bd0f0b7bd42 100644
--- a/app/components/Views/SendFlow/SendTo/index.js
+++ b/app/components/Views/SendFlow/SendTo/index.js
@@ -7,7 +7,6 @@ import {
StyleSheet,
View,
TouchableOpacity,
- Text,
TextInput,
SafeAreaView,
InteractionManager,
@@ -34,6 +33,9 @@ import Analytics from '../../../../core/Analytics';
import { ANALYTICS_EVENT_OPTS } from '../../../../util/analytics';
import { allowedToBuy } from '../../../UI/FiatOrders';
import NetworkList from '../../../../util/networks';
+import Text from '../../../Base/Text';
+import Icon from 'react-native-vector-icons/FontAwesome';
+import { collectConfusables, hasZeroWidthPoints } from '../../../../util/validators';
const { hexToBN } = util;
const styles = StyleSheet.create({
@@ -125,12 +127,41 @@ const styles = StyleSheet.create({
marginBottom: 32
},
buyEth: {
- ...fontStyles.bold,
color: colors.black,
textDecorationLine: 'underline'
},
- bold: {
- ...fontStyles.bold
+ confusabeError: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ margin: 16,
+ padding: 16,
+ borderWidth: 1,
+ borderColor: colors.red,
+ backgroundColor: colors.red000,
+ borderRadius: 8
+ },
+ confusabeWarning: {
+ borderColor: colors.yellow,
+ backgroundColor: colors.yellow100
+ },
+ confusableTitle: {
+ marginTop: -3,
+ color: colors.red,
+ ...fontStyles.bold,
+ fontSize: 14
+ },
+ confusableMsg: {
+ color: colors.red,
+ fontSize: 12,
+ lineHeight: 16,
+ paddingRight: 10
+ },
+ black: {
+ color: colors.black
+ },
+ warningIcon: {
+ marginRight: 8
}
});
@@ -210,6 +241,7 @@ class SendFlow extends PureComponent {
toEnsName: undefined,
addToAddressToAddressBook: false,
alias: undefined,
+ confusableCollection: [],
inputWidth: { width: '99%' }
};
@@ -274,7 +306,7 @@ class SendFlow extends PureComponent {
const { AssetsContractController } = Engine.context;
const { addressBook, network, identities, providerType } = this.props;
const networkAddressBook = addressBook[network] || {};
- let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning;
+ let addressError, toAddressName, toEnsName, errorContinue, isOnlyWarning, confusableCollection;
let [addToAddressToAddressBook, toSelectedAddressReady] = [false, false];
if (isValidAddress(toSelectedAddress)) {
const checksummedToSelectedAddress = toChecksumAddress(toSelectedAddress);
@@ -304,7 +336,7 @@ class SendFlow extends PureComponent {
addressError = (
{strings('transaction.tokenContractAddressWarning_1')}
- {strings('transaction.tokenContractAddressWarning_2')}
+ {strings('transaction.tokenContractAddressWarning_2')}
{strings('transaction.tokenContractAddressWarning_3')}
);
@@ -329,6 +361,7 @@ class SendFlow extends PureComponent {
*/
} else if (isENS(toSelectedAddress)) {
toEnsName = toSelectedAddress;
+ confusableCollection = collectConfusables(toEnsName);
const resolvedAddress = await doENSLookup(toSelectedAddress, network);
if (resolvedAddress) {
const checksummedResolvedAddress = toChecksumAddress(resolvedAddress);
@@ -352,7 +385,8 @@ class SendFlow extends PureComponent {
toSelectedAddressName: toAddressName,
toEnsName,
errorContinue,
- isOnlyWarning
+ isOnlyWarning,
+ confusableCollection
});
};
@@ -510,7 +544,7 @@ class SendFlow extends PureComponent {
return (
<>
{'\n'}
-
+
{strings('fiat_on_ramp.buy_eth')}
>
@@ -519,6 +553,7 @@ class SendFlow extends PureComponent {
render = () => {
const { ticker } = this.props;
+ const { addressBook, network } = this.props;
const {
fromSelectedAddress,
fromAccountName,
@@ -532,8 +567,16 @@ class SendFlow extends PureComponent {
toInputHighlighted,
inputWidth,
errorContinue,
- isOnlyWarning
+ isOnlyWarning,
+ confusableCollection
} = this.state;
+
+ const checksummedAddress = toSelectedAddress && toChecksumAddress(toSelectedAddress);
+ const existingContact = checksummedAddress && addressBook[network] && addressBook[network][checksummedAddress];
+ const displayConfusableWarning = !existingContact && confusableCollection && !!confusableCollection.length;
+ const displayAsWarning =
+ confusableCollection && confusableCollection.length && !confusableCollection.some(hasZeroWidthPoints);
+
return (
@@ -556,6 +599,7 @@ class SendFlow extends PureComponent {
onInputBlur={this.onToInputFocus}
onSubmit={this.onTransactionDirectionSet}
inputWidth={inputWidth}
+ confusableCollection={(!existingContact && confusableCollection) || []}
/>
@@ -578,6 +622,25 @@ class SendFlow extends PureComponent {
/>
)}
+ {displayConfusableWarning && (
+
+
+
+
+
+
+ {strings('transaction.confusable_title')}
+
+
+ {strings('transaction.confusable_msg')}
+
+
+
+ )}
{addToAddressToAddressBook && (
+
+
+ Customize transaction nonce
+
+
+ Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously.
+
+
+
+
+
{
- this.props.setShowHexData(showHexData);
- };
-
downloadStateLogs = async () => {
const appName = await getApplicationName();
const appVersion = await getVersion();
@@ -222,7 +226,7 @@ class AdvancedSettings extends PureComponent {
};
render = () => {
- const { showHexData, ipfsGateway } = this.props;
+ const { showHexData, showCustomNonce, setShowHexData, setShowCustomNonce, ipfsGateway } = this.props;
const { resetModalVisible, onlineIpfsGateways } = this.state;
return (
@@ -281,7 +285,19 @@ class AdvancedSettings extends PureComponent {
+
+
+
+ {strings('app_settings.show_custom_nonce')}
+ {strings('app_settings.custom_nonce_desc')}
+
+
@@ -308,11 +324,13 @@ class AdvancedSettings extends PureComponent {
const mapStateToProps = state => ({
ipfsGateway: state.engine.backgroundState.PreferencesController.ipfsGateway,
showHexData: state.settings.showHexData,
+ showCustomNonce: state.settings.showCustomNonce,
fullState: state
});
const mapDispatchToProps = dispatch => ({
- setShowHexData: showHexData => dispatch(setShowHexData(showHexData))
+ setShowHexData: showHexData => dispatch(setShowHexData(showHexData)),
+ setShowCustomNonce: showCustomNonce => dispatch(setShowCustomNonce(showCustomNonce))
});
export default connect(
diff --git a/app/components/Views/Settings/Contacts/ContactForm/__snapshots__/index.test.js.snap b/app/components/Views/Settings/Contacts/ContactForm/__snapshots__/index.test.js.snap
index c37bd5ce870..d176f0e713e 100644
--- a/app/components/Views/Settings/Contacts/ContactForm/__snapshots__/index.test.js.snap
+++ b/app/components/Views/Settings/Contacts/ContactForm/__snapshots__/index.test.js.snap
@@ -130,7 +130,7 @@ exports[`ContactForm should render correctly 1`] = `
style={
Array [
Object {
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontWeight": "400",
"padding": 0,
@@ -217,7 +217,7 @@ exports[`ContactForm should render correctly 1`] = `
style={
Array [
Object {
- "color": "#000000",
+ "color": "#24292E",
"fontFamily": "EuclidCircularB-Regular",
"fontWeight": "400",
"padding": 0,
diff --git a/app/components/Views/Settings/GeneralSettings/__snapshots__/index.test.js.snap b/app/components/Views/Settings/GeneralSettings/__snapshots__/index.test.js.snap
index 157dc464f67..faea4ed4e5f 100644
--- a/app/components/Views/Settings/GeneralSettings/__snapshots__/index.test.js.snap
+++ b/app/components/Views/Settings/GeneralSettings/__snapshots__/index.test.js.snap
@@ -675,7 +675,7 @@ exports[`GeneralSettings should render correctly 1`] = `
"marginHorizontal": 10,
},
Object {
- "color": "#000000",
+ "color": "#24292E",
},
]
}
diff --git a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js
index f8571bb9ee9..61c28898c06 100644
--- a/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js
+++ b/app/components/Views/Settings/NetworksSettings/NetworkSettings/index.js
@@ -17,6 +17,7 @@ import { jsonRpcRequest } from '../../../../../util/jsonRpcRequest';
import Logger from '../../../../../util/Logger';
import { isPrefixedFormattedHexString } from '../../../../../util/number';
import AppConstants from '../../../../../core/AppConstants';
+import AnalyticsV2 from '../../../../../util/analyticsV2';
const styles = StyleSheet.create({
wrapper: {
@@ -268,6 +269,17 @@ class NetworkSettings extends PureComponent {
blockExplorerUrl
});
NetworkController.setRpcTarget(url.href, decimalChainId, ticker, nickname);
+
+ const analyticsParamsAdd = {
+ rpc_url: url.href,
+ chain_id: decimalChainId,
+ source: 'Settings',
+ symbol: ticker,
+ block_explorer_url: blockExplorerUrl,
+ network_name: 'rpc'
+ };
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_ADDED, analyticsParamsAdd);
+
navigation.navigate('WalletView');
}
};
diff --git a/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.js.snap b/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.js.snap
index 2cade6fbd2f..7f7062dde0d 100644
--- a/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.js.snap
+++ b/app/components/Views/Settings/SecuritySettings/__snapshots__/index.test.js.snap
@@ -86,7 +86,7 @@ exports[`SecuritySettings should render correctly 1`] = `
style={
Array [
Object {
- "color": "#000000",
+ "color": "#24292E",
"flex": 1,
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 12,
diff --git a/app/components/Views/Settings/SecuritySettings/index.js b/app/components/Views/Settings/SecuritySettings/index.js
index 7017adb638c..8416178c9b3 100644
--- a/app/components/Views/Settings/SecuritySettings/index.js
+++ b/app/components/Views/Settings/SecuritySettings/index.js
@@ -228,7 +228,8 @@ class Settings extends PureComponent {
seedphraseBackedUp: PropTypes.bool
};
- static navigationOptions = ({ navigation }) => getNavigationOptionsTitle(strings('app_settings.back'), navigation);
+ static navigationOptions = ({ navigation }) =>
+ getNavigationOptionsTitle(strings('app_settings.security_title'), navigation);
state = {
approvalModalVisible: false,
diff --git a/app/components/Views/TransactionsView/index.js b/app/components/Views/TransactionsView/index.js
index 2505791f026..32704877d8c 100644
--- a/app/components/Views/TransactionsView/index.js
+++ b/app/components/Views/TransactionsView/index.js
@@ -7,6 +7,7 @@ import Engine from '../../../core/Engine';
import { showAlert } from '../../../actions/alert';
import Transactions from '../../UI/Transactions';
import { safeToChecksumAddress } from '../../../util/address';
+import { addAccountTimeFlagFilter } from '../../../util/transactions';
const styles = StyleSheet.create({
wrapper: {
@@ -18,6 +19,7 @@ const TransactionsView = ({
navigation,
conversionRate,
selectedAddress,
+ identities,
networkType,
currentCurrency,
transactions,
@@ -32,6 +34,10 @@ const TransactionsView = ({
const filterTransactions = useCallback(() => {
const network = Engine.context.NetworkController.state.network;
if (network === 'loading') return;
+
+ let accountAddedTimeInsertPointFound = false;
+ const addedAccountTime = identities[selectedAddress]?.importTime;
+
const ethFilter = tx => {
const {
transaction: { from, to },
@@ -61,6 +67,10 @@ const TransactionsView = ({
const allTransactions = allTransactionsSorted.filter(tx => {
const filter = ethFilter(tx);
if (!filter) return false;
+
+ tx.insertImportTime = addAccountTimeFlagFilter(tx, addedAccountTime, accountAddedTimeInsertPointFound);
+ if (tx.insertImportTime) accountAddedTimeInsertPointFound = true;
+
switch (tx.status) {
case 'submitted':
case 'signed':
@@ -84,15 +94,19 @@ const TransactionsView = ({
return !alreadySubmitted;
});
+ //if the account added insertpoint is not found add it to the last transaction
+ if (!accountAddedTimeInsertPointFound && allTransactions && allTransactions.length) {
+ allTransactions[allTransactions.length - 1].insertImportTime = true;
+ }
+
setAllTransactions(allTransactions);
setSubmittedTxs(submittedTxsFiltered);
setConfirmedTxs(confirmedTxs);
setLoading(false);
- }, [transactions, selectedAddress, tokens, chainId]);
+ }, [transactions, identities, selectedAddress, tokens, chainId]);
useEffect(() => {
setLoading(true);
-
/*
Since this screen is always mounted and computations happen on this screen everytime the user changes network
using the InteractionManager will help by giving enough time for any animations/screen transactions before it starts
@@ -131,8 +145,12 @@ TransactionsView.propTypes = {
*/
currentCurrency: PropTypes.string,
/**
- /* navigation object required to push new views
- */
+ /* Identities object required to get account name
+ */
+ identities: PropTypes.object,
+ /**
+ /* navigation object required to push new views
+ */
navigation: PropTypes.object,
/**
* A string that represents the selected address
@@ -161,6 +179,7 @@ const mapStateToProps = state => ({
currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency,
selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress,
tokens: state.engine.backgroundState.AssetsController.tokens,
+ identities: state.engine.backgroundState.PreferencesController.identities,
transactions: state.engine.backgroundState.TransactionController.transactions,
networkType: state.engine.backgroundState.NetworkController.provider.type,
chainId: state.engine.backgroundState.NetworkController.provider.chainId
diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js
index 48c3d4ca9ff..d0341617c51 100644
--- a/app/core/AppConstants.js
+++ b/app/core/AppConstants.js
@@ -61,11 +61,18 @@ export default {
CLIENT_ID: 'mobile',
LIVENESS_POLLING_FREQUENCY: 5 * 60 * 1000,
POLL_COUNT_LIMIT: 3,
- DEFAULT_SLIPPAGE: 3
+ DEFAULT_SLIPPAGE: 3,
+ CACHE_AGGREGATOR_METADATA_THRESHOLD: 5 * 60 * 1000,
+ CACHE_TOKENS_THRESHOLD: 5 * 60 * 1000,
+ CACHE_TOP_ASSETS_THRESHOLD: 5 * 60 * 1000
},
MAX_SAFE_CHAIN_ID: 4503599627370476,
URLS: {
TERMS_AND_CONDITIONS: 'https://consensys.net/terms-of-use/',
- PRIVACY_POLICY: 'https://consensys.net/privacy-policy/'
+ PRIVACY_POLICY: 'https://consensys.net/privacy-policy/',
+ CONNECTIVITY_ISSUES: 'https://metamask.zendesk.com/hc/en-us/articles/360059386712'
+ },
+ ERRORS: {
+ INFURA_BLOCKED_MESSAGE: 'EthQuery - RPC Error - This service is not available in your country'
}
};
diff --git a/app/core/BackgroundBridge.js b/app/core/BackgroundBridge.js
index 2a4e85ce765..42386131b41 100644
--- a/app/core/BackgroundBridge.js
+++ b/app/core/BackgroundBridge.js
@@ -10,8 +10,8 @@ import Engine from './Engine';
import { getAllNetworks } from '../util/networks';
import Logger from '../util/Logger';
import AppConstants from './AppConstants';
+import { createEngineStream } from 'json-rpc-middleware-stream';
-const createEngineStream = require('json-rpc-middleware-stream/engineStream');
const createFilterMiddleware = require('eth-json-rpc-filters');
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager');
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware');
diff --git a/app/core/DeeplinkManager.js b/app/core/DeeplinkManager.js
index 5e5cf90b337..cb025a67fa3 100644
--- a/app/core/DeeplinkManager.js
+++ b/app/core/DeeplinkManager.js
@@ -10,6 +10,7 @@ import Engine from './Engine';
import { generateApproveData } from '../util/transactions';
import { strings } from '../../locales/i18n';
import { getNetworkTypeById } from '../util/networks';
+import { WalletDevice } from '@metamask/controllers/';
class DeeplinkManager {
constructor(_navigation) {
@@ -63,9 +64,12 @@ class DeeplinkManager {
txParams.to = `${target_address}`;
txParams.from = `${PreferencesController.state.selectedAddress}`;
txParams.value = '0x0';
- const value = Number(uint256).toString(16);
+ const uint256Number = Number(uint256);
+ if (Number.isNaN(uint256Number)) throw new Error('The parameter uint256 should be a number');
+ if (!Number.isInteger(uint256Number)) throw new Error('The parameter uint256 should be an integer');
+ const value = uint256Number.toString(16);
txParams.data = generateApproveData({ spender: address, value });
- TransactionController.addTransaction(txParams, origin);
+ TransactionController.addTransaction(txParams, origin, WalletDevice.MM_MOBILE);
}
}
diff --git a/app/core/Engine.js b/app/core/Engine.js
index c0f46a229ab..2810a0ca719 100644
--- a/app/core/Engine.js
+++ b/app/core/Engine.js
@@ -15,10 +15,11 @@ import {
TokenBalancesController,
TokenRatesController,
TransactionController,
- TypedMessageManager
+ TypedMessageManager,
+ WalletDevice
} from '@metamask/controllers';
-import { SwapsController } from '@estebanmino/controllers';
+import { SwapsController } from '@metamask/swaps-controller';
import AsyncStorage from '@react-native-community/async-storage';
@@ -33,7 +34,7 @@ import contractMap from '@metamask/contract-metadata';
import Logger from '../util/Logger';
import { LAST_INCOMING_TX_BLOCK_INFO } from '../constants/storage';
-const EMPTY = 'EMPTY';
+const NON_EMPTY = 'NON_EMPTY';
const encryptor = new Encryptor();
let currentChainId;
@@ -64,70 +65,138 @@ class Engine {
currentCurrency: 'usd'
};
- this.datamodel = new ComposableController(
- [
- new KeyringController({ encryptor }, initialState.KeyringController),
- new AccountTrackerController(),
- new AddressBookController(),
- new AssetsContractController(),
- new AssetsController(),
- new AssetsDetectionController(),
- new CurrencyRateController({
- nativeCurrency,
- currentCurrency
- }),
- new PersonalMessageManager(),
- new MessageManager(),
- new NetworkController({
- infuraProjectId: process.env.MM_INFURA_PROJECT_ID || EMPTY,
- providerConfig: {
- static: {
- eth_sendTransaction: async (payload, next, end) => {
- const { TransactionController } = this.datamodel.context;
- try {
- const hash = await (await TransactionController.addTransaction(
- payload.params[0],
- payload.origin
- )).result;
- end(undefined, hash);
- } catch (error) {
- end(error);
- }
- }
- },
- getAccounts: (end, payload) => {
- const { approvedHosts, privacyMode } = store.getState();
- const isEnabled = !privacyMode || approvedHosts[payload.hostname];
- const { KeyringController } = this.datamodel.context;
- const isUnlocked = KeyringController.isUnlocked();
- const selectedAddress = this.datamodel.context.PreferencesController.state
- .selectedAddress;
- end(null, isUnlocked && isEnabled && selectedAddress ? [selectedAddress] : []);
+ const preferencesController = new PreferencesController(
+ {},
+ {
+ ipfsGateway: AppConstants.IPFS_DEFAULT_GATEWAY_URL
+ }
+ );
+ const networkController = new NetworkController({
+ infuraProjectId: process.env.MM_INFURA_PROJECT_ID || NON_EMPTY,
+ providerConfig: {
+ static: {
+ eth_sendTransaction: async (payload, next, end) => {
+ const { TransactionController } = this.context;
+ try {
+ const hash = await (await TransactionController.addTransaction(
+ payload.params[0],
+ payload.origin,
+ WalletDevice.MM_MOBILE
+ )).result;
+ end(undefined, hash);
+ } catch (error) {
+ end(error);
}
}
- }),
- new PhishingController(),
- new PreferencesController(
- {},
- {
- ipfsGateway: AppConstants.IPFS_DEFAULT_GATEWAY_URL
- }
+ },
+ getAccounts: (end, payload) => {
+ const { approvedHosts, privacyMode } = store.getState();
+ const isEnabled = !privacyMode || approvedHosts[payload.hostname];
+ const { KeyringController } = this.context;
+ const isUnlocked = KeyringController.isUnlocked();
+ const selectedAddress = this.context.PreferencesController.state.selectedAddress;
+ end(null, isUnlocked && isEnabled && selectedAddress ? [selectedAddress] : []);
+ }
+ }
+ });
+ const assetsContractController = new AssetsContractController();
+ const assetsController = new AssetsController({
+ onPreferencesStateChange: listener => preferencesController.subscribe(listener),
+ onNetworkStateChange: listener => networkController.subscribe(listener),
+ getAssetName: assetsContractController.getAssetName.bind(assetsContractController),
+ getAssetSymbol: assetsContractController.getAssetSymbol.bind(assetsContractController),
+ getCollectibleTokenURI: assetsContractController.getCollectibleTokenURI.bind(assetsContractController)
+ });
+ const currencyRateController = new CurrencyRateController({
+ nativeCurrency,
+ currentCurrency
+ });
+
+ const controllers = [
+ new KeyringController(
+ {
+ removeIdentity: preferencesController.removeIdentity.bind(preferencesController),
+ syncIdentities: preferencesController.syncIdentities.bind(preferencesController),
+ updateIdentities: preferencesController.updateIdentities.bind(preferencesController),
+ setSelectedAddress: preferencesController.setSelectedAddress.bind(preferencesController)
+ },
+ { encryptor },
+ initialState.KeyringController
+ ),
+ new AccountTrackerController({
+ onPreferencesStateChange: listener => preferencesController.subscribe(listener),
+ getIdentities: () => preferencesController.state.identities
+ }),
+ new AddressBookController(),
+ assetsContractController,
+ assetsController,
+ new AssetsDetectionController({
+ onAssetsStateChange: listener => assetsController.subscribe(listener),
+ onPreferencesStateChange: listener => preferencesController.subscribe(listener),
+ onNetworkStateChange: listener => networkController.subscribe(listener),
+ getOpenSeaApiKey: () => assetsController.openSeaApiKey,
+ getBalancesInSingleCall: assetsContractController.getBalancesInSingleCall.bind(
+ assetsContractController
),
- new TokenBalancesController({ interval: 10000 }),
- new TokenRatesController(),
- new TransactionController(),
- new TypedMessageManager(),
- new SwapsController({ clientId: AppConstants.SWAPS.CLIENT_ID })
- ],
- initialState
- );
+ addTokens: assetsController.addTokens.bind(assetsController),
+ addCollectible: assetsController.addCollectible.bind(assetsController),
+ removeCollectible: assetsController.removeCollectible.bind(assetsController),
+ getAssetsState: () => assetsController.state
+ }),
+ currencyRateController,
+ new PersonalMessageManager(),
+ new MessageManager(),
+ networkController,
+ new PhishingController(),
+ preferencesController,
+ new TokenBalancesController(
+ {
+ onAssetsStateChange: listener => assetsController.subscribe(listener),
+ getSelectedAddress: () => preferencesController.state.selectedAddress,
+ getBalanceOf: assetsContractController.getBalanceOf.bind(assetsContractController)
+ },
+ { interval: 10000 }
+ ),
+ new TokenRatesController({
+ onAssetsStateChange: listener => assetsController.subscribe(listener),
+ onCurrencyRateStateChange: listener => currencyRateController.subscribe(listener)
+ }),
+ new TransactionController({
+ getNetworkState: () => networkController.state,
+ onNetworkStateChange: listener => networkController.subscribe(listener),
+ getProvider: () => networkController.provider
+ }),
+ new TypedMessageManager(),
+ new SwapsController({
+ clientId: AppConstants.SWAPS.CLIENT_ID,
+ fetchAggregatorMetadataThreshold: AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD,
+ fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD,
+ fetchTopAssetsThreshold: AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD
+ })
+ ];
+
+ // set initial state
+ // TODO: Pass initial state into each controller constructor instead
+ // This is being set post-construction for now to ensure it's functionally equivalent with
+ // how the `ComponsedController` used to set initial state.
+ for (const controller of controllers) {
+ if (initialState[controller.name]) {
+ controller.update(initialState[controller.name]);
+ }
+ }
+
+ this.datamodel = new ComposableController(controllers, initialState);
+ this.context = controllers.reduce((context, controller) => {
+ context[controller.name] = controller;
+ return context;
+ }, {});
const {
AssetsController: assets,
KeyringController: keyring,
NetworkController: network,
TransactionController: transaction
- } = this.datamodel.context;
+ } = this.context;
assets.setApiKey(process.env.MM_OPENSEA_KEY);
network.refreshNetwork();
@@ -152,16 +221,18 @@ class Engine {
AccountTrackerController,
AssetsContractController,
AssetsDetectionController,
- NetworkController: { provider },
+ NetworkController: { provider, state: NetworkControllerState },
TransactionController,
SwapsController
- } = this.datamodel.context;
+ } = this.context;
provider.sendAsync = provider.sendAsync.bind(provider);
AccountTrackerController.configure({ provider });
AssetsContractController.configure({ provider });
+
SwapsController.configure({
provider,
+ chainId: NetworkControllerState?.provider?.chainId,
pollCountLimit: AppConstants.SWAPS.POLL_COUNT_LIMIT,
quotePollingInterval: AppConstants.SWAPS.POLLING_INTERVAL
});
@@ -172,7 +243,7 @@ class Engine {
}
refreshTransactionHistory = async forceCheck => {
- const { TransactionController, PreferencesController, NetworkController } = this.datamodel.context;
+ const { TransactionController, PreferencesController, NetworkController } = this.context;
const { selectedAddress } = PreferencesController.state;
const { type: networkType } = NetworkController.state.provider;
const { networkId } = Networks[networkType];
@@ -233,15 +304,16 @@ class Engine {
AssetsController,
TokenBalancesController,
TokenRatesController
- } = this.datamodel.context;
+ } = this.context;
const { selectedAddress } = PreferencesController.state;
- const { conversionRate } = CurrencyRateController.state;
+ const { conversionRate, currentCurrency } = CurrencyRateController.state;
const { accounts } = AccountTrackerController.state;
const { tokens } = AssetsController.state;
let ethFiat = 0;
let tokenFiat = 0;
+ const decimalsToShow = (currentCurrency === 'usd' && 2) || undefined;
if (accounts[selectedAddress]) {
- ethFiat = weiToFiatNumber(accounts[selectedAddress].balance, conversionRate);
+ ethFiat = weiToFiatNumber(accounts[selectedAddress].balance, conversionRate, decimalsToShow);
}
if (tokens.length > 0) {
const { contractBalances: tokenBalances } = TokenBalancesController.state;
@@ -253,7 +325,12 @@ class Engine {
(item.address in tokenBalances
? renderFromTokenMinimalUnit(tokenBalances[item.address], item.decimals)
: undefined);
- const tokenBalanceFiat = balanceToFiatNumber(tokenBalance, conversionRate, exchangeRate);
+ const tokenBalanceFiat = balanceToFiatNumber(
+ tokenBalance,
+ conversionRate,
+ exchangeRate,
+ decimalsToShow
+ );
tokenFiat += tokenBalanceFiat;
});
}
@@ -293,12 +370,7 @@ class Engine {
// Whenever we are gonna start a new wallet
// either imported or created, we need to
// get rid of the old data from state
- const {
- TransactionController,
- AssetsController,
- TokenBalancesController,
- TokenRatesController
- } = this.datamodel.context;
+ const { TransactionController, AssetsController, TokenBalancesController, TokenRatesController } = this.context;
//Clear assets info
AssetsController.update({
@@ -331,7 +403,7 @@ class Engine {
NetworkController,
TransactionController,
AssetsController
- } = this.datamodel.context;
+ } = this.context;
// Select same network ?
await NetworkController.setProviderType(network.provider.type);
@@ -380,6 +452,7 @@ class Engine {
const checksummedAddress = toChecksumAddress(address);
if (accounts.hd.includes(checksummedAddress) || accounts.simpleKeyPair.includes(checksummedAddress)) {
updatedPref.identities[checksummedAddress] = preferences.identities[address];
+ updatedPref.identities[checksummedAddress].importTime = Date.now();
}
});
await PreferencesController.update(updatedPref);
@@ -418,7 +491,7 @@ let instance;
export default {
get context() {
- return instance && instance.datamodel && instance.datamodel.context;
+ return instance && instance.context;
},
get state() {
const {
diff --git a/app/core/Engine.test.js b/app/core/Engine.test.js
index 8265d38a60f..a400440d10a 100644
--- a/app/core/Engine.test.js
+++ b/app/core/Engine.test.js
@@ -2,19 +2,19 @@ import Engine from './Engine';
describe('Engine', () => {
it('should expose an API', () => {
const engine = Engine.init({});
- expect(engine.datamodel.context).toHaveProperty('AccountTrackerController');
- expect(engine.datamodel.context).toHaveProperty('AddressBookController');
- expect(engine.datamodel.context).toHaveProperty('AssetsContractController');
- expect(engine.datamodel.context).toHaveProperty('AssetsController');
- expect(engine.datamodel.context).toHaveProperty('AssetsDetectionController');
- expect(engine.datamodel.context).toHaveProperty('CurrencyRateController');
- expect(engine.datamodel.context).toHaveProperty('KeyringController');
- expect(engine.datamodel.context).toHaveProperty('NetworkController');
- expect(engine.datamodel.context).toHaveProperty('PersonalMessageManager');
- expect(engine.datamodel.context).toHaveProperty('PhishingController');
- expect(engine.datamodel.context).toHaveProperty('PreferencesController');
- expect(engine.datamodel.context).toHaveProperty('TokenBalancesController');
- expect(engine.datamodel.context).toHaveProperty('TokenRatesController');
- expect(engine.datamodel.context).toHaveProperty('TypedMessageManager');
+ expect(engine.context).toHaveProperty('AccountTrackerController');
+ expect(engine.context).toHaveProperty('AddressBookController');
+ expect(engine.context).toHaveProperty('AssetsContractController');
+ expect(engine.context).toHaveProperty('AssetsController');
+ expect(engine.context).toHaveProperty('AssetsDetectionController');
+ expect(engine.context).toHaveProperty('CurrencyRateController');
+ expect(engine.context).toHaveProperty('KeyringController');
+ expect(engine.context).toHaveProperty('NetworkController');
+ expect(engine.context).toHaveProperty('PersonalMessageManager');
+ expect(engine.context).toHaveProperty('PhishingController');
+ expect(engine.context).toHaveProperty('PreferencesController');
+ expect(engine.context).toHaveProperty('TokenBalancesController');
+ expect(engine.context).toHaveProperty('TokenRatesController');
+ expect(engine.context).toHaveProperty('TypedMessageManager');
});
});
diff --git a/app/core/RPCMethods/wallet_addEthereumChain.js b/app/core/RPCMethods/wallet_addEthereumChain.js
index d33e50d1870..feceb2d88af 100644
--- a/app/core/RPCMethods/wallet_addEthereumChain.js
+++ b/app/core/RPCMethods/wallet_addEthereumChain.js
@@ -6,6 +6,7 @@ import Engine from '../Engine';
import { ethErrors } from 'eth-json-rpc-errors';
import { isPrefixedFormattedHexString, isSafeChainId } from '../../util/networks';
import URL from 'url-parse';
+import AnalyticsV2 from '../../util/analyticsV2';
const wallet_addEthereumChain = async ({
req,
@@ -111,7 +112,19 @@ const wallet_addEthereumChain = async ({
switchCustomNetworkRequest.current = { resolve, reject };
});
- if (!switchCustomNetworkApprove) throw ethErrors.provider.userRejectedRequest();
+ const analyticsParams = {
+ rpc_url: existingNetwork?.rpcUrl,
+ chain_id: _chainId,
+ source: 'Custom Network API',
+ symbol: existingNetwork?.ticker,
+ block_explorer_url: existingNetwork?.blockExplorerUrl,
+ network_name: 'rpc'
+ };
+
+ if (!switchCustomNetworkApprove) {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_REQUEST_REJECTED, analyticsParams);
+ throw ethErrors.provider.userRejectedRequest();
+ }
CurrencyRateController.configure({ nativeCurrency: existingNetwork.ticker });
NetworkController.setRpcTarget(
@@ -120,6 +133,9 @@ const wallet_addEthereumChain = async ({
existingNetwork.ticker,
existingNetwork.nickname
);
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_SWITCHED, analyticsParams);
+
res.result = null;
return;
}
@@ -213,6 +229,17 @@ const wallet_addEthereumChain = async ({
}
requestData.alert = alert;
+ const analyticsParamsAdd = {
+ rpc_url: firstValidRPCUrl,
+ chain_id: chainIdDecimal,
+ source: 'Custom Network API',
+ symbol: ticker,
+ block_explorer_url: firstValidBlockExplorerUrl,
+ network_name: 'rpc'
+ };
+
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_REQUESTED, analyticsParamsAdd);
+
setCustomNetworkToAdd(requestData);
setShowAddCustomNetworkDialog(true);
@@ -220,12 +247,17 @@ const wallet_addEthereumChain = async ({
addCustomNetworkRequest.current = { resolve, reject };
});
- if (!addCustomNetworkApprove) throw ethErrors.provider.userRejectedRequest();
+ if (!addCustomNetworkApprove) {
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_REQUEST_REJECTED, analyticsParamsAdd);
+ throw ethErrors.provider.userRejectedRequest();
+ }
PreferencesController.addToFrequentRpcList(firstValidRPCUrl, chainIdDecimal, ticker, _chainName, {
blockExplorerUrl: firstValidBlockExplorerUrl
});
+ AnalyticsV2.trackEvent(AnalyticsV2.ANALYTICS_EVENTS.NETWORK_ADDED, analyticsParamsAdd);
+
InteractionManager.runAfterInteractions(() => {
setCustomNetworkToSwitch(requestData);
setShowSwitchCustomNetworkDialog('new');
diff --git a/app/core/Vault.js b/app/core/Vault.js
index 471e6618d5a..6c3ef8684d1 100644
--- a/app/core/Vault.js
+++ b/app/core/Vault.js
@@ -1,5 +1,6 @@
import Engine from './Engine';
import Logger from '../util/Logger';
+import { syncPrefs, syncAccounts } from '../util/sync';
/**
* Returns current vault seed phrase
@@ -18,8 +19,10 @@ export const getSeedPhrase = async (password = '') => {
* @param password - Password to recreate and set the vault with
*/
export const recreateVaultWithSamePassword = async (password = '', selectedAddress) => {
- const { KeyringController, PreferencesController } = Engine.context;
+ const { KeyringController, PreferencesController, AccountTrackerController } = Engine.context;
const seedPhrase = await getSeedPhrase(password);
+ const oldPrefs = PreferencesController.state;
+ const oldAccounts = AccountTrackerController.accounts;
let importedAccounts = [];
try {
@@ -42,7 +45,6 @@ export const recreateVaultWithSamePassword = async (password = '', selectedAddre
// Get props to restore vault
const hdKeyring = KeyringController.state.keyrings[0];
const existingAccountCount = hdKeyring.accounts.length;
- let preferencesControllerState = PreferencesController.state;
// Create previous accounts again
for (let i = 0; i < existingAccountCount - 1; i++) {
@@ -58,11 +60,18 @@ export const recreateVaultWithSamePassword = async (password = '', selectedAddre
Logger.error(e, 'error while trying to import accounts on recreate vault');
}
- // Reset preferencesControllerState
- preferencesControllerState = PreferencesController.state;
+ //Persist old account/identities names
+ const preferencesControllerState = PreferencesController.state;
+ const prefUpdates = syncPrefs(oldPrefs, preferencesControllerState);
+
+ //Persist old account data
+ const accounts = AccountTrackerController.accounts;
+ const updateAccounts = syncAccounts(oldAccounts, accounts);
// Set preferencesControllerState again
- await PreferencesController.update(preferencesControllerState);
+ await PreferencesController.update(prefUpdates);
+ await AccountTrackerController.update(updateAccounts);
+
// Reselect previous selected account if still available
if (hdKeyring.accounts.includes(selectedAddress)) {
PreferencesController.setSelectedAddress(selectedAddress);
diff --git a/app/core/WalletConnect.js b/app/core/WalletConnect.js
index 3751a9f2757..802bcbfee8a 100644
--- a/app/core/WalletConnect.js
+++ b/app/core/WalletConnect.js
@@ -7,6 +7,7 @@ import { EventEmitter } from 'events';
import AsyncStorage from '@react-native-community/async-storage';
import { CLIENT_OPTIONS, WALLET_CONNECT_ORIGIN } from '../util/walletconnect';
import { WALLETCONNECT_SESSIONS } from '../constants/storage';
+import { WalletDevice } from '@metamask/controllers/';
const hub = new EventEmitter();
let connectors = [];
@@ -118,7 +119,8 @@ class WalletConnect {
txParams.data = payload.params[0].data;
const hash = await (await TransactionController.addTransaction(
txParams,
- meta ? WALLET_CONNECT_ORIGIN + meta.url : undefined
+ meta ? WALLET_CONNECT_ORIGIN + meta.url : undefined,
+ WalletDevice.MM_MOBILE
)).result;
this.walletConnector.approveRequest({
id: payload.id,
diff --git a/app/images/bnb-logo.png b/app/images/bnb-logo.png
new file mode 100644
index 00000000000..3712a470d2f
Binary files /dev/null and b/app/images/bnb-logo.png differ
diff --git a/app/reducers/index.js b/app/reducers/index.js
index e8085a142b5..66484349551 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -13,6 +13,7 @@ import onboardingReducer from './onboarding';
import fiatOrders from './fiatOrders';
import swapsReducer from './swaps';
import notificationReducer from './notification';
+import infuraAvailabilityReducer from './infuraAvailability';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
@@ -30,7 +31,8 @@ const rootReducer = combineReducers({
onboarding: onboardingReducer,
notification: notificationReducer,
swaps: swapsReducer,
- fiatOrders
+ fiatOrders,
+ infuraAvailability: infuraAvailabilityReducer
});
export default rootReducer;
diff --git a/app/reducers/infuraAvailability/index.js b/app/reducers/infuraAvailability/index.js
new file mode 100644
index 00000000000..4079763a530
--- /dev/null
+++ b/app/reducers/infuraAvailability/index.js
@@ -0,0 +1,26 @@
+const initialState = {
+ isBlocked: false
+};
+
+export const INFURA_AVAILABILITY_BLOCKED = 'INFURA_AVAILABILITY_BLOCKED';
+export const INFURA_AVAILABILITY_NOT_BLOCKED = 'INFURA_AVAILABILITY_NOT_BLOCKED';
+
+export const getInfuraBlockedSelector = state => state.infuraAvailability?.isBlocked;
+
+const infuraAvailabilityReducer = (state = initialState, action) => {
+ switch (action.type) {
+ case INFURA_AVAILABILITY_BLOCKED:
+ return {
+ ...state,
+ isBlocked: true
+ };
+ case INFURA_AVAILABILITY_NOT_BLOCKED:
+ return {
+ ...state,
+ isBlocked: false
+ };
+ default:
+ return state;
+ }
+};
+export default infuraAvailabilityReducer;
diff --git a/app/reducers/settings/index.js b/app/reducers/settings/index.js
index 2245d0f4974..f269bea8a51 100644
--- a/app/reducers/settings/index.js
+++ b/app/reducers/settings/index.js
@@ -30,6 +30,11 @@ const settingsReducer = (state = initialState, action) => {
...state,
showHexData: action.showHexData
};
+ case 'SET_SHOW_CUSTOM_NONCE':
+ return {
+ ...state,
+ showCustomNonce: action.showCustomNonce
+ };
case 'SET_USE_BLOCKIE_ICON':
return {
...state,
diff --git a/app/reducers/swaps/index.js b/app/reducers/swaps/index.js
index 48569c4e948..5c3bbd139f7 100644
--- a/app/reducers/swaps/index.js
+++ b/app/reducers/swaps/index.js
@@ -6,7 +6,7 @@ export const SWAPS_SET_HAS_ONBOARDED = 'SWAPS_SET_HAS_ONBOARDED';
const MAX_TOKENS_WITH_BALANCE = 5;
// * Action Creator
-export const setSwapsLiveness = live => ({ type: SWAPS_SET_LIVENESS, payload: live });
+export const setSwapsLiveness = (live, chainId) => ({ type: SWAPS_SET_LIVENESS, payload: { live, chainId } });
export const setSwapsHasOnboarded = hasOnboarded => ({ type: SWAPS_SET_HAS_ONBOARDED, payload: hasOnboarded });
// * Selectors
@@ -15,7 +15,10 @@ export const setSwapsHasOnboarded = hasOnboarded => ({ type: SWAPS_SET_HAS_ONBOA
* Returns the swaps liveness state
*/
-export const swapsLivenessSelector = state => state.swaps.isLive;
+export const swapsLivenessSelector = state => {
+ const chainId = state.engine.backgroundState.NetworkController.provider.chainId;
+ return state.swaps[chainId]?.isLive || false;
+};
/**
* Returns the swaps onboarded state
@@ -106,16 +109,25 @@ export const swapsTopAssetsSelector = createSelector(
// * Reducer
export const initialState = {
- isLive: true,
- hasOnboarded: false
+ isLive: true, // TODO: should we remove it?
+ hasOnboarded: false,
+
+ '1': {
+ isLive: true
+ }
};
function swapsReducer(state = initialState, action) {
switch (action.type) {
case SWAPS_SET_LIVENESS: {
+ const { live, chainId } = action.payload;
+ const data = state[chainId];
return {
...state,
- isLive: Boolean(action.payload)
+ [chainId]: {
+ ...data,
+ isLive: live
+ }
};
}
case SWAPS_SET_HAS_ONBOARDED: {
diff --git a/app/reducers/swaps/swaps.test.js b/app/reducers/swaps/swaps.test.js
index 8ad95806c35..94812cdeae6 100644
--- a/app/reducers/swaps/swaps.test.js
+++ b/app/reducers/swaps/swaps.test.js
@@ -10,10 +10,10 @@ describe('swaps reducer', () => {
it('should set liveness', () => {
const initalState = reducer(undefined, emptyAction);
- const notLiveState = reducer(initalState, { type: SWAPS_SET_LIVENESS, payload: false });
- expect(notLiveState.isLive).toBe(false);
- const liveState = reducer(initalState, { type: SWAPS_SET_LIVENESS, payload: true });
- expect(liveState.isLive).toBe(true);
+ const notLiveState = reducer(initalState, { type: SWAPS_SET_LIVENESS, payload: { live: false, chainId: 1 } });
+ expect(notLiveState['1'].isLive).toBe(false);
+ const liveState = reducer(initalState, { type: SWAPS_SET_LIVENESS, payload: { live: true, chainId: 1 } });
+ expect(liveState['1'].isLive).toBe(true);
});
it('should set has onboarded', () => {
diff --git a/app/reducers/transaction/index.js b/app/reducers/transaction/index.js
index a8e3f92d0c2..47a3ed17571 100644
--- a/app/reducers/transaction/index.js
+++ b/app/reducers/transaction/index.js
@@ -22,7 +22,9 @@ const initialState = {
paymentRequest: undefined,
readableValue: undefined,
id: undefined,
- type: undefined
+ type: undefined,
+ proposedNonce: undefined,
+ nonce: undefined
};
const getAssetType = selectedAsset => {
@@ -57,6 +59,16 @@ const transactionReducer = (state = initialState, action) => {
selectedAsset: action.selectedAsset,
assetType: action.assetType
};
+ case 'SET_NONCE':
+ return {
+ ...state,
+ nonce: action.nonce
+ };
+ case 'SET_PROPOSED_NONCE':
+ return {
+ ...state,
+ proposedNonce: action.proposedNonce
+ };
case 'SET_RECIPIENT':
return {
...state,
diff --git a/app/store/migrations.js b/app/store/migrations.js
index 6d13e2369e5..34b676ad182 100644
--- a/app/store/migrations.js
+++ b/app/store/migrations.js
@@ -72,7 +72,63 @@ export const migrations = {
};
}
return state;
+ },
+ 4: state => {
+ const { allCollectibleContracts, allCollectibles, allTokens } = state.engine.backgroundState.AssetsController;
+ const { frequentRpcList } = state.engine.backgroundState.PreferencesController;
+
+ const newAllCollectibleContracts = {};
+ const newAllCollectibles = {};
+ const newAllTokens = {};
+
+ Object.keys(allTokens).forEach(address => {
+ newAllTokens[address] = {};
+ Object.keys(allTokens[address]).forEach(networkType => {
+ if (NetworksChainId[networkType]) {
+ newAllTokens[address][NetworksChainId[networkType]] = allTokens[address][networkType];
+ } else {
+ frequentRpcList.forEach(({ chainId }) => {
+ newAllTokens[address][chainId] = allTokens[address][networkType];
+ });
+ }
+ });
+ });
+
+ Object.keys(allCollectibles).forEach(address => {
+ newAllCollectibles[address] = {};
+ Object.keys(allCollectibles[address]).forEach(networkType => {
+ if (NetworksChainId[networkType]) {
+ newAllCollectibles[address][NetworksChainId[networkType]] = allCollectibles[address][networkType];
+ } else {
+ frequentRpcList.forEach(({ chainId }) => {
+ newAllCollectibles[address][chainId] = allCollectibles[address][networkType];
+ });
+ }
+ });
+ });
+
+ Object.keys(allCollectibleContracts).forEach(address => {
+ newAllCollectibleContracts[address] = {};
+ Object.keys(allCollectibleContracts[address]).forEach(networkType => {
+ if (NetworksChainId[networkType]) {
+ newAllCollectibleContracts[address][NetworksChainId[networkType]] =
+ allCollectibleContracts[address][networkType];
+ } else {
+ frequentRpcList.forEach(({ chainId }) => {
+ newAllCollectibleContracts[address][chainId] = allCollectibleContracts[address][networkType];
+ });
+ }
+ });
+ });
+
+ state.engine.backgroundState.AssetsController = {
+ ...state.engine.backgroundState.AssetsController,
+ allTokens: newAllTokens,
+ allCollectibles: newAllCollectibles,
+ allCollectibleContracts: newAllCollectibleContracts
+ };
+ return state;
}
};
-export const version = 3;
+export const version = 4;
diff --git a/app/styles/common.js b/app/styles/common.js
index d6431dd19e9..fbde6595fab 100644
--- a/app/styles/common.js
+++ b/app/styles/common.js
@@ -12,7 +12,7 @@ export const colors = {
fontError: '#D73A49',
fontWarning: '#f66a0a',
primaryFox: '#f66a0a',
- black: '#000000',
+ black: '#24292E',
white: '#FFFFFF',
white100: '#F9FAFB',
grey450: '#8E8E93',
diff --git a/app/util/address.js b/app/util/address.js
index 842a597adbd..d7ae893516c 100644
--- a/app/util/address.js
+++ b/app/util/address.js
@@ -2,6 +2,9 @@ import { toChecksumAddress } from 'ethereumjs-util';
import Engine from '../core/Engine';
import AppConstants from '../core/AppConstants';
import { strings } from '../../locales/i18n';
+import { tlc } from '../util/general';
+
+const { supportedTLDs } = AppConstants;
/**
* Returns full checksummed address
@@ -67,11 +70,11 @@ export async function importAccountFromPrivateKey(private_key) {
* @returns {boolean} - Returns a boolean indicating if it is valid
*/
export function isENS(name) {
- const rec = name && name.split('.');
- if (!rec || rec.length === 1 || !AppConstants.supportedTLDs.includes(rec[rec.length - 1])) {
- return false;
- }
- return true;
+ const OFFSET = 1;
+ const index = name && name.lastIndexOf('.');
+ const tld = index && index >= OFFSET && tlc(name.substr(index + OFFSET, name.length - OFFSET));
+ if (index && tld && supportedTLDs.includes(tld)) return true;
+ return false;
}
/**
diff --git a/app/util/address.test.js b/app/util/address.test.js
new file mode 100644
index 00000000000..6d23dc837f6
--- /dev/null
+++ b/app/util/address.test.js
@@ -0,0 +1,19 @@
+import { isENS } from './address';
+
+describe('isENS', () => {
+ it('should return false by default', () => {
+ expect(isENS()).toBe(false);
+ });
+ it('should return false for normal domain', () => {
+ expect(isENS('ricky.codes')).toBe(false);
+ });
+ it('should return true for ens', () => {
+ expect(isENS('rickycodes.eth')).toBe(true);
+ });
+ it('should return true for ens', () => {
+ expect(isENS('ricky.eth.eth')).toBe(true);
+ });
+ it('should return true for ens', () => {
+ expect(isENS('ricky.metamask.eth')).toBe(true);
+ });
+});
diff --git a/app/util/analyticsV2.js b/app/util/analyticsV2.js
new file mode 100644
index 00000000000..c1083b179fe
--- /dev/null
+++ b/app/util/analyticsV2.js
@@ -0,0 +1,94 @@
+import Analytics from '../core/Analytics';
+import Logger from './Logger';
+import { InteractionManager } from 'react-native';
+
+const generateOpt = name => ({ category: name });
+
+export const ANALYTICS_EVENTS_V2 = {
+ // Approval
+ APPROVAL_STARTED: generateOpt('Approval Started'),
+ APPROVAL_COMPLETED: generateOpt('Approval Completed'),
+ APPROVAL_CANCELLED: generateOpt('Approval Cancelled'),
+ APPROVAL_PERMISSION_UPDATED: generateOpt('Approval Permission Updated'),
+ // Fee changed
+ GAS_FEE_CHANGED: generateOpt('Gas Fee Changed'),
+ // Dapp Transaction
+ DAPP_TRANSACTION_STARTED: generateOpt('Dapp Transaction Started'),
+ DAPP_TRANSACTION_COMPLETED: generateOpt('Dapp Transaction Completed'),
+ DAPP_TRANSACTION_CANCELLED: generateOpt('Dapp Transaction Cancelled'),
+ // Sign request
+ SIGN_REQUEST_STARTED: generateOpt('Sign Request Started'),
+ SIGN_REQUEST_COMPLETED: generateOpt('Sign Request Completed'),
+ SIGN_REQUEST_CANCELLED: generateOpt('Sign Request Cancelled'),
+ // Connect request
+ CONNECT_REQUEST_STARTED: generateOpt('Connect Request Started'),
+ CONNECT_REQUEST_COMPLETED: generateOpt('Connect Request Completed'),
+ CONNECT_REQUEST_CANCELLED: generateOpt('Connect Request Cancelled'),
+ // Wallet
+ WALLET_OPENED: generateOpt('Wallet Opened'),
+ TOKEN_ADDED: generateOpt('Token Added'),
+ COLLECTIBLE_ADDED: generateOpt('Collectible Added'),
+ // Network
+ NETWORK_SWITCHED: generateOpt('Network Switched'),
+ NETWORK_ADDED: generateOpt('Network Added'),
+ NETWORK_REQUESTED: generateOpt('Network Requested'),
+ NETWORK_REQUEST_REJECTED: generateOpt('Network Request Rejected'),
+ // Send transaction
+ SEND_TRANSACTION_STARTED: generateOpt('Send Transaction Started'),
+ SEND_TRANSACTION_COMPLETED: generateOpt('Send Transaction Completed')
+};
+
+/**
+ * This takes params with the following structure:
+ * { foo : 'this is not anonymous', bar: {value: 'this is anonymous', anonymous: true} }
+ * @param {String} eventName
+ * @param {Object} params
+ */
+export const trackEventV2 = (eventName, params) => {
+ InteractionManager.runAfterInteractions(() => {
+ try {
+ if (!params) {
+ Analytics.trackEvent(eventName);
+ }
+
+ const userParams = {};
+ const anonymousParams = {};
+
+ for (const key in params) {
+ const property = params[key];
+
+ if (property && typeof property === 'object') {
+ if (property.anonymous) {
+ // Anonymous property - add only to anonymous params
+ anonymousParams[key] = property.value;
+ } else {
+ // Non-anonymous property - add to both
+ userParams[key] = property.value;
+ anonymousParams[key] = property.value;
+ }
+ } else {
+ // Non-anonymous properties - add to both
+ userParams[key] = property;
+ anonymousParams[key] = property;
+ }
+ }
+
+ // Log all non-anonymous properties
+ if (Object.keys(userParams).length) {
+ Analytics.trackEventWithParameters(eventName, userParams);
+ }
+
+ // Log all anonymous properties
+ if (Object.keys(anonymousParams).length) {
+ Analytics.trackEventWithParameters(eventName, anonymousParams, true);
+ }
+ } catch (error) {
+ Logger.error(error, 'Error logging analytics');
+ }
+ });
+};
+
+export default {
+ ANALYTICS_EVENTS: ANALYTICS_EVENTS_V2,
+ trackEvent: trackEventV2
+};
diff --git a/app/util/assets.js b/app/util/assets.js
index af2d7737a1d..a1812397dc3 100644
--- a/app/util/assets.js
+++ b/app/util/assets.js
@@ -1,11 +1,14 @@
+const pack = require('../../package.json'); // eslint-disable-line
+
/**
* Utility function to return corresponding @metamask/contract-metadata logo
*
* @param {string} logo - Logo path from @metamask/contract-metadata
*/
export default function getAssetLogoPath(logo) {
+ const version = pack.dependencies['@metamask/contract-metadata']?.replace('^', '');
if (!logo) return;
- const path = 'https://raw.githubusercontent.com/metamask/contract-metadata/v1.23.0/images/';
+ const path = `https://raw.githubusercontent.com/metamask/contract-metadata/v${version}/images/`;
const uri = path + logo;
return uri;
}
diff --git a/app/util/custom-gas.js b/app/util/custom-gas.js
index 82cc62f2d3e..14ca609a9e4 100644
--- a/app/util/custom-gas.js
+++ b/app/util/custom-gas.js
@@ -1,8 +1,16 @@
import { BN } from 'ethereumjs-util';
-import { renderFromWei, weiToFiat, toWei } from './number';
+import { renderFromWei, weiToFiat, toWei, conversionUtil } from './number';
import { strings } from '../../locales/i18n';
import Logger from '../util/Logger';
import TransactionTypes from '../core/TransactionTypes';
+import Engine from '../core/Engine';
+import { isMainnetByChainId } from '../util/networks';
+import { util } from '@metamask/controllers';
+const { hexToBN } = util;
+
+export const ETH = 'ETH';
+export const GWEI = 'GWEI';
+export const WEI = 'WEI';
/**
* Calculates wei value of estimate gas price in gwei
@@ -103,7 +111,10 @@ export function parseWaitTime(min) {
* @returns {Object} - Object containing basic estimates
*/
export async function fetchBasicGasEstimates() {
- return await fetch(`https://api.metaswap.codefi.network/gasPrices`, {
+ // Timeout in 7 seconds
+ const timeout = 7000;
+
+ const fetchPromise = fetch(`https://api.metaswap.codefi.network/gasPrices`, {
headers: {},
referrerPolicy: 'no-referrer-when-downgrade',
body: null,
@@ -117,8 +128,14 @@ export async function fetchBasicGasEstimates() {
safeLow: SafeGasPrice,
fast: FastGasPrice
};
+
return basicEstimates;
});
+
+ return Promise.race([
+ fetchPromise,
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
+ ]);
}
/**
@@ -127,29 +144,13 @@ export async function fetchBasicGasEstimates() {
* @returns {Object} - Object containing formatted wait times
*/
export async function getBasicGasEstimates() {
- const {
- CUSTOM_GAS: { AVERAGE_GAS, FAST_GAS, LOW_GAS }
- } = TransactionTypes;
-
- let basicGasEstimates;
- try {
- basicGasEstimates = await fetchBasicGasEstimates();
- } catch (error) {
- Logger.log('Error while trying to get gas limit estimates', error);
- basicGasEstimates = {
- average: AVERAGE_GAS,
- safeLow: LOW_GAS,
- fast: FAST_GAS
- };
- }
+ const basicGasEstimates = await fetchBasicGasEstimates();
// Handle api failure returning same gas prices
- let { average, fast, safeLow } = basicGasEstimates;
+ const { average, fast, safeLow } = basicGasEstimates;
if (average === fast && average === safeLow) {
- average = AVERAGE_GAS;
- safeLow = LOW_GAS;
- fast = FAST_GAS;
+ throw new Error('Api returned same gas prices');
}
return {
@@ -158,3 +159,76 @@ export async function getBasicGasEstimates() {
safeLowGwei: convertApiValueToGWEI(safeLow)
};
}
+
+export async function getGasPriceByChainId(transaction) {
+ const { TransactionController, NetworkController } = Engine.context;
+ const chainId = NetworkController.state.provider.chainId;
+
+ let estimation, basicGasEstimates;
+ try {
+ estimation = await TransactionController.estimateGas(transaction);
+ basicGasEstimates = {
+ average: getValueFromWeiHex({
+ value: estimation.gasPrice.toString(16),
+ numberOfDecimals: 4,
+ toDenomination: 'GWEI'
+ })
+ };
+ } catch (error) {
+ estimation = {
+ gas: TransactionTypes.CUSTOM_GAS.DEFAULT_GAS_LIMIT,
+ gasPrice: TransactionTypes.CUSTOM_GAS.AVERAGE_GAS
+ };
+ basicGasEstimates = {
+ average: estimation.gasPrice
+ };
+ Logger.log('Error while trying to get gas price from the network', error);
+ }
+
+ if (isMainnetByChainId(chainId)) {
+ try {
+ basicGasEstimates = await fetchBasicGasEstimates();
+ } catch (error) {
+ Logger.log('Error while trying to get gas limit estimates', error);
+ // Will use gas price from network that was fetched above
+ }
+ }
+ const gas = hexToBN(estimation.gas);
+ const gasPrice = toWei(convertApiValueToGWEI(basicGasEstimates.average), 'gwei');
+ return { gas, gasPrice };
+}
+
+export async function getBasicGasEstimatesByChainId() {
+ const { NetworkController } = Engine.context;
+ const chainId = NetworkController.state.provider.chainId;
+
+ if (!isMainnetByChainId(chainId)) {
+ return null;
+ }
+ try {
+ const basicGasEstimates = await getBasicGasEstimates();
+ return basicGasEstimates;
+ } catch (e) {
+ return null;
+ }
+}
+
+export function getValueFromWeiHex({
+ value,
+ fromCurrency = ETH,
+ toCurrency,
+ conversionRate,
+ numberOfDecimals,
+ toDenomination
+}) {
+ return conversionUtil(value, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency,
+ toCurrency,
+ numberOfDecimals,
+ fromDenomination: WEI,
+ toDenomination,
+ conversionRate
+ });
+}
diff --git a/app/util/format.js b/app/util/format.js
deleted file mode 100644
index bc5eca37906..00000000000
--- a/app/util/format.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// eslint-disable-next-line import/prefer-default-export
-export const capitalize = str => (str && str.charAt(0).toUpperCase() + str.slice(1)) || false;
diff --git a/app/util/general.js b/app/util/general.js
index 9703817b192..18db0cff41a 100644
--- a/app/util/general.js
+++ b/app/util/general.js
@@ -1,3 +1,5 @@
+export const tlc = str => String(str).toLowerCase();
+
/**
* Fetch that fails after timeout
*
@@ -31,3 +33,6 @@ export function findRouteNameFromNavigatorState({ routes }) {
}
return route?.routeName;
}
+export const capitalize = str => (str && str.charAt(0).toUpperCase() + str.slice(1)) || false;
+
+export const toLowerCaseCompare = (a, b) => tlc(a) === tlc(b);
diff --git a/app/util/general.test.js b/app/util/general.test.js
new file mode 100644
index 00000000000..94bf225f425
--- /dev/null
+++ b/app/util/general.test.js
@@ -0,0 +1,27 @@
+import { capitalize, tlc, toLowerCaseCompare } from './general';
+
+describe('capitalize', () => {
+ const my_string = 'string';
+ it('should capitalize a string', () => {
+ expect(capitalize(my_string)).toEqual('String');
+ });
+ it('should return false if a string is not provided', () => {
+ expect(capitalize(null)).toEqual(false);
+ });
+});
+
+describe('tlc', () => {
+ it('should coerce a string toLowerCase', () => {
+ expect(tlc('aBCDefH')).toEqual('abcdefh');
+ expect(tlc(NaN)).toEqual('nan');
+ });
+});
+
+describe('toLowerCaseCompare', () => {
+ it('compare two things', () => {
+ expect(toLowerCaseCompare('A', 'A')).toEqual(true);
+ expect(toLowerCaseCompare('aBCDefH', 'abcdefh')).toEqual(true);
+ expect(toLowerCaseCompare('A', 'B')).toEqual(false);
+ expect(toLowerCaseCompare('aBCDefH', 'abcdefi')).toEqual(false);
+ });
+});
diff --git a/app/util/networks.js b/app/util/networks.js
index ab859d33e33..2d859ac5d8d 100644
--- a/app/util/networks.js
+++ b/app/util/networks.js
@@ -2,6 +2,8 @@ import { colors } from '../styles/common';
import URL from 'url-parse';
import AppConstants from '../core/AppConstants';
import { MAINNET, ROPSTEN, KOVAN, RINKEBY, GOERLI, RPC } from '../../app/constants/network';
+import { util } from '@metamask/controllers';
+import Engine from '../core/Engine';
/**
* List of the supported networks
@@ -72,6 +74,15 @@ export const getAllNetworks = () => NetworkListKeys.filter(name => name !== RPC)
export const isMainNet = network => network?.provider?.type === MAINNET || network === String(1);
+export const getDecimalChainId = chainId => {
+ if (!chainId || typeof chainId !== 'string' || !chainId.startsWith('0x')) {
+ return chainId;
+ }
+ return parseInt(chainId, 16).toString(10);
+};
+
+export const isMainnetByChainId = chainId => getDecimalChainId(String(chainId)) === String(1);
+
export const getNetworkName = id => NetworkListKeys.find(key => NetworkList[key].networkId === Number(id));
export function getNetworkTypeById(id) {
@@ -156,3 +167,9 @@ export function isPrefixedFormattedHexString(value) {
}
return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value);
}
+
+export const getNetworkNonce = async ({ from }) => {
+ const { TransactionController } = Engine.context;
+ const networkNonce = await util.query(TransactionController.ethQuery, 'getTransactionCount', [from, 'pending']);
+ return parseInt(networkNonce, 16);
+};
diff --git a/app/util/number.js b/app/util/number.js
index 33aed748ef5..13131b7227b 100644
--- a/app/util/number.js
+++ b/app/util/number.js
@@ -1,13 +1,40 @@
/**
* Collection of utility functions for consistent formatting and conversion
*/
-import { addHexPrefix, BN } from 'ethereumjs-util';
+import { addHexPrefix, BN, stripHexPrefix } from 'ethereumjs-util';
import { utils as ethersUtils } from 'ethers';
import convert from 'ethjs-unit';
import { util } from '@metamask/controllers';
import numberToBN from 'number-to-bn';
import currencySymbols from '../util/currency-symbols.json';
-
+import BigNumber from 'bignumber.js';
+
+// Big Number Constants
+const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber('1000000000000000000');
+const BIG_NUMBER_GWEI_MULTIPLIER = new BigNumber('1000000000');
+const BIG_NUMBER_ETH_MULTIPLIER = new BigNumber('1');
+
+// Setter Maps
+const toBigNumber = {
+ hex: n => new BigNumber(stripHexPrefix(n), 16),
+ dec: n => new BigNumber(String(n), 10),
+ BN: n => new BigNumber(n.toString(16), 16)
+};
+const toNormalizedDenomination = {
+ WEI: bigNumber => bigNumber.div(BIG_NUMBER_WEI_MULTIPLIER),
+ GWEI: bigNumber => bigNumber.div(BIG_NUMBER_GWEI_MULTIPLIER),
+ ETH: bigNumber => bigNumber.div(BIG_NUMBER_ETH_MULTIPLIER)
+};
+const toSpecifiedDenomination = {
+ WEI: bigNumber => bigNumber.times(BIG_NUMBER_WEI_MULTIPLIER).decimalPlaces(),
+ GWEI: bigNumber => bigNumber.times(BIG_NUMBER_GWEI_MULTIPLIER).decimalPlaces(9),
+ ETH: bigNumber => bigNumber.times(BIG_NUMBER_ETH_MULTIPLIER).decimalPlaces(9)
+};
+const baseChange = {
+ hex: n => n.toString(16),
+ dec: n => new BigNumber(n).toString(10),
+ BN: n => new BN(n.toString(16))
+};
/**
* Converts a BN object to a hex string with a '0x' prefix
*
@@ -321,7 +348,7 @@ export function weiToFiat(wei, conversionRate, currencyCode, decimalsToShow = 5)
}
return `0.00 ${currencyCode}`;
}
- decimalsToShow = currencyCode === 'usd' && 2;
+ decimalsToShow = (currencyCode === 'usd' && 2) || undefined;
const value = weiToFiatNumber(wei, conversionRate, decimalsToShow);
if (currencySymbols[currencyCode]) {
return `${currencySymbols[currencyCode]}${value}`;
@@ -496,3 +523,80 @@ export function isPrefixedFormattedHexString(value) {
}
return /^0x[1-9a-f]+[0-9a-f]*$/iu.test(value);
}
+
+const converter = ({
+ value,
+ fromNumericBase,
+ fromDenomination,
+ fromCurrency,
+ toNumericBase,
+ toDenomination,
+ toCurrency,
+ numberOfDecimals,
+ conversionRate,
+ invertConversionRate,
+ roundDown
+}) => {
+ let convertedValue = fromNumericBase ? toBigNumber[fromNumericBase](value) : value;
+
+ if (fromDenomination) {
+ convertedValue = toNormalizedDenomination[fromDenomination](convertedValue);
+ }
+
+ if (fromCurrency !== toCurrency) {
+ if (conversionRate === null || conversionRate === undefined) {
+ throw new Error(
+ `Converting from ${fromCurrency} to ${toCurrency} requires a conversionRate, but one was not provided`
+ );
+ }
+ let rate = toBigNumber.dec(conversionRate);
+ if (invertConversionRate) {
+ rate = new BigNumber(1.0).div(conversionRate);
+ }
+ convertedValue = convertedValue.times(rate);
+ }
+
+ if (toDenomination) {
+ convertedValue = toSpecifiedDenomination[toDenomination](convertedValue);
+ }
+
+ if (numberOfDecimals) {
+ convertedValue = convertedValue.decimalPlaces(numberOfDecimals, BigNumber.ROUND_HALF_DOWN);
+ }
+
+ if (roundDown) {
+ convertedValue = convertedValue.decimalPlaces(roundDown, BigNumber.ROUND_DOWN);
+ }
+
+ if (toNumericBase) {
+ convertedValue = baseChange[toNumericBase](convertedValue);
+ }
+ return convertedValue;
+};
+
+export const conversionUtil = (
+ value,
+ {
+ fromCurrency = null,
+ toCurrency = fromCurrency,
+ fromNumericBase,
+ toNumericBase,
+ fromDenomination,
+ toDenomination,
+ numberOfDecimals,
+ conversionRate,
+ invertConversionRate
+ }
+) =>
+ converter({
+ fromCurrency,
+ toCurrency,
+ fromNumericBase,
+ toNumericBase,
+ fromDenomination,
+ toDenomination,
+ numberOfDecimals,
+ conversionRate,
+ invertConversionRate,
+ value: value || '0'
+ });
diff --git a/app/util/password.js b/app/util/password.js
index e5d3ee89cfd..b7d81a1e390 100644
--- a/app/util/password.js
+++ b/app/util/password.js
@@ -1,4 +1,4 @@
-const MIN_PASSWORD_LENGTH = 8;
+export const MIN_PASSWORD_LENGTH = 8;
export const getPasswordStrengthWord = strength => {
switch (strength) {
case 0:
diff --git a/app/util/sync.js b/app/util/sync.js
new file mode 100644
index 00000000000..571599b377e
--- /dev/null
+++ b/app/util/sync.js
@@ -0,0 +1,37 @@
+/**
+ * Function to persist the old account name during an new preferences update
+ * @param {Object} oldPrefs - old preferences object containing the account names
+ * @param {Object} updatedPref - preferences object that will be updated with oldPrefs
+ */
+export async function syncPrefs(oldPrefs, updatedPref) {
+ try {
+ Object.keys(oldPrefs.identities).forEach(ids => {
+ if (updatedPref.identities[ids]) {
+ updatedPref.identities[ids] = oldPrefs.identities[ids];
+ }
+ });
+
+ return updatedPref;
+ } catch (err) {
+ return updatedPref;
+ }
+}
+
+/**
+ * Function to persist the old account balance during an vault update
+ * @param {Object} oldAccounts - old account object containing the account names
+ * @param {Object} updatedAccounts - accounts object that will be updated with old accout balance
+ */
+export async function syncAccounts(oldAccounts, updatedAccounts) {
+ try {
+ Object.keys(oldAccounts).forEach(account => {
+ if (updatedAccounts[account]) {
+ updatedAccounts[account] = oldAccounts[account];
+ }
+ });
+
+ return updatedAccounts;
+ } catch (err) {
+ return updatedAccounts;
+ }
+}
diff --git a/app/util/sync.test.js b/app/util/sync.test.js
new file mode 100644
index 00000000000..2fd5f8dfaf9
--- /dev/null
+++ b/app/util/sync.test.js
@@ -0,0 +1,106 @@
+import { syncPrefs, syncAccounts } from '../util/sync';
+
+const OLD_PREFS = {
+ accountTokens: {
+ '0x0942890c603273059a11a298F81cb137Be9CF704': { '0x1': [Array], '0x3': [Array] },
+ '0x120bfFfa4138fD00A8025a223C350b9ffaDAD8F5': { '0x3': [Array] },
+ '0x16C6C3079edE914e83B388a52fFD9255E1c3165': { '0x3': [Array] },
+ '0x223367C61c38FAcbdd0b92De5aA7B742e1e5a196': { '0x1': [Array], '0x3': [Array] },
+ '0x7b8C6B8363B9E7A77d279dDad49BEF2994a3bf28': { '0x3': [Array] },
+ '0x9236413AfD369B2aeb5e52C048f6B30e7308f2e3': { '0x1': [Array], '0x3': [Array] },
+ '0x9b07Ba86631bdb74eE2DDb5750440986DECB9e11': { '0x1': [Array], '0x3': [Array] },
+ '0xE4D7f194b07B85511973f1FAAB31b8C2F1f9F344': { '0x3': [Array] }
+ },
+ currentLocale: 'en',
+ featureFlags: {},
+ frequentRpcList: [],
+ identities: {
+ '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539': {
+ address: '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539',
+ name: 'Testy Account'
+ }
+ },
+ ipfsGateway: 'https://cloudflare-ipfs.com/ipfs/',
+ lostIdentities: {},
+ selectedAddress: '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539',
+ tokens: []
+};
+const OLD_ACCOUNTS = {
+ '0x0942890c603273059a11a298F81cb137Be9CF704': { balance: '0x365369025dd23000' },
+ '0x120bfFfa4138fD00A8025a223C350b9ffaDAD8F5': { balance: '0x0' },
+ '0x16C6C3079edE914e83B388a52fFD9255E1c3165': { balance: '0x0' },
+ '0x223367C61c38FAcbdd0b92De5aA7B742e1e5a196': { balance: '0x1bf5ef59d293408b' },
+ '0x7b8C6B8363B9E7A77d279dDad49BEF2994a3bf28': { balance: '0x0' },
+ '0x9236413AfD369B2aeb5e52C048f6B30e7308f2e3': { balance: '0x0' },
+ '0x9b07Ba86631bdb74eE2DDb5750440986DECB9e11': { balance: '0xe8d4a51000' },
+ '0xE4D7f194b07B85511973f1FAAB31b8C2F1f9F344': { balance: '0x0' }
+};
+const NEW_PREFS = {
+ accountTokens: {
+ '0x0942890c603273059a11a298F81cb137Be9CF704': { '0x1': [Array], '0x3': [Array] },
+ '0x120bfFfa4138fD00A8025a223C350b9ffaDAD8F5': { '0x3': [Array] },
+ '0x16C6C3079edE914e83B388a52fFD9255E1c3165': { '0x3': [Array] },
+ '0x223367C61c38FAcbdd0b92De5aA7B742e1e5a196': { '0x1': [Array], '0x3': [Array] },
+ '0x7b8C6B8363B9E7A77d279dDad49BEF2994a3bf28': { '0x3': [Array] },
+ '0x9236413AfD369B2aeb5e52C048f6B30e7308f2e3': { '0x1': [Array], '0x3': [Array] },
+ '0x9b07Ba86631bdb74eE2DDb5750440986DECB9e11': { '0x1': [Array], '0x3': [Array] },
+ '0xE4D7f194b07B85511973f1FAAB31b8C2F1f9F344': { '0x3': [Array] }
+ },
+ currentLocale: 'en',
+ featureFlags: {},
+ frequentRpcList: [],
+ identities: {
+ '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539': {
+ address: '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539',
+ name: 'Account 1'
+ },
+ '0x7f9f9A0e248Ef58298e911219e5B45D610C4B589': {
+ address: '0x7f9f9A0e248Ef58298e911219e5B45D610C4B589',
+ name: 'Account 2'
+ }
+ },
+ ipfsGateway: 'https://cloudflare-ipfs.com/ipfs/',
+ lostIdentities: {},
+ selectedAddress: '0x7f9f9A0e248Ef58298e911219e5B45D610C4B539',
+ tokens: []
+};
+const NEW_ACCOUNTS = {
+ '0x0942890c603273059a11a298F81cb137Be9CF704': { balance: '0x0' },
+ '0x120bfFfa4138fD00A8025a223C350b9ffaDAD8F5': { balance: '0x0' },
+ '0x16C6C3079edE914e83B388a52fFD9255E1c3165': { balance: '0x0' },
+ '0x223367C61c38FAcbdd0b92De5aA7B742e1e5a196': { balance: '0x0' },
+ '0x7b8C6B8363B9E7A77d279dDad49BEF2994a3bf28': { balance: '0x0' },
+ '0x9236413AfD369B2aeb5e52C048f6B30e7308f2e3': { balance: '0x0' },
+ '0x9b07Ba86631bdb74eE2DDb5750440986DECB9e11': { balance: '0x0' },
+ '0xE4D7f194b07B85511973f1FAAB31b8C2F1f9F344': { balance: '0x0' }
+};
+
+describe('Success Sync', () => {
+ it('should succeed sync prefs of varying lengths', async () => {
+ const syncedPrefs = await syncPrefs(OLD_PREFS, NEW_PREFS);
+ expect(Object.values(syncedPrefs.identities)[0]).toEqual(Object.values(syncedPrefs.identities)[0]);
+ expect(Object.values(syncedPrefs.identities)[1]).not.toBeUndefined();
+ expect(Object.values(syncedPrefs.identities).length).not.toEqual(Object.values(OLD_PREFS.identities).length);
+ });
+ it('should succeed sync accounts balances', async () => {
+ const syncedAccounts = await syncAccounts(OLD_ACCOUNTS, NEW_ACCOUNTS);
+ expect(Object.values(syncedAccounts)[0].balance).toEqual(Object.values(OLD_ACCOUNTS)[0].balance);
+ expect(Object.values(syncedAccounts)[3].balance).toEqual(Object.values(OLD_ACCOUNTS)[3].balance);
+ expect(Object.values(syncedAccounts)[6].balance).toEqual(Object.values(OLD_ACCOUNTS)[6].balance);
+ });
+});
+
+describe('Error Syncs', () => {
+ it('should return undefined sync prefs', async () => {
+ expect(await syncPrefs(OLD_PREFS, undefined)).toEqual(undefined);
+ });
+ it('should return new sync prefs', async () => {
+ expect(await syncPrefs(undefined, NEW_PREFS)).toEqual(NEW_PREFS);
+ });
+ it('should return new sync accounts', async () => {
+ expect(await syncAccounts(undefined, NEW_ACCOUNTS)).toEqual(NEW_ACCOUNTS);
+ });
+ it('should return undefined sync accounts', async () => {
+ expect(await syncAccounts(OLD_ACCOUNTS, undefined)).toEqual(undefined);
+ });
+});
diff --git a/app/util/testSetup.js b/app/util/testSetup.js
index 7f2d67781f7..f49f82f2be4 100644
--- a/app/util/testSetup.js
+++ b/app/util/testSetup.js
@@ -1,6 +1,7 @@
import Adapter from 'enzyme-adapter-react-16';
import Enzyme from 'enzyme';
import Engine from '../core/Engine';
+
import NotificationManager from '../core/NotificationManager';
import { NativeModules } from 'react-native';
import mockAsyncStorage from '../../node_modules/@react-native-community/async-storage/jest/async-storage-mock';
@@ -125,3 +126,10 @@ jest.mock('react-native/Libraries/Components/Touchable/TouchableHighlight', () =
jest.mock('react-native/Libraries/Components/TextInput/TextInput', () => 'TextInput');
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
+
+jest.mock('react-native/Libraries/Interaction/InteractionManager', () => ({
+ runAfterInteractions: jest.fn(),
+ createInteractionHandle: jest.fn(),
+ clearInteractionHandle: jest.fn(),
+ setDeadline: jest.fn()
+}));
diff --git a/app/util/transactions.js b/app/util/transactions.js
index 30d5243cc45..42d5aa9815d 100644
--- a/app/util/transactions.js
+++ b/app/util/transactions.js
@@ -5,9 +5,10 @@ import { strings } from '../../locales/i18n';
import contractMap from '@metamask/contract-metadata';
import { safeToChecksumAddress } from './address';
import { util } from '@metamask/controllers';
-import { swapsUtils } from '@estebanmino/controllers';
+import { swapsUtils } from '@metamask/swaps-controller';
import { hexToBN } from './number';
import AppConstants from '../core/AppConstants';
+import { isMainnetByChainId } from './networks';
const { SAI_ADDRESS } = AppConstants;
export const TOKEN_METHOD_TRANSFER = 'transfer';
@@ -41,7 +42,7 @@ export const TRANSACTION_TYPES = {
APPROVE: 'transaction_approve'
};
-const { SWAPS_CONTRACT_ADDRESS } = swapsUtils;
+const { getSwapsContractAddress } = swapsUtils;
/**
* Utility class with the single responsibility
* of caching CollectibleAddresses
@@ -210,13 +211,14 @@ export async function getMethodData(data) {
* Returns wether the given address is a contract
*
* @param {string} address - Ethereum address
+ * @param {string} chainId - Current chainId
* @returns {boolean} - Whether the given address is a contract
*/
-export async function isSmartContractAddress(address) {
+export async function isSmartContractAddress(address, chainId) {
if (!address) return false;
address = toChecksumAddress(address);
// If in contract map we don't need to cache it
- if (contractMap[address]) {
+ if (isMainnetByChainId(chainId) && contractMap[address]) {
return Promise.resolve(true);
}
const { TransactionController } = Engine.context;
@@ -250,12 +252,13 @@ export async function isCollectibleAddress(address, tokenId) {
* Returns corresponding transaction action key
*
* @param {object} transaction - Transaction object
+ * @param {string} chainId - Current chainId
* @returns {string} - Corresponding transaction action key
*/
-export async function getTransactionActionKey(transaction) {
+export async function getTransactionActionKey(transaction, chainId) {
const { transaction: { data, to } = {} } = transaction;
if (!to) return CONTRACT_METHOD_DEPLOY;
- if (to === SWAPS_CONTRACT_ADDRESS) return SWAPS_TRANSACTION_ACTION_KEY;
+ if (to === getSwapsContractAddress(chainId)) return SWAPS_TRANSACTION_ACTION_KEY;
let ret;
// if data in transaction try to get method data
if (data && data !== '0x') {
@@ -282,7 +285,7 @@ export async function getTransactionActionKey(transaction) {
* @param {string} selectedAddress - Current account public address
* @returns {string} - Transaction type message
*/
-export async function getActionKey(tx, selectedAddress, ticker) {
+export async function getActionKey(tx, selectedAddress, ticker, chainId) {
if (tx && tx.isTransfer) {
const selfSent = safeToChecksumAddress(tx.transaction.from) === selectedAddress;
const translationKey = selfSent ? 'transactions.self_sent_unit' : 'transactions.received_unit';
@@ -290,7 +293,7 @@ export async function getActionKey(tx, selectedAddress, ticker) {
if (tx.transferInformation.contractAddress === SAI_ADDRESS.toLowerCase()) tx.transferInformation.symbol = 'SAI';
return strings(translationKey, { unit: tx.transferInformation.symbol });
}
- const actionKey = await getTransactionActionKey(tx);
+ const actionKey = await getTransactionActionKey(tx, chainId);
if (actionKey === SEND_ETHER_ACTION_KEY) {
const incoming = safeToChecksumAddress(tx.transaction.to) === selectedAddress;
const selfSent = incoming && safeToChecksumAddress(tx.transaction.from) === selectedAddress;
@@ -319,10 +322,11 @@ export async function getActionKey(tx, selectedAddress, ticker) {
* Returns corresponding transaction function type
*
* @param {object} tx - Transaction object
+ * @param {string} chainId - Current chainId
* @returns {string} - Transaction function type
*/
-export async function getTransactionReviewActionKey(transaction) {
- const actionKey = await getTransactionActionKey({ transaction });
+export async function getTransactionReviewActionKey(transaction, chainId) {
+ const actionKey = await getTransactionActionKey({ transaction }, chainId);
const transactionReviewActionKey = reviewActionKeys[actionKey];
if (transactionReviewActionKey) {
return transactionReviewActionKey;
@@ -408,6 +412,17 @@ export function validateTransactionActionBalance(transaction, rate, accounts) {
}
}
+/**
+ * Return a boolen if the transaction should be flagged to add the account added label
+ *
+ * @param {object} transaction - Transaction object get time
+ * @param {object} addedAccountTime - Time the account was added to the wallet
+ * @param {object} accountAddedTimeInsertPointFound - Flag to see if the import time was already found
+ */
+export function addAccountTimeFlagFilter(transaction, addedAccountTime, accountAddedTimeInsertPointFound) {
+ return transaction.time <= addedAccountTime && !accountAddedTimeInsertPointFound;
+}
+
export function getNormalizedTxState(state) {
return { ...state.transaction, ...state.transaction.transaction };
}
diff --git a/app/util/validators.js b/app/util/validators.js
index 25195c8e272..1eb1f24207e 100644
--- a/app/util/validators.js
+++ b/app/util/validators.js
@@ -1,4 +1,5 @@
import { ethers } from 'ethers';
+import { confusables } from 'unicode-confusables';
export const failedSeedPhraseRequirements = seed => {
const wordCount = seed.split(/\s/u).length;
@@ -13,3 +14,23 @@ export const parseSeedPhrase = seedPhrase =>
?.join(' ') || '';
export const { isValidMnemonic } = ethers.utils;
+
+export const collectConfusables = ensName => {
+ const key = 'similarTo';
+ const collection = confusables(ensName).reduce(
+ (total, current) => (key in current ? [...total, current.point] : total),
+ []
+ );
+ return collection;
+};
+
+const zeroWidthPoints = new Set([
+ '\u200b', // zero width space
+ '\u200c', // zero width non-joiner
+ '\u200d', // zero width joiner
+ '\ufeff', // zero width no-break space
+ '\u2028', // line separator
+ '\u2029' // paragraph separator,
+]);
+
+export const hasZeroWidthPoints = char => zeroWidthPoints.has(char);
diff --git a/app/util/validators.test.js b/app/util/validators.test.js
index a9f8b7c90e6..0f676b55953 100644
--- a/app/util/validators.test.js
+++ b/app/util/validators.test.js
@@ -1,4 +1,4 @@
-import { failedSeedPhraseRequirements, parseSeedPhrase } from './validators';
+import { failedSeedPhraseRequirements, parseSeedPhrase, hasZeroWidthPoints, collectConfusables } from './validators';
const VALID_24 =
'verb middle giant soon wage common wide tool gentle garlic issue nut retreat until album recall expire bronze bundle live accident expect dry cook';
@@ -37,3 +37,23 @@ describe('parseSeedPhrase', () => {
expect(parseSeedPhrase(` ${String(VALID_12).toUpperCase()}`)).toEqual(VALID_12);
});
});
+
+describe('hasZeroWidthPoints', () => {
+ it('should detect zero-width unicode', () => {
+ expect('vitalik.eth'.split('').some(hasZeroWidthPoints)).toEqual(true);
+ });
+ it('should not detect zero-width unicode', () => {
+ expect('vitalik.eth'.split('').some(hasZeroWidthPoints)).toEqual(false);
+ });
+});
+
+describe('collectConfusables', () => {
+ it('should detect homoglyphic unicode points', () => {
+ expect(collectConfusables('vitalik.eth')).toHaveLength(1);
+ expect(collectConfusables('faceboоk.eth')).toHaveLength(1);
+ });
+
+ it('should detect multiple homoglyphic unicode points', () => {
+ expect(collectConfusables('ѕсоре.eth')).toHaveLength(5);
+ });
+});
diff --git a/index.js b/index.js
index e33a253cd25..f6f0e7e43aa 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,7 @@
import './shim.js';
import 'react-native-gesture-handler';
+import 'react-native-url-polyfill/auto';
import crypto from 'crypto'; // eslint-disable-line import/no-nodejs-modules, no-unused-vars
require('react-native-browser-polyfill'); // eslint-disable-line import/no-commonjs
diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock
index 00472fb18d5..264dfc268e1 100644
--- a/ios/Gemfile.lock
+++ b/ios/Gemfile.lock
@@ -4,22 +4,23 @@ GEM
CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
+ artifactory (3.0.15)
atomos (0.1.3)
- aws-eventstream (1.1.0)
- aws-partitions (1.414.0)
- aws-sdk-core (3.110.0)
+ aws-eventstream (1.1.1)
+ aws-partitions (1.446.0)
+ aws-sdk-core (3.114.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.40.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-kms (1.43.0)
+ aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.87.0)
- aws-sdk-core (~> 3, >= 3.109.0)
+ aws-sdk-s3 (1.93.1)
+ aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.2)
+ aws-sigv4 (1.2.3)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
@@ -28,28 +29,32 @@ GEM
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
- declarative-option (0.1.0)
digest-crc (0.6.3)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
- emoji_regex (3.2.1)
- excon (0.78.1)
- faraday (1.3.0)
+ emoji_regex (3.2.2)
+ excon (0.80.1)
+ faraday (1.4.1)
+ faraday-excon (~> 1.1)
faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
multipart-post (>= 1.2, < 3)
- ruby2_keywords
+ ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
- faraday-net_http (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.1.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
- fastimage (2.2.1)
- fastlane (2.171.0)
+ fastimage (2.2.3)
+ fastlane (2.180.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
+ artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
@@ -70,6 +75,7 @@ GEM
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
@@ -92,20 +98,35 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
- google-cloud-core (1.5.0)
+ google-apis-core (0.3.0)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (~> 0.14)
+ httpclient (>= 2.8.1, < 3.0)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ rexml
+ signet (~> 0.14)
+ webrick
+ google-apis-iamcredentials_v1 (0.3.0)
+ google-apis-core (~> 0.1)
+ google-apis-storage_v1 (0.3.0)
+ google-apis-core (~> 0.1)
+ google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.4.0)
+ google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
- google-cloud-errors (1.0.1)
- google-cloud-storage (1.29.2)
+ google-cloud-errors (1.1.0)
+ google-cloud-storage (1.31.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
- google-api-client (~> 0.33)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
- googleauth (0.14.0)
+ googleauth (0.16.1)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -121,25 +142,26 @@ GEM
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.11.0)
- mini_mime (1.0.2)
+ mini_mime (1.1.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
- naturally (2.2.0)
+ naturally (2.2.1)
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.3)
- representable (3.0.4)
+ representable (3.1.1)
declarative (< 0.1.0)
- declarative-option (< 0.2.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
+ rexml (3.2.5)
rouge (2.0.7)
- ruby2_keywords (0.0.2)
+ ruby2_keywords (0.0.4)
rubyzip (2.3.0)
security (0.1.3)
- signet (0.14.0)
+ signet (0.15.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
@@ -151,6 +173,7 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
@@ -160,6 +183,7 @@ GEM
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
+ webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj
index d610c89a9bf..6e7beb4b1f4 100644
--- a/ios/MetaMask.xcodeproj/project.pbxproj
+++ b/ios/MetaMask.xcodeproj/project.pbxproj
@@ -849,7 +849,7 @@
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 605;
+ CURRENT_PROJECT_VERSION = 613;
DEAD_CODE_STRIPPING = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 48XVW22RCG;
@@ -882,7 +882,7 @@
"\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 2.0.1;
+ MARKETING_VERSION = 2.2.0;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"$(inherited)",
@@ -913,7 +913,7 @@
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 605;
+ CURRENT_PROJECT_VERSION = 613;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 48XVW22RCG;
FRAMEWORK_SEARCH_PATHS = (
@@ -945,7 +945,7 @@
"\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 2.0.1;
+ MARKETING_VERSION = 2.2.0;
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = (
"$(inherited)",
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
index d841e745d97..dc3216bec8a 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -15,6 +15,8 @@
default_platform(:ios)
+ENV["DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS"] = "-t DAV"
+
platform :ios do
desc "Submit a new Beta Build to Testflight"
diff --git a/locales/languages/en.json b/locales/languages/en.json
index c51c0708d17..2067e452115 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -229,7 +229,7 @@
"coming_soon": "Coming soon...",
"wallet": "Wallet",
"transaction_history": "Transaction History",
- "submit_feedback": "Send Feedback",
+ "request_feature": "Request a Feature",
"submit_feedback_message": "Choose the type of feedback to send.",
"submit_bug": "Bug Report",
"submit_general_feedback": "General",
@@ -328,9 +328,10 @@
"description_content_2": "MetaMask will...",
"action_description_1": "Always allow you to opt-out via Settings",
"action_description_2": "Send anonymized click & pageview events",
- "action_description_3": "Never collect keys, addresses, transactions, balances, hashes, or any personal information",
- "action_description_4": "Never collect your IP address",
- "action_description_5": "Never sell data for profit. Ever!"
+ "action_description_3": "Send country, region, city data (not specific location)",
+ "action_description_4": "Never collect keys, addresses, transactions, balances, hashes, or any personal information",
+ "action_description_5": "Never collect your IP address",
+ "action_description_6": "Never sell data for profit. Ever!"
},
"token": {
"token_symbol": "Token Symbol",
@@ -429,6 +430,8 @@
"privacy_mode_desc": "Websites must request access to view your account information.",
"show_hex_data": "Show Hex Data",
"show_hex_data_desc": "Select this to show the hex data field on the send screen.",
+ "show_custom_nonce": "Customize transaction nonce",
+ "custom_nonce_desc": "Turn this on to change the nonce (transaction number) on confirmation screens. This is an advanced feature, use cautiously.",
"accounts_identicon_title": "Account Identicon",
"accounts_identicon_desc": "Jazzicons and Blockies are two different styles of unique icons that help you identify an account at a glance.",
"jazzicons": "Jazzicons",
@@ -590,6 +593,7 @@
"reject": "Reject",
"edit": "Edit",
"cancel": "Cancel",
+ "save": "Save",
"speedup": "Speed up",
"from": "From",
"gas_fee": "Network fee",
@@ -597,6 +601,12 @@
"gas_fee_average": "AVERAGE",
"gas_fee_slow": "SLOW",
"hex_data": "Hex Data",
+ "custom_nonce": "Nonce",
+ "this_is_an_advanced": "This is an advanced feature used to cancel or speed up any pending transactions.",
+ "current_suggested_nonce": "Current suggested nonce:",
+ "edit_transaction_nonce": "Edit transaction nonce",
+ "think_of_the_nonce": "Think of the nonce as the transaction number of an account. Every account's nonce begins with 0 for the first transaction and continues in sequential order.",
+ "nonce_warning": "Warning: You may encounter issues with future transactions if you continue. Use with caution.",
"review_details": "DETAILS",
"review_data": "DATA",
"data": "Data",
@@ -660,7 +670,9 @@
"tokenContractAddressWarning_2": "token contract address",
"tokenContractAddressWarning_3": ". If you send tokens to this address, you will lose them.",
"smartContractAddressWarning": "This address is a smart contract address. Please make sure you understand what this address is for, otherwise you risk losing your funds.",
- "continueError": "I understand the risks, continue"
+ "continueError": "I understand the risks, continue",
+ "confusable_title": "Check the recipient address",
+ "confusable_msg": "We have detected a confusable character in the ENS name. Check the ENS name to avoid a potential scam."
},
"custom_gas": {
"total": "Total",
@@ -812,7 +824,11 @@
"address_from_balance": "Balance:",
"status": "Status",
"date": "Date",
- "nonce": "Nonce"
+ "nonce": "Nonce",
+ "from_device_label": "from this device",
+ "import_wallet_row": "Account added to this device",
+ "import_wallet_label": "Account Added",
+ "import_wallet_tip": "All future transactions made from this device will include a label \"from this device\" next to the timestamp. For transactions dated before adding the account, this history will not indicate which outgoing transactions originated from this device."
},
"address_book": {
"recents": "Recents",
@@ -1247,8 +1263,9 @@
},
"offline_mode": {
"title": "You're offline",
- "text": "Check your internet connection and try again",
- "try_again": "Try again"
+ "text": "Unable to connect to the blockchain host.",
+ "try_again": "Try again",
+ "learn_more": "Learn more"
},
"walletconnect_return_modal": {
"title": "You're all set!",
@@ -1354,6 +1371,8 @@
"verify_on": "Always verify the token address on",
"verify_address_on": "Verify token address on",
"only_verified_on": "{{symbol}} is only verified on {{occurances}} source.",
+ "block_explorer": "block explorer",
+ "a_block_explorer": "a block explorer",
"token_verification": "Token verification",
"token_multiple": "Multiple tokens can use the same name and symbol.",
"token_check": "Check",
diff --git a/locales/languages/es-OLD.json b/locales/languages/es-OLD.json
index 82cff406943..f4d0d25e9ae 100644
--- a/locales/languages/es-OLD.json
+++ b/locales/languages/es-OLD.json
@@ -1088,7 +1088,7 @@
},
"offline_mode": {
"title": "Sin Conexión",
- "text": "Revisa tu conexión de internet e intenta nuevamente",
+ "text": "No se puede conectar al host de blockchain.",
"try_again": "Intentar de nuevo"
},
"walletconnect_return_modal": {
diff --git a/locales/languages/es.json b/locales/languages/es.json
index f8c08c1d07a..d9e9c954ae1 100644
--- a/locales/languages/es.json
+++ b/locales/languages/es.json
@@ -314,9 +314,10 @@
"description_content_2": "MetaMask…",
"action_description_1": "Siempre le permitirá optar por no participar a través de Configuración",
"action_description_2": "Enviará eventos de vistas de página y clics anónimos",
- "action_description_3": "Nunca recopilará claves, direcciones, transacciones, saldos, hashes o cualquier otra información personal",
- "action_description_4": "Nunca recopilará su dirección IP",
- "action_description_5": "Nunca venderá datos con afán de lucro. ¡Jamás!"
+ "action_description_3": "Enviar país, región, ciudad (no ubicación específica)",
+ "action_description_4": "Nunca recopilará claves, direcciones, transacciones, saldos, hashes o cualquier otra información personal",
+ "action_description_5": "Nunca recopilará su dirección IP",
+ "action_description_6": "Nunca venderá datos con afán de lucro. ¡Jamás!"
},
"token": {
"token_symbol": "Símbolo del token",
@@ -1291,7 +1292,7 @@
},
"offline_mode": {
"title": "Está desconectado",
- "text": "Compruebe la conexión a Internet y vuelva a intentarlo",
+ "text": "No se puede conectar al host de blockchain.",
"try_again": "Vuelva a intentarlo"
},
"payment_channel_request": {
diff --git a/locales/languages/hi-in.json b/locales/languages/hi-in.json
index afb8c033072..f1ea43747bd 100644
--- a/locales/languages/hi-in.json
+++ b/locales/languages/hi-in.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask निम्न चीज़ें करेगा...",
"action_description_1": "हमेशा आपको सेटिंग्स के माध्यम से ऑप्ट-आउट करने की अनुमति देगा",
"action_description_2": "बेनाम क्लिक और पेजव्यू ईवेंट भेजेगा",
- "action_description_3": "कुंजी, पते, लेनदेन, शेषराशि, हैश या कोई भी व्यक्तिगत जानकारी कभी एकत्र नहीं करेगा",
- "action_description_4": "आपका IP पता एकत्र नहीं करेगा",
- "action_description_5": "लाभ के लिए डेटा कभी नहीं बेचेगा। हमेशा!"
+ "action_description_4": "कुंजी, पते, लेनदेन, शेषराशि, हैश या कोई भी व्यक्तिगत जानकारी कभी एकत्र नहीं करेगा",
+ "action_description_5": "आपका IP पता एकत्र नहीं करेगा",
+ "action_description_6": "लाभ के लिए डेटा कभी नहीं बेचेगा। हमेशा!"
},
"token": {
"token_symbol": "टोकन का प्रतीक",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "आप ऑफ़लाइन हैं",
- "text": "अपना इंटरनेट कनेक्शन जाँचें और पुनः प्रयास करें",
+ "text": "ब्लॉकचैन होस्ट से कनेक्ट करने में असमर्थ।",
"try_again": "पुनः प्रयास करें"
},
"payment_channel_request": {
diff --git a/locales/languages/id-id.json b/locales/languages/id-id.json
index 202b22b59db..bd98a57fc64 100644
--- a/locales/languages/id-id.json
+++ b/locales/languages/id-id.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask akan...",
"action_description_1": "Selalu izinkan Anda untuk menyisih melalui Pengaturan",
"action_description_2": "Kirim kejadian pageview & klik anonim",
- "action_description_3": "Jangan mengumpulkan kunci, alamat, transaksi, saldo, hash, atau informasi pribadi lainnya",
- "action_description_4": "Jangan mengumpulkan alamat IP Anda",
- "action_description_5": "Jangan menjual data untuk mendapatkan keuntungan. Selamanya!"
+ "action_description_4": "Jangan mengumpulkan kunci, alamat, transaksi, saldo, hash, atau informasi pribadi lainnya",
+ "action_description_5": "Jangan mengumpulkan alamat IP Anda",
+ "action_description_6": "Jangan menjual data untuk mendapatkan keuntungan. Selamanya!"
},
"token": {
"token_symbol": "Simbol Token",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "Anda sedang offline",
- "text": "Periksa koneksi internet Anda dan coba lagi",
+ "text": "Tidak dapat terhubung ke host blockchain.",
"try_again": "Coba lagi"
},
"payment_channel_request": {
diff --git a/locales/languages/ja-jp.json b/locales/languages/ja-jp.json
index 6cb7b5c2e8e..8bade3dfb1f 100644
--- a/locales/languages/ja-jp.json
+++ b/locales/languages/ja-jp.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask が実行する内容...",
"action_description_1": "お客様がいつでも設定からオプトアウトできるようにします",
"action_description_2": "匿名化されたクリック イベントとページビュー イベントを送信します",
- "action_description_3": "キー、アドレス、トランザクション、残高、ハッシュなど、いかなる個人情報も収集しません",
- "action_description_4": "お客様の IP アドレスを収集することはありません",
- "action_description_5": "営利目的でデータを販売することは決してありません。"
+ "action_description_4": "キー、アドレス、トランザクション、残高、ハッシュなど、いかなる個人情報も収集しません",
+ "action_description_5": "お客様の IP アドレスを収集することはありません",
+ "action_description_6": "営利目的でデータを販売することは決してありません。"
},
"token": {
"token_symbol": "トークン シンボル",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "オフラインです",
- "text": "インターネット接続を確認して、もう一度実行してください",
+ "text": "ブロックチェーンホストに接続できません。",
"try_again": "再試行"
},
"payment_channel_request": {
diff --git a/locales/languages/ko-kr.json b/locales/languages/ko-kr.json
index 5450959fe90..4ef4f20c992 100644
--- a/locales/languages/ko-kr.json
+++ b/locales/languages/ko-kr.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask에서는...",
"action_description_1": "언제든 설정을 통해 옵트아웃이 가능합니다.",
"action_description_2": "익명화된 클릭 및 페이지뷰 이벤트를 보냅니다.",
- "action_description_3": "키, 주소, 거래, 잔액, 해시 또는 개인 정보는 절대 수집하지 않습니다.",
- "action_description_4": "IP 주소를 수집하지 않습니다.",
- "action_description_5": "수익을 위해 데이터를 판매하지 않습니다. 절대!"
+ "action_description_4": "키, 주소, 거래, 잔액, 해시 또는 개인 정보는 절대 수집하지 않습니다.",
+ "action_description_5": "IP 주소를 수집하지 않습니다.",
+ "action_description_6": "수익을 위해 데이터를 판매하지 않습니다. 절대!"
},
"token": {
"token_symbol": "토큰 기호",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "오프라인 상태입니다.",
- "text": "인터넷 연결을 확인하고 다시 시도하세요.",
+ "text": "블록 체인 호스트에 연결할 수 없습니다.",
"try_again": "다시 시도"
},
"payment_channel_request": {
diff --git a/locales/languages/pt-br.json b/locales/languages/pt-br.json
index 94d4a9ba48f..f47d2e8f816 100644
--- a/locales/languages/pt-br.json
+++ b/locales/languages/pt-br.json
@@ -313,9 +313,9 @@
"description_content_2": "O MetaMask...",
"action_description_1": "Sempre permitirá que você cancele o envio dos dados, via Configurações",
"action_description_2": "Enviará eventos anonimizados de cliques e visualização de página",
- "action_description_3": "Jamais coletará chaves, endereços, transações, saldos, hashes ou qualquer outra informação pessoal",
- "action_description_4": "Jamais coletará seu endereço IP",
- "action_description_5": "Nunca venderá dados em troca de lucro. Jamais!"
+ "action_description_4": "Jamais coletará chaves, endereços, transações, saldos, hashes ou qualquer outra informação pessoal",
+ "action_description_5": "Jamais coletará seu endereço IP",
+ "action_description_6": "Nunca venderá dados em troca de lucro. Jamais!"
},
"token": {
"token_symbol": "Símbolo do token",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "Você está offline",
- "text": "Verifique a conexão com a internet e tente novamente",
+ "text": "Não foi possível conectar ao host blockchain.",
"try_again": "Tente novamente"
},
"payment_channel_request": {
diff --git a/locales/languages/ru-ru.json b/locales/languages/ru-ru.json
index abbaf14093b..15144420bfb 100644
--- a/locales/languages/ru-ru.json
+++ b/locales/languages/ru-ru.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask будет...",
"action_description_1": "Всегда разрешать вам отказаться через настройки",
"action_description_2": "Отправлять анонимизированные события кликов и просмотров страниц",
- "action_description_3": "Никогда не хранить ключи, адреса, транзакции, балансы, хэши или любую персональную информацию",
- "action_description_4": "Никогда не сохранять ваш IP-адрес",
- "action_description_5": "Никогда не продавать данные для прибыли. Никогда!"
+ "action_description_4": "Никогда не хранить ключи, адреса, транзакции, балансы, хэши или любую персональную информацию",
+ "action_description_5": "Никогда не сохранять ваш IP-адрес",
+ "action_description_6": "Никогда не продавать данные для прибыли. Никогда!"
},
"token": {
"token_symbol": "Символ токена",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "Вы не в сети",
- "text": "Проверьте подключение к интернету и попробуйте еще раз",
+ "text": "Невозможно подключиться к хосту блокчейна.",
"try_again": "Попробуйте еще раз"
},
"payment_channel_request": {
diff --git a/locales/languages/tl.json b/locales/languages/tl.json
index 64987e9058d..83704061166 100644
--- a/locales/languages/tl.json
+++ b/locales/languages/tl.json
@@ -313,9 +313,9 @@
"description_content_2": "Gagawin ng MetaMask ang sumusunod...",
"action_description_1": "Palagi kang papayagang mag-opt out sa pamamagitan ng Mga Setting",
"action_description_2": "Magpapadala ng mga anonymous na kaganapang pag-click at pagtingin sa page",
- "action_description_3": "Huwag kailanman mangolekta ng mga key, address, transaksyon, balanse, hash, o anumang personal na impormasyon",
- "action_description_4": "Huwag kailanman kolektahin ang iyong IP address",
- "action_description_5": "Huwag kailanman magbenta ng data para pagkakitaan. Kahit kailan!"
+ "action_description_4": "Huwag kailanman mangolekta ng mga key, address, transaksyon, balanse, hash, o anumang personal na impormasyon",
+ "action_description_5": "Huwag kailanman kolektahin ang iyong IP address",
+ "action_description_6": "Huwag kailanman magbenta ng data para pagkakitaan. Kahit kailan!"
},
"token": {
"token_symbol": "Simbolo ng Token",
diff --git a/locales/languages/vi-vn.json b/locales/languages/vi-vn.json
index d5986aeeaa4..22bf24e6d65 100644
--- a/locales/languages/vi-vn.json
+++ b/locales/languages/vi-vn.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask sẽ...",
"action_description_1": "Luôn cho phép bạn chọn không tham gia thông qua phần Cài đặt",
"action_description_2": "Gửi các lượt nhấp và xem trang đã được ẩn danh",
- "action_description_3": "Không bao giờ thu thập mã khóa, địa chỉ, giao dịch, số dư, mã băm hoặc bất kỳ thông tin cá nhân nào",
- "action_description_4": "Không bao giờ thu thập địa chỉ IP của bạn",
- "action_description_5": "Không bao giờ bán dữ liệu để thu lợi. Tuyệt đối không bao giờ!"
+ "action_description_4": "Không bao giờ thu thập mã khóa, địa chỉ, giao dịch, số dư, mã băm hoặc bất kỳ thông tin cá nhân nào",
+ "action_description_5": "Không bao giờ thu thập địa chỉ IP của bạn",
+ "action_description_6": "Không bao giờ bán dữ liệu để thu lợi. Tuyệt đối không bao giờ!"
},
"token": {
"token_symbol": "Ký hiệu token",
@@ -1288,7 +1288,7 @@
},
"offline_mode": {
"title": "Bạn đang không kết nối mạng",
- "text": "Hãy kiểm tra kết nối internet của bạn và thử lại",
+ "text": "Không thể kết nối với máy chủ lưu trữ chuỗi khối.",
"try_again": "Thử lại"
},
"payment_channel_request": {
diff --git a/locales/languages/zh-cn.json b/locales/languages/zh-cn.json
index 09dc8d5b315..99fb869be60 100644
--- a/locales/languages/zh-cn.json
+++ b/locales/languages/zh-cn.json
@@ -313,9 +313,9 @@
"description_content_2": "MetaMask...",
"action_description_1": "始终允许您通过“设置”选择退出",
"action_description_2": "发送匿名化点击和页面浏览事件",
- "action_description_3": "决不收集密钥、地址、交易、余额、哈希或任何个人信息",
- "action_description_4": "决不收集您的 IP 地址",
- "action_description_5": "决不出售数据牟利。绝对不会!"
+ "action_description_4": "决不收集密钥、地址、交易、余额、哈希或任何个人信息",
+ "action_description_5": "决不收集您的 IP 地址",
+ "action_description_6": "决不出售数据牟利。绝对不会!"
},
"token": {
"token_symbol": "代币符号",
diff --git a/package.json b/package.json
index 655c9a26b13..96ed4b44b85 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "metamask",
- "version": "2.0.1",
+ "version": "2.2.0",
"private": true,
"scripts": {
"watch": "./scripts/build.sh watcher watch",
@@ -22,6 +22,8 @@
"build:announce": "node ./scripts/metamask-bot-build-announce.js",
"build:android:release": "./scripts/build.sh android release",
"build:android:release:e2e": "./scripts/build.sh android releaseE2E",
+ "build:android:checksum": "./scripts/checksum.sh",
+ "build:android:checksum:verify": "shasum -a 512 -c sha512sums.txt",
"build:android:pre-release": "./scripts/build.sh android release --pre",
"build:android:pre-release:bundle": "GENERATE_BUNDLE=true ./scripts/build.sh android release --pre",
"build:ios:release": "./scripts/build.sh ios release",
@@ -71,17 +73,18 @@
"react-native-level-fs/**/semver": "^4.3.2"
},
"dependencies": {
- "@estebanmino/controllers": "^3.3.17",
"@exodus/react-native-payments": "https://github.com/wachunei/react-native-payments.git#package-json-hack",
"@metamask/contract-metadata": "^1.23.0",
- "@metamask/controllers": "^6.1.1",
+ "@metamask/controllers": "^8.0.0",
+ "@metamask/etherscan-link": "^2.0.0",
+ "@metamask/swaps-controller": "^2.0.1",
"@react-native-community/async-storage": "1.12.1",
"@react-native-community/blur": "^3.6.0",
"@react-native-community/checkbox": "^0.4.2",
"@react-native-community/clipboard": "^1.2.2",
"@react-native-community/cookies": "^4.0.1",
"@react-native-community/masked-view": "^0.1.10",
- "@react-native-community/netinfo": "4.1.5",
+ "@react-native-community/netinfo": "6.0.0",
"@react-native-community/viewpager": "^3.3.0",
"@rnhooks/keyboard": "^0.0.3",
"@sentry/integrations": "5.13.0",
@@ -117,7 +120,7 @@
"https-browserify": "0.0.1",
"is-url": "^1.2.4",
"json-rpc-engine": "^6.1.0",
- "json-rpc-middleware-stream": "2.1.1",
+ "json-rpc-middleware-stream": "3.0.0",
"lottie-react-native": "git+https://github.com/MetaMask/lottie-react-native.git#7ce6a78ac4ac7b9891bc513cb3f12f8b9c9d9106",
"multihashes": "0.4.14",
"number-to-bn": "1.7.0",
@@ -170,12 +173,13 @@
"react-native-scrollable-tab-view": "^1.0.0",
"react-native-search-api": "ombori/react-native-search-api#8/head",
"react-native-sensors": "5.3.0",
- "react-native-share": "^3.2.0",
+ "react-native-share": "^5.2.2",
"react-native-splash-screen": "git+https://github.com/MetaMask/react-native-splash-screen.git",
"react-native-step-indicator": "^1.0.3",
"react-native-svg": "12.1.0",
"react-native-swipe-gestures": "1.0.3",
"react-native-tcp": "aprock/react-native-tcp#11/head",
+ "react-native-url-polyfill": "^1.3.0",
"react-native-v8": "^0.62.2-patch.1",
"react-native-vector-icons": "6.4.2",
"react-native-view-shot": "^3.1.2",
@@ -194,10 +198,11 @@
"rn-fetch-blob": "^0.12.0",
"stream-browserify": "1.0.0",
"through2": "3.0.1",
+ "unicode-confusables": "^0.1.1",
"url": "0.11.0",
"url-parse": "1.4.4",
"valid-url": "1.0.9",
- "vm-browserify": "0.0.4",
+ "vm-browserify": "1.1.2",
"web3-provider-engine": "^16.0.1",
"zxcvbn": "4.4.2"
},
@@ -208,22 +213,22 @@
"@react-native-community/eslint-config": "^1.1.0",
"assert": "1.4.1",
"babel-core": "7.0.0-bridge.0",
- "babel-eslint": "10.0.3",
+ "babel-eslint": "10.1.0",
"babel-jest": "^26.6.3",
"concat-cli": "4.0.0",
"detox": "17.3.1",
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.10.0",
"enzyme-to-json": "3.3.5",
- "eslint": "^6.5.1",
+ "eslint": "^7.14.0",
"eslint-config-react-native": "4.0.0",
"eslint-plugin-import": "2.18.2",
- "eslint-plugin-prettier": "^3.3.0",
+ "eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "7.16.0",
"eslint-plugin-react-native": "3.7.0",
"husky": "1.3.1",
"jest": "^25.2.7",
- "jest-serializer": "24.4.0",
+ "jest-serializer": "26.6.2",
"jetifier": "^1.6.6",
"lint-staged": "10.5.4",
"metro": "^0.59.0",
@@ -330,7 +335,7 @@
"fs": "react-native-level-fs"
},
"engines": {
- "node": "^10.17.0",
+ "node": "^14.0.0",
"yarn": "^1.22.0"
},
"rnpm": {
diff --git a/patches/unicode-confusables+0.1.1.patch b/patches/unicode-confusables+0.1.1.patch
new file mode 100644
index 00000000000..9b90e6b4c18
--- /dev/null
+++ b/patches/unicode-confusables+0.1.1.patch
@@ -0,0 +1,15 @@
+diff --git a/node_modules/unicode-confusables/data/confusables.json b/node_modules/unicode-confusables/data/confusables.json
+index 855e49c..b0b8a0b 100644
+--- a/node_modules/unicode-confusables/data/confusables.json
++++ b/node_modules/unicode-confusables/data/confusables.json
+@@ -157,8 +157,8 @@
+ "໊": "๊",
+ "໋": "๋",
+ "꙯": "⃩",
+- "
": " ",
+- "
": " ",
++ "\u2028": " ",
++ "\u2029": " ",
+ " ": " ",
+ " ": " ",
+ " ": " ",
diff --git a/scripts/build.sh b/scripts/build.sh
index 371c9d243b3..67797024d0e 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -235,6 +235,8 @@ buildAndroidRelease(){
if [ "$PRE_RELEASE" = true ] ; then
# Generate sourcemaps
yarn sourcemaps:android
+ # Generate checksum
+ yarn build:android:checksum
fi
if [ "$PRE_RELEASE" = false ] ; then
diff --git a/scripts/checksum.sh b/scripts/checksum.sh
new file mode 100755
index 00000000000..e5d27b93510
--- /dev/null
+++ b/scripts/checksum.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+FILE=./android/app/build/outputs/apk/release/app-release.apk
+
+if test -f "$FILE"; then
+ shasum -a 512 "$FILE" > ./android/app/build/outputs/apk/release/sha512sums.txt
+fi;
diff --git a/yarn.lock b/yarn.lock
index abe97353c8d..582ba06b42b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,14 +2,7 @@
# yarn lockfile v1
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
- integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
- dependencies:
- "@babel/highlight" "^7.10.1"
-
-"@babel/code-frame@^7.10.4":
+"@babel/code-frame@7.12.11", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.10.4":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
@@ -327,11 +320,6 @@
dependencies:
"@babel/types" "^7.12.11"
-"@babel/helper-validator-identifier@^7.10.1":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
- integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
-
"@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
@@ -365,15 +353,6 @@
"@babel/traverse" "^7.12.5"
"@babel/types" "^7.12.5"
-"@babel/highlight@^7.10.1":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
- integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
- dependencies:
- "@babel/helper-validator-identifier" "^7.10.1"
- chalk "^2.0.0"
- js-tokens "^4.0.0"
-
"@babel/highlight@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
@@ -383,12 +362,7 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.1", "@babel/parser@^7.7.0":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.1.tgz#2e142c27ca58aa2c7b119d09269b702c8bbad28c"
- integrity sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==
-
-"@babel/parser@^7.12.10", "@babel/parser@^7.12.7":
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.1", "@babel/parser@^7.12.10", "@babel/parser@^7.12.7", "@babel/parser@^7.7.0":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79"
integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==
@@ -853,22 +827,7 @@
"@babel/parser" "^7.12.7"
"@babel/types" "^7.12.7"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.7.0":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27"
- integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==
- dependencies:
- "@babel/code-frame" "^7.10.1"
- "@babel/generator" "^7.10.1"
- "@babel/helper-function-name" "^7.10.1"
- "@babel/helper-split-export-declaration" "^7.10.1"
- "@babel/parser" "^7.10.1"
- "@babel/types" "^7.10.1"
- debug "^4.1.0"
- globals "^11.1.0"
- lodash "^4.17.13"
-
-"@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5":
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5", "@babel/traverse@^7.7.0":
version "7.12.10"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.10.tgz#2d1f4041e8bf42ea099e5b2dc48d6a594c00017a"
integrity sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==
@@ -883,16 +842,7 @@
globals "^11.1.0"
lodash "^4.17.19"
-"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
- version "7.10.1"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.1.tgz#6886724d31c8022160a7db895e6731ca33483921"
- integrity sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==
- dependencies:
- "@babel/helper-validator-identifier" "^7.10.1"
- lodash "^4.17.13"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.5", "@babel/types@^7.12.7":
+"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.11.tgz#a86e4d71e30a9b6ee102590446c98662589283ce"
integrity sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==
@@ -921,34 +871,20 @@
dependencies:
"@types/hammerjs" "^2.0.36"
-"@estebanmino/controllers@^3.3.17":
- version "3.3.17"
- resolved "https://registry.yarnpkg.com/@estebanmino/controllers/-/controllers-3.3.17.tgz#22f06daf2b5a004bcf40a12f905699263276159f"
- integrity sha512-tmONppQxqLOW7uZSSk9gUAiNokU1tW2LezPggjZjzOp9CTBIc3cgr28o07hss7DF4+8IX6XOEtAjfeVgUUCQ2Q==
+"@eslint/eslintrc@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547"
+ integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==
dependencies:
- "@metamask/contract-metadata" "^1.22.0"
- abort-controller "^3.0.0"
- async-mutex "^0.3.1"
- bignumber.js "^9.0.1"
- eth-ens-namehash "^2.0.8"
- eth-json-rpc-infura "^5.1.0"
- eth-keyring-controller "^6.1.0"
- eth-method-registry "1.1.0"
- eth-phishing-detect "^1.1.13"
- eth-query "^2.1.2"
- eth-rpc-errors "^4.0.0"
- eth-sig-util "^3.0.0"
- ethereumjs-util "^6.1.0"
- ethereumjs-wallet "^1.0.1"
- human-standard-collectible-abi "^1.0.2"
- human-standard-token-abi "^2.0.0"
- isomorphic-fetch "^3.0.0"
- jsonschema "^1.2.4"
- nanoid "^3.1.12"
- single-call-balance-checker-abi "^1.0.0"
- uuid "^8.3.2"
- web3 "^0.20.7"
- web3-provider-engine "^16.0.1"
+ ajv "^6.12.4"
+ debug "^4.1.1"
+ espree "^7.3.0"
+ globals "^12.1.0"
+ ignore "^4.0.6"
+ import-fresh "^3.2.1"
+ js-yaml "^3.13.1"
+ minimatch "^3.0.4"
+ strip-json-comments "^3.1.1"
"@ethersproject/abi@^5.0.5":
version "5.0.5"
@@ -1598,22 +1534,23 @@
dependencies:
"@json-rpc-tools/types" "^1.5.7"
-"@metamask/contract-metadata@^1.22.0":
- version "1.22.0"
- resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.22.0.tgz#55cc84756c703c433176b484b1d34f0e03d16d1e"
- integrity sha512-t4ijbU+4OH9UAlrPkfLPFo6KmkRTRZJHB+Vly4ajF8oZMnota5YjVVl/SmltsoRC9xvJtRn9DUVf3YMHMIdofw==
-
-"@metamask/contract-metadata@^1.23.0":
+"@metamask/contract-metadata@^1.22.0", "@metamask/contract-metadata@^1.23.0":
version "1.23.0"
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.23.0.tgz#c70be7f3eaeeb791651ce793b7cdc230e9780b18"
integrity sha512-oTUqL9dtXtbng60DZMRsBmZ5HiOUUfEsZjuswOJ0yHO24YsW0ktCcgCJVYPv1HcOsF0SVrRtG4rtrvOl4nY+HA==
-"@metamask/controllers@^6.1.1":
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-6.1.1.tgz#9ce6b2d58e7205ad7e5bf30449f84385c6bed4bd"
- integrity sha512-RZ4YRT34+uV2dk1pITKc7+F4zvd5jJO3y3U1xP16C/ATu1rSw57EM4pOlJmERTbRZ2ImP8wFZm9e4wpbpQuUQg==
+"@metamask/contract-metadata@^1.24.0":
+ version "1.25.0"
+ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.25.0.tgz#442ace91fb40165310764b68d8096d0017bb0492"
+ integrity sha512-yhmYB9CQPv0dckNcPoWDcgtrdUp0OgK0uvkRE5QIBv4b3qENI1/03BztvK2ijbTuMlORUpjPq7/1MQDUPoRPVw==
+
+"@metamask/controllers@^8.0.0":
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-8.0.0.tgz#42ac5aaef67a03d3fe599a67a36597e01902ca8d"
+ integrity sha512-TrteMifsCxV1g3WHcSD1X98fF4hKep3sXZNGfrvkPqa8mrF03hJke21WBSTRtvJ3vkNLRWgi+5I6lVXFTzbYuQ==
dependencies:
- "@metamask/contract-metadata" "^1.23.0"
+ "@metamask/contract-metadata" "^1.24.0"
+ "@types/uuid" "^8.3.0"
async-mutex "^0.2.6"
babel-runtime "^6.26.0"
eth-ens-namehash "^2.0.8"
@@ -1624,8 +1561,10 @@
eth-query "^2.1.2"
eth-rpc-errors "^4.0.0"
eth-sig-util "^3.0.0"
+ ethereumjs-tx "^1.3.7"
ethereumjs-util "^6.1.0"
ethereumjs-wallet "^1.0.1"
+ ethjs-util "^0.1.6"
human-standard-collectible-abi "^1.0.2"
human-standard-token-abi "^2.0.0"
immer "^8.0.1"
@@ -1637,6 +1576,11 @@
web3 "^0.20.7"
web3-provider-engine "^16.0.1"
+"@metamask/etherscan-link@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/etherscan-link/-/etherscan-link-2.0.0.tgz#89035736515a39532ba1142d87b9a8c2b4f920f1"
+ integrity sha512-/YS32hS2UTTxs0KyUmAgaDj1w4dzAvOrT+p4TJtpICeH3E/k51r2FO0Or7WJJI/mpzTqNKgcH5yyS2oCtupGiA==
+
"@metamask/mobile-provider@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@metamask/mobile-provider/-/mobile-provider-2.0.1.tgz#892f883deafe49200a3ae57d85237016ded63c12"
@@ -1647,6 +1591,35 @@
resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c"
integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==
+"@metamask/swaps-controller@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-2.0.1.tgz#b75aad7ec4c6a3d97d2d869564035561d13b0938"
+ integrity sha512-LEyNpQVF/0++tWu0yRJn2FJ+0ADmvnaNdkjUQef9rlOoFUCsN4OMFYivYnA5pe//VM0SCWXxMUP5gY+oYdjIVg==
+ dependencies:
+ "@metamask/contract-metadata" "^1.22.0"
+ abort-controller "^3.0.0"
+ async-mutex "^0.3.1"
+ bignumber.js "^9.0.1"
+ eth-ens-namehash "^2.0.8"
+ eth-json-rpc-infura "^5.1.0"
+ eth-keyring-controller "^6.1.0"
+ eth-method-registry "1.1.0"
+ eth-phishing-detect "^1.1.13"
+ eth-query "^2.1.2"
+ eth-rpc-errors "^4.0.0"
+ eth-sig-util "^3.0.0"
+ ethereumjs-util "^6.1.0"
+ ethereumjs-wallet "^1.0.1"
+ human-standard-collectible-abi "^1.0.2"
+ human-standard-token-abi "^2.0.0"
+ isomorphic-fetch "^3.0.0"
+ jsonschema "^1.2.4"
+ nanoid "^3.1.12"
+ single-call-balance-checker-abi "^1.0.0"
+ uuid "^8.3.2"
+ web3 "^0.20.7"
+ web3-provider-engine "^16.0.1"
+
"@pedrouid/iso-crypto@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@pedrouid/iso-crypto/-/iso-crypto-1.0.0.tgz#cf06b40ef3da3d7ca7363bd7a521ed59fa2fd13d"
@@ -1837,10 +1810,10 @@
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401"
integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ==
-"@react-native-community/netinfo@4.1.5":
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-4.1.5.tgz#4bb44842db6a1a18f00a0f061b0e3dcc638f67dd"
- integrity sha512-lagdZr9UiVAccNXYfTEj+aUcPCx9ykbMe9puffeIyF3JsRuMmlu3BjHYx1klUHX7wNRmFNC8qVP0puxUt1sZ0A==
+"@react-native-community/netinfo@6.0.0":
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-6.0.0.tgz#2a4d7190b508dd0c2293656c9c1aa068f6f60a71"
+ integrity sha512-Z9M8VGcF2IZVOo2x+oUStvpCW/8HjIRi4+iQCu5n+PhC7OqCQX58KYAzdBr///alIfRXiu6oMb+lK+rXQH1FvQ==
"@react-native-community/viewpager@^2.0.1":
version "2.0.2"
@@ -2145,6 +2118,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/uuid@^8.3.0":
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
+ integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==
+
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@@ -2340,7 +2318,7 @@ acorn-jsx@^5.0.0:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
-acorn-jsx@^5.2.0:
+acorn-jsx@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
@@ -2360,7 +2338,7 @@ acorn@^7.1.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe"
integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==
-acorn@^7.1.1:
+acorn@^7.4.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
@@ -2437,6 +2415,26 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ajv@^8.0.1:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.1.0.tgz#45d5d3d36c7cdd808930cc3e603cf6200dbeb736"
+ integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
anser@^1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
@@ -2821,18 +2819,6 @@ babel-core@7.0.0-bridge.0:
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-babel-eslint@10.0.3:
- version "10.0.3"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
- integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.0.0"
- "@babel/traverse" "^7.0.0"
- "@babel/types" "^7.0.0"
- eslint-visitor-keys "^1.0.0"
- resolve "^1.12.0"
-
babel-eslint@10.1.0, babel-eslint@^10.0.1:
version "10.1.0"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
@@ -3671,11 +3657,6 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
-cli-width@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
- integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
-
cliui@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
@@ -4080,7 +4061,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.0:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -4284,7 +4265,7 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-deep-is@~0.1.3:
+deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
@@ -4638,7 +4619,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
-enquirer@^2.3.6:
+enquirer@^2.3.5, enquirer@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
@@ -4942,10 +4923,10 @@ eslint-plugin-prettier@3.1.2:
dependencies:
prettier-linter-helpers "^1.0.0"
-eslint-plugin-prettier@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.0.tgz#61e295349a65688ffac0b7808ef0a8244bdd8d40"
- integrity sha512-tMTwO8iUWlSRZIwS9k7/E4vrTsfvsrcM5p1eftyuqWH25nKsz/o6/54I7jwQ/3zobISyC7wMy9ZsFwgTxOcOpQ==
+eslint-plugin-prettier@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz#7079cfa2497078905011e6f82e8dd8453d1371b7"
+ integrity sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==
dependencies:
prettier-linter-helpers "^1.0.0"
@@ -5044,14 +5025,22 @@ eslint-scope@^5.0.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-utils@^1.3.1, eslint-utils@^1.4.3:
+eslint-scope@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+eslint-utils@^1.3.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
dependencies:
eslint-visitor-keys "^1.1.0"
-eslint-utils@^2.0.0:
+eslint-utils@^2.0.0, eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
@@ -5063,6 +5052,16 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
+eslint-visitor-keys@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
+ integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+
+eslint-visitor-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+ integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
eslint@^5.6.0:
version "5.16.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
@@ -5105,46 +5104,46 @@ eslint@^5.6.0:
table "^5.2.3"
text-table "^0.2.0"
-eslint@^6.5.1:
- version "6.8.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
- integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
+eslint@^7.14.0:
+ version "7.24.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.24.0.tgz#2e44fa62d93892bfdb100521f17345ba54b8513a"
+ integrity sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ==
dependencies:
- "@babel/code-frame" "^7.0.0"
+ "@babel/code-frame" "7.12.11"
+ "@eslint/eslintrc" "^0.4.0"
ajv "^6.10.0"
- chalk "^2.1.0"
- cross-spawn "^6.0.5"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
debug "^4.0.1"
doctrine "^3.0.0"
- eslint-scope "^5.0.0"
- eslint-utils "^1.4.3"
- eslint-visitor-keys "^1.1.0"
- espree "^6.1.2"
- esquery "^1.0.1"
+ enquirer "^2.3.5"
+ eslint-scope "^5.1.1"
+ eslint-utils "^2.1.0"
+ eslint-visitor-keys "^2.0.0"
+ espree "^7.3.1"
+ esquery "^1.4.0"
esutils "^2.0.2"
- file-entry-cache "^5.0.1"
+ file-entry-cache "^6.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^5.0.0"
- globals "^12.1.0"
+ globals "^13.6.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
- inquirer "^7.0.0"
is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.3.0"
- lodash "^4.17.14"
+ levn "^0.4.1"
+ lodash "^4.17.21"
minimatch "^3.0.4"
- mkdirp "^0.5.1"
natural-compare "^1.4.0"
- optionator "^0.8.3"
+ optionator "^0.9.1"
progress "^2.0.0"
- regexpp "^2.0.1"
- semver "^6.1.2"
- strip-ansi "^5.2.0"
- strip-json-comments "^3.0.1"
- table "^5.2.3"
+ regexpp "^3.1.0"
+ semver "^7.2.1"
+ strip-ansi "^6.0.0"
+ strip-json-comments "^3.1.0"
+ table "^6.0.4"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
@@ -5157,14 +5156,14 @@ espree@^5.0.1:
acorn-jsx "^5.0.0"
eslint-visitor-keys "^1.0.0"
-espree@^6.1.2:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
- integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
+espree@^7.3.0, espree@^7.3.1:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
+ integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
dependencies:
- acorn "^7.1.1"
- acorn-jsx "^5.2.0"
- eslint-visitor-keys "^1.1.0"
+ acorn "^7.4.0"
+ acorn-jsx "^5.3.1"
+ eslint-visitor-keys "^1.3.0"
esprima@3.x.x:
version "3.1.3"
@@ -5183,6 +5182,13 @@ esquery@^1.0.1:
dependencies:
estraverse "^5.1.0"
+esquery@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
+ integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
+ dependencies:
+ estraverse "^5.1.0"
+
esrecurse@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
@@ -5190,6 +5196,13 @@ esrecurse@^4.1.0:
dependencies:
estraverse "^4.1.0"
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
@@ -5200,6 +5213,11 @@ estraverse@^5.1.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
+estraverse@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
+ integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
+
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -5855,7 +5873,7 @@ ethjs-util@0.1.3:
is-hex-prefixed "1.0.0"
strip-hex-prefix "1.0.0"
-ethjs-util@0.1.6, ethjs-util@^0.1.3:
+ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536"
integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==
@@ -6201,7 +6219,7 @@ figures@^2.0.0:
dependencies:
escape-string-regexp "^1.0.5"
-figures@^3.0.0, figures@^3.2.0:
+figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
@@ -6215,6 +6233,13 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
file-uri-to-path@1, file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -6303,11 +6328,24 @@ flat-cache@^2.0.1:
rimraf "2.6.3"
write "1.0.3"
+flat-cache@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
+ integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+ dependencies:
+ flatted "^3.1.0"
+ rimraf "^3.0.2"
+
flatted@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
+flatted@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
+ integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
+
follow-redirects@^1.10.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
@@ -6643,6 +6681,13 @@ globals@^12.1.0:
dependencies:
type-fest "^0.8.1"
+globals@^13.6.0:
+ version "13.8.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.8.0.tgz#3e20f504810ce87a8d72e55aecf8435b50f4c1b3"
+ integrity sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==
+ dependencies:
+ type-fest "^0.20.2"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@@ -7029,7 +7074,7 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
-indexof@0.0.1, indexof@~0.0.1:
+indexof@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
@@ -7101,25 +7146,6 @@ inquirer@^6.2.0, inquirer@^6.2.2:
strip-ansi "^5.1.0"
through "^2.3.6"
-inquirer@^7.0.0:
- version "7.3.3"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
- integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
- dependencies:
- ansi-escapes "^4.2.1"
- chalk "^4.1.0"
- cli-cursor "^3.1.0"
- cli-width "^3.0.0"
- external-editor "^3.0.3"
- figures "^3.0.0"
- lodash "^4.17.19"
- mute-stream "0.0.8"
- run-async "^2.4.0"
- rxjs "^6.6.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
- through "^2.3.6"
-
internal-slot@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
@@ -7914,10 +7940,13 @@ jest-runtime@^25.5.4:
strip-bom "^4.0.0"
yargs "^15.3.1"
-jest-serializer@24.4.0:
- version "24.4.0"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3"
- integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==
+jest-serializer@26.6.2, jest-serializer@^26.6.2:
+ version "26.6.2"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1"
+ integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==
+ dependencies:
+ "@types/node" "*"
+ graceful-fs "^4.2.4"
jest-serializer@^24.4.0, jest-serializer@^24.9.0:
version "24.9.0"
@@ -7931,14 +7960,6 @@ jest-serializer@^25.5.0:
dependencies:
graceful-fs "^4.2.4"
-jest-serializer@^26.6.2:
- version "26.6.2"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1"
- integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==
- dependencies:
- "@types/node" "*"
- graceful-fs "^4.2.4"
-
jest-snapshot@^25.5.1:
version "25.5.1"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.5.1.tgz#1a2a576491f9961eb8d00c2e5fd479bc28e5ff7f"
@@ -8187,13 +8208,13 @@ json-rpc-engine@^6.1.0:
"@metamask/safe-event-emitter" "^2.0.0"
eth-rpc-errors "^4.0.2"
-json-rpc-middleware-stream@2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-2.1.1.tgz#06e5409e201e7ddeae47bef29f7059eafd4d5325"
- integrity sha512-WZheufPN+/RKkjXQP3lK5tFYblqG0n+oYv5qpammwwY2vsJRB7mM4Txhr4ajzvYEZi1UkENnplrmaYiqaqafaA==
+json-rpc-middleware-stream@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-3.0.0.tgz#8540331d884f36b9e0ad31054cc68ac6b5a89b52"
+ integrity sha512-JmZmlehE0xF3swwORpLHny/GvW3MZxCsb2uFNBrn8TOqMqivzCfz232NSDLLOtIQlrPlgyEjiYpyzyOPFOzClw==
dependencies:
+ "@metamask/safe-event-emitter" "^2.0.0"
readable-stream "^2.3.3"
- safe-event-emitter "^1.0.1"
json-rpc-random-id@^1.0.0, json-rpc-random-id@^1.0.1:
version "1.0.1"
@@ -8205,6 +8226,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -8519,6 +8545,14 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
lil-uuid@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/lil-uuid/-/lil-uuid-0.1.1.tgz#f9edcf23f00e42bf43f0f843d98d8b53f3341f16"
@@ -8603,11 +8637,21 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+lodash.clonedeep@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+ integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
+
lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
+lodash.flatten@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
+ integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
+
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -8638,6 +8682,11 @@ lodash.toarray@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE=
+lodash.truncate@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
+ integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
+
lodash@4.x.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
@@ -8648,6 +8697,11 @@ lodash@^4.17.19:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -9523,7 +9577,7 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-mute-stream@0.0.8, mute-stream@~0.0.4:
+mute-stream@~0.0.4:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
@@ -9974,7 +10028,7 @@ opn@^5.4.0:
dependencies:
is-wsl "^1.1.0"
-optionator@^0.8.1, optionator@^0.8.2, optionator@^0.8.3:
+optionator@^0.8.1, optionator@^0.8.2:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
@@ -9986,6 +10040,18 @@ optionator@^0.8.1, optionator@^0.8.2, optionator@^0.8.3:
type-check "~0.3.2"
word-wrap "~1.2.3"
+optionator@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+ integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.3"
+
options@>=0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
@@ -10459,6 +10525,11 @@ precond@0.2:
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
integrity sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -11140,10 +11211,10 @@ react-native-sensors@5.3.0:
dependencies:
rxjs ">= 6"
-react-native-share@^3.2.0:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-3.3.2.tgz#06d1d3f14ba8eeb95e7e94e4db6a286e9902bd29"
- integrity sha512-Pvkr62TiCX511RMPL+wvy9Fofre4HQnvUT5zzgPPN3vszP/C8lUb7cmFu/8x5U14t3JQg+xW/svNK5eKNebJKw==
+react-native-share@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/react-native-share/-/react-native-share-5.2.2.tgz#7161fd37bc861e6a63df5d5c5fafbff10c7ff5e5"
+ integrity sha512-Jn92T+fXzq8ZIfiZllznFYrhDQoFUcMZ6vO0oXgQJYR5leVZuesqy8II3taWLtQzbAD5tl4Y+EaNYo7Z6TNGTw==
"react-native-splash-screen@git+https://github.com/MetaMask/react-native-splash-screen.git":
version "3.2.0"
@@ -11190,6 +11261,13 @@ react-native-tcp@aprock/react-native-tcp#11/head:
process "^0.11.9"
util "^0.12.1"
+react-native-url-polyfill@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a"
+ integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ==
+ dependencies:
+ whatwg-url-without-unicode "8.0.0-3"
+
react-native-v8@^0.62.2-patch.1:
version "0.62.2-patch.1"
resolved "https://registry.yarnpkg.com/react-native-v8/-/react-native-v8-0.62.2-patch.1.tgz#016a932ed5e60f6bca6803fbdf6c746fe1b55bf5"
@@ -11530,7 +11608,7 @@ regexpp@^2.0.1:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
-regexpp@^3.0.0:
+regexpp@^3.0.0, regexpp@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
@@ -11638,6 +11716,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@@ -11742,7 +11825,7 @@ rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
dependencies:
glob "^7.1.3"
-rimraf@^3.0.0:
+rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -11819,7 +11902,7 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
-run-async@^2.2.0, run-async@^2.4.0:
+run-async@^2.2.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
@@ -11865,13 +11948,6 @@ rxjs@^5.4.3:
dependencies:
symbol-observable "1.0.1"
-rxjs@^6.6.0:
- version "6.6.3"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
- integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
- dependencies:
- tslib "^1.9.0"
-
rxjs@^6.6.6:
version "6.6.6"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70"
@@ -12044,11 +12120,18 @@ semver@^4.3.2, semver@~2.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=
-semver@^6.0.0, semver@^6.1.2, semver@^6.3.0:
+semver@^6.0.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+semver@^7.2.1:
+ version "7.3.5"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+ integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+ dependencies:
+ lru-cache "^6.0.0"
+
semver@^7.3.2:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
@@ -12711,7 +12794,7 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
-strip-json-comments@^3.0.1:
+strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@@ -12804,6 +12887,18 @@ table@^5.2.3:
slice-ansi "^2.1.0"
string-width "^3.0.0"
+table@^6.0.4:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/table/-/table-6.4.0.tgz#9501324358c313162cf52b2843a8b221e75fbefc"
+ integrity sha512-/Vfr23BDjJT2kfsCmYtnJqEPdD/8Dh/MDIQxfcbe+09lZUel6gluquwdMTrLERBw623Nv34DLGZ11krWn5AAqw==
+ dependencies:
+ ajv "^8.0.1"
+ lodash.clonedeep "^4.5.0"
+ lodash.flatten "^4.4.0"
+ lodash.truncate "^4.4.2"
+ slice-ansi "^4.0.0"
+ string-width "^4.2.0"
+
tail@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/tail/-/tail-2.0.3.tgz#37567adc4624a70b35f1d146c3376fa3d6ef7c04"
@@ -13032,11 +13127,16 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
-tslib@^2.0.0, tslib@^2.1.0:
+tslib@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
+tslib@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
+ integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
+
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
@@ -13066,6 +13166,13 @@ tweetnacl@^1.0.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -13083,6 +13190,11 @@ type-fest@^0.11.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
@@ -13138,6 +13250,11 @@ unicode-canonical-property-names-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
+unicode-confusables@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/unicode-confusables/-/unicode-confusables-0.1.1.tgz#17f14e8dc53ff81c12e92fd86e836ebdf14ea0c2"
+ integrity sha512-XTPBWmT88BDpXz9NycWk4KxDn+/AJmJYYaYBwuIH9119sopwk2E9GxU9azc+JNbhEsfiPul78DGocEihCp6MFQ==
+
unicode-match-property-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
@@ -13368,12 +13485,10 @@ vlq@^1.0.0:
resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468"
integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==
-vm-browserify@0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
- integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=
- dependencies:
- indexof "0.0.1"
+vm-browserify@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
+ integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
w3c-hr-time@^1.0.1:
version "1.0.2"
@@ -13457,6 +13572,11 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+webidl-conversions@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+ integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
@@ -13484,6 +13604,15 @@ whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+whatwg-url-without-unicode@8.0.0-3:
+ version "8.0.0-3"
+ resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b"
+ integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==
+ dependencies:
+ buffer "^5.4.3"
+ punycode "^2.1.1"
+ webidl-conversions "^5.0.0"
+
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
@@ -13587,7 +13716,7 @@ winston@0.8.x:
pkginfo "0.3.x"
stack-trace "0.0.x"
-word-wrap@~1.2.3:
+word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==