mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Implement price impact acknowledgement button (#10347)
This commit is contained in:
parent
efd280172f
commit
eeca0af5b9
@ -1735,6 +1735,12 @@
|
||||
"message": "You are about to swap $1 $2 (~$3) for $4 $5 (~$6).",
|
||||
"description": "This message represents the price slippage for the swap. $1 and $4 are a number (ex: 2.89), $2 and $5 are symbols (ex: ETH), and $3 and $6 are fiat currency amounts."
|
||||
},
|
||||
"swapPriceDifferenceAcknowledgement": {
|
||||
"message": "I'm aware"
|
||||
},
|
||||
"swapPriceDifferenceAcknowledgementNoFiat": {
|
||||
"message": "Continue"
|
||||
},
|
||||
"swapPriceDifferenceTitle": {
|
||||
"message": "Price difference of ~$1%",
|
||||
"description": "$1 is a number (ex: 1.23) that represents the price difference."
|
||||
|
@ -15,6 +15,11 @@
|
||||
padding-right: 20px;
|
||||
justify-content: space-between;
|
||||
|
||||
&_modal > div:not(.view-quote__warning-wrapper) {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
overflow-y: auto;
|
||||
max-height: 428px;
|
||||
@ -98,14 +103,30 @@
|
||||
.actionable-message__message {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
background: $Yellow-500;
|
||||
border-radius: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
&.high .actionable-message {
|
||||
border-color: $Red-500;
|
||||
background: $Red-100;
|
||||
&.high {
|
||||
.actionable-message {
|
||||
border-color: $Red-500;
|
||||
background: $Red-100;
|
||||
|
||||
.actionable-message__message {
|
||||
color: $Red-500;
|
||||
.actionable-message__message {
|
||||
color: $Red-500;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background: $Red-500;
|
||||
color: #fff;
|
||||
border-radius: 42px;
|
||||
|
||||
/* Offsets the width of ActionableMessage icon */
|
||||
margin-right: -22px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,11 +139,17 @@
|
||||
|
||||
&-contents {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
text-align: end;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-inline-start: 10px;
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ describe('View Price Quote Difference', function () {
|
||||
})
|
||||
|
||||
it('displays a fiat error when calculationError is present', function () {
|
||||
const props = { ...DEFAULT_PROPS }
|
||||
const props = { ...DEFAULT_PROPS, priceSlippageUnknownFiatValue: true }
|
||||
props.usedQuote.priceSlippage.calculationError =
|
||||
'Could not determine price.'
|
||||
|
||||
|
@ -2,64 +2,37 @@ import React, { useContext } from 'react'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
|
||||
import ActionableMessage from '../actionable-message'
|
||||
import Tooltip from '../../../components/ui/tooltip'
|
||||
|
||||
export default function ViewQuotePriceDifference(props) {
|
||||
const { usedQuote, sourceTokenValue, destinationTokenValue } = props
|
||||
const {
|
||||
usedQuote,
|
||||
sourceTokenValue,
|
||||
destinationTokenValue,
|
||||
onAcknowledgementClick,
|
||||
acknowledged,
|
||||
priceSlippageFromSource,
|
||||
priceSlippageFromDestination,
|
||||
priceDifferencePercentage,
|
||||
priceSlippageUnknownFiatValue,
|
||||
} = props
|
||||
|
||||
const t = useContext(I18nContext)
|
||||
|
||||
const priceSlippageFromSource = useEthFiatAmount(
|
||||
usedQuote?.priceSlippage?.sourceAmountInETH || 0,
|
||||
)
|
||||
const priceSlippageFromDestination = useEthFiatAmount(
|
||||
usedQuote?.priceSlippage?.destinationAmountInEth || 0,
|
||||
)
|
||||
|
||||
if (!usedQuote || !usedQuote.priceSlippage) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { priceSlippage } = usedQuote
|
||||
|
||||
// We cannot present fiat value if there is a calculation error or no slippage
|
||||
// from source or destination
|
||||
const priceSlippageUnknownFiatValue =
|
||||
!priceSlippageFromSource ||
|
||||
!priceSlippageFromDestination ||
|
||||
priceSlippage.calculationError
|
||||
|
||||
let priceDifferencePercentage = 0
|
||||
if (priceSlippage.ratio) {
|
||||
priceDifferencePercentage = parseFloat(
|
||||
new BigNumber(priceSlippage.ratio, 10)
|
||||
.minus(1, 10)
|
||||
.times(100, 10)
|
||||
.toFixed(2),
|
||||
10,
|
||||
)
|
||||
}
|
||||
|
||||
const shouldShowPriceDifferenceWarning =
|
||||
['high', 'medium'].includes(priceSlippage.bucket) ||
|
||||
priceSlippageUnknownFiatValue
|
||||
|
||||
if (!shouldShowPriceDifferenceWarning) {
|
||||
return null
|
||||
}
|
||||
|
||||
let priceDifferenceTitle = ''
|
||||
let priceDifferenceMessage = ''
|
||||
let priceDifferenceClass = ''
|
||||
let priceDifferenceAcknowledgementText = ''
|
||||
if (priceSlippageUnknownFiatValue) {
|
||||
// A calculation error signals we cannot determine dollar value
|
||||
priceDifferenceMessage = t('swapPriceDifferenceUnavailable')
|
||||
priceDifferenceClass = 'fiat-error'
|
||||
priceDifferenceAcknowledgementText = t(
|
||||
'swapPriceDifferenceAcknowledgementNoFiat',
|
||||
)
|
||||
} else {
|
||||
priceDifferenceTitle = t('swapPriceDifferenceTitle', [
|
||||
priceDifferencePercentage,
|
||||
@ -72,7 +45,8 @@ export default function ViewQuotePriceDifference(props) {
|
||||
usedQuote.destinationTokenInfo.symbol, // Destination token symbol,
|
||||
priceSlippageFromDestination, // Destination tokens total value
|
||||
])
|
||||
priceDifferenceClass = priceSlippage.bucket
|
||||
priceDifferenceClass = usedQuote.priceSlippage.bucket
|
||||
priceDifferenceAcknowledgementText = t('swapPriceDifferenceAcknowledgement')
|
||||
}
|
||||
|
||||
return (
|
||||
@ -92,6 +66,17 @@ export default function ViewQuotePriceDifference(props) {
|
||||
</div>
|
||||
)}
|
||||
{priceDifferenceMessage}
|
||||
{!acknowledged && (
|
||||
<div className="view-quote__price-difference-warning-contents-actions">
|
||||
<button
|
||||
onClick={() => {
|
||||
onAcknowledgementClick()
|
||||
}}
|
||||
>
|
||||
{priceDifferenceAcknowledgementText}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
@ -111,4 +96,10 @@ ViewQuotePriceDifference.propTypes = {
|
||||
usedQuote: PropTypes.object,
|
||||
sourceTokenValue: PropTypes.string,
|
||||
destinationTokenValue: PropTypes.string,
|
||||
onAcknowledgementClick: PropTypes.func,
|
||||
acknowledged: PropTypes.bool,
|
||||
priceSlippageFromSource: PropTypes.string,
|
||||
priceSlippageFromDestination: PropTypes.string,
|
||||
priceDifferencePercentage: PropTypes.number,
|
||||
priceSlippageUnknownFiatValue: PropTypes.bool,
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { isEqual } from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
import SelectQuotePopover from '../select-quote-popover'
|
||||
import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount'
|
||||
import { useEqualityCheck } from '../../../hooks/useEqualityCheck'
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'
|
||||
import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken'
|
||||
@ -88,6 +89,11 @@ export default function ViewQuote() {
|
||||
const [warningHidden, setWarningHidden] = useState(false)
|
||||
const [originalApproveAmount, setOriginalApproveAmount] = useState(null)
|
||||
|
||||
const [
|
||||
acknowledgedPriceDifference,
|
||||
setAcknowledgedPriceDifference,
|
||||
] = useState(false)
|
||||
|
||||
const routeState = useSelector(getBackgroundSwapRouteState)
|
||||
const quotes = useSelector(getQuotes, isEqual)
|
||||
useEffect(() => {
|
||||
@ -474,20 +480,70 @@ export default function ViewQuote() {
|
||||
: 'ETH',
|
||||
])
|
||||
|
||||
const viewQuotePriceDifferenceComponent = (
|
||||
<ViewQuotePriceDifference
|
||||
usedQuote={usedQuote}
|
||||
sourceTokenValue={sourceTokenValue}
|
||||
destinationTokenValue={destinationTokenValue}
|
||||
/>
|
||||
// Price difference warning
|
||||
let viewQuotePriceDifferenceComponent = null
|
||||
const priceSlippageFromSource = useEthFiatAmount(
|
||||
usedQuote?.priceSlippage?.sourceAmountInETH || 0,
|
||||
)
|
||||
const priceSlippageFromDestination = useEthFiatAmount(
|
||||
usedQuote?.priceSlippage?.destinationAmountInEth || 0,
|
||||
)
|
||||
|
||||
// We cannot present fiat value if there is a calculation error or no slippage
|
||||
// from source or destination
|
||||
const priceSlippageUnknownFiatValue =
|
||||
!priceSlippageFromSource ||
|
||||
!priceSlippageFromDestination ||
|
||||
usedQuote?.priceSlippage?.calculationError
|
||||
|
||||
let priceDifferencePercentage = 0
|
||||
if (usedQuote?.priceSlippage?.ratio) {
|
||||
priceDifferencePercentage = parseFloat(
|
||||
new BigNumber(usedQuote.priceSlippage.ratio, 10)
|
||||
.minus(1, 10)
|
||||
.times(100, 10)
|
||||
.toFixed(2),
|
||||
10,
|
||||
)
|
||||
}
|
||||
|
||||
const shouldShowPriceDifferenceWarning =
|
||||
!showInsufficientWarning &&
|
||||
usedQuote &&
|
||||
(['high', 'medium'].includes(usedQuote.priceSlippage.bucket) ||
|
||||
priceSlippageUnknownFiatValue)
|
||||
|
||||
if (shouldShowPriceDifferenceWarning) {
|
||||
viewQuotePriceDifferenceComponent = (
|
||||
<ViewQuotePriceDifference
|
||||
usedQuote={usedQuote}
|
||||
sourceTokenValue={sourceTokenValue}
|
||||
destinationTokenValue={destinationTokenValue}
|
||||
priceSlippageFromSource={priceSlippageFromSource}
|
||||
priceSlippageFromDestination={priceSlippageFromDestination}
|
||||
priceDifferencePercentage={priceDifferencePercentage}
|
||||
priceSlippageUnknownFiatValue={priceSlippageUnknownFiatValue}
|
||||
onAcknowledgementClick={() => {
|
||||
setAcknowledgedPriceDifference(true)
|
||||
}}
|
||||
acknowledged={acknowledgedPriceDifference}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const disableSubmissionDueToPriceWarning =
|
||||
shouldShowPriceDifferenceWarning && !acknowledgedPriceDifference
|
||||
|
||||
const isShowingWarning =
|
||||
showInsufficientWarning || viewQuotePriceDifferenceComponent !== null
|
||||
showInsufficientWarning || shouldShowPriceDifferenceWarning
|
||||
|
||||
return (
|
||||
<div className="view-quote">
|
||||
<div className="view-quote__content">
|
||||
<div
|
||||
className={classnames('view-quote__content', {
|
||||
'view-quote__content_modal': disableSubmissionDueToPriceWarning,
|
||||
})}
|
||||
>
|
||||
{selectQuotePopoverShown && (
|
||||
<SelectQuotePopover
|
||||
quoteDataRows={renderablePopoverData}
|
||||
@ -503,7 +559,7 @@ export default function ViewQuote() {
|
||||
'view-quote__warning-wrapper--thin': !isShowingWarning,
|
||||
})}
|
||||
>
|
||||
{!showInsufficientWarning && viewQuotePriceDifferenceComponent}
|
||||
{viewQuotePriceDifferenceComponent}
|
||||
{showInsufficientWarning && (
|
||||
<ActionableMessage
|
||||
message={actionableInsufficientMessage}
|
||||
@ -585,6 +641,7 @@ export default function ViewQuote() {
|
||||
disabled={
|
||||
submitClicked ||
|
||||
balanceError ||
|
||||
disableSubmissionDueToPriceWarning ||
|
||||
gasPrice === null ||
|
||||
gasPrice === undefined
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user