From a8dc0950fa6c790a8c56a8087f96a4db475e2b3a Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Fri, 10 Nov 2023 13:56:17 +0100 Subject: [PATCH] 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 --- .env.example | 3 +- src/api/swap/getSwapRoute.ts | 5 - ...OsmosisTheGraphDataFeed.ts => DataFeed.ts} | 260 ++++++++++++++---- .../Trade/TradeChart/TVChartContainer.tsx | 7 +- src/components/Trade/TradeChart/constants.ts | 2 +- .../TradeModule/SwapForm/TradeSummary.tsx | 21 +- .../Trade/TradeModule/SwapForm/index.tsx | 19 +- src/constants/assets.ts | 7 + src/constants/env.ts | 8 +- src/constants/math.ts | 2 + src/types/interfaces/asset.d.ts | 1 + src/types/interfaces/components/swapForm.d.ts | 4 + src/utils/math.ts | 4 + 13 files changed, 258 insertions(+), 85 deletions(-) rename src/components/Trade/TradeChart/{OsmosisTheGraphDataFeed.ts => DataFeed.ts} (55%) create mode 100644 src/types/interfaces/components/swapForm.d.ts create mode 100644 src/utils/math.ts diff --git a/.env.example b/.env.example index 5c508803..238c2a0b 100644 --- a/.env.example +++ b/.env.example @@ -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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 diff --git a/src/api/swap/getSwapRoute.ts b/src/api/swap/getSwapRoute.ts index 82c87f77..c5501c49 100644 --- a/src/api/swap/getSwapRoute.ts +++ b/src/api/swap/getSwapRoute.ts @@ -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 { try { const swapperClient = await getSwapperQueryClient() diff --git a/src/components/Trade/TradeChart/OsmosisTheGraphDataFeed.ts b/src/components/Trade/TradeChart/DataFeed.ts similarity index 55% rename from src/components/Trade/TradeChart/OsmosisTheGraphDataFeed.ts rename to src/components/Trade/TradeChart/DataFeed.ts index 384c9d13..606121d7 100644 --- a/src/components/Trade/TradeChart/OsmosisTheGraphDataFeed.ts +++ b/src/components/Trade/TradeChart/DataFeed.ts @@ -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 { - 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 | 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 | 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 { + async queryBarData( + feedSymbol: string, + resolution: ResolutionString, + from: PeriodParams['from'], + to: PeriodParams['to'], + ): Promise { + 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 { + 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 } diff --git a/src/components/Trade/TradeChart/TVChartContainer.tsx b/src/components/Trade/TradeChart/TVChartContainer.tsx index 317a4691..50eb78e2 100644 --- a/src/components/Trade/TradeChart/TVChartContainer.tsx +++ b/src/components/Trade/TradeChart/TVChartContainer.tsx @@ -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], ) diff --git a/src/components/Trade/TradeChart/constants.ts b/src/components/Trade/TradeChart/constants.ts index 79632248..8b2689d4 100644 --- a/src/components/Trade/TradeChart/constants.ts +++ b/src/components/Trade/TradeChart/constants.ts @@ -28,7 +28,7 @@ export const overrides = { } export const defaultSymbolInfo: Partial = { - listed_exchange: 'Osmosis', + listed_exchange: 'Pyth Oracle', type: 'AMM', session: '24x7', minmov: 1, diff --git a/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx b/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx index d694d4d7..c7721f56 100644 --- a/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx +++ b/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx @@ -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 (
+ sellAssetAmount.isZero() || + depositCapReachedCoins.length > 0 || + borrowAmount.isGreaterThanOrEqualTo(availableLiquidity), + [sellAssetAmount, depositCapReachedCoins, borrowAmount, availableLiquidity], + ) + return ( <> @@ -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} /> diff --git a/src/constants/assets.ts b/src/constants/assets.ts index 76b718ed..1d6a344f 100644 --- a/src/constants/assets.ts +++ b/src/constants/assets.ts @@ -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, }, { diff --git a/src/constants/env.ts b/src/constants/env.ts index 37639d89..1949e422 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -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 || '', diff --git a/src/constants/math.ts b/src/constants/math.ts index 80110d2e..29b68b4b 100644 --- a/src/constants/math.ts +++ b/src/constants/math.ts @@ -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 diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index b0670be8..d2426d5c 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -52,6 +52,7 @@ interface Asset { isAutoLendEnabled?: boolean isBorrowEnabled?: boolean pythPriceFeedId?: string + pythHistoryFeedId?: string forceFetchPrice?: boolean testnetDenom?: string isStaking?: boolean diff --git a/src/types/interfaces/components/swapForm.d.ts b/src/types/interfaces/components/swapForm.d.ts new file mode 100644 index 00000000..a6d1e695 --- /dev/null +++ b/src/types/interfaces/components/swapForm.d.ts @@ -0,0 +1,4 @@ +interface Route { + pool_id: string + token_out_denom: string +} diff --git a/src/utils/math.ts b/src/utils/math.ts new file mode 100644 index 00000000..998bec5f --- /dev/null +++ b/src/utils/math.ts @@ -0,0 +1,4 @@ +export const devideByPotentiallyZero = (numerator: number, denominator: number): number => { + if (denominator === 0) return 0 + return numerator / denominator +}