From 3133b25faf8bf198f0d87543de482420c75279c3 Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Mon, 1 Mar 2021 19:25:27 -0300 Subject: [PATCH 01/21] REVERT --- .circleci/config.yml | 2 ++ app/core/AppConstants.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a60479e104..2d41d946e17 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -197,12 +197,14 @@ workflows: only: - master - develop + - swaps-v1 - prep-node-deps: filters: branches: ignore: - master - develop + - swaps-v1 - lint: requires: - prep-node-deps diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js index aa12dbb6968..71ac7c8b334 100644 --- a/app/core/AppConstants.js +++ b/app/core/AppConstants.js @@ -57,7 +57,7 @@ export default { ORIGIN_QR_CODE: 'qr-code' }, SWAPS: { - ACTIVE: false, + ACTIVE: true, ONLY_MAINNET: false, LIVENESS_POLLING_FREQUENCY: 5 * 60 * 1000, POLL_COUNT_LIMIT: 3, From c13997d9457d2142a2ffedc53da7f41a08ebeae0 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Mon, 1 Mar 2021 19:45:13 -0300 Subject: [PATCH 02/21] Swaps: Add info modal when swaps is off (#2320) --- app/components/UI/AccountOverview/index.js | 11 ++--- .../__snapshots__/index.test.js.snap | 6 +++ app/components/UI/AssetActionButton/index.js | 7 +-- app/components/UI/AssetOverview/index.js | 13 ++--- .../UI/Swaps/components/AssetSwapButton.js | 47 +++++++++++++++++++ locales/en.json | 6 +++ 6 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 app/components/UI/Swaps/components/AssetSwapButton.js diff --git a/app/components/UI/AccountOverview/index.js b/app/components/UI/AccountOverview/index.js index a90a2649457..97cecaf15c9 100644 --- a/app/components/UI/AccountOverview/index.js +++ b/app/components/UI/AccountOverview/index.js @@ -27,6 +27,7 @@ import AssetActionButton from '../AssetActionButton'; import EthereumAddress from '../EthereumAddress'; import { colors, fontStyles, baseStyles } from '../../../styles/common'; import { allowedToBuy } from '../FiatOrders'; +import AssetSwapButton from '../Swaps/components/AssetSwapButton'; const styles = StyleSheet.create({ scrollView: { @@ -351,13 +352,11 @@ class AccountOverview extends PureComponent { label={strings('asset_overview.send_button')} /> {AppConstants.SWAPS.ACTIVE && ( - )} diff --git a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap index 1f3b9e6cef1..43cef6abc9b 100644 --- a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap +++ b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap @@ -12,6 +12,7 @@ exports[`AssetActionButtons should render correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > @@ -67,6 +68,7 @@ exports[`AssetActionButtons should render type add correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > @@ -122,6 +124,7 @@ exports[`AssetActionButtons should render type information correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > @@ -177,6 +180,7 @@ exports[`AssetActionButtons should render type receive correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > @@ -232,6 +236,7 @@ exports[`AssetActionButtons should render type send correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > @@ -287,6 +292,7 @@ exports[`AssetActionButtons should render type swap correctly 1`] = ` "minWidth": 60, }, undefined, + undefined, ] } > diff --git a/app/components/UI/AssetActionButton/index.js b/app/components/UI/AssetActionButton/index.js index 7ff41bec459..79cff05ea3e 100644 --- a/app/components/UI/AssetActionButton/index.js +++ b/app/components/UI/AssetActionButton/index.js @@ -88,11 +88,11 @@ function getIcon(type) { } } -function AssetActionButton({ onPress, icon, label, disabled }) { +function AssetActionButton({ onPress, icon, label, disabled, style }) { return ( {icon && (typeof icon === 'string' ? getIcon(icon) : icon)} @@ -107,7 +107,8 @@ AssetActionButton.propTypes = { onPress: PropTypes.func, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), label: PropTypes.string, - disabled: PropTypes.bool + disabled: PropTypes.bool, + style: PropTypes.object }; export default AssetActionButton; diff --git a/app/components/UI/AssetOverview/index.js b/app/components/UI/AssetOverview/index.js index 7ae3afb4564..8ecd66229f0 100644 --- a/app/components/UI/AssetOverview/index.js +++ b/app/components/UI/AssetOverview/index.js @@ -21,6 +21,7 @@ import Logger from '../../../util/Logger'; import Analytics from '../../../core/Analytics'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; import { allowedToBuy } from '../FiatOrders'; +import AssetSwapButton from '../Swaps/components/AssetSwapButton'; const styles = StyleSheet.create({ wrapper: { @@ -297,14 +298,10 @@ class AssetOverview extends PureComponent { label={strings('asset_overview.send_button')} /> {AppConstants.SWAPS.ACTIVE && ( - )} diff --git a/app/components/UI/Swaps/components/AssetSwapButton.js b/app/components/UI/Swaps/components/AssetSwapButton.js new file mode 100644 index 00000000000..115621c662a --- /dev/null +++ b/app/components/UI/Swaps/components/AssetSwapButton.js @@ -0,0 +1,47 @@ +import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { StyleSheet } from 'react-native'; + +import Text from '../../../Base/Text'; +import AssetActionButton from '../../AssetActionButton'; +import InfoModal from './InfoModal'; + +import useModalHandler from '../../../Base/hooks/useModalHandler'; +import { strings } from '../../../../../locales/i18n'; + +const styles = StyleSheet.create({ + disabledButton: { + opacity: 0.5 + } +}); + +function AssetSwapButton({ isFeatureLive, isNetworkAllowed, isAssetAllowed, onPress }) { + const [isModalOpen, , showModal, hideModal] = useModalHandler(false); + const isDisabled = !isFeatureLive || !isNetworkAllowed || !isAssetAllowed; + + const [title, body] = useMemo(() => { + if (!isNetworkAllowed) return [strings('swaps.wrong_network_title'), strings('swaps.wrong_network_body')]; + if (!isAssetAllowed) return [strings('swaps.unallowed_asset_title'), strings('swaps.unallowed_asset_body')]; + if (!isFeatureLive) return [strings('swaps.feature_off_title'), strings('swaps.feature_off_body')]; + }, [isAssetAllowed, isFeatureLive, isNetworkAllowed]); + return ( + <> + + {body}} /> + + ); +} + +AssetSwapButton.propTypes = { + isFeatureLive: PropTypes.bool, + isNetworkAllowed: PropTypes.bool, + isAssetAllowed: PropTypes.bool, + onPress: PropTypes.func +}; + +export default AssetSwapButton; diff --git a/locales/en.json b/locales/en.json index 5b3c1e48638..9d0a93b44e3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1425,6 +1425,12 @@ "review_audits": "Review our official contracts audit", "start_swapping": "Start swapping" }, + "feature_off_title": "Temporarily unavailable", + "feature_off_body": "MetaMask Swaps is undergoing maintenance. Please check back later.", + "wrong_network_title": "Swaps not available", + "wrong_network_body": "You’re only able to swap tokens on the Ethereum Main Network.", + "unallowed_asset_title": "Can’t swap this token", + "unallowed_asset_body": "Some tokens with unique mechanics are currently not supported for swapping.", "convert_from": "Convert from", "convert_to": "Convert to", "verify": "Verify", From 0db05ae816d43301ef95ae61b90b5cba565919f9 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Mon, 1 Mar 2021 19:55:51 -0300 Subject: [PATCH 03/21] Fix info modal when enabled --- app/components/UI/Swaps/components/AssetSwapButton.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/components/UI/Swaps/components/AssetSwapButton.js b/app/components/UI/Swaps/components/AssetSwapButton.js index 115621c662a..386d1d992d2 100644 --- a/app/components/UI/Swaps/components/AssetSwapButton.js +++ b/app/components/UI/Swaps/components/AssetSwapButton.js @@ -23,6 +23,7 @@ function AssetSwapButton({ isFeatureLive, isNetworkAllowed, isAssetAllowed, onPr if (!isNetworkAllowed) return [strings('swaps.wrong_network_title'), strings('swaps.wrong_network_body')]; if (!isAssetAllowed) return [strings('swaps.unallowed_asset_title'), strings('swaps.unallowed_asset_body')]; if (!isFeatureLive) return [strings('swaps.feature_off_title'), strings('swaps.feature_off_body')]; + return ['', '']; }, [isAssetAllowed, isFeatureLive, isNetworkAllowed]); return ( <> @@ -32,7 +33,12 @@ function AssetSwapButton({ isFeatureLive, isNetworkAllowed, isAssetAllowed, onPr onPress={isDisabled ? showModal : onPress} style={isDisabled && styles.disabledButton} /> - {body}} /> + {body}} + /> ); } From 9d0b663093f9a5429eda18781b2ba35040d17a77 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Tue, 2 Mar 2021 16:37:21 -0300 Subject: [PATCH 04/21] Swaps: Add clientId (#2324) --- app/components/UI/Swaps/components/AssetSwapButton.js | 2 +- app/core/AppConstants.js | 1 + app/core/Engine.js | 2 +- package.json | 2 +- yarn.lock | 8 ++++---- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/components/UI/Swaps/components/AssetSwapButton.js b/app/components/UI/Swaps/components/AssetSwapButton.js index 386d1d992d2..072d10a822f 100644 --- a/app/components/UI/Swaps/components/AssetSwapButton.js +++ b/app/components/UI/Swaps/components/AssetSwapButton.js @@ -31,7 +31,7 @@ function AssetSwapButton({ isFeatureLive, isNetworkAllowed, isAssetAllowed, onPr icon="swap" label={strings('asset_overview.swap')} onPress={isDisabled ? showModal : onPress} - style={isDisabled && styles.disabledButton} + style={isDisabled ? styles.disabledButton : undefined} /> Date: Wed, 3 Mar 2021 22:05:37 -0300 Subject: [PATCH 05/21] Add a longer list of tokens (#2331) --- .../UI/Swaps/components/TokenSelectModal.js | 40 +++++++++++++------ app/components/UI/Swaps/index.js | 6 ++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/components/UI/Swaps/components/TokenSelectModal.js b/app/components/UI/Swaps/components/TokenSelectModal.js index 64f5cbaba8d..a552b9ac68c 100644 --- a/app/components/UI/Swaps/components/TokenSelectModal.js +++ b/app/components/UI/Swaps/components/TokenSelectModal.js @@ -1,6 +1,6 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet, TextInput, SafeAreaView, TouchableOpacity, View } from 'react-native'; +import { StyleSheet, TextInput, SafeAreaView, TouchableOpacity, View, TouchableWithoutFeedback } from 'react-native'; import { FlatList } from 'react-native-gesture-handler'; import Modal from 'react-native-modal'; import Icon from 'react-native-vector-icons/Ionicons'; @@ -65,6 +65,8 @@ const styles = StyleSheet.create({ } }); +const MAX_TOKENS_RESULTS = 20; + function TokenSelectModal({ isVisible, dismiss, @@ -81,6 +83,7 @@ function TokenSelectModal({ balances }) { const searchInput = useRef(null); + const list = useRef(); const [searchString, setSearchString] = useState(''); const filteredTokens = useMemo(() => tokens?.filter(token => !excludeAddresses.includes(token.address)), [ @@ -108,7 +111,10 @@ function TokenSelectModal({ [filteredTokens] ); const tokenSearchResults = useMemo( - () => (searchString.length > 0 ? tokenFuse.search(searchString)?.slice(0, 5) : filteredInitialTokens), + () => + searchString.length > 0 + ? tokenFuse.search(searchString)?.slice(0, MAX_TOKENS_RESULTS) + : filteredInitialTokens, [searchString, tokenFuse, filteredInitialTokens] ); @@ -160,6 +166,11 @@ function TokenSelectModal({ [searchString] ); + const handleSearchTextChange = useCallback(text => { + setSearchString(text); + if (list.current) list.current.scrollToOffset({ animated: false, y: 0 }); + }, []); + return ( {title} - - - - + + + + + + - {Boolean(destinationToken) && destinationToken.symbol !== 'ETH' ? ( + {Boolean(destinationToken) && destinationToken?.address !== SWAPS_ETH_ADDRESS ? ( destinationTokenHasEnoughOcurrances ? ( From 0ebbec1db0125790321b095243a555584be9a116 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Thu, 4 Mar 2021 12:47:30 -0300 Subject: [PATCH 06/21] Swaps: Adjust Keypad size for medium and smaller devices (#2335) --- app/components/Base/Keypad/components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Base/Keypad/components.js b/app/components/Base/Keypad/components.js index 6e3c30e9027..1a78aecf960 100644 --- a/app/components/Base/Keypad/components.js +++ b/app/components/Base/Keypad/components.js @@ -17,7 +17,7 @@ const styles = StyleSheet.create({ }, keypadButton: { paddingHorizontal: 20, - paddingVertical: Device.isMediumDevice() ? (Device.isIphone5() ? 5 : 10) : 12, + paddingVertical: Device.isMediumDevice() ? (Device.isIphone5() ? 4 : 8) : 12, flex: 1, justifyContent: 'center', alignItems: 'center' From 08fa75e0cb5e956ed05b38ceddcfd34a112b225c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Thu, 4 Mar 2021 13:03:34 -0300 Subject: [PATCH 07/21] swaps: anonymous tracking (#2325) * addtrack * booleam * fixanalytics * trackanon * devmoderevert * track anonymously * dont send data for non anonymized * Revert "devmoderevert" This reverts commit ff70e389f4793ea7f1c181107b5b211305f7902b. Co-authored-by: Ethan Wessel --- .../metamask/nativeModules/RCTAnalytics.java | 10 +++ app/components/Nav/Main/index.js | 8 ++- app/components/UI/Swaps/QuotesView.js | 50 ++++++++++----- app/components/UI/Swaps/index.js | 6 +- app/core/Analytics.js | 64 ++++++++++++------- .../NativeModules/RCTAnalytics/RCTAnalytics.m | 8 +++ 6 files changed, 101 insertions(+), 45 deletions(-) diff --git a/android/app/src/main/java/io/metamask/nativeModules/RCTAnalytics.java b/android/app/src/main/java/io/metamask/nativeModules/RCTAnalytics.java index 62c2409e175..c6fce4ffcb2 100644 --- a/android/app/src/main/java/io/metamask/nativeModules/RCTAnalytics.java +++ b/android/app/src/main/java/io/metamask/nativeModules/RCTAnalytics.java @@ -54,6 +54,16 @@ public void trackEvent(ReadableMap e) { this.mixpanel.track(eventCategory, props); } + @ReactMethod + public void trackEventAnonymously(ReadableMap e) { + String eventCategory = e.getString("category"); + String distinctId = this.mixpanel.getDistinctId(); + this.mixpanel.identify("0x0000000000000000"); + JSONObject props = toJSONObject(e); + this.mixpanel.track(eventCategory, props); + this.mixpanel.identify(distinctId); + } + @ReactMethod public void getDistinctId(Promise promise) { String distinctId = this.mixpanel.getDistinctId(); diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 465e3cfb1c9..d9bd8db1849 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -269,13 +269,15 @@ const Main = props => { delete newSwapsTransactions[transactionMeta.id].paramsForAnalytics; InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(event, { + const parameters = { ...analyticsParams, time_to_mine: timeToMine, estimated_vs_used_gasRatio: estimatedVsUsedGasRatio, quote_vs_executionRatio: quoteVsExecutionRatio, - token_to_amount_received: tokenToAmountReceived - }); + token_to_amount_received: tokenToAmountReceived.toString() + }; + Analytics.trackEventWithParameters(event, {}); + Analytics.trackEventWithParameters(event, parameters, true); }); } catch (e) { Logger.error(e, ANALYTICS_EVENT_OPTS.SWAP_TRACKING_FAILED); diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 4b49f1c2905..3c8726475e3 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -475,9 +475,12 @@ function SwapsQuotesView({ ), analytics: { token_from: sourceToken.symbol, - token_from_amount: sourceAmount, + token_from_amount: fromTokenMinimalUnitString(sourceAmount, sourceToken.decimals), token_to: destinationToken.symbol, - token_to_amount: selectedQuote.destinationAmount, + token_to_amount: fromTokenMinimalUnitString( + selectedQuote.destinationAmount, + destinationToken.decimals + ), request_type: hasEnoughTokenBalance ? 'Order' : 'Quote', custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, best_quote_source: selectedQuote.aggregator, @@ -518,7 +521,7 @@ function SwapsQuotesView({ } InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAP_STARTED, { + const parameters = { token_from: sourceToken.symbol, token_from_amount: fromTokenMinimalUnitString(sourceAmount, sourceToken.decimals), token_to: destinationToken.symbol, @@ -531,7 +534,9 @@ function SwapsQuotesView({ other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, network_fees_USD: weiToFiat(toWei(selectedQuoteValue.ethFee), conversionRate, 'usd'), network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)) - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAP_STARTED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAP_STARTED, parameters, true); }); const { TransactionController } = Engine.context; @@ -615,7 +620,7 @@ function SwapsQuotesView({ setEditQuoteTransactionsVisible(true); InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.EDIT_SPEND_LIMIT_OPENED, { + const parameters = { token_from: sourceToken.symbol, token_from_amount: fromTokenMinimalUnitString(sourceAmount, sourceToken.decimals), token_to: destinationToken.symbol, @@ -629,7 +634,9 @@ function SwapsQuotesView({ gas_fees: weiToFiat(toWei(gasFee), conversionRate, currentCurrency), custom_spend_limit_set: originalAmount !== currentAmount, custom_spend_limit_amount: currentAmount - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.EDIT_SPEND_LIMIT_OPENED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.EDIT_SPEND_LIMIT_OPENED, parameters, true); }); }, [ allQuotes, @@ -652,7 +659,7 @@ function SwapsQuotesView({ setCustomGasPrice(gasPrice); setCustomGasLimit(gas); InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, { + const parameters = { speed_set: details.mode === 'advanced' ? undefined : details.mode, gas_mode: details.mode === 'advanced' ? 'Advanced' : 'Basic', gas_fees: weiToFiat( @@ -660,7 +667,9 @@ function SwapsQuotesView({ conversionRate, currentCurrency ) - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, parameters, true); }); }, [conversionRate, currentCurrency] @@ -669,7 +678,7 @@ function SwapsQuotesView({ const handleQuotesReceivedMetric = useCallback(() => { if (!selectedQuote || !selectedQuoteValue) return; InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_RECEIVED, { + const parameters = { token_from: sourceToken.symbol, token_from_amount: fromTokenMinimalUnitString(sourceAmount, sourceToken.decimals), token_to: destinationToken.symbol, @@ -682,7 +691,9 @@ function SwapsQuotesView({ network_fees_USD: weiToFiat(toWei(selectedQuoteValue.ethFee), conversionRate, 'usd'), network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), available_quotes: allQuotes.length - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_RECEIVED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_RECEIVED, parameters, true); }); }, [ sourceToken, @@ -701,7 +712,7 @@ function SwapsQuotesView({ if (!selectedQuote || !selectedQuoteValue) return; toggleQuotesModal(); InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.ALL_AVAILABLE_QUOTES_OPENED, { + const parameters = { token_from: sourceToken.symbol, token_from_amount: fromTokenMinimalUnitString(sourceAmount, sourceToken.decimals), token_to: destinationToken.symbol, @@ -714,7 +725,9 @@ function SwapsQuotesView({ network_fees_USD: weiToFiat(toWei(selectedQuoteValue.ethFee), conversionRate, 'usd'), network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)), available_quotes: allQuotes.length - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.ALL_AVAILABLE_QUOTES_OPENED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.ALL_AVAILABLE_QUOTES_OPENED, parameters, true); }); }, [ selectedQuote, @@ -743,14 +756,18 @@ function SwapsQuotesView({ Logger.error(error?.description, `Swaps: ${error?.key}`); if (error?.key === swapsUtils.SwapsError.QUOTES_EXPIRED_ERROR) { InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_TIMED_OUT, { + const parameters = { ...data, gas_fees: '' - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_TIMED_OUT, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_TIMED_OUT, parameters, true); }); } else if (error?.key === swapsUtils.SwapsError.QUOTES_NOT_AVAILABLE_ERROR) { InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.NO_QUOTES_AVAILABLE, { data }); + const parameters = { data }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.NO_QUOTES_AVAILABLE, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.NO_QUOTES_AVAILABLE, parameters, true); }); } }, @@ -902,7 +919,8 @@ function SwapsQuotesView({ navigation.setParams({ selectedQuote: undefined }); navigation.setParams({ quoteBegin: Date.now() }); InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_REQUESTED, data); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_REQUESTED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.QUOTES_REQUESTED, data, true); }); }, [ destinationToken, diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index 32efe373c4a..c3eb263c915 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -175,12 +175,14 @@ function SwapsAmountView({ if (liveness) { // Triggered when a user enters the MetaMask Swap feature InteractionManager.runAfterInteractions(() => { - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAPS_OPENED, { + const parameters = { source: initialSource === SWAPS_ETH_ADDRESS ? 'MainView' : 'TokenView', activeCurrency: swapsTokens?.find( token => token.address?.toLowerCase() === initialSource.toLowerCase() )?.symbol - }); + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAPS_OPENED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAPS_OPENED, parameters, true); }); } else { navigation.pop(); diff --git a/app/core/Analytics.js b/app/core/Analytics.js index db3b24ccde2..8f1c1f655f7 100644 --- a/app/core/Analytics.js +++ b/app/core/Analytics.js @@ -41,15 +41,24 @@ class Analytics { /** * Track event if enabled and not DEV mode */ - _trackEvent(name, { event, params = {}, value, info }) { + _trackEvent(name, { event, params = {}, value, info, anonymously = false }) { if (!this.enabled) return; if (!__DEV__) { - RCTAnalytics.trackEvent({ - ...event, - ...params, - value, - info - }); + if (!anonymously) { + RCTAnalytics.trackEvent({ + ...event, + ...params, + value, + info + }); + } else { + RCTAnalytics.trackEventAnonymously({ + ...event, + ...params, + value, + info + }); + } } else { Logger.log(`Analytics '${name}' -`, event, params, value, info); } @@ -119,8 +128,9 @@ class Analytics { * Track event * * @param {object} event - Object containing event category, action and name + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEvent = event => { + trackEvent = (event, anonymously = false) => { this._trackEvent('trackEvent', { event }); }; @@ -129,9 +139,10 @@ class Analytics { * * @param {object} event - Object containing event category, action and name * @param {number} value - Value number to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithValue = (event, value) => { - this._trackEvent('trackEventWithValue', { event, value }); + trackEventWithValue = (event, value, anonymously = false) => { + this._trackEvent('trackEventWithValue', { event, value, anonymously }); }; /** @@ -139,9 +150,10 @@ class Analytics { * * @param {object} event - Object containing event category, action and name * @param {string} info - Information string to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithInfo = (event, info) => { - this._trackEvent('trackEventWithInfo', { event, info }); + trackEventWithInfo = (event, info, anonymously = false) => { + this._trackEvent('trackEventWithInfo', { event, info, anonymously }); }; /** @@ -150,9 +162,10 @@ class Analytics { * @param {object} event - Object containing event category, action and name * @param {number} value - Value number to send with event * @param {string} info - Information string to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithValueAndInfo = (event, value, info) => { - this._trackEvent('trackEventWithValueAndInfo', { event, value, info }); + trackEventWithValueAndInfo = (event, value, info, anonymously = false) => { + this._trackEvent('trackEventWithValueAndInfo', { event, value, info, anonymously }); }; /** @@ -160,9 +173,10 @@ class Analytics { * * @param {object} event - Object containing event category, action and name * @param {object} params - Object containing other params to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithParameters = (event, params) => { - this._trackEvent('trackEventWithParameters', { event, params }); + trackEventWithParameters = (event, params, anonymously = false) => { + this._trackEvent('trackEventWithParameters', { event, params, anonymously }); }; /** @@ -171,9 +185,10 @@ class Analytics { * @param {object} event - Object containing event category, action and name * @param {number} value - Value number to send with event * @param {object} params - Object containing other params to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithValueAndParameters = (event, value, params) => { - this._trackEvent('trackEventWithValueAndParameters', { event, value, params }); + trackEventWithValueAndParameters = (event, value, params, anonymously = false) => { + this._trackEvent('trackEventWithValueAndParameters', { event, value, params, anonymously }); }; /** @@ -183,9 +198,10 @@ class Analytics { * @param {number} value - Value number to send with event * @param {string} info - Information string to send with event * @param {object} params - Object containing other params to send with event + * @param {boolean} anonymously - Whether the tracking should be without the right distinctId */ - trackEventWithValueAndInfoAndParameters = (event, value, info, params) => { - this._trackEvent('trackEventWithValueAndInfoAndParameters', { event, value, info, params }); + trackEventWithValueAndInfoAndParameters = (event, value, info, params, anonymously = false) => { + this._trackEvent('trackEventWithValueAndInfoAndParameters', { event, value, info, params, anonymously }); }; } @@ -220,11 +236,11 @@ export default { getDistinctId() { return instance && instance.getDistinctId(); }, - trackEvent(event) { - return instance && instance.trackEvent(event); + trackEvent(event, anonymously) { + return instance && instance.trackEvent(event, anonymously); }, - trackEventWithParameters(event, parameters) { - return instance && instance.trackEventWithParameters(event, parameters); + trackEventWithParameters(event, parameters, anonymously) { + return instance && instance.trackEventWithParameters(event, parameters, anonymously); }, getRemoteVariables() { return instance.remoteVariables; diff --git a/ios/MetaMask/NativeModules/RCTAnalytics/RCTAnalytics.m b/ios/MetaMask/NativeModules/RCTAnalytics/RCTAnalytics.m index 26d9f200ea3..4a30ca11653 100644 --- a/ios/MetaMask/NativeModules/RCTAnalytics/RCTAnalytics.m +++ b/ios/MetaMask/NativeModules/RCTAnalytics/RCTAnalytics.m @@ -29,6 +29,14 @@ @implementation RCTAnalytics [[Mixpanel sharedInstance] track: [self getCategory:event] properties:[self getInfo:event]]; } +RCT_EXPORT_METHOD(trackEventAnonymously:(NSDictionary *)event) +{ + NSString *const distinctId = [[Mixpanel sharedInstance] distinctId]; + [[Mixpanel sharedInstance] identify:@"0x0000000000000000"]; + [[Mixpanel sharedInstance] track: [self getCategory:event] properties:[self getInfo:event]]; + [[Mixpanel sharedInstance] identify:distinctId]; +} + RCT_EXPORT_METHOD(peopleIdentify) { From f4065a7a69ba62b8ae004d363d60ecc829ce4206 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Thu, 4 Mar 2021 13:24:57 -0300 Subject: [PATCH 08/21] Swaps: Use decimal value for swaps tx (#2338) --- app/components/UI/TransactionElement/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/UI/TransactionElement/utils.js b/app/components/UI/TransactionElement/utils.js index 45a68665cf3..c8a442327b8 100644 --- a/app/components/UI/TransactionElement/utils.js +++ b/app/components/UI/TransactionElement/utils.js @@ -691,7 +691,7 @@ function decodeSwapsTx(args) { ? 1 : contractExchangeRates[safeToChecksumAddress(destinationToken.address)]; const renderDestinationTokenFiatNumber = balanceToFiatNumber( - decimalSourceAmount, + decimalDestinationAmount, conversionRate, destinationExchangeRate ); From a723a42ed399ec3a19a03c1e8a3e3085f79f04b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Thu, 4 Mar 2021 13:32:36 -0300 Subject: [PATCH 09/21] swaps: custom gas price (#2337) * fix * comment * controller --- app/components/UI/Swaps/QuotesView.js | 60 +++++++++++++-------------- package.json | 2 +- yarn.lock | 8 ++-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 3c8726475e3..38f76b5c3df 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -320,11 +320,16 @@ function SwapsQuotesView({ }, [quoteValues, quotes]); /* Get the selected quote, by default is topAggId */ - const selectedQuote = useMemo(() => allQuotes.find(quote => quote.aggregator === selectedQuoteId), [ + const selectedQuote = useMemo(() => allQuotes.find(quote => quote?.aggregator === selectedQuoteId), [ allQuotes, selectedQuoteId ]); - const selectedQuoteValue = useMemo(() => quoteValues[selectedQuoteId], [quoteValues, selectedQuoteId]); + const selectedQuoteValue = useMemo(() => quoteValues[selectedQuoteId], [ + // eslint-disable-next-line react-hooks/exhaustive-deps + quoteValues[selectedQuoteId], + quoteValues, + selectedQuoteId + ]); /* gas estimations */ const gasPrice = useMemo( @@ -343,22 +348,6 @@ function SwapsQuotesView({ [customGasLimit, selectedQuote] ); - /* Total gas fee in decimal */ - const gasFee = useMemo(() => { - if (customGasPrice) { - return util.calcTokenAmount(customGasPrice * gasLimit, 18); - } - return selectedQuoteValue?.ethFee; - }, [selectedQuoteValue, customGasPrice, gasLimit]); - - /* Maximum gas fee in decimal */ - const maxGasFee = useMemo(() => { - if (customGasPrice && selectedQuote?.maxGas) { - return util.calcTokenAmount(customGasPrice * selectedQuote?.maxGas, 18); - } - return selectedQuoteValue?.maxEthFee; - }, [selectedQuote, selectedQuoteValue, customGasPrice]); - /* Balance */ const balance = useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits: true }); const [hasEnoughTokenBalance, missingTokenBalance, hasEnoughEthBalance, missingEthBalance] = useMemo(() => { @@ -370,12 +359,12 @@ function SwapsQuotesView({ const ethAmountBN = sourceToken.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS ? sourceBN : new BigNumber(0); const ethBalanceBN = new BigNumber(accounts[selectedAddress].balance); - const gasBN = new BigNumber((maxGasFee && toWei(maxGasFee)) || 0); + const gasBN = new BigNumber(selectedQuoteValue?.maxEthFee ? toWei(selectedQuoteValue.maxEthFee) : 0); const hasEnoughEthBalance = ethBalanceBN.gte(gasBN.plus(ethAmountBN)); const missingEthBalance = hasEnoughEthBalance ? null : gasBN.plus(ethAmountBN).minus(ethBalanceBN); return [hasEnoughTokenBalance, missingTokenBalance, hasEnoughEthBalance, missingEthBalance]; - }, [accounts, balance, maxGasFee, selectedAddress, sourceAmount, sourceToken.address]); + }, [accounts, balance, selectedQuoteValue, selectedAddress, sourceAmount, sourceToken.address]); /* Selected quote slippage */ const shouldDisplaySlippage = useMemo( @@ -485,8 +474,8 @@ function SwapsQuotesView({ custom_slippage: slippage !== AppConstants.SWAPS.DEFAULT_SLIPPAGE, best_quote_source: selectedQuote.aggregator, available_quotes: allQuotes.length, - network_fees_USD: weiToFiat(toWei(gasFee), conversionRate, currentCurrency), - network_fees_ETH: renderFromWei(toWei(gasFee)), + network_fees_USD: weiToFiat(toWei(selectedQuoteValue?.ethFee), conversionRate, currentCurrency), + network_fees_ETH: renderFromWei(toWei(selectedQuoteValue?.ethFee)), other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote }, paramsForAnalytics: { @@ -511,7 +500,7 @@ function SwapsQuotesView({ allQuotes, selectedQuoteId, conversionRate, - gasFee + selectedQuoteValue ] ); @@ -631,7 +620,7 @@ function SwapsQuotesView({ available_quotes: allQuotes.length, best_quote_source: selectedQuote.aggregator, other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, - gas_fees: weiToFiat(toWei(gasFee), conversionRate, currentCurrency), + gas_fees: weiToFiat(toWei(selectedQuoteValue?.ethFee), conversionRate, currentCurrency), custom_spend_limit_set: originalAmount !== currentAmount, custom_spend_limit_amount: currentAmount }; @@ -644,7 +633,7 @@ function SwapsQuotesView({ conversionRate, currentCurrency, destinationToken, - gasFee, + selectedQuoteValue, hasEnoughTokenBalance, originalApprovalTransaction, selectedQuote, @@ -656,8 +645,11 @@ function SwapsQuotesView({ const onHandleGasFeeSelection = useCallback( (gas, gasPrice, details) => { + const { SwapsController } = Engine.context; setCustomGasPrice(gasPrice); setCustomGasLimit(gas); + const hexGasPrice = new BigNumber(gasPrice).toString(16); + SwapsController.updateSelectedQuoteWithGasPrice(hexGasPrice); InteractionManager.runAfterInteractions(() => { const parameters = { speed_set: details.mode === 'advanced' ? undefined : details.mode, @@ -839,7 +831,7 @@ function SwapsQuotesView({ useEffect(() => { let maxFetchTime = 0; allQuotes.forEach(quote => { - maxFetchTime = Math.max(maxFetchTime, quote.fetchTime); + maxFetchTime = Math.max(maxFetchTime, quote?.fetchTime); }); setAllQuotesFetchTime(maxFetchTime); }, [allQuotes]); @@ -1184,10 +1176,14 @@ function SwapsQuotesView({ - {renderFromWei(toWei(gasFee))} ETH + {renderFromWei(toWei(selectedQuoteValue?.ethFee))} ETH - {` ${weiToFiat(toWei(gasFee), conversionRate, currentCurrency)}`} + {` ${weiToFiat( + toWei(selectedQuoteValue?.ethFee), + conversionRate, + currentCurrency + )}`} @@ -1204,9 +1200,13 @@ function SwapsQuotesView({ - {renderFromWei(toWei(maxGasFee))} ETH + {renderFromWei(toWei(selectedQuoteValue?.maxEthFee))} ETH - {` ${weiToFiat(toWei(maxGasFee), conversionRate, currentCurrency)}`} + {` ${weiToFiat( + toWei(selectedQuoteValue?.maxEthFee), + conversionRate, + currentCurrency + )}`} diff --git a/package.json b/package.json index 48c651b549b..cacd1e468a8 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "react-native-level-fs/**/semver": "^4.3.2" }, "dependencies": { - "@estebanmino/controllers": "^3.3.15", + "@estebanmino/controllers": "^3.3.16", "@exodus/react-native-payments": "https://github.com/wachunei/react-native-payments.git#package-json-hack", "@metamask/contract-metadata": "^1.22.0", "@metamask/controllers": "^6.0.1", diff --git a/yarn.lock b/yarn.lock index 020fcda6e05..35b0b6a3a7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -909,10 +909,10 @@ dependencies: "@types/hammerjs" "^2.0.36" -"@estebanmino/controllers@^3.3.15": - version "3.3.15" - resolved "https://registry.yarnpkg.com/@estebanmino/controllers/-/controllers-3.3.15.tgz#356efc5d6ac8b28bf9e5a1c3abe389288f480914" - integrity sha512-ecnKBwmSrQcGBGqSplcOyYuuKnFmHU/G/SPwt+7utydzw7P+4SV2FpVUo66mtPu6E9WJOJCg5rSbujdoLyAnag== +"@estebanmino/controllers@^3.3.16": + version "3.3.16" + resolved "https://registry.yarnpkg.com/@estebanmino/controllers/-/controllers-3.3.16.tgz#bcf299b5c98716b711543ca14d289d43c6cc74b5" + integrity sha512-Se/JSLuzmjw0yOdDlsI3lagas6T0VxZlVIs0VqoC/cSmowmgJiIxQzlpWYYLzHAYYvfBs569jDgTI2Bs6bZUFg== dependencies: "@metamask/contract-metadata" "^1.22.0" abort-controller "^3.0.0" From 32630d94c2c972afd6e2e69a97d0ca5a2be54949 Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Thu, 4 Mar 2021 13:36:35 -0300 Subject: [PATCH 10/21] 588 --- ios/MetaMask.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index b9bb9e91c6b..bf569a0306a 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 = 587; + CURRENT_PROJECT_VERSION = 588; DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -913,7 +913,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 587; + CURRENT_PROJECT_VERSION = 588; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; FRAMEWORK_SEARCH_PATHS = ( From 7162fd423b5f1c29f7764eed1c59899fa05f81a5 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Thu, 4 Mar 2021 14:50:16 -0300 Subject: [PATCH 11/21] Swaps: Fix disabled button opacity (#2339) --- .../__snapshots__/index.test.js.snap | 6 ------ app/components/UI/AssetActionButton/index.js | 7 +++---- .../UI/Swaps/components/AssetSwapButton.js | 15 ++++++++------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap index 43cef6abc9b..1f3b9e6cef1 100644 --- a/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap +++ b/app/components/UI/AssetActionButton/__snapshots__/index.test.js.snap @@ -12,7 +12,6 @@ exports[`AssetActionButtons should render correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > @@ -68,7 +67,6 @@ exports[`AssetActionButtons should render type add correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > @@ -124,7 +122,6 @@ exports[`AssetActionButtons should render type information correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > @@ -180,7 +177,6 @@ exports[`AssetActionButtons should render type receive correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > @@ -236,7 +232,6 @@ exports[`AssetActionButtons should render type send correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > @@ -292,7 +287,6 @@ exports[`AssetActionButtons should render type swap correctly 1`] = ` "minWidth": 60, }, undefined, - undefined, ] } > diff --git a/app/components/UI/AssetActionButton/index.js b/app/components/UI/AssetActionButton/index.js index 79cff05ea3e..7ff41bec459 100644 --- a/app/components/UI/AssetActionButton/index.js +++ b/app/components/UI/AssetActionButton/index.js @@ -88,11 +88,11 @@ function getIcon(type) { } } -function AssetActionButton({ onPress, icon, label, disabled, style }) { +function AssetActionButton({ onPress, icon, label, disabled }) { return ( {icon && (typeof icon === 'string' ? getIcon(icon) : icon)} @@ -107,8 +107,7 @@ AssetActionButton.propTypes = { onPress: PropTypes.func, icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), label: PropTypes.string, - disabled: PropTypes.bool, - style: PropTypes.object + disabled: PropTypes.bool }; export default AssetActionButton; diff --git a/app/components/UI/Swaps/components/AssetSwapButton.js b/app/components/UI/Swaps/components/AssetSwapButton.js index 072d10a822f..c8632a4b983 100644 --- a/app/components/UI/Swaps/components/AssetSwapButton.js +++ b/app/components/UI/Swaps/components/AssetSwapButton.js @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import Text from '../../../Base/Text'; import AssetActionButton from '../../AssetActionButton'; @@ -27,12 +27,13 @@ function AssetSwapButton({ isFeatureLive, isNetworkAllowed, isAssetAllowed, onPr }, [isAssetAllowed, isFeatureLive, isNetworkAllowed]); return ( <> - + + + Date: Tue, 9 Mar 2021 11:49:12 -0300 Subject: [PATCH 12/21] Swaps: Fix use max (#2361) --- app/components/UI/Swaps/QuotesView.js | 6 +++--- .../UI/Swaps/components/TransactionsEditionModal.js | 4 ++-- app/components/UI/Swaps/index.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 38f76b5c3df..a7e240be75a 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -353,7 +353,7 @@ function SwapsQuotesView({ const [hasEnoughTokenBalance, missingTokenBalance, hasEnoughEthBalance, missingEthBalance] = useMemo(() => { // Token const sourceBN = new BigNumber(sourceAmount); - const tokenBalanceBN = new BigNumber(balance.toString()); + const tokenBalanceBN = new BigNumber(balance.toString(10)); const hasEnoughTokenBalance = tokenBalanceBN.gte(sourceBN); const missingTokenBalance = hasEnoughTokenBalance ? null : sourceBN.minus(tokenBalanceBN); @@ -594,14 +594,14 @@ function SwapsQuotesView({ const originalApprovalTransactionEncodedAmount = decodeApproveData(originalApprovalTransaction.data) .encodedAmount; const originalAmount = fromTokenMinimalUnitString( - hexToBN(originalApprovalTransactionEncodedAmount).toString(), + hexToBN(originalApprovalTransactionEncodedAmount).toString(10), sourceToken.decimals ); const currentApprovalTransactionEncodedAmount = approvalTransaction ? decodeApproveData(approvalTransaction.data).encodedAmount : '0'; const currentAmount = fromTokenMinimalUnitString( - hexToBN(currentApprovalTransactionEncodedAmount).toString(), + hexToBN(currentApprovalTransactionEncodedAmount).toString(10), sourceToken.decimals ); diff --git a/app/components/UI/Swaps/components/TransactionsEditionModal.js b/app/components/UI/Swaps/components/TransactionsEditionModal.js index 628a5227ef1..0b2538322aa 100644 --- a/app/components/UI/Swaps/components/TransactionsEditionModal.js +++ b/app/components/UI/Swaps/components/TransactionsEditionModal.js @@ -59,7 +59,7 @@ function TransactionsEditionModal({ const uint = toTokenMinimalUnit( spendLimitUnlimitedSelected ? approvalTransactionAmount : approvalCustomValue, sourceToken.decimals - ).toString(); + ).toString(10); const approvalData = generateApproveData({ spender: SWAPS_CONTRACT_ADDRESS, value: Number(uint).toString(16) @@ -85,7 +85,7 @@ function TransactionsEditionModal({ setApprovalTransaction(originalApprovalTransaction); if (originalApprovalTransaction) { const approvalTransactionAmount = decodeApproveData(originalApprovalTransaction.data).encodedAmount; - const amountDec = hexToBN(approvalTransactionAmount).toString(); + const amountDec = hexToBN(approvalTransactionAmount).toString(10); setApprovalTransactionAmount(fromTokenMinimalUnitString(amountDec, sourceToken.decimals)); } }, [originalApprovalTransaction, sourceToken.decimals, setApprovalTransaction]); diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index c3eb263c915..06fdd93a6c1 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -302,7 +302,7 @@ function SwapsAmountView({ setQuotesNavigationsParams( sourceToken?.address, destinationToken?.address, - toTokenMinimalUnit(amount, sourceToken?.decimals).toString(), + toTokenMinimalUnit(amount, sourceToken?.decimals).toString(10), slippage ) ); @@ -340,7 +340,7 @@ function SwapsAmountView({ if (!sourceToken || !balanceAsUnits) { return; } - setAmount(fromTokenMinimalUnitString(balanceAsUnits.toString(), sourceToken.decimals)); + setAmount(fromTokenMinimalUnitString(balanceAsUnits.toString(10), sourceToken.decimals)); }, [balanceAsUnits, sourceToken]); const handleSlippageChange = useCallback(value => { From c37ddf7e48167770894310a61c86c9a83173e7dd Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Tue, 9 Mar 2021 12:08:07 -0300 Subject: [PATCH 13/21] Swaps: Use contract balance when token is not added (#2360) --- app/components/UI/Swaps/index.js | 67 ++++++++++++++++++++++---- app/components/UI/Swaps/utils/index.js | 4 ++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/app/components/UI/Swaps/index.js b/app/components/UI/Swaps/index.js index 06fdd93a6c1..4e3ef506ebf 100644 --- a/app/components/UI/Swaps/index.js +++ b/app/components/UI/Swaps/index.js @@ -5,9 +5,16 @@ import { connect } from 'react-redux'; import { NavigationContext } from 'react-navigation'; import { View as AnimatableView } from 'react-native-animatable'; import IonicIcon from 'react-native-vector-icons/Ionicons'; +import numberToBN from 'number-to-bn'; import Logger from '../../../util/Logger'; import { toChecksumAddress } from 'ethereumjs-util'; -import { balanceToFiat, fromTokenMinimalUnitString, toTokenMinimalUnit, weiToFiat } from '../../../util/number'; +import { + balanceToFiat, + fromTokenMinimalUnitString, + renderFromTokenMinimalUnit, + toTokenMinimalUnit, + weiToFiat +} from '../../../util/number'; import { swapsUtils } from '@estebanmino/controllers'; import { ANALYTICS_EVENT_OPTS } from '../../../util/analytics'; @@ -26,7 +33,7 @@ import AppConstants from '../../../core/AppConstants'; import { getEtherscanAddressUrl } from '../../../util/etherscan'; import { strings } from '../../../../locales/i18n'; import { colors } from '../../../styles/common'; -import { setQuotesNavigationsParams } from './utils'; +import { setQuotesNavigationsParams, isSwapsETH } from './utils'; import { getSwapsAmountNavbar } from '../Navbar'; import Onboarding from './components/Onboarding'; @@ -156,6 +163,8 @@ function SwapsAmountView({ ); const [destinationToken, setDestinationToken] = useState(null); const [hasDismissedTokenAlert, setHasDismissedTokenAlert] = useState(true); + const [contractBalance, setContractBalance] = useState(null); + const [contractBalanceAsUnits, setContractBalanceAsUnits] = useState(numberToBN(0)); const [isSourceModalVisible, toggleSourceModal] = useModalHandler(false); const [isDestinationModalVisible, toggleDestinationModal] = useModalHandler(false); @@ -241,6 +250,26 @@ function SwapsAmountView({ setHasDismissedTokenAlert(false); }, [destinationToken]); + const isTokenInBalances = + sourceToken && !isSwapsETH(sourceToken) ? toChecksumAddress(sourceToken.address) in balances : false; + + useEffect(() => { + (async () => { + if (sourceToken && !isSwapsETH(sourceToken) && !isTokenInBalances) { + setContractBalance(null); + setContractBalanceAsUnits(numberToBN(0)); + const { AssetsContractController } = Engine.context; + try { + const balance = await AssetsContractController.getBalanceOf(sourceToken.address, selectedAddress); + setContractBalanceAsUnits(balance); + setContractBalance(renderFromTokenMinimalUnit(balance, sourceToken.decimals)); + } catch (e) { + // Don't validate balance if error + } + } + })(); + }, [isTokenInBalances, selectedAddress, sourceToken]); + const hasInvalidDecimals = useMemo(() => { if (sourceToken) { return amount?.split('.')[1]?.length > sourceToken.decimals; @@ -253,8 +282,12 @@ function SwapsAmountView({ hasInvalidDecimals, sourceToken ]); - const balance = useBalance(accounts, balances, selectedAddress, sourceToken); - const balanceAsUnits = useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits: true }); + const controllerBalance = useBalance(accounts, balances, selectedAddress, sourceToken); + const controllerBalanceAsUnits = useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits: true }); + + const balance = isSwapsETH(sourceToken) || isTokenInBalances ? controllerBalance : contractBalance; + const balanceAsUnits = + isSwapsETH(sourceToken) || isTokenInBalances ? controllerBalanceAsUnits : contractBalanceAsUnits; const hasBalance = useMemo(() => { if (!balanceAsUnits || !sourceToken) { return false; @@ -275,7 +308,7 @@ function SwapsAmountView({ return undefined; } let balanceFiat; - if (sourceToken.address === SWAPS_ETH_ADDRESS) { + if (isSwapsETH(sourceToken)) { balanceFiat = weiToFiat(toTokenMinimalUnit(amount, sourceToken?.decimals), conversionRate, currentCurrency); } else { const sourceAddress = toChecksumAddress(sourceToken.address); @@ -286,17 +319,22 @@ function SwapsAmountView({ }, [amount, conversionRate, currentCurrency, hasInvalidDecimals, sourceToken, tokenExchangeRates]); const destinationTokenHasEnoughOcurrances = useMemo(() => { - if (!destinationToken || destinationToken?.address === SWAPS_ETH_ADDRESS) { + if (!destinationToken || isSwapsETH(destinationToken)) { return true; } return destinationToken?.occurances > TOKEN_MINIMUM_SOURCES; }, [destinationToken]); /* Navigation handler */ - const handleGetQuotesPress = useCallback(() => { + const handleGetQuotesPress = useCallback(async () => { if (hasInvalidDecimals) { return; } + if (!isSwapsETH(sourceToken) && !isTokenInBalances && !balanceAsUnits?.isZero()) { + const { AssetsController } = Engine.context; + const { address, symbol, decimals } = sourceToken; + await AssetsController.addToken(address, symbol, decimals); + } return navigation.navigate( 'SwapsQuotesView', setQuotesNavigationsParams( @@ -306,7 +344,16 @@ function SwapsAmountView({ slippage ) ); - }, [amount, destinationToken, hasInvalidDecimals, navigation, slippage, sourceToken]); + }, [ + amount, + balanceAsUnits, + destinationToken, + hasInvalidDecimals, + isTokenInBalances, + navigation, + slippage, + sourceToken + ]); /* Keypad Handlers */ const handleKeypadChange = useCallback( @@ -434,7 +481,7 @@ function SwapsAmountView({ strings('swaps.available_to_swap', { asset: `${balance} ${sourceToken.symbol}` })} - {sourceToken.address !== SWAPS_ETH_ADDRESS && hasBalance && ( + {!isSwapsETH(sourceToken) && hasBalance && ( {' '} {strings('swaps.use_max')} @@ -478,7 +525,7 @@ function SwapsAmountView({ /> - {Boolean(destinationToken) && destinationToken?.address !== SWAPS_ETH_ADDRESS ? ( + {Boolean(destinationToken) && !isSwapsETH(destinationToken) ? ( destinationTokenHasEnoughOcurrances ? ( diff --git a/app/components/UI/Swaps/utils/index.js b/app/components/UI/Swaps/utils/index.js index 3ebcde802ca..31bf2a6f10c 100644 --- a/app/components/UI/Swaps/utils/index.js +++ b/app/components/UI/Swaps/utils/index.js @@ -3,6 +3,10 @@ import BigNumber from 'bignumber.js'; import { swapsUtils } from '@estebanmino/controllers'; import { strings } from '../../../../../locales/i18n'; +export function isSwapsETH(token) { + return Boolean(token) && token?.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS; +} + /** * Sets required parameters for Swaps Quotes View * @param {string} sourceTokenAddress Token contract address used as swaps source From 7f41d0871d9ddc53e67c08dc6f3038956fdfe770 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Tue, 9 Mar 2021 15:50:07 -0300 Subject: [PATCH 14/21] Swaps: use destination amount when no rate is available (#2362) --- app/components/UI/Swaps/QuotesView.js | 28 ++++++++++----- .../UI/Swaps/components/QuotesModal.js | 35 ++++++++++++++----- .../UI/Swaps/components/TokenSelectModal.js | 4 +-- app/components/UI/Swaps/utils/useBalance.js | 4 +-- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index a7e240be75a..da2827123fd 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -6,7 +6,6 @@ import IonicIcon from 'react-native-vector-icons/Ionicons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import FAIcon from 'react-native-vector-icons/FontAwesome'; import BigNumber from 'bignumber.js'; -import { toChecksumAddress } from 'ethereumjs-util'; import { NavigationContext } from 'react-navigation'; import { swapsUtils, util } from '@estebanmino/controllers'; @@ -20,8 +19,9 @@ import { toWei, weiToFiat } from '../../../util/number'; +import { safeToChecksumAddress } from '../../../util/address'; import { apiEstimateModifiedToWEI } from '../../../util/custom-gas'; -import { getErrorMessage, getFetchParams, getQuotesNavigationsParams } from './utils'; +import { getErrorMessage, getFetchParams, getQuotesNavigationsParams, isSwapsETH } from './utils'; import { colors } from '../../../styles/common'; import { strings } from '../../../../locales/i18n'; @@ -210,7 +210,7 @@ async function resetAndStartPolling({ slippage, sourceToken, destinationToken, s // ff the token is not in the wallet, we'll add it if ( destinationToken.address !== swapsUtils.ETH_SWAPS_TOKEN_ADDRESS && - !contractExchangeRates[toChecksumAddress(destinationToken.address)] + !contractExchangeRates[safeToChecksumAddress(destinationToken.address)] ) { const { address, symbol, decimals } = destinationToken; await AssetsController.addToken(address, symbol, decimals); @@ -221,7 +221,7 @@ async function resetAndStartPolling({ slippage, sourceToken, destinationToken, s ); } const destinationTokenConversionRate = - TokenRatesController.state.contractExchangeRates[toChecksumAddress(destinationToken.address)] || 0; + TokenRatesController.state.contractExchangeRates[safeToChecksumAddress(destinationToken.address)] || 0; // TODO: destinationToken could be the 0 address for ETH, also tokens that aren't on the wallet const fetchParams = getFetchParams({ @@ -280,6 +280,15 @@ function SwapsQuotesView({ token => token.address?.toLowerCase() === destinationTokenAddress.toLowerCase() ); + const hasConversionRate = + Boolean(destinationToken) && + (isSwapsETH(destinationToken) || + Boolean( + Engine.context.TokenRatesController.state.contractExchangeRates?.[ + safeToChecksumAddress(destinationToken.address) + ] + )); + /* State */ const [firstLoadTime, setFirstLoadTime] = useState(Date.now()); const [isFirstLoad, setIsFirstLoad] = useState(true); @@ -312,12 +321,12 @@ function SwapsQuotesView({ return []; } - const orderedValues = Object.values(quoteValues).sort( - (a, b) => Number(b.overallValueOfQuote) - Number(a.overallValueOfQuote) - ); + const orderedAggregators = hasConversionRate + ? Object.values(quoteValues).sort((a, b) => Number(b.overallValueOfQuote) - Number(a.overallValueOfQuote)) + : Object.values(quotes).sort((a, b) => new BigNumber(b.destinationAmount).comparedTo(a.destinationAmount)); - return orderedValues.map(quoteValue => quotes[quoteValue.aggregator]); - }, [quoteValues, quotes]); + return orderedAggregators.map(quoteValue => quotes[quoteValue.aggregator]); + }, [hasConversionRate, quoteValues, quotes]); /* Get the selected quote, by default is topAggId */ const selectedQuote = useMemo(() => allQuotes.find(quote => quote?.aggregator === selectedQuoteId), [ @@ -1292,6 +1301,7 @@ function SwapsQuotesView({ sourceToken={sourceToken} destinationToken={destinationToken} selectedQuote={selectedQuoteId} + showOverallValue={hasConversionRate} /> {index === 0 ? ( - - - - {strings('swaps.best')} - + showOverallValue ? ( + + + + {strings('swaps.best')} + + - - ) : ( + ) : ( + - + ) + ) : showOverallValue ? ( - {weiToFiat( @@ -407,6 +413,16 @@ function QuotesModal({ currentCurrency )} + ) : ( + + - + {renderFromTokenMinimalUnit( + new BigNumber(quotes[0].destinationAmount) + .minus(quote.destinationAmount) + .toString(10), + destinationToken.decimals + )} + )} ({ diff --git a/app/components/UI/Swaps/components/TokenSelectModal.js b/app/components/UI/Swaps/components/TokenSelectModal.js index a552b9ac68c..3af36b70f47 100644 --- a/app/components/UI/Swaps/components/TokenSelectModal.js +++ b/app/components/UI/Swaps/components/TokenSelectModal.js @@ -5,12 +5,12 @@ import { FlatList } from 'react-native-gesture-handler'; import Modal from 'react-native-modal'; import Icon from 'react-native-vector-icons/Ionicons'; import Fuse from 'fuse.js'; -import { toChecksumAddress } from 'ethereumjs-util'; import { connect } from 'react-redux'; import { swapsUtils } from '@estebanmino/controllers'; import Device from '../../../../util/Device'; import { balanceToFiat, hexToBN, renderFromTokenMinimalUnit, renderFromWei, weiToFiat } from '../../../../util/number'; +import { safeToChecksumAddress } from '../../../../util/address'; import { strings } from '../../../../../locales/i18n'; import { colors, fontStyles } from '../../../../styles/common'; @@ -120,7 +120,7 @@ function TokenSelectModal({ const renderItem = useCallback( ({ item }) => { - const itemAddress = toChecksumAddress(item.address); + const itemAddress = safeToChecksumAddress(item.address); let balance, balanceFiat; if (item.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS) { diff --git a/app/components/UI/Swaps/utils/useBalance.js b/app/components/UI/Swaps/utils/useBalance.js index ecc423832e6..4d4e09bfee1 100644 --- a/app/components/UI/Swaps/utils/useBalance.js +++ b/app/components/UI/Swaps/utils/useBalance.js @@ -1,8 +1,8 @@ import { swapsUtils } from '@estebanmino/controllers'; -import { toChecksumAddress } from '@walletconnect/utils'; import { useMemo } from 'react'; import numberToBN from 'number-to-bn'; import { renderFromTokenMinimalUnit, renderFromWei } from '../../../../util/number'; +import { safeToChecksumAddress } from '../../../../util/address'; function useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits = false } = {}) { const balance = useMemo(() => { @@ -16,7 +16,7 @@ function useBalance(accounts, balances, selectedAddress, sourceToken, { asUnits } return renderFromWei(accounts[selectedAddress] && accounts[selectedAddress].balance); } - const tokenAddress = toChecksumAddress(sourceToken.address); + const tokenAddress = safeToChecksumAddress(sourceToken.address); if (tokenAddress in balances) { if (asUnits) { From b20a652ebdbacbb5fa0a5932f4101c4b085e8f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Tue, 9 Mar 2021 23:04:31 -0300 Subject: [PATCH 15/21] swaps: improve custom gas price and limit selection (#2358) * onHandleGasFeeSelection * minimumGasLimit * fixes * fixminimumlimit --- app/components/UI/CustomGas/index.js | 13 ++- app/components/UI/Swaps/QuotesView.js | 86 ++++++++++--------- .../components/TransactionsEditionModal.js | 6 +- locales/en.json | 1 + package.json | 2 +- yarn.lock | 8 +- 6 files changed, 68 insertions(+), 48 deletions(-) diff --git a/app/components/UI/CustomGas/index.js b/app/components/UI/CustomGas/index.js index 9935e63d620..6c25be7e6af 100644 --- a/app/components/UI/CustomGas/index.js +++ b/app/components/UI/CustomGas/index.js @@ -225,6 +225,10 @@ class CustomGas extends PureComponent { * Object BN containing estimated gas limit */ gas: PropTypes.object, + /** + * Gas limit estimation that should be the minimum value to set + */ + minimumGasLimit: PropTypes.string, /** * Object BN containing gas price */ @@ -417,7 +421,10 @@ class CustomGas extends PureComponent { else if (bnValue && !isBN(bnValue)) warningGasLimit = strings('transaction.invalid_gas'); else if (bnValue.lt(new BN(21000)) || bnValue.gt(new BN(7920028))) warningGasLimit = strings('custom_gas.warning_gas_limit'); - + else if (this.props.minimumGasLimit && bnValue.lt(new BN(this.props.minimumGasLimit))) + warningGasLimit = strings('custom_gas.warning_gas_limit_estimated', { + gas: this.props.minimumGasLimit.toString(10) + }); this.setState({ customGasLimit: value, customGasLimitBN: bnValue, @@ -450,7 +457,7 @@ class CustomGas extends PureComponent { //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 { gasSpeedSelected, customGasLimit, customGasPriceBNWei } = this.state; const { review, gas, @@ -459,7 +466,7 @@ class CustomGas extends PureComponent { basicGasEstimates: { fastGwei, averageGwei, safeLowGwei } } = this.props; if (advancedCustomGas) { - handleGasFeeSelection(new BN(customGasLimit), apiEstimateModifiedToWEI(customGasPrice), { + handleGasFeeSelection(new BN(customGasLimit), customGasPriceBNWei, { mode: 'advanced' }); } else { diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index da2827123fd..2384eef5560 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -20,7 +20,6 @@ import { weiToFiat } from '../../../util/number'; import { safeToChecksumAddress } from '../../../util/address'; -import { apiEstimateModifiedToWEI } from '../../../util/custom-gas'; import { getErrorMessage, getFetchParams, getQuotesNavigationsParams, isSwapsETH } from './utils'; import { colors } from '../../../styles/common'; import { strings } from '../../../../locales/i18n'; @@ -243,10 +242,7 @@ async function resetAndStartPolling({ slippage, sourceToken, destinationToken, s */ const gasLimitWithMultiplier = (gasLimit, multiplier) => { if (!gasLimit || !multiplier) return; - return new BigNumber(gasLimit) - .times(multiplier) - .integerValue() - .toString(16); + return new BigNumber(gasLimit).times(multiplier).integerValue(); }; function SwapsQuotesView({ @@ -265,7 +261,8 @@ function SwapsQuotesView({ quotes, quoteValues, error, - quoteRefreshSeconds + quoteRefreshSeconds, + usedGasPrice }) { const navigation = useContext(NavigationContext); /* Get params from navigation */ @@ -341,18 +338,15 @@ function SwapsQuotesView({ ]); /* gas estimations */ - const gasPrice = useMemo( - () => - customGasPrice - ? customGasPrice.toString(16) - : !!apiGasPrice && apiEstimateModifiedToWEI(apiGasPrice?.averageGwei).toString(16), - [customGasPrice, apiGasPrice] - ); + const gasPrice = useMemo(() => customGasPrice?.toString(16) || usedGasPrice?.toString(16), [ + customGasPrice, + usedGasPrice + ]); const gasLimit = useMemo( () => (Boolean(customGasLimit) && BNToHex(customGasLimit)) || - gasLimitWithMultiplier(selectedQuote?.gasEstimate, selectedQuote?.gasMultiplier) || + gasLimitWithMultiplier(selectedQuote?.gasEstimate, selectedQuote?.gasMultiplier)?.toString(16) || selectedQuote?.maxGas?.toString(16), [customGasLimit, selectedQuote] ); @@ -368,7 +362,7 @@ function SwapsQuotesView({ const ethAmountBN = sourceToken.address === swapsUtils.ETH_SWAPS_TOKEN_ADDRESS ? sourceBN : new BigNumber(0); const ethBalanceBN = new BigNumber(accounts[selectedAddress].balance); - const gasBN = new BigNumber(selectedQuoteValue?.maxEthFee ? toWei(selectedQuoteValue.maxEthFee) : 0); + const gasBN = new BigNumber(selectedQuoteValue?.maxEthFee || '0x0'); const hasEnoughEthBalance = ethBalanceBN.gte(gasBN.plus(ethAmountBN)); const missingEthBalance = hasEnoughEthBalance ? null : gasBN.plus(ethAmountBN).minus(ethBalanceBN); @@ -530,8 +524,8 @@ function SwapsQuotesView({ best_quote_source: selectedQuote.aggregator, available_quotes: allQuotes, other_quote_selected: allQuotes[selectedQuoteId] === selectedQuote, - network_fees_USD: weiToFiat(toWei(selectedQuoteValue.ethFee), conversionRate, 'usd'), - network_fees_ETH: renderFromWei(toWei(selectedQuoteValue.ethFee)) + network_fees_USD: weiToFiat(toWei(selectedQuoteValue?.ethFee), conversionRate, 'usd'), + network_fees_ETH: renderFromWei(toWei(selectedQuoteValue?.ethFee)) }; Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAP_STARTED, {}); Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.SWAP_STARTED, parameters, true); @@ -653,27 +647,35 @@ function SwapsQuotesView({ ]); const onHandleGasFeeSelection = useCallback( - (gas, gasPrice, details) => { + (customGasLimit, customGasPrice, details) => { const { SwapsController } = Engine.context; - setCustomGasPrice(gasPrice); - setCustomGasLimit(gas); - const hexGasPrice = new BigNumber(gasPrice).toString(16); - SwapsController.updateSelectedQuoteWithGasPrice(hexGasPrice); - InteractionManager.runAfterInteractions(() => { - const parameters = { - speed_set: details.mode === 'advanced' ? undefined : details.mode, - gas_mode: details.mode === 'advanced' ? 'Advanced' : 'Basic', - gas_fees: weiToFiat( - toWei(util.calcTokenAmount(gasPrice * gas, 18)), - conversionRate, - currentCurrency - ) - }; - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, {}); - Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, parameters, true); - }); + const newGasLimit = new BigNumber(customGasLimit); + const newGasPrice = new BigNumber(customGasPrice); + if (newGasPrice.toString(16) !== gasPrice) { + setCustomGasPrice(newGasPrice); + SwapsController.updateQuotesWithGasPrice(newGasPrice.toString(16)); + } + if (newGasLimit.toString(16) !== gasLimit) { + setCustomGasLimit(newGasLimit); + SwapsController.updateSelectedQuoteWithGasLimit(newGasLimit.toString(16)); + } + if (newGasLimit?.toString(16) !== gasLimit || newGasPrice?.toString(16) !== gasPrice) { + InteractionManager.runAfterInteractions(() => { + const parameters = { + speed_set: details.mode === 'advanced' ? undefined : details.mode, + gas_mode: details.mode === 'advanced' ? 'Advanced' : 'Basic', + gas_fees: weiToFiat( + toWei(util.calcTokenAmount(newGasLimit.times(newGasPrice), 18)), + conversionRate, + currentCurrency + ) + }; + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, {}); + Analytics.trackEventWithParameters(ANALYTICS_EVENT_OPTS.GAS_FEES_CHANGED, parameters, true); + }); + } }, - [conversionRate, currentCurrency] + [conversionRate, currentCurrency, gasLimit, gasPrice] ); const handleQuotesReceivedMetric = useCallback(() => { @@ -1209,7 +1211,7 @@ function SwapsQuotesView({ - {renderFromWei(toWei(selectedQuoteValue?.maxEthFee))} ETH + {renderFromWei(toWei(selectedQuoteValue?.maxEthFee || '0x0'))} ETH {` ${weiToFiat( toWei(selectedQuoteValue?.maxEthFee), @@ -1315,6 +1317,10 @@ function SwapsQuotesView({ onHandleGasFeeSelection={onHandleGasFeeSelection} setApprovalTransaction={setApprovalTransaction} minimumSpendLimit={approvalMinimumSpendLimit} + minimumGasLimit={gasLimitWithMultiplier( + selectedQuote?.gasEstimate, + selectedQuote?.gasMultiplier + ).toString(10)} selectedQuote={selectedQuote} sourceToken={sourceToken} /> @@ -1358,7 +1364,8 @@ SwapsQuotesView.propTypes = { quoteValues: PropTypes.object, approvalTransaction: PropTypes.object, error: PropTypes.object, - quoteRefreshSeconds: PropTypes.number + quoteRefreshSeconds: PropTypes.number, + usedGasPrice: PropTypes.string }; const mapStateToProps = state => ({ @@ -1377,7 +1384,8 @@ const mapStateToProps = state => ({ quoteValues: state.engine.backgroundState.SwapsController.quoteValues, approvalTransaction: state.engine.backgroundState.SwapsController.approvalTransaction, error: state.engine.backgroundState.SwapsController.error, - quoteRefreshSeconds: state.engine.backgroundState.SwapsController.quoteRefreshSeconds + quoteRefreshSeconds: state.engine.backgroundState.SwapsController.quoteRefreshSeconds, + usedGasPrice: state.engine.backgroundState.SwapsController.usedGasPrice }); export default connect(mapStateToProps)(SwapsQuotesView); diff --git a/app/components/UI/Swaps/components/TransactionsEditionModal.js b/app/components/UI/Swaps/components/TransactionsEditionModal.js index 0b2538322aa..8bc85366019 100644 --- a/app/components/UI/Swaps/components/TransactionsEditionModal.js +++ b/app/components/UI/Swaps/components/TransactionsEditionModal.js @@ -39,7 +39,8 @@ function TransactionsEditionModal({ setApprovalTransaction, selectedQuote, sourceToken, - minimumSpendLimit + minimumSpendLimit, + minimumGasLimit }) { /* Approval transaction if any */ const [approvalTransactionAmount, setApprovalTransactionAmount] = useState(null); @@ -89,6 +90,7 @@ function TransactionsEditionModal({ setApprovalTransactionAmount(fromTokenMinimalUnitString(amountDec, sourceToken.decimals)); } }, [originalApprovalTransaction, sourceToken.decimals, setApprovalTransaction]); + return ( Date: Tue, 9 Mar 2021 23:07:39 -0300 Subject: [PATCH 16/21] 590 --- ios/MetaMask.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index bf569a0306a..b30638432d4 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 = 588; + CURRENT_PROJECT_VERSION = 590; DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -913,7 +913,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 588; + CURRENT_PROJECT_VERSION = 590; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; FRAMEWORK_SEARCH_PATHS = ( From fe1b505ea6e2d4d6876246dfd74cf7802a194d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Mi=C3=B1o?= Date: Wed, 10 Mar 2021 13:04:58 -0300 Subject: [PATCH 17/21] Swaps: transactions general view filtering (#2364) * filtercorrectly * improvefilters * bumpir * filtercorrectly --- app/components/UI/Swaps/QuotesView.js | 2 +- app/components/Views/Asset/index.js | 41 +++++++++------- .../Views/TransactionsView/index.js | 20 ++++---- app/core/Engine.js | 3 +- package.json | 2 +- yarn.lock | 47 +++++++++---------- 6 files changed, 62 insertions(+), 53 deletions(-) diff --git a/app/components/UI/Swaps/QuotesView.js b/app/components/UI/Swaps/QuotesView.js index 2384eef5560..6b8704a56b0 100644 --- a/app/components/UI/Swaps/QuotesView.js +++ b/app/components/UI/Swaps/QuotesView.js @@ -1320,7 +1320,7 @@ function SwapsQuotesView({ minimumGasLimit={gasLimitWithMultiplier( selectedQuote?.gasEstimate, selectedQuote?.gasMultiplier - ).toString(10)} + )?.toString(10)} selectedQuote={selectedQuote} sourceToken={sourceToken} /> diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index 1570c80a6a8..22ef7c967d4 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -130,15 +130,18 @@ class Asset extends PureComponent { isTransfer, transferInformation } = tx; - if (isTransfer) - return this.props.tokens.find( - ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() + + if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + if (isTransfer) + return this.props.tokens.find( + ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() + ); + return ( + (safeToChecksumAddress(from) === selectedAddress || safeToChecksumAddress(to) === selectedAddress) && + tx.status !== 'unapproved' ); - return ( - (safeToChecksumAddress(from) === selectedAddress || safeToChecksumAddress(to) === selectedAddress) && - chainId === tx.networkID && - tx.status !== 'unapproved' - ); + } + return false; }; noEthFilter = tx => { @@ -148,16 +151,18 @@ class Asset extends PureComponent { isTransfer, transferInformation } = tx; - if (isTransfer) return this.navAddress === transferInformation.contractAddress.toLowerCase(); - if ( - (from?.toLowerCase() === this.navAddress || to?.toLowerCase() === this.navAddress) && - chainId === tx.networkID && - tx.status !== 'unapproved' - ) - return true; - if (swapsTransactions[tx.id] && to?.toLowerCase() === SWAPS_CONTRACT_ADDRESS) { - const { destinationToken, sourceToken } = swapsTransactions[tx.id]; - return destinationToken.address === this.navAddress || sourceToken.address === this.navAddress; + if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + if (isTransfer) return this.navAddress === transferInformation.contractAddress.toLowerCase(); + if ( + (from?.toLowerCase() === this.navAddress || to?.toLowerCase() === this.navAddress) && + tx.status !== 'unapproved' + ) { + return true; + } + if (swapsTransactions[tx.id] && to?.toLowerCase() === SWAPS_CONTRACT_ADDRESS) { + const { destinationToken, sourceToken } = swapsTransactions[tx.id]; + return destinationToken.address === this.navAddress || sourceToken.address === this.navAddress; + } } return false; }; diff --git a/app/components/Views/TransactionsView/index.js b/app/components/Views/TransactionsView/index.js index 08831670647..ebf92070c6a 100644 --- a/app/components/Views/TransactionsView/index.js +++ b/app/components/Views/TransactionsView/index.js @@ -38,15 +38,19 @@ const TransactionsView = ({ isTransfer, transferInformation } = tx; - if (isTransfer) - return tokens.find( - ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() + if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + if (isTransfer) { + return tokens.find( + ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() + ); + } + return ( + (safeToChecksumAddress(from) === selectedAddress || + safeToChecksumAddress(to) === selectedAddress) && + tx.status !== 'unapproved' ); - return ( - (safeToChecksumAddress(from) === selectedAddress || safeToChecksumAddress(to) === selectedAddress) && - (chainId === tx.chainId || (!tx.chainId && network === tx.networkID)) && - tx.status !== 'unapproved' - ); + } + return false; }; const submittedTxs = []; diff --git a/app/core/Engine.js b/app/core/Engine.js index a319c52859b..5d0ef9d04c7 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -14,10 +14,11 @@ import { PreferencesController, TokenBalancesController, TokenRatesController, + TransactionController, TypedMessageManager } from '@metamask/controllers'; -import { TransactionController, SwapsController } from '@estebanmino/controllers'; +import { SwapsController } from '@estebanmino/controllers'; import AsyncStorage from '@react-native-community/async-storage'; diff --git a/package.json b/package.json index cd91961acab..2945908b8e5 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@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.0.1", + "@metamask/controllers": "^6.1.0", "@react-native-community/async-storage": "1.12.1", "@react-native-community/blur": "^3.6.0", "@react-native-community/checkbox": "^0.4.2", diff --git a/yarn.lock b/yarn.lock index 1b3276507e0..c09371c1457 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1564,13 +1564,14 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.23.0.tgz#c70be7f3eaeeb791651ce793b7cdc230e9780b18" integrity sha512-oTUqL9dtXtbng60DZMRsBmZ5HiOUUfEsZjuswOJ0yHO24YsW0ktCcgCJVYPv1HcOsF0SVrRtG4rtrvOl4nY+HA== -"@metamask/controllers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-6.0.1.tgz#957ea51e032ef03d93222623a19e5c9d5e36f81f" - integrity sha512-tkwAnosa5WyRu9SwOcMFF7ixI8VHXqwf//dzcTKvKE0YAadLHLymTjiA4+BISoqTFZ3JbicRr2KeSMnPGg/hnA== +"@metamask/controllers@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-6.1.0.tgz#bddad71778f5ec217015b503a10ffd29573e24eb" + integrity sha512-6TL8evEVjrYaHe306JdD1HwQWo4vU79uuBsNTRFL4nhBaWjF3lPewznKKBR2AssYClVGF0cLfWS0AXFitrV49g== dependencies: - "@metamask/contract-metadata" "^1.22.0" - await-semaphore "^0.1.3" + "@metamask/contract-metadata" "^1.23.0" + async-mutex "^0.2.6" + babel-runtime "^6.26.0" eth-ens-namehash "^2.0.8" eth-json-rpc-infura "^5.1.0" eth-keyring-controller "^6.1.0" @@ -1580,14 +1581,15 @@ eth-rpc-errors "^4.0.0" eth-sig-util "^3.0.0" ethereumjs-util "^6.1.0" - ethereumjs-wallet "^0.6.4" + ethereumjs-wallet "^1.0.1" human-standard-collectible-abi "^1.0.2" human-standard-token-abi "^2.0.0" + immer "^8.0.1" isomorphic-fetch "^3.0.0" jsonschema "^1.2.4" nanoid "^3.1.12" single-call-balance-checker-abi "^1.0.0" - uuid "^3.3.2" + uuid "^8.3.2" web3 "^0.20.7" web3-provider-engine "^16.0.1" @@ -2688,6 +2690,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-mutex@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.6.tgz#0d7a3deb978bc2b984d5908a2038e1ae2e54ff40" + integrity sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw== + dependencies: + tslib "^2.0.0" + async-mutex@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.1.tgz#7033af665f1c7cebed8b878267a43ba9e77c5f67" @@ -5566,21 +5575,6 @@ ethereumjs-wallet@^0.6.0: utf8 "^3.0.0" uuid "^3.3.2" -ethereumjs-wallet@^0.6.4: - version "0.6.5" - resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz#685e9091645cee230ad125c007658833991ed474" - integrity sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA== - dependencies: - aes-js "^3.1.1" - bs58check "^2.1.2" - ethereum-cryptography "^0.1.3" - ethereumjs-util "^6.0.0" - randombytes "^2.0.6" - safe-buffer "^5.1.2" - scryptsy "^1.2.1" - utf8 "^3.0.0" - uuid "^3.3.2" - ethereumjs-wallet@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-1.0.1.tgz#664a4bcacfc1291ca2703de066df1178938dba1c" @@ -6949,6 +6943,11 @@ immediate@^3.2.3: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" integrity sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw= +immer@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" + integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -13053,7 +13052,7 @@ 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.1.0: +tslib@^2.0.0, tslib@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== From f9b92246dce9d8b72716d103e6caaf00e0c7b13e Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Wed, 10 Mar 2021 13:11:14 -0300 Subject: [PATCH 18/21] filters --- app/components/Views/Asset/index.js | 6 ++++-- app/components/Views/TransactionsView/index.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/components/Views/Asset/index.js b/app/components/Views/Asset/index.js index 22ef7c967d4..caa84918936 100644 --- a/app/components/Views/Asset/index.js +++ b/app/components/Views/Asset/index.js @@ -131,7 +131,8 @@ class Asset extends PureComponent { transferInformation } = tx; - if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + const network = Engine.context.NetworkController.state.network; + if (chainId === tx.chainId || (!tx.chainId && network === tx.networkID)) { if (isTransfer) return this.props.tokens.find( ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() @@ -151,7 +152,8 @@ class Asset extends PureComponent { isTransfer, transferInformation } = tx; - if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + const network = Engine.context.NetworkController.state.network; + if (chainId === tx.chainId || (!tx.chainId && network === tx.networkID)) { if (isTransfer) return this.navAddress === transferInformation.contractAddress.toLowerCase(); if ( (from?.toLowerCase() === this.navAddress || to?.toLowerCase() === this.navAddress) && diff --git a/app/components/Views/TransactionsView/index.js b/app/components/Views/TransactionsView/index.js index ebf92070c6a..69042341bc0 100644 --- a/app/components/Views/TransactionsView/index.js +++ b/app/components/Views/TransactionsView/index.js @@ -38,7 +38,7 @@ const TransactionsView = ({ isTransfer, transferInformation } = tx; - if (tx.chainId ? chainId === tx.chainId : chainId === tx.networkID) { + if (chainId === tx.chainId || (!tx.chainId && network === tx.networkID)) { if (isTransfer) { return tokens.find( ({ address }) => address.toLowerCase() === transferInformation.contractAddress.toLowerCase() From 17c320f83154f03b9a3e8bc18cbb139787036a48 Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Wed, 10 Mar 2021 13:55:01 -0300 Subject: [PATCH 19/21] circle --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dd8db43a44b..29ca80eddf2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -201,14 +201,12 @@ workflows: only: - master - develop - - swaps-v1 - prep-node-deps: filters: branches: ignore: - master - develop - - swaps-v1 - lint: requires: - prep-node-deps From 505ee03dec311f9de1dd6a4b9edc0d5d41299cdf Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Wed, 10 Mar 2021 14:18:12 -0300 Subject: [PATCH 20/21] ONLY_MAINNET --- app/core/AppConstants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/AppConstants.js b/app/core/AppConstants.js index 306550d077d..3d686cb5164 100644 --- a/app/core/AppConstants.js +++ b/app/core/AppConstants.js @@ -58,7 +58,7 @@ export default { }, SWAPS: { ACTIVE: true, - ONLY_MAINNET: false, + ONLY_MAINNET: true, CLIENT_ID: 'mobile', LIVENESS_POLLING_FREQUENCY: 5 * 60 * 1000, POLL_COUNT_LIMIT: 3, From 525244fbb545a96f0bd703ead1d51344c6c6d45a Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Wed, 10 Mar 2021 14:23:56 -0300 Subject: [PATCH 21/21] rmtestnet --- app/core/Engine.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/app/core/Engine.js b/app/core/Engine.js index 9540526ea84..c0f46a229ab 100644 --- a/app/core/Engine.js +++ b/app/core/Engine.js @@ -126,8 +126,7 @@ class Engine { AssetsController: assets, KeyringController: keyring, NetworkController: network, - TransactionController: transaction, - PreferencesController: preferences + TransactionController: transaction } = this.datamodel.context; assets.setApiKey(process.env.MM_OPENSEA_KEY); @@ -144,24 +143,6 @@ class Engine { }); this.configureControllersOnNetworkChange(); Engine.instance = this; - - if (AppConstants.SWAPS.ACTIVE) { - const swapsTestInState = preferences.state.frequentRpcList.find(({ chainId }) => chainId === 1337); - if (!swapsTestInState) { - preferences.addToFrequentRpcList( - 'https://ganache-testnet.airswap-dev.codefi.network/', - '1337', - 'ETH', - 'Swaps Test Network' - ); - network.setRpcTarget( - 'https://ganache-testnet.airswap-dev.codefi.network/', - '1337', - 'ETH', - 'Swaps Test Network' - ); - } - } } return Engine.instance; }