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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 94 additions and 144 deletions

View File

@ -55,7 +55,6 @@
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
"@testing-library/react": "^14.0.0",
"@types/debounce-promise": "^3.1.9",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.8",

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 })

View File

@ -13,7 +13,7 @@ export default async function getAssetParams(
return iterateContractQuery(paramsQueryClient.allAssetParams)
},
assetParamsCache,
'assetParams',
`${chainConfig.id}/assetParams`,
600,
)
} catch (ex) {

View File

@ -19,7 +19,7 @@ export default async function getOraclePrices(
const priceResults = await cacheFn(
() => iterateContractQuery(oracleQueryClient.prices),
oraclePriceCache,
'oraclePrices',
`${chainConfig.id}/oraclePrices`,
60,
)

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

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
}

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)
}

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]
}

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(

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
}

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

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) {

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))
}

View File

@ -1,5 +1,6 @@
import { defaultSymbolInfo } from 'components/Trade/TradeChart/constants'
import { MILLISECONDS_PER_MINUTE } from 'constants/math'
import { pythEndpoints } from 'constants/pyth'
import { byDenom } from 'utils/array'
import {
Bar,
@ -74,7 +75,7 @@ export class DataFeed implements IDatafeedChartApi {
chainConfig: ChainConfig,
) {
if (debug) console.log('Start charting library datafeed')
this.candlesEndpoint = chainConfig.endpoints.pythCandles
this.candlesEndpoint = pythEndpoints.candles
this.candlesEndpointTheGraph = chainConfig.endpoints.graphCandles ?? ''
this.assets = assets
this.debug = debug

View File

@ -160,7 +160,7 @@ export const TVChartContainer = (props: Props) => {
)}
</div>
}
contentClassName='px-0.5 pb-0.5 h-full bg-chart'
contentClassName='px-0.5 pb-0.5 h-full bg-chart w-[calc(100%-2px)] ml-[1px]'
className='h-[70dvh] max-h-[980px] min-h-[560px]'
>
<div ref={chartContainerRef} className='h-[calc(100%-32px)] overflow-hidden' />

View File

@ -45,7 +45,7 @@ export default function TradeChart(props: Props) {
<Loading className='h-4 mr-4 w-60' />
</div>
}
contentClassName='px-0.5 pb-0.5 h-full'
contentClassName='px-0.5 pb-0.5 h-full bg-chart w-[calc(100%-2px)] ml-[1px]'
className='h-[70dvh] max-h-[980px] min-h-[560px]'
>
<div className='flex items-center justify-center w-full h-[calc(100%-32px)] rounded-b-base bg-chart'>

View File

@ -35,8 +35,6 @@ const Pion1: ChainConfig = {
rest: 'https://rest-palvus.pion-1.ntrn.tech/',
rpc: 'https://rpc-palvus.pion-1.ntrn.tech/',
swap: 'https://testnet-neutron.astroport.fi/swap',
pyth: 'https://hermes.pyth.network/api',
pythCandles: 'https://benchmarks.pyth.network',
pools: '', //TODO: ⛓️ Implement this
explorer: 'https://testnet.mintscan.io/neutron-testnet',
aprs: {

View File

@ -134,8 +134,6 @@ const Osmosis1: ChainConfig = {
rpc: 'https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/',
rest: 'https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/',
swap: 'https://app.osmosis.zone',
pyth: 'https://hermes.pyth.network/api',
pythCandles: 'https://benchmarks.pyth.network',
graphCandles: 'https://osmosis-candles.marsprotocol.io',
explorer: 'https://www.mintscan.io/osmosis/transactions/',
pools:

4
src/constants/pyth.ts Normal file
View File

@ -0,0 +1,4 @@
export const pythEndpoints = {
api: 'https://hermes.pyth.network/api',
candles: 'https://benchmarks.pyth.network',
}

View File

@ -7,7 +7,7 @@ import useStore from 'store'
export default function usePricesData() {
const assets = useStore((s) => s.chainConfig.assets)
const chainConfig = useChainConfig()
return useSWR(`chains/${chainConfig.id}/pricesData`, () => getPricesData(chainConfig, assets), {
return useSWR(`chains/${chainConfig.id}/pricesData`, () => getPricesData(assets), {
fallbackData: [],
refreshInterval: 30_000,
revalidateOnFocus: false,

View File

@ -27,8 +27,6 @@ interface ChainConfig {
rest: string
rpc: string
swap: string
pyth: string
pythCandles: string
graphCandles?: string
explorer: string
pools: string

View File

@ -45,3 +45,14 @@ export function sortAssetsOrPairs(
return bMarketValue - aMarketValue
})
}
export function getAllAssetsWithPythId(chains: { [key: string]: ChainConfig }) {
return Object.entries(chains)
.map(([_, chainConfig]) => chainConfig.assets)
.flatMap((assets) => assets)
.filter(
(item, index, array) =>
index === array.findIndex((foundItem) => foundItem['denom'] === item['denom']),
)
.filter((asset) => asset.pythPriceFeedId)
}

View File

@ -78,6 +78,7 @@ export function resolveHLSStrategies(
}
export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsPosition[] {
if (!perpPositions) return []
return perpPositions.map((position) => {
return {
denom: position.denom,

View File

@ -39,7 +39,7 @@
tslib "^2.3.0"
zen-observable-ts "^1.2.5"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.21.4":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.21.4":
version "7.21.4"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz"
integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
@ -1103,7 +1103,7 @@
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4":
version "7.21.5"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz"
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
@ -3406,29 +3406,6 @@
long "^4.0.0"
protobufjs "~6.11.2"
"@testing-library/dom@^9.0.0":
version "9.2.0"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz"
integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^5.0.1"
aria-query "^5.0.0"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.5.0"
pretty-format "^27.0.2"
"@testing-library/react@^14.0.0":
version "14.0.0"
resolved "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz"
integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^9.0.0"
"@types/react-dom" "^18.0.0"
"@tippyjs/react@^4.2.6":
version "4.2.6"
resolved "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz"
@ -3441,11 +3418,6 @@
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@types/aria-query@^5.0.1":
version "5.0.1"
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz"
integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==
"@types/bn.js@5.1.1", "@types/bn.js@^5.1.0":
version "5.1.1"
resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz"
@ -3581,7 +3553,7 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-dom@18.2.15", "@types/react-dom@^18.0.0":
"@types/react-dom@18.2.15":
version "18.2.15"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522"
integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==
@ -3958,11 +3930,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
ansi-styles@^5.0.0:
version "5.2.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
ansi-styles@^6.0.0, ansi-styles@^6.2.1:
version "6.2.1"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
@ -3991,7 +3958,7 @@ argparse@^2.0.1:
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-query@^5.0.0, aria-query@^5.1.3:
aria-query@^5.1.3:
version "5.1.3"
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz"
integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
@ -4429,7 +4396,7 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.0:
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -4968,11 +4935,6 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz"
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
dom-helpers@^3.4.0:
version "3.4.0"
resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz"
@ -6887,11 +6849,6 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
magic-string@^0.27.0:
version "0.27.0"
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz"
@ -7516,15 +7473,6 @@ prettier@^3.0.3:
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz"
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
pretty-format@^27.0.2:
version "27.5.1"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
dependencies:
ansi-regex "^5.0.1"
ansi-styles "^5.0.0"
react-is "^17.0.1"
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
@ -7670,11 +7618,6 @@ react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz"