Custom Pyth Endpoint & Price Data on Tx's (#736)
* 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 * feat: added basic auth * fix: added env validation * fix: added ‘no-cors’ and included credentials * fix: removed ‘no-cors’ mode * fix: change the isLessThanACent logic * tidy: console log * fix: added fallback api * feat: progress on pythVaas * fix: getting prices * fix: try catch * feat: add pythPriceData to transactions based on setting * fix: disable Pyth Update when user uses Ledger * tidy: copy
This commit is contained in:
parent
68206ae14c
commit
fd924c885e
@ -12,4 +12,5 @@ CHARTING_LIBRARY_USERNAME=git_username
|
||||
CHARTING_LIBRARY_ACCESS_TOKEN=access_token
|
||||
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library/
|
||||
|
||||
NEXT_PUBLIC_PYTH_API=https://mars.rpc.p2p.world/api
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
import fetchPythPriceData from 'api/prices/getPythPriceData'
|
||||
|
||||
export default async function getPricesData(assets: Asset[]): Promise<string[]> {
|
||||
try {
|
||||
const assetsWithPythPriceFeedId = assets.filter((asset) => !!asset.pythPriceFeedId)
|
||||
return await requestPythPriceData(assetsWithPythPriceFeedId)
|
||||
} 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)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { cacheFn, pythPriceCache } from 'api/cache'
|
||||
import { pythEndpoints } from 'constants/pyth'
|
||||
|
||||
export default async function fetchPythPriceData(priceFeedIds: string[]) {
|
||||
export default async function getPythPriceData(priceFeedIds: string[]) {
|
||||
try {
|
||||
const pricesUrl = new URL(`${pythEndpoints.api}/latest_vaas`)
|
||||
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
|
||||
@ -14,6 +14,7 @@ export default async function fetchPythPriceData(priceFeedIds: string[]) {
|
||||
)
|
||||
return pythDataResponse
|
||||
} catch (ex) {
|
||||
throw ex
|
||||
console.log(ex)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache'
|
||||
|
||||
export default async function getAprs(chainConfig: ChainConfig) {
|
||||
if (!chainConfig.farm) return []
|
||||
try {
|
||||
const response = await cacheFn(
|
||||
() => fetch(chainConfig.endpoints.aprs.vaults),
|
||||
|
@ -1,16 +1,16 @@
|
||||
import classNames from 'classnames'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import AssetImage from 'components/common/assets/AssetImage'
|
||||
import Button from 'components/common/Button'
|
||||
import { ArrowCircle, Enter } from 'components/common/Icons'
|
||||
import Modal from 'components/Modals/Modal'
|
||||
import SettingsOptions from 'components/Modals/Settings/SettingsOptions'
|
||||
import SettingsSwitch from 'components/Modals/Settings/SettingsSwitch'
|
||||
import Button from 'components/common/Button'
|
||||
import { ArrowCircle, Enter } from 'components/common/Icons'
|
||||
import NumberInput from 'components/common/NumberInput'
|
||||
import Select from 'components/common/Select'
|
||||
import Text from 'components/common/Text'
|
||||
import { TextLink } from 'components/common/TextLink'
|
||||
import AssetImage from 'components/common/assets/AssetImage'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
@ -48,6 +48,10 @@ export default function SettingsModal() {
|
||||
LocalStorageKeys.SLIPPAGE,
|
||||
DEFAULT_SETTINGS.slippage,
|
||||
)
|
||||
const [updateOracle, setUpdateOracle] = useLocalStorage<boolean>(
|
||||
LocalStorageKeys.UPDATE_ORACLE,
|
||||
DEFAULT_SETTINGS.updateOracle,
|
||||
)
|
||||
|
||||
const displayCurrenciesOptions = useMemo(
|
||||
() =>
|
||||
@ -148,6 +152,13 @@ export default function SettingsModal() {
|
||||
handleLendAssets(DEFAULT_SETTINGS.enableAutoLendGlobal)
|
||||
}, [handleDisplayCurrency, handleReduceMotion, handleLendAssets, handleSlippage])
|
||||
|
||||
const handleUpdateOracle = useCallback(
|
||||
(value: boolean) => {
|
||||
setUpdateOracle(value)
|
||||
},
|
||||
[setUpdateOracle],
|
||||
)
|
||||
|
||||
const showResetModal = useCallback(() => {
|
||||
showResetDialog({
|
||||
icon: (
|
||||
@ -196,6 +207,15 @@ export default function SettingsModal() {
|
||||
description='By turning this on you will automatically lend out all the assets you deposit into your Credit Accounts to earn yield.'
|
||||
withStatus
|
||||
/>
|
||||
|
||||
<SettingsSwitch
|
||||
onChange={handleUpdateOracle}
|
||||
name='updateOracle'
|
||||
value={updateOracle}
|
||||
label='Update Pyth Oracles on transactions'
|
||||
description={`When this setting is on, your Mars transactions will automatically trigger an update of Pyth's price oracles. This increases the accuracy of the prices shown in the UI. In some instances, this could cause your transactions to fail when using a Ledger hardware wallet. If you encounter transaction failures with Ledger, please turn this setting off.`}
|
||||
withStatus
|
||||
/>
|
||||
<SettingsSwitch
|
||||
onChange={handleReduceMotion}
|
||||
name='reduceMotion'
|
||||
|
@ -24,7 +24,6 @@ export const valueSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceR
|
||||
export default function Value(props: Props) {
|
||||
const { amountChange, type, value } = props
|
||||
const color = getAmountChangeColor(type, amountChange)
|
||||
const allowZero = !amountChange.isZero()
|
||||
const coin = new BNCoin({
|
||||
denom: ORACLE_DENOM,
|
||||
amount: value,
|
||||
@ -34,7 +33,7 @@ export default function Value(props: Props) {
|
||||
<DisplayCurrency
|
||||
coin={coin}
|
||||
className={classNames('text-xs text-right', color)}
|
||||
showZero={!allowZero}
|
||||
showZero={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -52,10 +52,12 @@ export default function DisplayCurrency(props: Props) {
|
||||
return [amount, Math.abs(amount)]
|
||||
}, [assets, displayCurrency, displayCurrencyAsset.decimals, prices, props.coin])
|
||||
|
||||
const isLessThanACent =
|
||||
(isUSD && absoluteAmount < 0.01 && absoluteAmount > 0) ||
|
||||
(absoluteAmount === 0 && props.showZero)
|
||||
const smallerThanPrefix = isLessThanACent ? '< ' : ''
|
||||
const isLessThanACent = useMemo(
|
||||
() => isUSD && absoluteAmount < 0.01 && absoluteAmount > 0,
|
||||
[absoluteAmount, isUSD],
|
||||
)
|
||||
|
||||
const smallerThanPrefix = isLessThanACent && !props.showZero ? '< ' : ''
|
||||
|
||||
const prefix = isUSD
|
||||
? `${props.isApproximation ? '~ ' : smallerThanPrefix}$`
|
||||
|
@ -3,14 +3,11 @@ import { useCallback } from 'react'
|
||||
import Button from 'components/common/Button'
|
||||
import { ExclamationMarkCircled } from 'components/common/Icons'
|
||||
import { Tooltip } from 'components/common/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, updateOracle])
|
||||
const updatePythOracle = useCallback(() => updateOracle(), [])
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
@ -21,4 +21,5 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
tutorial: true,
|
||||
migrationBanner: true,
|
||||
perpsAsset: '',
|
||||
updateOracle: true,
|
||||
}
|
||||
|
@ -15,4 +15,5 @@ export enum LocalStorageKeys {
|
||||
HLS_INFORMATION = 'hlsInformation',
|
||||
CURRENT_CHAIN_ID = 'currentChainId',
|
||||
PERPS_ASSET = 'perpsAsset',
|
||||
UPDATE_ORACLE = 'updateOracle',
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const pythEndpoints = {
|
||||
api: 'https://hermes.pyth.network/api',
|
||||
api: process.env.NEXT_PUBLIC_PYTH_API ?? 'https://hermes.pyth.network/api',
|
||||
candles: 'https://benchmarks.pyth.network/v1/shims/tradingview',
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import useSWR from 'swr'
|
||||
|
||||
import getPricesData from 'api/prices/getPriceData'
|
||||
import useChainConfig from 'hooks/useChainConfig'
|
||||
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(assets), {
|
||||
fallbackData: [],
|
||||
refreshInterval: 30_000,
|
||||
revalidateOnFocus: false,
|
||||
})
|
||||
}
|
@ -4,6 +4,7 @@ import moment from 'moment'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
import getPythPriceData from 'api/prices/getPythPriceData'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { Store } from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
@ -19,6 +20,7 @@ import { AccountKind } from 'types/generated/mars-rover-health-types/MarsRoverHe
|
||||
import { byDenom, bySymbol } from 'utils/array'
|
||||
import { generateErrorMessage, getSingleValueFromBroadcastResult } from 'utils/broadcast'
|
||||
import checkAutoLendEnabled from 'utils/checkAutoLendEnabled'
|
||||
import checkPythUpdateEnabled from 'utils/checkPythUpdateEnabled'
|
||||
import { defaultFee } from 'utils/constants'
|
||||
import { formatAmountWithSymbol } from 'utils/formatters'
|
||||
import getTokenOutFromSwapResponse from 'utils/getTokenOutFromSwapResponse'
|
||||
@ -832,16 +834,10 @@ export default function createBroadcastSlice(
|
||||
|
||||
return { estimateFee, execute }
|
||||
},
|
||||
updateOracle: async (pricesData: string[]) => {
|
||||
const msg: PythUpdateExecuteMsg = { update_price_feeds: { data: pricesData } }
|
||||
const pythAssets = get().chainConfig.assets.filter((asset) => !!asset.pythPriceFeedId)
|
||||
const pythContract = get().chainConfig.contracts.pyth
|
||||
updateOracle: async () => {
|
||||
const response = get().executeMsg({
|
||||
messages: [
|
||||
generateExecutionMessage(get().address, pythContract, msg, [
|
||||
{ denom: get().chainConfig.assets[0].denom, amount: String(pythAssets.length) },
|
||||
]),
|
||||
],
|
||||
messages: [],
|
||||
isPythUpdate: true,
|
||||
})
|
||||
|
||||
get().setToast({
|
||||
@ -901,10 +897,19 @@ export default function createBroadcastSlice(
|
||||
})
|
||||
})
|
||||
},
|
||||
executeMsg: async (options: { messages: MsgExecuteContract[] }): Promise<BroadcastResult> => {
|
||||
executeMsg: async (options: {
|
||||
messages: MsgExecuteContract[]
|
||||
isPythUpdate?: boolean
|
||||
}): Promise<BroadcastResult> => {
|
||||
try {
|
||||
const client = get().client
|
||||
if (!client) return { error: 'no client detected' }
|
||||
const isLedger = client?.connectedWallet?.account.isLedger
|
||||
if (!client)
|
||||
return { result: undefined, error: 'No client detected. Please reconnect your wallet.' }
|
||||
if ((checkPythUpdateEnabled() || options.isPythUpdate) && !isLedger) {
|
||||
const pythUpdateMsg = await get().getPythVaas()
|
||||
options.messages.unshift(pythUpdateMsg)
|
||||
}
|
||||
const fee = await getEstimatedFee(options.messages)
|
||||
const broadcastOptions = {
|
||||
messages: options.messages,
|
||||
@ -989,5 +994,18 @@ export default function createBroadcastSlice(
|
||||
|
||||
return response.then((response) => !!response.result)
|
||||
},
|
||||
getPythVaas: async () => {
|
||||
const priceFeedIds = get()
|
||||
.chainConfig.assets.filter((asset) => !!asset.pythPriceFeedId)
|
||||
.map((asset) => asset.pythPriceFeedId as string)
|
||||
const pricesData = await getPythPriceData(priceFeedIds)
|
||||
const msg: PythUpdateExecuteMsg = { update_price_feeds: { data: pricesData } }
|
||||
const pythAssets = get().chainConfig.assets.filter((asset) => !!asset.pythPriceFeedId)
|
||||
const pythContract = get().chainConfig.contracts.pyth
|
||||
|
||||
return generateExecutionMessage(get().address, pythContract, msg, [
|
||||
{ denom: get().chainConfig.assets[0].denom, amount: String(pythAssets.length) },
|
||||
])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
8
src/types/interfaces/store/broadcast.d.ts
vendored
8
src/types/interfaces/store/broadcast.d.ts
vendored
@ -118,7 +118,10 @@ interface BroadcastSlice {
|
||||
kind: import('types/generated/mars-rover-health-types/MarsRoverHealthTypes.types').AccountKind
|
||||
}) => Promise<boolean>
|
||||
execute: (contract: string, msg: ExecuteMsg, funds: Coin[]) => Promise<BroadcastResult>
|
||||
executeMsg: (options: { messages: MsgExecuteContract[] }) => Promise<BroadcastResult>
|
||||
executeMsg: (options: {
|
||||
messages: MsgExecuteContract[]
|
||||
isPythUpdate?: boolean
|
||||
}) => Promise<BroadcastResult>
|
||||
lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean>
|
||||
closePerpPosition: (options: { accountId: string; denom: string }) => Promise<boolean>
|
||||
openPerpPosition: (options: { accountId: string; coin: BNCoin }) => Promise<boolean>
|
||||
@ -147,7 +150,8 @@ interface BroadcastSlice {
|
||||
vault: DepositedVault
|
||||
amount: string
|
||||
}) => Promise<boolean>
|
||||
updateOracle: (pricesData: string[]) => Promise<boolean>
|
||||
updateOracle: () => Promise<boolean>
|
||||
getPythVaas: () => Promise<import('@delphi-labs/shuttle-react').MsgExecuteContract>
|
||||
withdrawFromVaults: (options: {
|
||||
accountId: string
|
||||
vaults: DepositedVault[]
|
||||
|
1
src/types/interfaces/store/settings.d.ts
vendored
1
src/types/interfaces/store/settings.d.ts
vendored
@ -9,4 +9,5 @@ interface Settings {
|
||||
slippage: number
|
||||
tutorial: boolean
|
||||
migrationBanner: boolean
|
||||
updateOracle: boolean
|
||||
}
|
||||
|
5
src/utils/checkPythUpdateEnabled.ts
Normal file
5
src/utils/checkPythUpdateEnabled.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||
|
||||
export default function checkPythUpdateEnabled() {
|
||||
return localStorage.getItem(LocalStorageKeys.UPDATE_ORACLE) === 'true'
|
||||
}
|
@ -4148,6 +4148,11 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base-64@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a"
|
||||
integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==
|
||||
|
||||
base-x@^3.0.2:
|
||||
version "3.0.9"
|
||||
resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user