Oracle update button (#606)

* feat: added a resync button to the header

* fix: updated the text

* fix: remove pulsing
This commit is contained in:
Linkie Link 2023-11-02 09:40:17 +01:00 committed by GitHub
parent c69dd9e172
commit 03ce931687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 130 additions and 3 deletions

View File

@ -0,0 +1,20 @@
import fetchPythPriceData from 'api/prices/getPythPriceData'
import { getPythAssets } from 'utils/assets'
export default async function getPricesData(): Promise<string[]> {
try {
const assetsWithPythPriceFeedId = getPythAssets()
const pythAndOraclePriceData = await requestPythPriceData(assetsWithPythPriceFeedId)
return pythAndOraclePriceData
} catch (ex) {
console.error(ex)
throw ex
}
}
async function requestPythPriceData(assets: Asset[]): Promise<string[]> {
if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPriceData(...priceFeedIds)
}

View File

@ -2,6 +2,7 @@ import getOraclePrices from 'api/prices/getOraclePrices'
import getPoolPrice from 'api/prices/getPoolPrice'
import fetchPythPrices from 'api/prices/getPythPrices'
import { ENV } from 'constants/env'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { NETWORK } from 'types/enums/network'
import { partition } from 'utils/array'
@ -22,9 +23,16 @@ export default async function getPrices(): Promise<BNCoin[]> {
).flat()
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
useStore.setState({ isOracleStale: false })
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
} catch (ex) {
console.error(ex)
let message = 'Unknown Error'
if (ex instanceof Error) message = ex.message
if (message.includes('price publish time is too old'))
useStore.setState({ isOracleStale: true })
throw ex
}
}

View File

