Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: swaps on send flow when amount is insufficient #6980

Merged
merged 34 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
328168a
swaps when amount is insufficient
tommasini Aug 7, 2023
48db8ba
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Aug 21, 2023
71c4fad
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Aug 24, 2023
69dee6b
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Sep 15, 2023
9f41fa4
added new label swap tokens and re used the warning of transactions m…
tommasini Sep 15, 2023
0a083af
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Sep 21, 2023
d288988
merge main
tommasini Oct 9, 2023
350d2ca
add events for metrics purposes, remove swaps button and added the na…
tommasini Oct 9, 2023
841b44d
remove unused imports
tommasini Oct 10, 2023
544750b
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 11, 2023
b7702fe
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 12, 2023
91b9bf3
address review, by changing conditions into variables for better self…
tommasini Oct 12, 2023
035f466
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 12, 2023
e9bfa9f
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 13, 2023
8ae884c
refactor for better variable names
tommasini Oct 13, 2023
34a8a07
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 16, 2023
9347ce6
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 26, 2023
7b5cf0d
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 27, 2023
aba6299
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Oct 27, 2023
5d82d82
solve conflicts and merge main
tommasini Nov 10, 2023
abcf16d
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Nov 10, 2023
98ee256
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Nov 10, 2023
6d5c130
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Nov 13, 2023
593f446
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Nov 24, 2023
70aaf44
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 4, 2023
3f15d44
change old util to new util of ramp and new route to buy ramp
tommasini Dec 5, 2023
3377693
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 5, 2023
224dfba
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 6, 2023
577af97
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 20, 2023
e29df93
merge main and solve conflicts
tommasini Dec 21, 2023
0ff692e
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 21, 2023
3cf462f
Merge branch 'main' into feat/swaps-on-send-flow
tommasini Dec 21, 2023
36ddc1d
update snapshot
tommasini Dec 21, 2023
a7f57fd
Merge branch 'main' into feat/swaps-on-send-flow
cortisiko Dec 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions app/components/UI/Swaps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ function SwapsAmountView({

const explorer = useBlockExplorer(providerConfig, networkConfigurations);
const initialSource = route.params?.sourceToken ?? SWAPS_NATIVE_ADDRESS;
const initialDestination = route.params?.destinationToken;

const [amount, setAmount] = useState('0');
const [slippage, setSlippage] = useState(AppConstants.SWAPS.DEFAULT_SLIPPAGE);
const [isInitialLoadingTokens, setInitialLoadingTokens] = useState(false);
Expand All @@ -219,13 +221,18 @@ function SwapsAmountView({
),
),
);
const [isDestinationSet, setIsDestinationSet] = useState(false);

const [sourceToken, setSourceToken] = useState(() =>
swapsTokens?.find((token) =>
toLowerCaseEquals(token.address, initialSource),
),
);
const [destinationToken, setDestinationToken] = useState(null);
const [destinationToken, setDestinationToken] = useState(
swapsTokens?.find((token) =>
toLowerCaseEquals(token.address, initialDestination),
),
);
const [hasDismissedTokenAlert, setHasDismissedTokenAlert] = useState(true);
const [contractBalance, setContractBalance] = useState(null);
const [contractBalanceAsUnits, setContractBalanceAsUnits] = useState(
Expand Down Expand Up @@ -344,28 +351,41 @@ function SwapsAmountView({
})();
}, [swapsControllerTokens, swapsTokens]);

const canSetAnInitialSourceToken =
!isSourceSet &&
initialSource &&
swapsControllerTokens &&
swapsTokens?.length > 0 &&
!sourceToken;

useEffect(() => {
if (
!isSourceSet &&
initialSource &&
swapsControllerTokens &&
swapsTokens?.length > 0 &&
!sourceToken
) {
if (canSetAnInitialSourceToken) {
setIsSourceSet(true);
setSourceToken(
swapsTokens.find((token) =>
toLowerCaseEquals(token.address, initialSource),
),
);
}
}, [
initialSource,
isSourceSet,
sourceToken,
swapsControllerTokens,
swapsTokens,
]);
}, [canSetAnInitialSourceToken, initialSource, swapsTokens]);

const canSetAnInitialTokenDestination =
!isDestinationSet &&
initialDestination &&
swapsControllerTokens &&
swapsTokens?.length > 0 &&
!destinationToken;

useEffect(() => {
if (canSetAnInitialTokenDestination) {
setIsDestinationSet(true);
setDestinationToken(
swapsTokens.find((token) =>
toLowerCaseEquals(token.address, initialDestination),
),
);
}
}, [canSetAnInitialTokenDestination, initialDestination, swapsTokens]);

