Pyth price fetching (#723)

* env: remove testing library

* fix: use pyth over oracle

* fix: fix the endpoints

* fix: fix build

* tidy: refactor

* fix: fixed account fetching

* fix: made all queries chain agnostic

* fix: fixed the chart position
This commit is contained in:
Linkie Link
2024-01-11 12:16:47 +01:00
committed by GitHub
parent f1f934d4c1
commit 0960f84b58
24 changed files with 94 additions and 144 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ export default async function getAccount(
const accountPosition: Positions = await cacheFn(
() => creditManagerQueryClient.positions({ accountId: accountId }),
positionsCache,
`account/${accountId}`,
`${chainConfig.id}/account/${accountId}`,
)
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
+1 -1
View File
@@ -13,7 +13,7 @@ export default async function getAssetParams(
return iterateContractQuery(paramsQueryClient.allAssetParams)
},
assetParamsCache,
'assetParams',
`${chainConfig.id}/assetParams`,
600,
)
} catch (ex) {
+1 -1
View File
@@ -19,7 +19,7 @@ export default async function getOraclePrices(
const priceResults = await cacheFn(
() => iterateContractQuery(oracleQueryClient.prices),
oraclePriceCache,
'oraclePrices',
`${chainConfig.id}/oraclePrices`,
60,
)
+1 -1
View File
@@ -38,7 +38,7 @@ const getAssetRate = async (chainConfig: ChainConfig, asset: Asset) => {
const response = await cacheFn(
() => fetch(url).then((res) => res.json()),
poolPriceCache,
`poolPrices/${(asset.poolId || 0).toString()}`,
`${chainConfig.id}/poolPrices/${(asset.poolId || 0).toString()}`,
60,
)
const pool = response.pool
+4 -23
View File
@@ -1,10 +1,7 @@
import { cacheFn, priceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client'
import getPoolPrice from 'api/prices/getPoolPrice'
import getPythPrice from 'api/prices/getPythPrices'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import getPrices from 'api/prices/getPrices'
import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
export default async function getPrice(
chainConfig: ChainConfig,
@@ -15,25 +12,9 @@ export default async function getPrice(
async function fetchPrice(chainConfig: ChainConfig, denom: string) {
try {
const asset = chainConfig.assets.find(byDenom(denom)) as Asset
const prices = await getPrices(chainConfig)
if (asset.pythPriceFeedId) {
return (await getPythPrice(chainConfig, [asset.pythPriceFeedId]))[0]
}
if (asset.hasOraclePrice) {
const oracleQueryClient = await getOracleQueryClient(chainConfig)
const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BN(priceResponse.price).shiftedBy(decimalDiff)
}
if (asset.poolId) {
return await getPoolPrice(chainConfig, asset)
}
throw `could not fetch the price info for the given denom: ${denom}`
return prices.find(byDenom(denom))?.amount ?? BN_ZERO
} catch (ex) {
throw ex
}
+4 -7
View File
@@ -1,21 +1,18 @@
import fetchPythPriceData from 'api/prices/getPythPriceData'
export default async function getPricesData(
chainConfig: ChainConfig,
assets: Asset[],
): Promise<string[]> {
export default async function getPricesData(assets: Asset[]): Promise<string[]> {
try {
const assetsWithPythPriceFeedId = assets.filter((asset) => !!asset.pythPriceFeedId)
return await requestPythPriceData(chainConfig, assetsWithPythPriceFeedId)
return await requestPythPriceData(assetsWithPythPriceFeedId)
} catch (ex) {
console.error(ex)
throw ex
}
}
async function requestPythPriceData(chainConfig: ChainConfig, assets: Asset[]): Promise<string[]> {
async function requestPythPriceData(assets: Asset[]): Promise<string[]> {
if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPriceData(chainConfig, priceFeedIds)
return await fetchPythPriceData(priceFeedIds)
}
+29 -28
View File
@@ -1,32 +1,35 @@
import getOraclePrices from 'api/prices/getOraclePrices'
import getPoolPrice from 'api/prices/getPoolPrice'
import fetchPythPrices from 'api/prices/getPythPrices'
import chains from 'configs/chains'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { partition } from 'utils/array'
import { getAllAssetsWithPythId } from 'utils/assets'
export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoin[]> {
const usdPrice = new BNCoin({ denom: 'usd', amount: '1' })
try {
const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices)
const pythAndOraclePrices = (
await Promise.all([
requestPythPrices(chainConfig, assetsWithPythPriceFeedId),
getOraclePrices(chainConfig, assetsWithOraclePrices),
])
).flat()
const pythAndOraclePrices = []
const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const assetsWithPythPriceFeedId = getAllAssetsWithPythId(chains)
const pythPrices = await requestPythPrices(assetsWithPythPriceFeedId)
pythAndOraclePrices.push(...pythPrices)
try {
const [assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices)
const oraclePrices = await getOraclePrices(chainConfig, assetsWithOraclePrices)
const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
useStore.setState({ isOracleStale: false })
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
return [...pythAndOraclePrices, ...oraclePrices, ...poolPrices, usdPrice]
} catch (ex) {
console.error(ex)
let message = 'Unknown Error'
@@ -34,15 +37,17 @@ export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoi
if (message.includes('price publish time is too old'))
useStore.setState({ isOracleStale: true })
throw ex
return [...pythAndOraclePrices, usdPrice]
}
}
async function requestPythPrices(chainConfig: ChainConfig, assets: Asset[]): Promise<BNCoin[]> {
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> {
if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPrices(chainConfig, priceFeedIds).then(mapResponseToBnCoin(assets))
const priceFeedIds = assets
.map((a) => a.pythPriceFeedId)
.filter((priceFeedId, index, array) => array.indexOf(priceFeedId) === index) as string[]
return await fetchPythPrices(priceFeedIds, assets)
}
async function requestPoolPrices(
@@ -55,24 +60,20 @@ async function requestPoolPrices(
return await Promise.all(requests).then(mapResponseToBnCoin(assets))
}
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) =>
prices.map((price: BigNumber, index: number) =>
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => {
return prices.map((price: BigNumber, index: number) =>
BNCoin.fromDenomAndBigNumber(assets[index].denom, price),
)
}
function separateAssetsByPriceSources(assets: Asset[]) {
// Only fetch Pyth prices for mainnet
const [assetsWithPythPriceFeedId, assetsWithoutPythPriceFeedId] = partition(
assets,
(asset) => !!asset.pythPriceFeedId,
)
const assetsWithoutPythPriceFeedId = assets.filter((asset) => !asset.pythPriceFeedId)
// Don't get oracle price if it's not mainnet and there is a poolId
const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition(
assetsWithoutPythPriceFeedId,
(asset) => asset.hasOraclePrice || !asset.poolId,
)
const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId)
return [assetsWithPythPriceFeedId, assetsWithOraclePrice, assetsWithPoolId]
return [assetsWithOraclePrice, assetsWithPoolId]
}
+3 -2
View File
@@ -1,8 +1,9 @@
import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth'
export default async function fetchPythPriceData(chainConfig: ChainConfig, priceFeedIds: string[]) {
export default async function fetchPythPriceData(priceFeedIds: string[]) {
try {
const pricesUrl = new URL(`${chainConfig.endpoints.pyth}/latest_vaas`)
const pricesUrl = new URL(`${pythEndpoints.api}/latest_vaas`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythDataResponse: string[] = await cacheFn(
+16 -3
View File
@@ -1,9 +1,11 @@
import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
export default async function fetchPythPrices(chainConfig: ChainConfig, priceFeedIds: string[]) {
export default async function fetchPythPrices(priceFeedIds: string[], assets: Asset[]) {
try {
const pricesUrl = new URL(`${chainConfig.endpoints.pyth}/latest_price_feeds`)
const pricesUrl = new URL(`${pythEndpoints.api}/latest_price_feeds`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythResponse: PythPriceData[] = await cacheFn(
@@ -13,7 +15,18 @@ export default async function fetchPythPrices(chainConfig: ChainConfig, priceFee
30,
)
return pythResponse.map(({ price }) => BN(price.price).shiftedBy(price.expo))
const mappedPriceData = [] as BNCoin[]
assets.forEach((asset) => {
const price = pythResponse.find((pythPrice) => asset.pythPriceFeedId === pythPrice.id)?.price
if (price)
mappedPriceData.push(
BNCoin.fromDenomAndBigNumber(asset.denom, BN(price.price).shiftedBy(price.expo)),
)
return
})
return mappedPriceData
} catch (ex) {
throw ex
}
+7 -2
View File
@@ -5,12 +5,17 @@ export default async function getAprs(chainConfig: ChainConfig) {
const response = await cacheFn(
() => fetch(chainConfig.endpoints.aprs.vaults),
aprsCacheResponse,
'aprsResponse',
`${chainConfig.id}/aprsResponse`,
60,
)
if (response.ok) {
const data: AprResponse = await cacheFn(() => response.json(), aprsCache, 'aprs', 60)
const data: AprResponse = await cacheFn(
() => response.json(),
aprsCache,
`${chainConfig.id}/aprs`,
60,
)
return data.vaults.map((aprData) => {
const finalApr = aprData.apr.projected_apr * 100
+1 -1
View File
@@ -11,7 +11,7 @@ export const getVaultConfigs = async (
return await cacheFn(
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
vaultConfigsCache,
'vaultConfigs',
`${chainConfig.id}/vaultConfigs`,
600,
)
} catch (ex) {
-1
View File
@@ -15,7 +15,6 @@ export default async function getAccounts(
.map((account) => getAccount(chainConfig, account.id))
const accounts = await Promise.all($accounts).then((accounts) => accounts)
if (accounts) {
return accounts.sort((a, b) => Number(a.id) - Number(b.id))
}