@ -0,0 +1,19 @@
import { cacheFn, pythPriceCache } from 'api/cache'
import { ENV } from 'constants/env'
export default async function fetchPythPriceData(...priceFeedIds: string[]) {
try {
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_vaas`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythDataResponse: string[] = await cacheFn(
() => fetch(pricesUrl).then((res) => res.json()),
pythPriceCache,
`pythPricData/${priceFeedIds.flat().join('-')}`,
30,
)
return pythDataResponse
} catch (ex) {
throw ex
}
}

View File

@ -3,6 +3,7 @@ import { isDesktop } from 'react-device-detect'
import AccountMenu from 'components/Account/AccountMenu'
import EscButton from 'components/Button/EscButton'
import OracleResyncButton from 'components/Header/OracleResyncButton'
import DesktopNavigation from 'components/Navigation/DesktopNavigation'
import RewardsCenter from 'components/RewardsCenter'
import Settings from 'components/Settings'
@ -22,6 +23,7 @@ export const menuTree: { pages: Page[]; label: string }[] = [
export default function DesktopHeader() {
const address = useStore((s) => s.address)
const focusComponent = useStore((s) => s.focusComponent)
const isOracleStale = useStore((s) => s.isOracleStale)
const accountId = useAccountId()
function handleCloseFocusMode() {
@ -46,6 +48,7 @@ export default function DesktopHeader() {
)}
>
<DesktopNavigation />
{focusComponent ? (
<div className='flex justify-between w-full'>
<div className='flex h-5 w-13' />
@ -54,6 +57,7 @@ export default function DesktopHeader() {
</div>
) : (
<div className='flex gap-4'>
{isOracleStale && <OracleResyncButton />}
{accountId && <RewardsCenter />}
{address && <AccountMenu />}
<Wallet />

View File

@ -0,0 +1,30 @@
import { useCallback } from 'react'
import Button from 'components/Button'
import { ExclamationMarkCircled } from 'components/Icons'
import { Tooltip } from 'components/Tooltip'
import usePricesData from 'hooks/usePriceData'
import useStore from 'store'
export default function OracleResyncButton() {
const { data: pricesData } = usePricesData()
const updateOracle = useStore((s) => s.updateOracle)
const updatePythOracle = useCallback(() => updateOracle(pricesData), [pricesData])
return (
<Tooltip
type='warning'
content='The on-chain Pyth oracle prices are too old/stale. Update them by executing a resync transaction.'
hideArrow={true}
>
<Button
leftIcon={<ExclamationMarkCircled className='w-4' />}
text='Resync Oracle'
className='!text-warning !border-warning hover:bg-warning/20 active:bg-warning/20 focus:bg-warning/20'
color='secondary'
onClick={updatePythOracle}
/>
</Tooltip>
)
}

View File

@ -89,7 +89,7 @@ export default function Toaster() {
const handleResponse = (toast: ToastResponse, details?: boolean) => {
const isError = toast?.isError
if (!isError) addTransaction(toast)
if (!isError && toast.accountId) addTransaction(toast)
const generalMessage = isError ? 'Transaction failed!' : 'Transaction completed successfully!'
const showDetailElement = !!(!details && toast.hash)
const Msg = () => (

View File

@ -4,6 +4,7 @@ interface EnvironmentVariables {
ADDRESS_INCENTIVES: string
ADDRESS_ORACLE: string
ADDRESS_PARAMS: string
ADDRESS_PYTH: string
ADDRESS_RED_BANK: string
ADDRESS_SWAPPER: string
ADDRESS_ZAPPER: string
@ -26,6 +27,7 @@ export const ENV: EnvironmentVariables = {
ADDRESS_INCENTIVES: process.env.NEXT_PUBLIC_INCENTIVES || '',
ADDRESS_ORACLE: process.env.NEXT_PUBLIC_ORACLE || '',
ADDRESS_PARAMS: process.env.NEXT_PUBLIC_PARAMS || '',
ADDRESS_PYTH: process.env.NEXT_PUBLIC_PYTH || '',
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK || '',
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER || '',
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER || '',

View File

@ -0,0 +1,11 @@
import useSWR from 'swr'
import getPricesData from 'api/prices/getPriceData'
export default function usePricesData() {
return useSWR('pricesData', getPricesData, {
fallbackData: [],
refreshInterval: 30_000,
revalidateOnFocus: false,
})
}

View File

@ -14,7 +14,7 @@ import {
ExecuteMsg as CreditManagerExecuteMsg,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { AccountKind } from 'types/generated/mars-rover-health-types/MarsRoverHealthTypes.types'
import { getAssetByDenom, getAssetBySymbol } from 'utils/assets'
import { getAssetByDenom, getAssetBySymbol, getPythAssets } from 'utils/assets'
import { generateErrorMessage, getSingleValueFromBroadcastResult } from 'utils/broadcast'
import checkAutoLendEnabled from 'utils/checkAutoLendEnabled'
import { defaultFee } from 'utils/constants'
@ -26,7 +26,7 @@ import { getVaultDepositCoinsFromActions } from 'utils/vaults'
function generateExecutionMessage(
sender: string | undefined = '',
contract: string,
msg: CreditManagerExecuteMsg | AccountNftExecuteMsg,
msg: CreditManagerExecuteMsg | AccountNftExecuteMsg | PythUpdateExecuteMsg,
funds: Coin[],
) {
return new MsgExecuteContract({
@ -643,6 +643,27 @@ export default function createBroadcastSlice(
return { estimateFee, execute }
},
updateOracle: async (pricesData: string[]) => {
const msg: PythUpdateExecuteMsg = { update_price_feeds: { data: pricesData } }
const pythAssets = getPythAssets()
const response = get().executeMsg({
messages: [
generateExecutionMessage(get().address, ENV.ADDRESS_PYTH, msg, [
{ denom: get().baseCurrency.denom, amount: String(pythAssets.length) },
]),
],
})
get().setToast({
response,
options: {
action: 'oracle',
message: 'Oracle updated successfully!',
},
})
return response.then((response) => !!response.result)
},
setToast: (toast: ToastObject) => {
const id = moment().unix()
set({

View File

@ -13,5 +13,6 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
migrationBanner: true,
tutorial: true,
useMargin: true,
isOracleStale: false,
}
}

View File

@ -10,3 +10,7 @@ interface PythConfidenceData {
price: string
publish_time: number
}
interface PythUpdateExecuteMsg {
update_price_feeds: { data: string[] }
}

View File

@ -70,6 +70,7 @@ interface HandleResponseProps {
| 'claim'
| 'unlock'
| 'swap'
| 'oracle'
lend?: boolean
accountId?: string
changes?: { debts?: BNCoin[]; deposits?: BNCoin[]; lends?: BNCoin[] }
@ -121,6 +122,7 @@ interface BroadcastSlice {
vault: DepositedVault
amount: string
}) => Promise<boolean>
updateOracle: (pricesData: string[]) => Promise<boolean>
withdrawFromVaults: (options: {
accountId: string
vaults: DepositedVault[]

View File

@ -12,6 +12,7 @@ interface CommonSlice {
migrationBanner: boolean
tutorial: boolean
useMargin: boolean
isOracleStale: boolean
}
interface FocusComponent {

View File

@ -16,6 +16,10 @@ export function getAssetsMustHavePriceInfo(): Asset[] {
return ASSETS.filter((asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice)
}
export function getPythAssets(): Asset[] {
return ASSETS.filter((asset) => !!asset.pythPriceFeedId)
}
export function getBaseAsset() {
return ASSETS.find((asset) => asset.denom === 'uosmo')!
}