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: warn users about metamask transactions #5362

Merged
merged 5 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getIsNativeToken } from '@cowprotocol/common-utils'
import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk'
import { InlineBanner } from '@cowprotocol/ui'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
Expand Down Expand Up @@ -26,7 +27,7 @@ export interface TwapSuggestionBannerProps {
priceImpact: Percent | undefined
buyingFiatAmount: CurrencyAmount<Currency> | null
tradeUrlParams: TradeUrlParams
sellAmount: string | undefined
sellAmount: CurrencyAmount<Currency> | undefined
}

const PRICE_IMPACT_LIMIT = 1 // 1%
Expand All @@ -47,16 +48,19 @@ export function TwapSuggestionBanner({
}: TwapSuggestionBannerProps) {
if (!priceImpact || priceImpact.lessThan(0)) return null

const shouldSuggestTwap =
+priceImpact.toFixed(2) > PRICE_IMPACT_LIMIT && +(buyingFiatAmount?.toExact() || 0) > AMOUNT_LIMIT[chainId]
const isSellNative = !!sellAmount?.currency && getIsNativeToken(sellAmount?.currency)
const priceImpactIsHighEnough = +priceImpact.toFixed(2) > PRICE_IMPACT_LIMIT
const buyAmountIsBigEnough = +(buyingFiatAmount?.toExact() || 0) > AMOUNT_LIMIT[chainId]

const shouldSuggestTwap = !isSellNative && priceImpactIsHighEnough && buyAmountIsBigEnough
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small enhancement: do not show the twap suggestion banner when selling ETH


if (!shouldSuggestTwap) return null

const routePath =
parameterizeTradeRoute(tradeUrlParams, Routes.ADVANCED_ORDERS) +
'?' +
parameterizeTradeSearch('', {
amount: sellAmount,
amount: sellAmount?.toExact(),
kind: OrderKind.SELL,
})

Expand Down
5 changes: 4 additions & 1 deletion apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SellNativeWarningBanner } from 'modules/trade/containers/SellNativeWarn
import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssuesWarning'
import { TradeUrlParams } from 'modules/trade/types/TradeRawState'
import { BundleTxWrapBanner, HighFeeWarning } from 'modules/tradeWidgetAddons'
import { MetamaskTransactionWarning } from 'modules/tradeWidgetAddons'

import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner'

Expand Down Expand Up @@ -40,9 +41,11 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps)
tradeUrlParams,
isNativeSellInHooksStore,
} = props
const sellToken = trade?.inputAmount.currency

