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:
parent
7439bea0d8
commit
a8dc0950fa
@ -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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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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],
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
@ -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 || '',
|
||||
|
@ -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
|
||||
|
1
src/types/interfaces/asset.d.ts
vendored
1
src/types/interfaces/asset.d.ts
vendored
@ -52,6 +52,7 @@ interface Asset {
|
||||
isAutoLendEnabled?: boolean
|
||||
isBorrowEnabled?: boolean
|
||||
pythPriceFeedId?: string
|
||||
pythHistoryFeedId?: string
|
||||
forceFetchPrice?: boolean
|
||||
testnetDenom?: string
|
||||
isStaking?: boolean
|
||||
|
4
src/types/interfaces/components/swapForm.d.ts
vendored
Normal file
4
src/types/interfaces/components/swapForm.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
interface Route {
|
||||
pool_id: string
|
||||
token_out_denom: string
|
||||
}
|
4
src/utils/math.ts
Normal file
4
src/utils/math.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const devideByPotentiallyZero = (numerator: number, denominator: number): number => {
|
||||
if (denominator === 0) return 0
|
||||
return numerator / denominator
|
||||
}
|
Loading…
Reference in New Issue
Block a user