Commit a8864614 authored by Tina's avatar Tina Committed by GitHub

feat: Use ETH based pricing instead of USDC based pricing for quote USD values (#6132)

* add eth denominated usd pricing mecanism

* fix usd value change and trade price calculations

* remove console log

* break out 10k into constant

* put back faulty block number check for now - separate PR

* add 1 min poll interval

* use lib/utils/tryParseCurrencyAmount

---------
Co-authored-by: default avatarJordan Frankfurt <jordanwfrankfurt@gmail.com>
parent 19d25de1
import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { formatCurrencyAmount, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
import { Percent } from '@uniswap/sdk-core'
import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip'
import { useEffect, useMemo, useState } from 'react'
......@@ -22,7 +22,7 @@ export function FiatValue({
priceImpact,
isLoading = false,
}: {
fiatValue: CurrencyAmount<Currency> | null | undefined
fiatValue: number | null | undefined
priceImpact?: Percent
isLoading?: boolean
}) {
......@@ -56,7 +56,7 @@ export function FiatValue({
<FiatLoadingBubble />
) : (
<div>
{fiatValue && <>{formatCurrencyAmount(fiatValue, NumberType.FiatTokenPrice)}</>}
{fiatValue ? formatNumber(fiatValue, NumberType.FiatTokenPrice) : undefined}
{priceImpact && (
<span style={{ color: priceImpactColor }}>
{' '}
......
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core'
import { AutoColumn } from 'components/Column'
......@@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: CurrencyAmount<Token> | null
fiatValue?: number | null
priceImpact?: Percent
id: string
showCommonBases?: boolean
......
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core'
import { AutoColumn } from 'components/Column'
......@@ -182,7 +182,7 @@ interface CurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: CurrencyAmount<Token> | null
fiatValue?: number | null
priceImpact?: Percent
id: string
showCommonBases?: boolean
......
......@@ -108,7 +108,7 @@ export function useSyncWidgetTransactions() {
...formatSwapSignedAnalyticsEventProperties({
trade,
// TODO: add once Widgets adds fiat values to callback
fiatValues: { amountIn: null, amountOut: null },
fiatValues: { amountIn: undefined, amountOut: undefined },
txHash: transaction.receipt?.transactionHash ?? '',
}),
...trace,
......
......@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics'
import { InterfaceModalName } from '@uniswap/analytics-events'
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { ReactNode, useCallback, useMemo, useState } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
......@@ -42,8 +42,8 @@ export default function ConfirmSwapModal({
swapErrorMessage: ReactNode | undefined
onDismiss: () => void
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
fiatValueInput?: number
fiatValueOutput?: number
}) {
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
// and an event triggered by modal closing should be logged.
......
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import {
formatPercentInBasisPointsNumber,
......@@ -31,8 +31,8 @@ interface AnalyticsEventProps {
isAutoRouterApi: boolean
swapQuoteReceivedDate: Date | undefined
routes: RoutingDiagramEntry[]
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
fiatValueInput?: number
fiatValueOutput?: number
}
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
......@@ -83,8 +83,8 @@ const formatAnalyticsEventProperties = ({
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
token_in_amount_usd: fiatValueInput ? parseFloat(fiatValueInput.toFixed(2)) : undefined,
token_out_amount_usd: fiatValueOutput ? parseFloat(fiatValueOutput.toFixed(2)) : undefined,
token_in_amount_usd: fiatValueInput,
token_out_amount_usd: fiatValueOutput,
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
is_auto_router_api: isAutoRouterApi,
......@@ -118,8 +118,8 @@ export default function SwapModalFooter({
swapErrorMessage: ReactNode | undefined
disabledConfirm: boolean
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
fiatValueInput?: number
fiatValueOutput?: number
}) {
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
......
......@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useUSDPrice } from 'hooks/useUSDPrice'
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
import { useEffect, useState } from 'react'
import { AlertTriangle, ArrowDown } from 'react-feather'
......@@ -9,7 +10,6 @@ import { Text } from 'rebass'
import { InterfaceTrade } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
import { ThemedText } from '../../theme'
import { isAddress, shortenAddress } from '../../utils'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
......@@ -78,8 +78,8 @@ export default function SwapModalHeader({
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
const fiatValueInput = useStablecoinValue(trade.inputAmount)
const fiatValueOutput = useStablecoinValue(trade.outputAmount)
const fiatValueInput = useUSDPrice(trade.inputAmount)
const fiatValueOutput = useUSDPrice(trade.outputAmount)
useEffect(() => {
if (!trade.executionPrice.equalTo(lastExecutionPrice)) {
......
import { Trans } from '@lingui/macro'
import { formatNumber, NumberType } from '@uniswap/conedison/format'
import { Currency, Price } from '@uniswap/sdk-core'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
import { useUSDPrice } from 'hooks/useUSDPrice'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useCallback, useState } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
interface TradePriceProps {
price: Price<Currency, Currency>
......@@ -30,7 +32,8 @@ const StyledPriceContainer = styled.button`
export default function TradePrice({ price }: TradePriceProps) {
const [showInverted, setShowInverted] = useState<boolean>(false)
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
const { baseCurrency, quoteCurrency } = price
const usdPrice = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency))
let formattedPrice: string
try {
......@@ -56,9 +59,9 @@ export default function TradePrice({ price }: TradePriceProps) {
title={text}
>
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
{usdcPrice && (
{usdPrice && (
<ThemedText.DeprecatedDarkGray>
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice), isPrice: true })})</Trans>
<Trans>({formatNumber(usdPrice, NumberType.FiatTokenPrice)})</Trans>
</ThemedText.DeprecatedDarkGray>
)}
</StyledPriceContainer>
......
import gql from 'graphql-tag'
gql`
query TokenSpotPrice($chain: Chain!, $address: String) {
token(chain: $chain, address: $address) {
id
address
chain
name
symbol
project {
id
markets(currencies: [USD]) {
id
price {
id
value
}
}
}
}
}
`
......@@ -73,6 +73,10 @@ export function chainIdToBackendName(chainId: number | undefined) {
: CHAIN_ID_TO_BACKEND_NAME[SupportedChainId.MAINNET]
}
export function isGqlSupportedChain(chainId: number | undefined): chainId is SupportedChainId {
return Boolean(chainId && CHAIN_ID_TO_BACKEND_NAME[chainId])
}
const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = {
ethereum: Chain.Ethereum,
polygon: Chain.Polygon,
......
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { PermitSignature } from 'hooks/usePermitAllowance'
import { useMemo } from 'react'
......@@ -13,7 +13,7 @@ import { useUniversalRouterSwapCallback } from './useUniversalRouter'
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
trade: Trade<Currency, Currency, TradeType> | undefined, // trade to execute, required
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null }, // usd values for amount in and out, logged for analytics
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }, // usd values for amount in and out, logged for analytics
allowedSlippage: Percent, // in bips
permitSignature: PermitSignature | undefined
): { callback: null | (() => Promise<string>) } {
......
import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@uniswap/sdk-core'
import { nativeOnChain } from 'constants/tokens'
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
import { RouterPreference } from 'state/routing/slice'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
import useStablecoinPrice from './useStablecoinPrice'
// ETH amounts used when calculating spot price for a given currency.
// The amount is large enough to filter low liquidity pairs.
const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = {
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 100),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 100),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e6),
}
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): CurrencyAmount<Currency> | undefined {
const chainId = currencyAmount?.currency?.chainId
const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined
const { trade } = useRoutingAPITrade(
TradeType.EXACT_OUTPUT,
amountOut,
currencyAmount?.currency,
RouterPreference.PRICE
)
if (chainId && currencyAmount && currencyAmount.currency.equals(nativeOnChain(chainId))) {
return new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount)
}
if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) return
const { numerator, denominator } = trade.routes[0].midPrice
const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator)
return price.quote(currencyAmount)
}
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): number | undefined {
const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined
const currency = currencyAmount?.currency
const ethValue = useETHValue(currencyAmount)
const { data } = useTokenSpotPriceQuery({
variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) },
skip: !chain || !isGqlSupportedChain(currency?.chainId),
pollInterval: PollingInterval.Normal,
})
// Use USDC price for chains not supported by backend yet
const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined)
if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) {
return parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant())
}
// Otherwise, get the price of the token in ETH, and then multiple by the price of ETH
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
if (!ethUSDPrice || !ethValue) return undefined
return parseFloat(ethValue.toExact()) * ethUSDPrice
}
......@@ -4,7 +4,7 @@ import { t } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { SwapEventName } from '@uniswap/analytics-events'
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk'
import { FeeOptions, toHex } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
......@@ -27,7 +27,7 @@ interface SwapOptions {
export function useUniversalRouterSwapCallback(
trade: Trade<Currency, Currency, TradeType> | undefined,
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null },
fiatValues: { amountIn: number | undefined; amountOut: number | undefined },
options: SwapOptions
) {
const { account, chainId, provider } = useWeb3React()
......
......@@ -40,7 +40,7 @@ export const formatSwapSignedAnalyticsEventProperties = ({
txHash,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
fiatValues: { amountIn: CurrencyAmount<Currency> | null; amountOut: CurrencyAmount<Currency> | null }
fiatValues: { amountIn: number | undefined; amountOut: number | undefined }
txHash: string
}) => ({
transaction_hash: txHash,
......@@ -50,8 +50,8 @@ export const formatSwapSignedAnalyticsEventProperties = ({
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
token_in_amount_usd: fiatValues.amountIn ? parseFloat(fiatValues.amountIn.toFixed(2)) : undefined,
token_out_amount_usd: fiatValues.amountOut ? parseFloat(fiatValues.amountOut.toFixed(2)) : undefined,
token_in_amount_usd: fiatValues.amountIn,
token_out_amount_usd: fiatValues.amountOut,
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
......
......@@ -501,6 +501,8 @@ export default function AddLiquidity() {
</AutoColumn>
)
const currencyAFiat = usdcValues[Field.CURRENCY_A]
const currencyBFiat = usdcValues[Field.CURRENCY_B]
return (
<>
<ScrollablePage>
......@@ -651,7 +653,7 @@ export default function AddLiquidity() {
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A] ?? null}
id="add-liquidity-input-tokena"
fiatValue={usdcValues[Field.CURRENCY_A]}
fiatValue={currencyAFiat && parseFloat(currencyAFiat.toSignificant())}
showCommonBases
locked={depositADisabled}
/>
......@@ -663,7 +665,7 @@ export default function AddLiquidity() {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
fiatValue={usdcValues[Field.CURRENCY_B]}
fiatValue={currencyBFiat && parseFloat(currencyBFiat.toSignificant())}
currency={currencies[Field.CURRENCY_B] ?? null}
id="add-liquidity-input-tokenb"
showCommonBases
......
......@@ -25,6 +25,7 @@ import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import useENSAddress from 'hooks/useENSAddress'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import { useSwapCallback } from 'hooks/useSwapCallback'
import { useUSDPrice } from 'hooks/useUSDPrice'
import JSBI from 'jsbi'
import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics'
import { useCallback, useEffect, useMemo, useState } from 'react'
......@@ -54,7 +55,6 @@ import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { useAllTokens, useCurrency } from '../../hooks/Tokens'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useStablecoinValue } from '../../hooks/useStablecoinPrice'
import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
import { Field } from '../../state/swap/actions'
import {
......@@ -230,16 +230,16 @@ export default function Swap({ className }: { className?: string }) {
},
[independentField, parsedAmount, showWrap, trade]
)
const fiatValueInput = useStablecoinValue(parsedAmounts[Field.INPUT])
const fiatValueOutput = useStablecoinValue(parsedAmounts[Field.OUTPUT])
const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT])
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT])
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
() => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.SYNCING === tradeState],
[trade, tradeState]
)
const fiatValueTradeInput = useStablecoinValue(trade?.inputAmount)
const fiatValueTradeOutput = useStablecoinValue(trade?.outputAmount)
const fiatValueTradeInput = useUSDPrice(trade?.inputAmount)
const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
const stablecoinPriceImpact = useMemo(
() =>
routeIsSyncing || !trade ? undefined : computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
......
......@@ -62,7 +62,7 @@ export function useRoutingAPITrade<TTradeType extends TradeType>(
const isSyncing = currentData !== data
return useMemo(() => {
if (!currencyIn || !currencyOut) {
if (!currencyIn || !currencyOut || currencyIn.equals(currencyOut)) {
return {
state: TradeState.INVALID,
trade: undefined,
......
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import { ONE_HUNDRED_PERCENT } from '../constants/misc'
import { Percent } from '@uniswap/sdk-core'
const PRECISION = 10000
export function computeFiatValuePriceImpact(
fiatValueInput: CurrencyAmount<Currency> | undefined | null,
fiatValueOutput: CurrencyAmount<Currency> | undefined | null
fiatValueInput: number | undefined | null,
fiatValueOutput: number | undefined | null
): Percent | undefined {
if (!fiatValueOutput || !fiatValueInput) return undefined
if (!fiatValueInput.currency.equals(fiatValueOutput.currency)) return undefined
if (JSBI.equal(fiatValueInput.quotient, JSBI.BigInt(0))) return undefined
const pct = ONE_HUNDRED_PERCENT.subtract(fiatValueOutput.divide(fiatValueInput))
return new Percent(pct.numerator, pct.denominator)
if (fiatValueInput === 0) return undefined
const ratio = 1 - fiatValueOutput / fiatValueInput
const numerator = Math.floor(ratio * PRECISION)
return new Percent(numerator, PRECISION)
}
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