return (
<>
{sellToken && !isNativeSellInHooksStore && <MetamaskTransactionWarning sellToken={sellToken} />}
alfetopito marked this conversation as resolved.
Show resolved Hide resolved
{isNativeSellInHooksStore ? (
<SellNativeWarningBanner />
) : (
Expand All @@ -56,7 +59,7 @@ export const SwapWarningsTop = React.memo(function (props: SwapWarningsTopProps)
priceImpact={priceImpact}
buyingFiatAmount={buyingFiatAmount}
tradeUrlParams={tradeUrlParams}
sellAmount={trade?.inputAmount.toExact()}
sellAmount={trade?.inputAmount}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ export function TradeWidgetForm(props: TradeWidgetProps) {
const alternativeOrderModalVisible = useIsAlternativeOrderModalVisible()
const primaryFormValidation = useGetTradeFormValidation()

const areCurrenciesLoading = !inputCurrencyInfo.currency && !outputCurrencyInfo.currency
const bothCurrenciesSet = !!inputCurrencyInfo.currency && !!outputCurrencyInfo.currency
const sellToken = inputCurrencyInfo.currency
const areCurrenciesLoading = !sellToken && !outputCurrencyInfo.currency
const bothCurrenciesSet = !!sellToken && !!outputCurrencyInfo.currency

const hasRecipientInUrl = !!tradeStateFromUrl?.recipient
const withRecipient = !isWrapOrUnwrap && (showRecipient || hasRecipientInUrl)
Expand Down Expand Up @@ -141,9 +142,9 @@ export function TradeWidgetForm(props: TradeWidgetProps) {

const openBuyTokenSelect = useCallback(
(selectedToken: string | undefined, field: Field | undefined, onSelectToken: (currency: Currency) => void) => {
openTokenSelectWidget(selectedToken, field, inputCurrencyInfo.currency || undefined, onSelectToken)
openTokenSelectWidget(selectedToken, field, sellToken || undefined, onSelectToken)
},
[openTokenSelectWidget, inputCurrencyInfo.currency],
[openTokenSelectWidget, sellToken],
)

const toggleAccountModal = useToggleAccountModal()
Expand Down Expand Up @@ -198,7 +199,7 @@ export function TradeWidgetForm(props: TradeWidgetProps) {
isCollapsed={compactView}
hasSeparatorLine={!compactView}
onSwitchTokens={isChainIdUnsupported ? () => void 0 : throttledOnSwitchTokens}
isLoading={Boolean(inputCurrencyInfo.currency && outputCurrencyInfo.currency && isTradePriceUpdating)}
isLoading={Boolean(sellToken && outputCurrencyInfo.currency && isTradePriceUpdating)}
disabled={isAlternativeOrderModalVisible}
/>
</styledEl.CurrencySeparatorBox>
Expand All @@ -223,7 +224,9 @@ export function TradeWidgetForm(props: TradeWidgetProps) {
{withRecipient && <SetRecipient recipient={recipient || ''} onChangeRecipient={onChangeRecipient} />}

{isWrapOrUnwrap ? (
<WrapFlowActionButton />
sellToken ? (
<WrapFlowActionButton sellToken={sellToken} />
) : null
) : (
bottomContent?.(
hideTradeWarnings ? null : (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import React from 'react'
shoom3301 marked this conversation as resolved.
Show resolved Hide resolved

import { Currency } from '@uniswap/sdk-core'

import { TradeFormButtons, useGetTradeFormValidation, useTradeFormButtonContext } from 'modules/tradeFormValidation'
import { MetamaskTransactionWarning } from 'modules/tradeWidgetAddons'

const doTradeText = ''
const confirmText = ''
Expand All @@ -8,13 +13,16 @@ const confirmTrade = () => void 0
* This component is used only to display a button for Wrap/Unwrap
* because of it, we just stub parameters above
*/
export function WrapFlowActionButton() {
export function WrapFlowActionButton({ sellToken }: { sellToken: Currency }) {
const primaryFormValidation = useGetTradeFormValidation()
const tradeFormButtonContext = useTradeFormButtonContext(doTradeText, confirmTrade)

if (!tradeFormButtonContext) return null

return (
<TradeFormButtons confirmText={confirmText} validation={primaryFormValidation} context={tradeFormButtonContext} />
<>
<MetamaskTransactionWarning sellToken={sellToken} />
<TradeFormButtons confirmText={confirmText} validation={primaryFormValidation} context={tradeFormButtonContext} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CHAIN_INFO } from '@cowprotocol/common-const'
import { getIsNativeToken } from '@cowprotocol/common-utils'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { BannerOrientation, InlineBanner } from '@cowprotocol/ui'
import { useWalletProvider } from '@cowprotocol/wallet-provider'
import { Currency } from '@uniswap/sdk-core'

import SVG from 'react-inlinesvg'
import styled from 'styled-components/macro'

const Banner = styled(InlineBanner)`
font-size: 14px;
text-align: center;
`

const NetworkInfo = styled.div`
display: flex;
align-items: center;
gap: 8px;
`

export function MetamaskTransactionWarning({ sellToken }: { sellToken: Currency }) {
const provider = useWalletProvider()
const ethereumProvider = (provider as unknown as { provider: typeof window.ethereum })?.provider
const isMetamask = !!ethereumProvider?.isMetaMask && !ethereumProvider.isRabby
const isNativeSellToken = getIsNativeToken(sellToken)

if (!isMetamask || !isNativeSellToken) return null

const chainInfo = CHAIN_INFO[sellToken.chainId as SupportedChainId]

return (
<Banner bannerType="danger" orientation={BannerOrientation.Vertical} iconSize={32}>
Be careful when signing transactions in Metamask and check all the details carefully! Make sure that the
transaction will be sent to the network:{' '}
alfetopito marked this conversation as resolved.
Show resolved Hide resolved
<NetworkInfo>
<SVG src={chainInfo.logo.light} height={24} /> <span>{chainInfo.label}</span>
</NetworkInfo>
</Banner>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export { TradeRateDetails } from './containers/TradeRateDetails'
export { SettingsTab } from './containers/SettingsTab'
export { HighFeeWarning } from './containers/HighFeeWarning'
export { BundleTxWrapBanner } from './containers/BundleTxWrapBanner'
export { MetamaskTransactionWarning } from './containers/MetamaskTransactionWarning'
export { useHighFeeWarning } from './containers/HighFeeWarning/hooks/useHighFeeWarning'
export { NetworkCostsTooltipSuffix } from './pure/NetworkCostsTooltipSuffix'