Oracle update button (#606)
* feat: added a resync button to the header * fix: updated the text * fix: remove pulsing
This commit is contained in:
parent
c69dd9e172
commit
03ce931687
20
src/api/prices/getPriceData.ts
Normal file
20
src/api/prices/getPriceData.ts
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
19
src/api/prices/getPythPriceData.ts
Normal file
19
src/api/prices/getPythPriceData.ts
Normal 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
|
||||
}
|
||||
}
|
@ -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 />
|
||||
|
30
src/components/Header/OracleResyncButton.tsx
Normal file
30
src/components/Header/OracleResyncButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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 = () => (
|
||||
|
@ -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 || '',
|
||||
|
11
src/hooks/usePriceData.tsx
Normal file
11
src/hooks/usePriceData.tsx
Normal 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,
|
||||
})
|
||||
}
|
@ -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({
|
||||
|
@ -13,5 +13,6 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
|
||||
migrationBanner: true,
|
||||
tutorial: true,
|
||||
useMargin: true,
|
||||
isOracleStale: false,
|
||||
}
|
||||
}
|
||||
|
4
src/types/interfaces/pyth.d.ts
vendored
4
src/types/interfaces/pyth.d.ts
vendored
@ -10,3 +10,7 @@ interface PythConfidenceData {
|
||||
price: string
|
||||
publish_time: number
|
||||
}
|
||||
|
||||
interface PythUpdateExecuteMsg {
|
||||
update_price_feeds: { data: string[] }
|
||||
}
|
||||
|
2
src/types/interfaces/store/broadcast.d.ts
vendored
2
src/types/interfaces/store/broadcast.d.ts
vendored
@ -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[]
|
||||
|
1
src/types/interfaces/store/common.d.ts
vendored
1
src/types/interfaces/store/common.d.ts
vendored
@ -12,6 +12,7 @@ interface CommonSlice {
|
||||
migrationBanner: boolean
|
||||
tutorial: boolean
|
||||
useMargin: boolean
|
||||
isOracleStale: boolean
|
||||
}
|
||||
|
||||
interface FocusComponent {
|
||||
|
@ -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')!
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user