useEffect(() => {
setHasDismissedTokenAlert(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7772,80 +7772,38 @@ exports[`Amount should show an error message if balance is insufficient 1`] = `
}
testID="amount-error"
>
<View
<TouchableOpacity
onPress={[Function]}
style={
Array [
Object {
"borderRadius": 8,
"borderWidth": 1,
"flexDirection": "row",
"paddingHorizontal": 12,
"paddingVertical": 8,
},
undefined,
Object {
"backgroundColor": "#D7384719",
"borderColor": "#D73847",
},
undefined,
]
Object {
"alignItems": "center",
"backgroundColor": "#D7384719",
"borderColor": "#D73847",
"borderRadius": 8,
"borderWidth": 1,
"justifyContent": "center",
"marginHorizontal": 24,
"marginTop": 12,
"paddingHorizontal": 10,
"paddingVertical": 6,
}
}
>
<View>
<Text
style={
Array [
Object {
"color": "#24272A",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 30,
"fontWeight": "400",
"marginVertical": 2,
},
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
Object {
"fontSize": 12,
},
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
Array [
Object {
"color": "#24272A",
"flex": 1,
"fontSize": 14,
"lineHeight": 17,
},
Object {
"flex": 0,
},
],
]
<Text
style={
Object {
"color": "#24272A",
"fontFamily": "EuclidCircularB-Regular",
"fontSize": 12,
"fontWeight": "400",
"lineHeight": 16,
"textAlign": "center",
}
testID="error-message-warning"
>
Insufficient funds
</Text>
</View>
</View>
}
>
Insufficient funds
</Text>
</TouchableOpacity>
</View>
</View>
</View>
Expand Down
107 changes: 105 additions & 2 deletions app/components/Views/SendFlow/Amount/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import {
} from '../../../../../wdio/screen-objects/testIDs/Screens/AmountScreen.testIds.js';
import generateTestId from '../../../../../wdio/utils/generateTestId';
import {
selectChainId,
selectProviderType,
selectTicker,
} from '../../../../selectors/networkController';
Expand All @@ -98,8 +99,13 @@ import { selectContractBalances } from '../../../../selectors/tokenBalancesContr
import { selectSelectedAddress } from '../../../../selectors/preferencesController';
import { PREFIX_HEX_STRING } from '../../../../constants/transaction';
import Routes from '../../../../constants/navigation/Routes';
import { getRampNetworks } from '../../../../reducers/fiatOrders';
import { swapsLivenessSelector } from '../../../../reducers/swaps';
import { isSwapsAllowed } from '../../../../components/UI/Swaps/utils';
import { swapsUtils } from '@metamask/swaps-controller';
import { regex } from '../../../../util/regex';
import { AmountViewSelectorsIDs } from '../../../../../e2e/selectors/SendFlow/AmountView.selectors';
import { isNetworkRampNativeTokenSupported } from '../../../UI/Ramp/common/utils';

const KEYBOARD_OFFSET = Device.isSmallDevice() ? 80 : 120;

Expand Down Expand Up @@ -285,6 +291,18 @@ const createStyles = (colors) =>
errorMessageWrapper: {
marginVertical: 16,
},
errorBuyWrapper: {
marginHorizontal: 24,
marginTop: 12,
paddingHorizontal: 10,
paddingVertical: 6,
backgroundColor: colors.error.muted,
borderColor: colors.error.default,
borderRadius: 8,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
},
CollectibleMedia: {
width: 120,
height: 120,
Expand Down Expand Up @@ -339,6 +357,18 @@ const createStyles = (colors) =>
marginTop: 20,
marginHorizontal: 20,
},
swapOrBuyButton: { width: '100%', marginTop: 16 },
error: {
color: colors.text.default,
fontSize: 12,
lineHeight: 16,
...fontStyles.normal,
textAlign: 'center',
},
underline: {
textDecorationLine: 'underline',
...fontStyles.bold,
},
});

/**
Expand Down Expand Up @@ -434,6 +464,18 @@ class Amount extends PureComponent {
* Resets transaction state
*/
resetTransaction: PropTypes.func,
/**
* Boolean that indicates if the network supports buy
*/
isNetworkBuyNativeTokenSupported: PropTypes.bool,
/**
* Boolean that indicates if the swap is live
*/
swapsIsLive: PropTypes.bool,
/**
* String that indicates the current chain id
*/
chainId: PropTypes.string,
};

state = {
Expand Down Expand Up @@ -1188,10 +1230,49 @@ class Amount extends PureComponent {
internalPrimaryCurrencyIsCrypto,
currentBalance,
} = this.state;
const { currentCurrency } = this.props;
const {
currentCurrency,
selectedAsset,
navigation,
isNetworkBuyNativeTokenSupported,
swapsIsLive,
chainId,
} = this.props;
const colors = this.context.colors || mockTheme.colors;
const themeAppearance = this.context.themeAppearance || 'light';
const styles = createStyles(colors);
const navigateToSwap = () => {
navigation.replace('Swaps', {
screen: 'SwapsAmountView',
params: {
sourceToken: swapsUtils.NATIVE_SWAPS_TOKEN_ADDRESS,
destinationToken: selectedAsset.address,
},
});
};

const isSwappable =
!selectedAsset.isETH &&
AppConstants.SWAPS.ACTIVE &&
swapsIsLive &&
isSwapsAllowed(chainId) &&
amountError === strings('transaction.insufficient');

const navigateToBuyOrSwaps = () => {
if (isSwappable) {
Analytics.trackEventWithParameters(MetaMetricsEvents.LINK_CLICKED, {
location: 'insufficient_funds_warning',
text: 'swap_tokens',
});
navigateToSwap();
} else if (isNetworkBuyNativeTokenSupported && selectedAsset.isETH) {
Analytics.trackEventWithParameters(MetaMetricsEvents.LINK_CLICKED, {
location: 'insufficient_funds_warning',
text: 'buy_more',
});
navigation.navigate(Routes.RAMP.BUY);
}
};

return (
<View>
Expand Down Expand Up @@ -1255,7 +1336,23 @@ class Amount extends PureComponent {
style={styles.errorMessageWrapper}
{...generateTestId(Platform, AmountViewSelectorsIDs.AMOUNT_ERROR)}
>
<ErrorMessage errorMessage={amountError} />
<TouchableOpacity
onPress={navigateToBuyOrSwaps}
style={styles.errorBuyWrapper}
>
<Text style={styles.error}>{amountError}</Text>
{isNetworkBuyNativeTokenSupported && selectedAsset.isETH && (
<Text style={[styles.error, styles.underline]}>
{strings('transaction.buy_more')}
</Text>
)}

{isSwappable && (
<Text style={[styles.error, styles.underline]}>
{strings('transaction.swap_tokens')}
</Text>
)}
</TouchableOpacity>
</View>
)}
</View>
Expand Down Expand Up @@ -1423,6 +1520,12 @@ const mapStateToProps = (state, ownProps) => ({
transactionState: ownProps.transaction || state.transaction,
selectedAsset: state.transaction.selectedAsset,
isPaymentRequest: state.transaction.paymentRequest,
isNetworkBuyNativeTokenSupported: isNetworkRampNativeTokenSupported(
selectChainId(state),
getRampNetworks(state),
),
swapsIsLive: swapsLivenessSelector(state),
chainId: selectChainId(state),
});

const mapDispatchToProps = (dispatch) => ({
Expand Down
4 changes: 4 additions & 0 deletions app/core/Analytics/MetaMetrics.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ enum EVENT_NAME {
// Portfolio
PORTFOLIO_LINK_CLICKED = 'Portfolio Link Clicked',

// Link redirects
LINK_CLICKED = 'Link Clicked',

// On-ramp [LEGACY]
ONRAMP_OPENED = 'On-ramp Opened',
ONRAMP_CLOSED = 'On-ramp Closed',
Expand Down Expand Up @@ -455,6 +458,7 @@ const events = {
),

PORTFOLIO_LINK_CLICKED: generateOpt(EVENT_NAME.PORTFOLIO_LINK_CLICKED),
LINK_CLICKED: generateOpt(EVENT_NAME.LINK_CLICKED),
WALLET_SECURITY_STARTED: generateOpt(EVENT_NAME.WALLET_SECURITY_STARTED),
WALLET_SECURITY_MANUAL_BACKUP_INITIATED: generateOpt(
EVENT_NAME.WALLET_SECURITY_MANUAL_BACKUP_INITIATED,
Expand Down
3 changes: 2 additions & 1 deletion locales/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,8 @@
"fiat_conversion_not_available": "Fiat conversions are not available at this moment",
"hex_data_copied": "Hex data copied to clipboard",
"invalid_recipient": "Invalid recipient",
"invalid_recipient_description": "Check the address and make sure it's valid"
"invalid_recipient_description": "Check the address and make sure it's valid",
"swap_tokens": "Swap tokens"
},
"custom_gas": {
"total": "Total",
Expand Down
Loading