Commit 3eaeb65b authored by Jack Short's avatar Jack Short Committed by GitHub

feat: implementing permit2 with pay with any token (#5926)

* feat: implementing permit2 with pay with any token

* permit2 hook

* usePayWithAnyTokenHook

* removing ternary operators

* weird export type bug

* resolving merge

* fixing nft test

* styles

* refactoring styles

* reformatting

* price impact warnings

* forgot trans tag

* responding to comments

* fixes

* disabling pay with any token when on the wrong chain

* missing enabled flag

* vertically centering button
parent 6df2f367
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { useWeb3React } from '@web3-react/core'
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index' import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function usePayWithAnyTokenFlag(): BaseVariant { export function usePayWithAnyTokenFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.payWithAnyToken) return useBaseFlag(FeatureFlag.payWithAnyToken)
} }
export function usePayWithAnyTokenEnabled(): boolean {
const flagEnabled = usePayWithAnyTokenFlag() === BaseVariant.Enabled
const { chainId } = useWeb3React()
try {
// Detect if the Universal Router is not yet deployed to chainId.
// This is necessary so that we can fallback correctly on chains without a Universal Router deployment.
// It will be removed once Universal Router is deployed on all supported chains.
chainId && UNIVERSAL_ROUTER_ADDRESS(chainId)
return flagEnabled
} catch {
return false
}
}
export { BaseVariant as PayWithAnyTokenVariant } export { BaseVariant as PayWithAnyTokenVariant }
...@@ -579,7 +579,6 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = { ...@@ -579,7 +579,6 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = {
export type Query = { export type Query = {
__typename?: 'Query'; __typename?: 'Query';
assetActivities?: Maybe<Array<Maybe<AssetActivity>>>;
nftAssets?: Maybe<NftAssetConnection>; nftAssets?: Maybe<NftAssetConnection>;
nftBalances?: Maybe<NftBalanceConnection>; nftBalances?: Maybe<NftBalanceConnection>;
nftCollections?: Maybe<NftCollectionConnection>; nftCollections?: Maybe<NftCollectionConnection>;
...@@ -595,13 +594,6 @@ export type Query = { ...@@ -595,13 +594,6 @@ export type Query = {
}; };
export type QueryAssetActivitiesArgs = {
address: Scalars['String'];
page?: InputMaybe<Scalars['Int']>;
pageSize?: InputMaybe<Scalars['Int']>;
};
export type QueryNftAssetsArgs = { export type QueryNftAssetsArgs = {
address: Scalars['String']; address: Scalars['String'];
after?: InputMaybe<Scalars['String']>; after?: InputMaybe<Scalars['String']>;
......
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { formatEther } from '@ethersproject/units' import { formatEther } from '@ethersproject/units'
import { parseEther } from '@ethersproject/units' import { parseEther } from '@ethersproject/units'
import { Trans } from '@lingui/macro' import { t, Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics' import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
import { Currency, TradeType } from '@uniswap/sdk-core' import { formatPriceImpact } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import Loader from 'components/Loader' import Loader from 'components/Loader'
import CurrencyLogo from 'components/Logo/CurrencyLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo'
import Row from 'components/Row' import Row from 'components/Row'
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken' import { usePayWithAnyTokenEnabled } from 'featureFlags/flags/payWithAnyToken'
import { useCurrency } from 'hooks/Tokens' import { useCurrency } from 'hooks/Tokens'
import { useBestTrade } from 'hooks/useBestTrade' import { AllowanceState } from 'hooks/usePermit2Allowance'
import { useStablecoinValue } from 'hooks/useStablecoinPrice' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useBag } from 'nft/hooks/useBag' import { useBag } from 'nft/hooks/useBag'
import usePayWithAnyTokenSwap from 'nft/hooks/usePayWithAnyTokenSwap'
import usePermit2Approval from 'nft/hooks/usePermit2Approval'
import { useTokenInput } from 'nft/hooks/useTokenInput' import { useTokenInput } from 'nft/hooks/useTokenInput'
import { useWalletBalance } from 'nft/hooks/useWalletBalance' import { useWalletBalance } from 'nft/hooks/useWalletBalance'
import { BagStatus } from 'nft/types' import { BagStatus } from 'nft/types'
...@@ -25,11 +30,16 @@ import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils' ...@@ -25,11 +30,16 @@ import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils'
import { PropsWithChildren, useMemo, useState } from 'react' import { PropsWithChildren, useMemo, useState } from 'react'
import { AlertTriangle, ChevronDown } from 'react-feather' import { AlertTriangle, ChevronDown } from 'react-feather'
import { useToggleWalletModal } from 'state/application/hooks' import { useToggleWalletModal } from 'state/application/hooks'
import { TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { warningSeverity } from 'utils/prices'
import { switchChain } from 'utils/switchChain' import { switchChain } from 'utils/switchChain'
const LOW_SEVERITY_THRESHOLD = 1
const MEDIUM_SEVERITY_THRESHOLD = 3
const FooterContainer = styled.div` const FooterContainer = styled.div`
padding: 0px 12px; padding: 0px 12px;
` `
...@@ -45,14 +55,12 @@ const Footer = styled.div` ...@@ -45,14 +55,12 @@ const Footer = styled.div`
border-bottom-right-radius: 12px; border-bottom-right-radius: 12px;
` `
const FooterHeader = styled(Column)<{ warningText?: boolean }>` const FooterHeader = styled(Column)<{ usingPayWithAnyToken?: boolean }>`
padding-top: 8px; padding-top: 8px;
padding-bottom: ${({ warningText }) => (warningText ? '8px' : '20px')}; padding-bottom: ${({ usingPayWithAnyToken }) => (usingPayWithAnyToken ? '16px' : '20px')};
` `
const CurrencyRow = styled(Row)<{ warningText?: boolean }>` const CurrencyRow = styled(Row)`
padding-top: 4px;
padding-bottom: ${({ warningText }) => (warningText ? '8px' : '20px')};
justify-content: space-between; justify-content: space-between;
align-items: start; align-items: start;
` `
...@@ -71,8 +79,16 @@ const WarningText = styled(ThemedText.BodyPrimary)` ...@@ -71,8 +79,16 @@ const WarningText = styled(ThemedText.BodyPrimary)`
color: ${({ theme }) => theme.accentWarning}; color: ${({ theme }) => theme.accentWarning};
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: 12px 0 !important; margin-bottom: 10px !important;
text-align: center;
`
const HelperText = styled(ThemedText.Caption)<{ $color: string }>`
color: ${({ $color }) => $color};
display: flex;
justify-content: center;
text-align: center; text-align: center;
margin-bottom: 10px !important;
` `
const CurrencyInput = styled(Row)` const CurrencyInput = styled(Row)`
...@@ -80,8 +96,9 @@ const CurrencyInput = styled(Row)` ...@@ -80,8 +96,9 @@ const CurrencyInput = styled(Row)`
cursor: pointer; cursor: pointer;
` `
const PayButton = styled(Row)<{ disabled?: boolean }>` const PayButton = styled.button<{ $backgroundColor: string }>`
background: ${({ theme }) => theme.accentAction}; display: flex;
background: ${({ $backgroundColor }) => $backgroundColor};
color: ${({ theme }) => theme.accentTextLightPrimary}; color: ${({ theme }) => theme.accentTextLightPrimary};
font-weight: 600; font-weight: 600;
line-height: 24px; line-height: 24px;
...@@ -91,18 +108,41 @@ const PayButton = styled(Row)<{ disabled?: boolean }>` ...@@ -91,18 +108,41 @@ const PayButton = styled(Row)<{ disabled?: boolean }>`
border: none; border: none;
border-radius: 12px; border-radius: 12px;
padding: 12px 0px; padding: 12px 0px;
opacity: ${({ disabled }) => (disabled ? 0.6 : 1)}; cursor: pointer;
cursor: ${({ disabled }) => (disabled ? 'auto' : 'pointer')}; align-items: center;
&:disabled {
opacity: 0.6;
cursor: auto;
}
`
const FiatLoadingBubble = styled(LoadingBubble)`
border-radius: 4px;
width: 4rem;
height: 1rem;
align-self: end;
`
const PriceImpactContainer = styled(Row)`
align-items: center;
gap: 8px;
width: 100%;
justify-content: flex-end;
`
const PriceImpactRow = styled(Row)`
align-items: center;
gap: 8px;
` `
interface ActionButtonProps { interface ActionButtonProps {
disabled?: boolean disabled?: boolean
onClick: () => void onClick: () => void
backgroundColor: string
} }
const ActionButton = ({ disabled, children, onClick }: PropsWithChildren<ActionButtonProps>) => { const ActionButton = ({ disabled, children, onClick, backgroundColor }: PropsWithChildren<ActionButtonProps>) => {
return ( return (
<PayButton disabled={disabled} onClick={onClick}> <PayButton disabled={disabled} onClick={onClick} $backgroundColor={backgroundColor}>
{children} {children}
</PayButton> </PayButton>
) )
...@@ -120,6 +160,97 @@ const Warning = ({ children }: PropsWithChildren<unknown>) => { ...@@ -120,6 +160,97 @@ const Warning = ({ children }: PropsWithChildren<unknown>) => {
) )
} }
interface HelperTextProps {
color: string
}
const Helper = ({ children, color }: PropsWithChildren<HelperTextProps>) => {
if (!children) {
return null
}
return (
<HelperText lineHeight="16px" $color={color}>
{children}
</HelperText>
)
}
// TODO: ask design about no route found
const InputCurrencyValue = ({
usingPayWithAnyToken,
totalEthPrice,
activeCurrency,
tradeState,
trade,
}: {
usingPayWithAnyToken: boolean
totalEthPrice: BigNumber
activeCurrency: Currency | undefined | null
tradeState: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
}) => {
if (!usingPayWithAnyToken) {
return (
<ThemedText.BodyPrimary lineHeight="20px" fontWeight="500">
{formatWeiToDecimal(totalEthPrice.toString())}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.BodyPrimary>
)
}
if (tradeState === TradeState.VALID || tradeState === TradeState.SYNCING) {
return (
<ThemedText.BodyPrimary
lineHeight="20px"
fontWeight="500"
color={tradeState === TradeState.VALID ? 'textPrimary' : 'textTertiary'}
>
{ethNumberStandardFormatter(trade?.inputAmount.toExact())}
</ThemedText.BodyPrimary>
)
}
return (
<ThemedText.BodyPrimary color="textTertiary" lineHeight="20px" fontWeight="500">
<Trans>Fetching price...</Trans>
</ThemedText.BodyPrimary>
)
}
const FiatValue = ({
usdcValue,
priceImpact,
priceImpactColor,
}: {
usdcValue: CurrencyAmount<Token> | null
priceImpact: Percent | undefined
priceImpactColor: string | undefined
}) => {
if (!usdcValue) {
return <FiatLoadingBubble />
}
return (
<PriceImpactContainer>
{priceImpact && priceImpactColor && (
<>
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
<PriceImpactRow>
<AlertTriangle color={priceImpactColor} size="16px" />
<ThemedText.BodySmall style={{ color: priceImpactColor }} lineHeight="20px">
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
</ThemedText.BodySmall>
</PriceImpactRow>
</MouseoverTooltip>
</>
)}
<ThemedText.BodySmall color="textTertiary" lineHeight="20px">
{`${ethNumberStandardFormatter(usdcValue?.toExact(), true)}`}
</ThemedText.BodySmall>
</PriceImpactContainer>
)
}
interface BagFooterProps { interface BagFooterProps {
totalEthPrice: BigNumber totalEthPrice: BigNumber
bagStatus: BagStatus bagStatus: BagStatus
...@@ -139,7 +270,7 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -139,7 +270,7 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
const theme = useTheme() const theme = useTheme()
const { account, chainId, connector } = useWeb3React() const { account, chainId, connector } = useWeb3React()
const connected = Boolean(account && chainId) const connected = Boolean(account && chainId)
const shouldUsePayWithAnyToken = usePayWithAnyTokenFlag() === PayWithAnyTokenVariant.Enabled const shouldUsePayWithAnyToken = usePayWithAnyTokenEnabled()
const inputCurrency = useTokenInput((state) => state.inputCurrency) const inputCurrency = useTokenInput((state) => state.inputCurrency)
const setInputCurrency = useTokenInput((state) => state.setInputCurrency) const setInputCurrency = useTokenInput((state) => state.setInputCurrency)
const defaultCurrency = useCurrency('ETH') const defaultCurrency = useCurrency('ETH')
...@@ -155,11 +286,52 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -155,11 +286,52 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
return parseEther(balanceInEth).gte(totalEthPrice) return parseEther(balanceInEth).gte(totalEthPrice)
}, [connected, chainId, balanceInEth, totalEthPrice]) }, [connected, chainId, balanceInEth, totalEthPrice])
const { buttonText, disabled, warningText, handleClick } = useMemo(() => { const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
const activeCurrency = inputCurrency ?? defaultCurrency
const usingPayWithAnyToken = !!inputCurrency && shouldUsePayWithAnyToken
const parsedOutputAmount = useMemo(() => {
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined)
}, [defaultCurrency, totalEthPrice])
const { state: tradeState, trade, maximumAmountIn } = usePayWithAnyTokenSwap(inputCurrency, parsedOutputAmount)
const { allowance, isAllowancePending, isApprovalLoading, updateAllowance } = usePermit2Approval(
trade?.inputAmount.currency.isToken ? (trade?.inputAmount as CurrencyAmount<Token>) : undefined,
maximumAmountIn,
shouldUsePayWithAnyToken
)
const fiatValueTradeInput = useStablecoinValue(trade?.inputAmount)
const fiatValueTradeOutput = useStablecoinValue(parsedOutputAmount)
const usdcValue = usingPayWithAnyToken ? fiatValueTradeInput : fiatValueTradeOutput
const stablecoinPriceImpact = useMemo(
() =>
tradeState === TradeState.SYNCING || !trade
? undefined
: computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
[fiatValueTradeInput, fiatValueTradeOutput, tradeState, trade]
)
const { priceImpactWarning, priceImpactColor } = useMemo(() => {
const severity = warningSeverity(stablecoinPriceImpact)
if (severity < LOW_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: undefined }
}
if (severity < MEDIUM_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: theme.accentWarning }
}
return { priceImpactWarning: true, priceImpactColor: theme.accentCritical }
}, [stablecoinPriceImpact, theme.accentCritical, theme.accentWarning])
const { buttonText, disabled, warningText, helperText, helperTextColor, handleClick, buttonColor } = useMemo(() => {
let handleClick = fetchAssets let handleClick = fetchAssets
let buttonText = <Trans>Something went wrong</Trans> let buttonText = <Trans>Something went wrong</Trans>
let disabled = true let disabled = true
let warningText = null let warningText = undefined
let helperText = undefined
let helperTextColor = theme.textSecondary
let buttonColor = theme.accentAction
if (connected && chainId !== SupportedChainId.MAINNET) { if (connected && chainId !== SupportedChainId.MAINNET) {
handleClick = () => switchChain(connector, SupportedChainId.MAINNET) handleClick = () => switchChain(connector, SupportedChainId.MAINNET)
...@@ -179,34 +351,62 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -179,34 +351,62 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
} }
disabled = false disabled = false
buttonText = <Trans>Connect wallet</Trans> buttonText = <Trans>Connect wallet</Trans>
} else if (usingPayWithAnyToken && tradeState !== TradeState.VALID) {
disabled = true
buttonText = <Trans>Fetching Route</Trans>
} else if (allowance.state === AllowanceState.REQUIRED || allowance.state === AllowanceState.LOADING) {
handleClick = () => updateAllowance()
disabled = isAllowancePending || isApprovalLoading || allowance.state === AllowanceState.LOADING
if (allowance.state === AllowanceState.LOADING) {
buttonText = <Trans>Loading Allowance</Trans>
} else if (isAllowancePending) {
buttonText = <Trans>Approve in your wallet</Trans>
} else if (isApprovalLoading) {
buttonText = <Trans>Approval pending</Trans>
} else {
helperText = <Trans>An approval is needed to use this token. </Trans>
buttonText = <Trans>Approve</Trans>
}
} else if (bagStatus === BagStatus.FETCHING_FINAL_ROUTE || bagStatus === BagStatus.CONFIRMING_IN_WALLET) { } else if (bagStatus === BagStatus.FETCHING_FINAL_ROUTE || bagStatus === BagStatus.CONFIRMING_IN_WALLET) {
disabled = true disabled = true
buttonText = <Trans>Proceed in wallet</Trans> buttonText = <Trans>Proceed in wallet</Trans>
} else if (bagStatus === BagStatus.PROCESSING_TRANSACTION) { } else if (bagStatus === BagStatus.PROCESSING_TRANSACTION) {
disabled = true disabled = true
buttonText = <Trans>Transaction pending</Trans> buttonText = <Trans>Transaction pending</Trans>
} else if (priceImpactWarning && priceImpactColor) {
disabled = false
buttonColor = priceImpactColor
helperText = <Trans>Price impact warning</Trans>
helperTextColor = priceImpactColor
buttonText = <Trans>Pay Anyway</Trans>
} else if (sufficientBalance === true) { } else if (sufficientBalance === true) {
disabled = false disabled = false
buttonText = <Trans>Pay</Trans> buttonText = <Trans>Pay</Trans>
} }
return { buttonText, disabled, warningText, handleClick } return { buttonText, disabled, warningText, helperText, helperTextColor, handleClick, buttonColor }
}, [bagStatus, chainId, connected, connector, fetchAssets, setBagExpanded, sufficientBalance, toggleWalletModal]) }, [
fetchAssets,
const isPending = PENDING_BAG_STATUSES.includes(bagStatus) theme.textSecondary,
const activeCurrency = inputCurrency ?? defaultCurrency theme.accentAction,
connected,
const parsedAmount = useMemo(() => { chainId,
return tryParseCurrencyAmount(formatEther(totalEthPrice.toString()), defaultCurrency ?? undefined) sufficientBalance,
}, [defaultCurrency, totalEthPrice]) bagStatus,
usingPayWithAnyToken,
const { state: swapState, trade: swapTrade } = useBestTrade( tradeState,
TradeType.EXACT_OUTPUT, allowance.state,
parsedAmount, priceImpactWarning,
inputCurrency ?? undefined priceImpactColor,
) connector,
toggleWalletModal,
setBagExpanded,
isAllowancePending,
isApprovalLoading,
updateAllowance,
])
const usdcValue = useStablecoinValue(inputCurrency ? swapTrade?.inputAmount : parsedAmount)
const traceEventProperties = { const traceEventProperties = {
usd_value: usdcValue?.toExact(), usd_value: usdcValue?.toExact(),
...eventProperties, ...eventProperties,
...@@ -216,55 +416,50 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -216,55 +416,50 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
<FooterContainer> <FooterContainer>
<Footer> <Footer>
{shouldUsePayWithAnyToken && ( {shouldUsePayWithAnyToken && (
<CurrencyRow> <FooterHeader gap="xs" usingPayWithAnyToken={shouldUsePayWithAnyToken}>
<Column gap="xs"> <CurrencyRow>
<ThemedText.SubHeaderSmall> <Column gap="xs">
<Trans>Pay with</Trans> <ThemedText.SubHeaderSmall>
</ThemedText.SubHeaderSmall> <Trans>Pay with</Trans>
<CurrencyInput onClick={() => setTokenSelectorOpen(true)}> </ThemedText.SubHeaderSmall>
<CurrencyLogo currency={activeCurrency} size="24px" /> <CurrencyInput onClick={() => setTokenSelectorOpen(true)}>
<ThemedText.HeadlineSmall fontWeight={500} lineHeight="24px"> <CurrencyLogo currency={activeCurrency} size="24px" />
{activeCurrency?.symbol} <ThemedText.HeadlineSmall fontWeight={500} lineHeight="24px">
</ThemedText.HeadlineSmall> {activeCurrency?.symbol}
<ChevronDown size={20} color={theme.textSecondary} /> </ThemedText.HeadlineSmall>
</CurrencyInput> <ChevronDown size={20} color={theme.textSecondary} />
</Column> </CurrencyInput>
<TotalColumn gap="xs"> </Column>
<ThemedText.SubHeaderSmall marginBottom="4px"> <TotalColumn gap="xs">
<Trans>Total</Trans> <ThemedText.SubHeaderSmall marginBottom="4px">
</ThemedText.SubHeaderSmall> <Trans>Total</Trans>
<ThemedText.HeadlineSmall> </ThemedText.SubHeaderSmall>
{inputCurrency <InputCurrencyValue
? swapState !== TradeState.VALID usingPayWithAnyToken={usingPayWithAnyToken}
? '-' totalEthPrice={totalEthPrice}
: ethNumberStandardFormatter(swapTrade?.inputAmount.toExact()) activeCurrency={activeCurrency}
: formatWeiToDecimal(totalEthPrice.toString())} tradeState={tradeState}
&nbsp;{activeCurrency?.symbol ?? 'ETH'} trade={trade}
</ThemedText.HeadlineSmall> />
<ThemedText.BodySmall color="textSecondary" lineHeight="20px"> </TotalColumn>
{`${ethNumberStandardFormatter(usdcValue?.toExact(), true)}`} </CurrencyRow>
</ThemedText.BodySmall> <FiatValue usdcValue={usdcValue} priceImpact={stablecoinPriceImpact} priceImpactColor={priceImpactColor} />
</TotalColumn> </FooterHeader>
</CurrencyRow>
)} )}
{!shouldUsePayWithAnyToken && ( {!shouldUsePayWithAnyToken && (
<FooterHeader gap="xs" warningText={!!warningText}> <FooterHeader gap="xs">
<Row justify="space-between"> <Row justify="space-between">
<div> <div>
<ThemedText.HeadlineSmall>Total</ThemedText.HeadlineSmall> <ThemedText.HeadlineSmall>Total</ThemedText.HeadlineSmall>
</div> </div>
<div> <div>
<ThemedText.HeadlineSmall> <ThemedText.HeadlineSmall>
{formatWeiToDecimal(totalEthPrice.toString())}&nbsp;ETH {formatWeiToDecimal(totalEthPrice.toString())}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.HeadlineSmall> </ThemedText.HeadlineSmall>
</div> </div>
</Row> </Row>
<Row justify="flex-end"> <FiatValue usdcValue={usdcValue} priceImpact={stablecoinPriceImpact} priceImpactColor={priceImpactColor} />
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">{`${ethNumberStandardFormatter(
usdcValue?.toExact(),
true
)}`}</ThemedText.BodySmall>
</Row>
</FooterHeader> </FooterHeader>
)} )}
<TraceEvent <TraceEvent
...@@ -275,7 +470,8 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti ...@@ -275,7 +470,8 @@ export const BagFooter = ({ totalEthPrice, bagStatus, fetchAssets, eventProperti
shouldLogImpression={connected && !disabled} shouldLogImpression={connected && !disabled}
> >
<Warning>{warningText}</Warning> <Warning>{warningText}</Warning>
<ActionButton onClick={handleClick} disabled={disabled}> <Helper color={helperTextColor}>{helperText}</Helper>
<ActionButton onClick={handleClick} disabled={disabled} backgroundColor={buttonColor}>
{isPending && <Loader size="20px" stroke="white" />} {isPending && <Loader size="20px" stroke="white" />}
{buttonText} {buttonText}
</ActionButton> </ActionButton>
......
import { Currency, CurrencyAmount, NativeCurrency, Token, TradeType } from '@uniswap/sdk-core'
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
import { useBestTrade } from 'hooks/useBestTrade'
import { useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types'
export default function usePayWithAnyTokenSwap(
inputCurrency?: Currency,
parsedOutputAmount?: CurrencyAmount<NativeCurrency | Token>
): {
state: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
maximumAmountIn: CurrencyAmount<Token> | undefined
} {
const { state, trade } = useBestTrade(TradeType.EXACT_OUTPUT, parsedOutputAmount, inputCurrency ?? undefined)
const allowedSlippage = useAutoSlippageTolerance(trade)
const maximumAmountIn = useMemo(() => {
const maximumAmountIn = trade?.maximumAmountIn(allowedSlippage)
return maximumAmountIn?.currency.isToken ? (maximumAmountIn as CurrencyAmount<Token>) : undefined
}, [allowedSlippage, trade])
return useMemo(() => {
return {
state,
trade,
maximumAmountIn,
}
}, [maximumAmountIn, state, trade])
}
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceEventName } from '@uniswap/analytics-events'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { useWeb3React } from '@web3-react/core'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useCallback, useMemo, useState } from 'react'
import invariant from 'tiny-invariant'
export default function usePermit2Approval(
amount?: CurrencyAmount<Token>,
maximumAmount?: CurrencyAmount<Token>,
enabled?: boolean
) {
const { chainId } = useWeb3React()
const allowance = usePermit2Allowance(
enabled ? maximumAmount ?? (amount?.currency.isToken ? (amount as CurrencyAmount<Token>) : undefined) : undefined,
enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
)
const isApprovalLoading = allowance.state === AllowanceState.REQUIRED && allowance.isApprovalLoading
const [isAllowancePending, setIsAllowancePending] = useState(false)
const updateAllowance = useCallback(async () => {
invariant(allowance.state === AllowanceState.REQUIRED)
setIsAllowancePending(true)
try {
await allowance.approveAndPermit()
sendAnalyticsEvent(InterfaceEventName.APPROVE_TOKEN_TXN_SUBMITTED, {
chain_id: chainId,
token_symbol: maximumAmount?.currency.symbol,
token_address: maximumAmount?.currency.address,
})
} catch (e) {
console.error(e)
} finally {
setIsAllowancePending(false)
}
}, [allowance, chainId, maximumAmount?.currency.address, maximumAmount?.currency.symbol])
return useMemo(() => {
return {
allowance,
isApprovalLoading,
isAllowancePending,
updateAllowance,
}
}, [allowance, isAllowancePending, isApprovalLoading, updateAllowance])
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment