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:
@@ -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 })
|
||||
|
||||
@@ -13,7 +13,7 @@ export default async function getAssetParams(
|
||||
return iterateContractQuery(paramsQueryClient.allAssetParams)
|
||||
},
|
||||
assetParamsCache,
|
||||
'assetParams',
|
||||
`${chainConfig.id}/assetParams`,
|
||||
600,
|
||||
)
|
||||
} catch (ex) {
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function getOraclePrices(
|
||||
const priceResults = await cacheFn(
|
||||
() => iterateContractQuery(oracleQueryClient.prices),
|
||||
oraclePriceCache,
|
||||
'oraclePrices',
|
||||
`${chainConfig.id}/oraclePrices`,
|
||||
60,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@ export const getVaultConfigs = async (
|
||||
return await cacheFn(
|
||||
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
|
||||
vaultConfigsCache,
|
||||
'vaultConfigs',
|
||||
`${chainConfig.id}/vaultConfigs`,
|
||||
600,
|
||||
)
|
||||
} catch (ex) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user