Pyth history feed (#623)

* MP-3556: first PythDataFeed class

* fix: fixed the timestamp

* fix: fixed the chart data

* fix: fixed the prices

* fix: fixed the math and decimal scale

* tidy: refactor

* fix: update the pythFeedId

* fix: updated OsmosisTheGraphDataFeed

* fix: add a fallback for non pyth data

* tidy: refactor

* fix: adjusted to feedback
This commit is contained in:
Linkie Link 2023-11-10 13:56:17 +01:00 committed by GitHub
parent 7439bea0d8
commit a8dc0950fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 258 additions and 85 deletions

View File

@ -28,7 +28,8 @@ NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf
NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
NEXT_PUBLIC_PYTH_ENDPOINT=https://hermes.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_CANDLES_ENDPOINT=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_CANDLES_ENDPOINT_THE_GRAPH=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_CANDLES_ENDPOINT_PYTH=https://benchmarks.pyth.network
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
CHARTING_LIBRARY_ACCESS_TOKEN=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

View File

@ -1,10 +1,5 @@
import { getSwapperQueryClient } from 'api/cosmwasm-client'
interface Route {
pool_id: string
token_out_denom: string
}
export default async function getSwapRoute(denomIn: string, denomOut: string): Promise<Route[]> {
try {
const swapperClient = await getSwapperQueryClient()

View File

@ -1,6 +1,8 @@
import { defaultSymbolInfo } from 'components/Trade/TradeChart/constants'
import { ASSETS } from 'constants/assets'
import { ENV } from 'constants/env'
import { MILLISECONDS_PER_MINUTE } from 'constants/math'
import { byDenom } from 'utils/array'
import { getAssetByDenom, getEnabledMarketAssets } from 'utils/assets'
import {
Bar,
@ -14,8 +16,19 @@ import {
ResolveCallback,
} from 'utils/charting_library'
import { BN } from 'utils/helpers'
import { devideByPotentiallyZero } from 'utils/math'
interface BarQueryData {
interface PythBarQueryData {
s: string
t: number[]
o: number[]
h: number[]
l: number[]
c: number[]
v: number[]
}
interface TheGraphBarQueryData {
close: string
high: string
low: string
@ -26,41 +39,40 @@ interface BarQueryData {
export const PAIR_SEPARATOR = '<>'
export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
candlesEndpoint = ENV.CANDLES_ENDPOINT
export class DataFeed implements IDatafeedChartApi {
candlesEndpoint = ENV.CANDLES_ENDPOINT_PYTH
candlesEndpointTheGraph = ENV.CANDLES_ENDPOINT_THE_GRAPH
debug = false
exchangeName = 'Osmosis'
enabledMarketAssetDenoms: string[] = []
batchSize = 1000
baseDecimals: number = 6
baseDenom: string = 'uosmo'
batchSize = 1000
enabledMarketAssetDenoms: string[] = []
pairs: { baseAsset: string; quoteAsset: string }[] = []
pairsWithData: string[] = []
intervals: { [key: string]: string } = {
intervalsTheGraph: { [key: string]: string } = {
'15': '15m',
'30': '30m',
'60': '1h',
'240': '4h',
'1D': '1d',
}
minutesPerInterval: { [key: string]: number } = {
'15': 15,
'30': 30,
'60': 60,
'240': 60 * 4,
'1D': 60 * 24,
millisecondsPerInterval: { [key: string]: number } = {
'15': MILLISECONDS_PER_MINUTE * 15,
'30': MILLISECONDS_PER_MINUTE * 30,
'60': MILLISECONDS_PER_MINUTE * 60,
'240': MILLISECONDS_PER_MINUTE * 240,
'1D': MILLISECONDS_PER_MINUTE * 1440,
}
pairs: { baseAsset: string; quoteAsset: string }[] = []
pairsWithData: string[] = []
supportedPools: string[] = []
supportedResolutions = ['15', '30', '60', '4h', 'D'] as ResolutionString[]
supportedResolutions = ['15', '30', '60', '240', 'D'] as ResolutionString[]
constructor(debug = false, baseDecimals: number, baseDenom: string) {
if (debug) console.log('Start TheGraph charting library datafeed')
if (debug) console.log('Start charting library datafeed')
this.debug = debug
this.baseDecimals = baseDecimals
this.baseDenom = baseDenom
const enabledMarketAssets = getEnabledMarketAssets()
this.enabledMarketAssetDenoms = enabledMarketAssets.map((asset) => asset.mainnetDenom)
this.enabledMarketAssetDenoms = enabledMarketAssets.map((asset) => asset.denom)
this.supportedPools = enabledMarketAssets
.map((asset) => asset.poolId?.toString())
.filter((poolId) => typeof poolId === 'string') as string[]
@ -69,8 +81,8 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
getDescription(pairName: string) {
const denom1 = pairName.split(PAIR_SEPARATOR)[0]
const denom2 = pairName.split(PAIR_SEPARATOR)[1]
const asset1 = ASSETS.find((asset) => asset.mainnetDenom === denom1)
const asset2 = ASSETS.find((asset) => asset.mainnetDenom === denom2)
const asset1 = ASSETS.find(byDenom(denom1))
const asset2 = ASSETS.find(byDenom(denom2))
return `${asset1?.symbol}/${asset2?.symbol}`
}
@ -91,7 +103,7 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
}
}`
return fetch(this.candlesEndpoint, {
return fetch(this.candlesEndpointTheGraph, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
@ -131,10 +143,11 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
full_name: this.getDescription(pairName),
description: this.getDescription(pairName),
ticker: this.getDescription(pairName),
exchange: this.exchangeName,
listed_exchange: this.exchangeName,
exchange: this.getExchangeName(pairName),
listed_exchange: this.getExchangeName(pairName),
supported_resolutions: this.supportedResolutions,
base_name: [this.getDescription(pairName)],
pricescale: this.getPriceScale(pairName),
} as LibrarySymbolInfo
onResolve(info)
})
@ -146,11 +159,45 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
periodParams: PeriodParams,
onResult: HistoryCallback,
): Promise<void> {
const interval = this.intervals[resolution]
try {
let bars = [] as Bar[]
const pythFeedIds = this.getPythFeedIds(symbolInfo.full_name)
const now = new Date().getTime()
const to = BN(now).dividedBy(1000).integerValue().toNumber()
const from = BN(now)
.minus(this.batchSize * this.millisecondsPerInterval[resolution])
.dividedBy(1000)
.integerValue()
.toNumber()
const pythFeedId1 = pythFeedIds[0]
const pythFeedId2 = pythFeedIds[1]
if (pythFeedId1 && pythFeedId2) {
const asset1Bars = this.queryBarData(pythFeedId1, resolution, from, to)
const asset2Bars = this.queryBarData(pythFeedId2, resolution, from, to)
await Promise.all([asset1Bars, asset2Bars]).then(([asset1Bars, asset2Bars]) => {
bars = this.combineBars(asset1Bars, asset2Bars)
onResult(bars)
})
} else {
await this.getBarsFromTheGraph(symbolInfo, resolution, to).then((bars) => onResult(bars))
}
} catch (error) {
console.error(error)
return onResult([], { noData: true })
}
}
async getBarsFromTheGraph(
symbolInfo: LibrarySymbolInfo,
resolution: ResolutionString,
to: number,
) {
let pair1 = this.getPairName(symbolInfo.full_name)
let pair2: string = ''
let pair3: string = ''
let theGraphBars = [] as Bar[]
if (!this.pairsWithData.includes(pair1)) {
if (this.debug) console.log('Pair does not have data, need to combine with other pairs')
@ -181,29 +228,32 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
}
}
const pair1Bars = this.queryBarData(
const pair1Bars = this.queryBarDataTheGraph(
pair1.split(PAIR_SEPARATOR)[0],
pair1.split(PAIR_SEPARATOR)[1],
interval,
resolution,
to,
)
let pair2Bars: Promise<Bar[]> | null = null
if (pair2) {
pair2Bars = this.queryBarData(
pair2Bars = this.queryBarDataTheGraph(
pair2.split(PAIR_SEPARATOR)[0],
pair2.split(PAIR_SEPARATOR)[1],
interval,
resolution,
to,
)
}
let pair3Bars: Promise<Bar[]> | null = null
if (pair3) {
pair3Bars = this.queryBarData(
pair3Bars = this.queryBarDataTheGraph(
pair3.split(PAIR_SEPARATOR)[0],
pair3.split(PAIR_SEPARATOR)[1],
interval,
resolution,
to,
)
}
@ -212,7 +262,6 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
let bars = pair1Bars
if (!bars.length) {
onResult([], { noData: true })
return
}
@ -225,20 +274,55 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
const filler = Array.from({ length: this.batchSize - bars.length }).map((_, index) => ({
time:
(bars[0]?.time || new Date().getTime()) - index * this.minutesPerInterval[resolution],
(bars[0]?.time || new Date().getTime()) -
(index * this.millisecondsPerInterval[resolution]) / 1000,
close: 0,
open: 0,
high: 0,
low: 0,
volume: 0,
}))
onResult([...filler, ...bars])
theGraphBars = [...filler, ...bars]
},
)
return theGraphBars
}
async queryBarData(quote: string, base: string, interval: string): Promise<Bar[]> {
async queryBarData(
feedSymbol: string,
resolution: ResolutionString,
from: PeriodParams['from'],
to: PeriodParams['to'],
): Promise<Bar[]> {
const URI = new URL('/v1/shims/tradingview/history', this.candlesEndpoint)
const params = new URLSearchParams(URI.search)
params.append('to', to.toString())
params.append('from', from.toString())
params.append('resolution', resolution)
params.append('symbol', feedSymbol)
URI.search = params.toString()
return fetch(URI)
.then((res) => res.json())
.then((json) => {
return this.resolveBarData(json, resolution, to)
})
.catch((err) => {
if (this.debug) console.error(err)
throw err
})
}
async queryBarDataTheGraph(
quote: string,
base: string,
resolution: ResolutionString,
to: PeriodParams['to'],
): Promise<Bar[]> {
const interval = this.intervalsTheGraph[resolution]
const query = `
{
candles(
@ -262,14 +346,20 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
}
`
return fetch(this.candlesEndpoint, {
return fetch(this.candlesEndpointTheGraph, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
})
.then((res) => res.json())
.then((json: { data?: { candles: BarQueryData[] } }) => {
return this.resolveBarData(json.data?.candles.reverse() || [], base, quote)
.then((json: { data?: { candles: TheGraphBarQueryData[] } }) => {
return this.resolveBarDataTheGraph(
json.data?.candles.reverse() || [],
base,
quote,
resolution,
to,
)
})
.catch((err) => {
if (this.debug) console.error(err)
@ -277,12 +367,35 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
})
}
resolveBarData(bars: BarQueryData[], toDenom: string, fromDenom: string) {
resolveBarData(data: PythBarQueryData, resolution: ResolutionString, to: number) {
let barData = [] as Bar[]
if (data['s'] === 'ok') {
barData = data['t'].map((timestamp, index) => ({
time: timestamp * 1000,
close: data['c'][index],
open: data['o'][index],
high: data['h'][index],
low: data['l'][index],
}))
}
return this.fillBarData(barData, resolution, to)
}
resolveBarDataTheGraph(
bars: TheGraphBarQueryData[],
toDenom: string,
fromDenom: string,
resolution: ResolutionString,
to: number,
) {
let barData = [] as Bar[]
const toDecimals = getAssetByDenom(toDenom)?.decimals || 6
const fromDecimals = getAssetByDenom(fromDenom)?.decimals || 6
const additionalDecimals = toDecimals - fromDecimals
return bars.map((bar) => ({
barData = bars.map((bar) => ({
time: BN(bar.timestamp).multipliedBy(1000).toNumber(),
close: BN(bar.close).shiftedBy(additionalDecimals).toNumber(),
open: BN(bar.open).shiftedBy(additionalDecimals).toNumber(),
@ -290,23 +403,40 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
low: BN(bar.low).shiftedBy(additionalDecimals).toNumber(),
volume: BN(bar.volume).shiftedBy(additionalDecimals).toNumber(),
}))
return this.fillBarData(barData, resolution, to)
}
fillBarData(barData: Bar[], resolution: ResolutionString, to: number) {
if (barData.length < this.batchSize) {
const filler = Array.from({ length: this.batchSize - barData.length }).map((_, index) => ({
time: (barData[0]?.time || to) - index * this.millisecondsPerInterval[resolution],
close: 0,
open: 0,
high: 0,
low: 0,
volume: 0,
}))
barData = [...filler, ...barData]
}
return barData.length > this.batchSize ? barData.slice(0, this.batchSize) : barData
}
combineBars(pair1Bars: Bar[], pair2Bars: Bar[]): Bar[] {
const bars: Bar[] = []
pair1Bars.forEach((pair1Bar) => {
const pair2Bar = pair2Bars.find((pair2Bar) => pair2Bar.time == pair1Bar.time)
pair1Bars.forEach((pair1Bar, index) => {
const pair2Bar = pair2Bars[index]
if (pair2Bar) {
bars.push({
time: pair1Bar.time,
open: pair1Bar.open * pair2Bar.open,
close: pair1Bar.close * pair2Bar.close,
high: pair1Bar.high * pair2Bar.high,
low: pair1Bar.low * pair2Bar.low,
})
}
bars.push({
time: pair1Bar.time,
open: devideByPotentiallyZero(pair1Bar.open, pair2Bar.open),
close: devideByPotentiallyZero(pair1Bar.close, pair2Bar.close),
high: devideByPotentiallyZero(pair1Bar.high, pair2Bar.high),
low: devideByPotentiallyZero(pair1Bar.low, pair2Bar.low),
})
})
return bars
}
@ -321,6 +451,32 @@ export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
return `${asset1?.denom}${PAIR_SEPARATOR}${asset2?.denom}`
}
getPriceScale(name: string) {
const denoms = name.split(PAIR_SEPARATOR)
const asset2 = ASSETS.find(byDenom(denoms[1]))
const decimalsOut = asset2?.decimals ?? 6
return BN(1)
.shiftedBy(decimalsOut > 8 ? 8 : decimalsOut)
.toNumber()
}
getExchangeName(name: string) {
const denoms = name.split(PAIR_SEPARATOR)
const pythFeedId1 = ASSETS.find(byDenom(denoms[0]))?.pythHistoryFeedId
const pythFeedId2 = ASSETS.find(byDenom(denoms[1]))?.pythHistoryFeedId
if (!pythFeedId1 || !pythFeedId2) return 'Osmosis'
return 'Pyth Oracle'
}
getPythFeedIds(name: string) {
if (name.includes(PAIR_SEPARATOR)) return []
const [symbol1, symbol2] = name.split('/')
const feedId1 = ASSETS.find((asset) => asset.symbol === symbol1)?.pythHistoryFeedId
const feedId2 = ASSETS.find((asset) => asset.symbol === symbol2)?.pythHistoryFeedId
return [feedId1, feedId2]
}
searchSymbols(): void {
// Don't allow to search for symbols
}

View File

@ -1,11 +1,8 @@
import { useEffect, useMemo, useRef } from 'react'
import Card from 'components/Card'
import { DataFeed, PAIR_SEPARATOR } from 'components/Trade/TradeChart/DataFeed'
import { disabledFeatures, enabledFeatures, overrides } from 'components/Trade/TradeChart/constants'
import {
OsmosisTheGraphDataFeed,
PAIR_SEPARATOR,
} from 'components/Trade/TradeChart/OsmosisTheGraphDataFeed'
import useStore from 'store'
import {
ChartingLibraryWidgetOptions,
@ -28,7 +25,7 @@ export const TVChartContainer = (props: Props) => {
)
const baseCurrency = useStore((s) => s.baseCurrency)
const dataFeed = useMemo(
() => new OsmosisTheGraphDataFeed(false, baseCurrency.decimals, baseCurrency.denom),
() => new DataFeed(false, baseCurrency.decimals, baseCurrency.denom),
[baseCurrency],
)

View File

@ -28,7 +28,7 @@ export const overrides = {
}
export const defaultSymbolInfo: Partial<LibrarySymbolInfo> = {
listed_exchange: 'Osmosis',
listed_exchange: 'Pyth Oracle',
type: 'AMM',
session: '24x7',
minmov: 1,

View File

@ -3,7 +3,6 @@ import { useMemo } from 'react'
import ActionButton from 'components/Button/ActionButton'
import { FormattedNumber } from 'components/FormattedNumber'
import useSwapRoute from 'hooks/useSwapRoute'
import { getAssetByDenom } from 'utils/assets'
import { formatAmountWithSymbol, formatPercent } from 'utils/formatters'
@ -18,7 +17,9 @@ interface Props {
borrowAmount: BigNumber
estimatedFee: StdFee
buyAction: () => void
route: Route[]
}
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
export default function TradeSummary(props: Props) {
const {
@ -32,25 +33,23 @@ export default function TradeSummary(props: Props) {
borrowAmount,
estimatedFee,
showProgressIndicator,
route,
} = props
const { data: routes, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom)
const parsedRoutes = useMemo(() => {
if (!routes.length) return '-'
if (!route.length) return '-'
const routeSymbols = routes.map((r) => getAssetByDenom(r.token_out_denom)?.symbol)
const routeSymbols = route.map((r) => getAssetByDenom(r.token_out_denom)?.symbol)
routeSymbols.unshift(sellAsset.symbol)
return routeSymbols.join(' -> ')
}, [routes, sellAsset.symbol])
}, [route, sellAsset.symbol])
const buttonText = useMemo(
() => (routes.length ? `Buy ${buyAsset.symbol}` : 'No route found'),
[buyAsset.symbol, routes],
() => (route.length ? `Buy ${buyAsset.symbol}` : 'No route found'),
[buyAsset.symbol, route],
)
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
return (
<div
className={classNames(
@ -94,8 +93,8 @@ export default function TradeSummary(props: Props) {
</div>
</div>
<ActionButton
disabled={routes.length === 0 || buyButtonDisabled}
showProgressIndicator={showProgressIndicator || isRouteLoading}
disabled={buyButtonDisabled}
showProgressIndicator={showProgressIndicator}
text={buttonText}
onClick={buyAction}
size='md'

View File

@ -20,6 +20,7 @@ import useHealthComputer from 'hooks/useHealthComputer'
import useLocalStorage from 'hooks/useLocalStorage'
import useMarketAssets from 'hooks/useMarketAssets'
import useMarketBorrowings from 'hooks/useMarketBorrowings'
import useSwapRoute from 'hooks/useSwapRoute'
import useToggle from 'hooks/useToggle'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
@ -43,6 +44,7 @@ export default function SwapForm(props: Props) {
const { computeMaxSwapAmount } = useHealthComputer(account)
const { data: borrowAssets } = useMarketBorrowings()
const { data: marketAssets } = useMarketAssets()
const { data: route, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom)
const isBorrowEnabled = !!marketAssets.find(byDenom(sellAsset.denom))?.borrowEnabled
const [isMarginChecked, setMarginChecked] = useToggle(isBorrowEnabled ? useMargin : false)
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
@ -265,6 +267,14 @@ export default function SwapForm(props: Props) {
[borrowAsset?.liquidity?.amount],
)
const isSwapDisabled = useMemo(
() =>
sellAssetAmount.isZero() ||
depositCapReachedCoins.length > 0 ||
borrowAmount.isGreaterThanOrEqualTo(availableLiquidity),
[sellAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity],
)
return (
<>
<Divider />
@ -323,15 +333,12 @@ export default function SwapForm(props: Props) {
sellAsset={sellAsset}
borrowRate={borrowAsset?.borrowRate}
buyAction={handleBuyClick}
buyButtonDisabled={
sellAssetAmount.isZero() ||
depositCapReachedCoins.length > 0 ||
borrowAmount.isGreaterThanOrEqualTo(availableLiquidity)
}
showProgressIndicator={isConfirming}
buyButtonDisabled={isSwapDisabled || route.length === 0}
showProgressIndicator={isConfirming || isRouteLoading}
isMargin={isMarginChecked}
borrowAmount={borrowAmount}
estimatedFee={estimatedFee}
route={route}
/>
</div>
</>

View File

@ -21,6 +21,7 @@ export const ASSETS: Asset[] = [
isDisplayCurrency: true,
isAutoLendEnabled: true,
pythPriceFeedId: '5867f5683c757393a0670ef0f701490950fe93fdb006d181c8265a831ac0c5c6',
pythHistoryFeedId: 'Crypto.OSMO/USD',
},
{
symbol: 'ATOM',
@ -42,6 +43,7 @@ export const ASSETS: Asset[] = [
isBorrowEnabled: true,
pythPriceFeedId: 'b00b60f88b03a6a625a8d1c048c3f66653edf217439983d037e7222c4e612819',
poolId: 1,
pythHistoryFeedId: 'Crypto.ATOM/USD',
},
{
symbol: 'stATOM',
@ -76,6 +78,7 @@ export const ASSETS: Asset[] = [
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: 'e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
pythHistoryFeedId: 'Crypto.BTC/USD',
poolId: 712,
},
{
@ -94,6 +97,7 @@ export const ASSETS: Asset[] = [
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
pythHistoryFeedId: 'Crypto.ETH/USD',
poolId: 704,
},
{
@ -134,6 +138,7 @@ export const ASSETS: Asset[] = [
isBorrowEnabled: true,
isAutoLendEnabled: true,
pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
pythHistoryFeedId: 'Crypto.USDC/USD',
poolId: 678,
},
{
@ -156,6 +161,7 @@ export const ASSETS: Asset[] = [
isBorrowEnabled: true,
isAutoLendEnabled: true,
pythPriceFeedId: 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
pythHistoryFeedId: 'Crypto.USDC/USD',
poolId: ENV.NETWORK === NETWORK.DEVNET ? 678 : 1221,
},
{
@ -173,6 +179,7 @@ export const ASSETS: Asset[] = [
isDisplayCurrency: ENV.NETWORK !== NETWORK.TESTNET,
isAutoLendEnabled: false,
pythPriceFeedId: '60144b1d5c9e9851732ad1d9760e3485ef80be39b984f6bf60f82b28a2b7f126',
pythHistoryFeedId: 'Crypto.AXL/USD',
poolId: 812,
},
{

View File

@ -8,14 +8,14 @@ interface EnvironmentVariables {
ADDRESS_RED_BANK: string
ADDRESS_SWAPPER: string
ADDRESS_ZAPPER: string
CANDLES_ENDPOINT: string
CANDLES_ENDPOINT_THE_GRAPH: string
CANDLES_ENDPOINT_PYTH: string
CHAIN_ID: string
NETWORK: string
URL_GQL: string
URL_REST: string
URL_RPC: string
URL_VAULT_APR: string
WALLETS: string[]
PYTH_ENDPOINT: string
MAINNET_REST_API: string
WALLET_CONNECT_ID: string
@ -31,14 +31,14 @@ export const ENV: EnvironmentVariables = {
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK || '',
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER || '',
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER || '',
CANDLES_ENDPOINT: process.env.NEXT_PUBLIC_CANDLES_ENDPOINT || '',
CANDLES_ENDPOINT_THE_GRAPH: process.env.NEXT_PUBLIC_CANDLES_ENDPOINT_THE_GRAPH || '',
CANDLES_ENDPOINT_PYTH: process.env.NEXT_PUBLIC_CANDLES_ENDPOINT_PYTH || '',
CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID || '',
NETWORK: process.env.NEXT_PUBLIC_NETWORK || '',
URL_GQL: process.env.NEXT_PUBLIC_GQL || '',
URL_REST: process.env.NEXT_PUBLIC_REST || '',
URL_RPC: process.env.NEXT_PUBLIC_RPC || '',
URL_VAULT_APR: process.env.NEXT_PUBLIC_VAULT_APR || '',
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(',') || [],
PYTH_ENDPOINT: process.env.NEXT_PUBLIC_PYTH_ENDPOINT || '',
MAINNET_REST_API: process.env.NEXT_PUBLIC_MAINNET_REST || '',
WALLET_CONNECT_ID: process.env.NEXT_PUBLIC_WALLET_CONNECT_ID || '',

View File

@ -6,3 +6,5 @@ export const BN_ONE = BN(1)
export const MARGIN_TRADE_BUFFER = 0.9
export const MIN_AMOUNT = 0.000001
export const MAX_AMOUNT_DECIMALS = 6
export const MILLISECONDS_PER_MINUTE = 60000

View File

@ -52,6 +52,7 @@ interface Asset {
isAutoLendEnabled?: boolean
isBorrowEnabled?: boolean
pythPriceFeedId?: string
pythHistoryFeedId?: string
forceFetchPrice?: boolean
testnetDenom?: string
isStaking?: boolean

View File

@ -0,0 +1,4 @@
interface Route {
pool_id: string
token_out_denom: string
}

4
src/utils/math.ts Normal file
View File

@ -0,0 +1,4 @@
export const devideByPotentiallyZero = (numerator: number, denominator: number): number => {
if (denominator === 0) return 0
return numerator / denominator
}