Skip to content

Commit

Permalink
feat(usd): use bff usd for spot prices (#5281)
Browse files Browse the repository at this point in the history
* refactor: move usdcPriceLoader to its own file

* feat: use BFF for fetching spot price

* refactor: use async fns

* fix: remove promise caching cache is done at api level

Caching added on https://github.com/cowprotocol/cowswap/pull/5266/files
  • Loading branch information
alfetopito authored Jan 15, 2025
1 parent ead6388 commit 2a7a51d
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,87 +1,45 @@
import { useEffect, useState } from 'react'

import { useIsWindowVisible } from '@cowprotocol/common-hooks'
import { getAddress, getIsNativeToken } from '@cowprotocol/common-utils'
import { getWrappedToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { Currency, Fraction } from '@uniswap/sdk-core'

import * as Sentry from '@sentry/browser'
import ms from 'ms.macro'
import { useAsyncMemo } from 'use-async-memo'

import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState'
import { parsePrice } from 'modules/limitOrders/utils/parsePrice'

import { getNativePrice } from 'api/cowProtocol'
import { useSafeMemo } from 'common/hooks/useSafeMemo'

type PriceResult = number | Error | undefined
import { fetchCurrencyUsdPrice, usdcPriceLoader } from '../../usdAmount'

const PRICE_UPDATE_INTERVAL = ms`10sec`

async function requestPriceForCurrency(chainId: number | undefined, currency: Currency | null): Promise<PriceResult> {
const currencyAddress = getAddress(currency)

if (!chainId || !currency) {
return
}

try {
if (getIsNativeToken(currency) || !currencyAddress) {
return parsePrice(1, currency)
}

const result = await getNativePrice(chainId, currencyAddress)

if (!result) {
throw new Error('No result from native_price endpoint')
}

const price = parsePrice(result.price || 0, currency)
if (!price) {
throw new Error("Couldn't parse native_price result")
}

return price
} catch (error: any) {
console.warn('[requestPriceForCurrency] Error fetching native_price', error)

const sentryError = Object.assign(error, {
message: error.message || 'Error fetching native_price ',
name: 'NativePriceFetchError',
})

const params = {
chainId,
tokenAddress: currencyAddress,
tokenName: currency?.name,
tokenSymbol: currency.symbol,
}

Sentry.captureException(sentryError, {
contexts: {
params,
},
})

return error
}
}

export async function requestPrice(
chainId: number | undefined,
inputCurrency: Currency | null,
outputCurrency: Currency | null
outputCurrency: Currency | null,
): Promise<Fraction | null> {
if (!chainId || !inputCurrency || !outputCurrency) {
return null
}

const inputToken = getWrappedToken(inputCurrency)
const outputToken = getWrappedToken(outputCurrency)

// Only needed for the fallback CoW price, which needs to know the USDC price
const getUsdPrice = usdcPriceLoader(chainId)

return Promise.all([
requestPriceForCurrency(chainId, inputCurrency),
requestPriceForCurrency(chainId, outputCurrency),
fetchCurrencyUsdPrice(inputToken, getUsdPrice),
fetchCurrencyUsdPrice(outputToken, getUsdPrice),
]).then(([inputPrice, outputPrice]) => {
if (!inputPrice || !outputPrice || inputPrice instanceof Error || outputPrice instanceof Error) {
if (!inputPrice || !outputPrice) {
return null
}

const result = new Fraction(inputPrice, outputPrice)
const result = inputPrice.divide(outputPrice)

console.debug('Updated limit orders initial price: ', result.toSignificant(18))

Expand All @@ -91,7 +49,6 @@ export async function requestPrice(

// Fetches the INPUT and OUTPUT price and calculates initial Active rate
// When return null it means we failed on price loading
// TODO: rename it to useNativeBasedPrice
export function useGetInitialPrice(): { price: Fraction | null; isLoading: boolean } {
const { chainId } = useWalletInfo()
const { inputCurrency, outputCurrency } = useLimitOrdersDerivedState()
Expand All @@ -100,16 +57,18 @@ export function useGetInitialPrice(): { price: Fraction | null; isLoading: boole
const isWindowVisible = useIsWindowVisible()

const price = useAsyncMemo(
() => {
async () => {
setIsLoading(true)

console.debug('[useGetInitialPrice] Fetching price')
return requestPrice(chainId, inputCurrency, outputCurrency).finally(() => {
try {
return await requestPrice(chainId, inputCurrency, outputCurrency)
} finally {
setIsLoading(false)
})
}
},
[chainId, inputCurrency, outputCurrency, updateTimestamp],
null
null,
)

// Update initial price every 10 seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ export function useUpdateActiveRate(): UpdateRateCallback {
inputCurrencyAmount,
outputCurrencyAmount,
updateLimitOrdersState,
]
],
)
}
2 changes: 2 additions & 0 deletions apps/cowswap-frontend/src/modules/usdAmount/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { useTradeUsdAmounts } from './hooks/useTradeUsdAmounts'
export { UsdPricesUpdater } from './updaters/UsdPricesUpdater'
export { useUsdAmount } from './hooks/useUsdAmount'
export { useUsdPrice } from './hooks/useUsdPrice'
export { usdcPriceLoader } from './utils/usdcPriceLoader'
export { fetchCurrencyUsdPrice } from './services/fetchCurrencyUsdPrice'
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useMemo } from 'react'

import { USDC } from '@cowprotocol/common-const'
import { getWrappedToken } from '@cowprotocol/common-utils'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { useWalletInfo } from '@cowprotocol/wallet'
import { Fraction, Token } from '@uniswap/sdk-core'

import ms from 'ms.macro'
import useSWR, { SWRConfiguration } from 'swr'

import { getCowProtocolNativePrice } from '../apis/getCowProtocolNativePrice'
import { fetchCurrencyUsdPrice } from '../services/fetchCurrencyUsdPrice'
import {
currenciesUsdPriceQueueAtom,
Expand All @@ -19,6 +16,7 @@ import {
usdRawPricesAtom,
UsdRawPriceState,
} from '../state/usdRawPricesAtom'
import { usdcPriceLoader } from '../utils/usdcPriceLoader'

const swrOptions: SWRConfiguration = {
refreshInterval: ms`60s`,
Expand Down Expand Up @@ -66,19 +64,6 @@ export function UsdPricesUpdater() {
return null
}

function usdcPriceLoader(chainId: SupportedChainId): () => Promise<Fraction | null> {
let usdcPricePromise: Promise<Fraction | null> | null = null

return () => {
// Cache the result to avoid fetching it multiple times
if (!usdcPricePromise) {
usdcPricePromise = getCowProtocolNativePrice(USDC[chainId])
}

return usdcPricePromise
}
}

async function processQueue(queue: Token[], getUsdcPrice: () => Promise<Fraction | null>): Promise<UsdRawPrices> {
const results = await Promise.all(
queue.map(async (currency) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { USDC } from '@cowprotocol/common-const'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { Fraction } from '@uniswap/sdk-core'

import { getCowProtocolNativePrice } from '../apis/getCowProtocolNativePrice'

export function usdcPriceLoader(chainId: SupportedChainId): () => Promise<Fraction | null> {
return () => getCowProtocolNativePrice(USDC[chainId])
}

0 comments on commit 2a7a51d

Please sign in to comment.