WIP: Tradingview charts (#296)
* almost finished custom styling for TV add basic chart intermittent update finished basic route pairing datafeed update datafeed fix relative import * finish shell script add shell script for charting_library * remove wrong line in shell * fixed pr comments * fixed pr comments * fix config for TV CHart * add example for thegraph api * update favorite assets * add + to boolean operation * remove usecallback
This commit is contained in:
parent
380bfb7189
commit
4aec1bee67
@ -15,6 +15,7 @@ NEXT_PUBLIC_ZAPPER=osmo1dz3ysw5sl0rvvnvatv7nu6vyam687tentfuxfa22sxqqafdcnkdqht3u
|
|||||||
NEXT_PUBLIC_SWAPPER=osmo1q3p82qtudu7f5edgvqyzf6hk8xanezlr0w7ntypnsea4jfpe37ps29eay3
|
NEXT_PUBLIC_SWAPPER=osmo1q3p82qtudu7f5edgvqyzf6hk8xanezlr0w7ntypnsea4jfpe37ps29eay3
|
||||||
NEXT_PUBLIC_PARAMS=osmo1xvg28lrr72662t9u0hntt76lyax9zvptdvdmff4k2q9dhjm8x6ws9zym4v
|
NEXT_PUBLIC_PARAMS=osmo1xvg28lrr72662t9u0hntt76lyax9zvptdvdmff4k2q9dhjm8x6ws9zym4v
|
||||||
NEXT_PUBLIC_API=http://localhost:3000/api
|
NEXT_PUBLIC_API=http://localhost:3000/api
|
||||||
|
NEXT_PUBLIC_CANDLES_ENDPOINT="https://api.thegraph.com/subgraphs/name/{NAME}/{GRAPH_NAME}"
|
||||||
|
|
||||||
# MAINNET #
|
# MAINNET #
|
||||||
# NEXT_PUBLIC_NETWORK=mainnet
|
# NEXT_PUBLIC_NETWORK=mainnet
|
||||||
|
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
src/utils/charting_library
|
||||||
|
src/utils/datafeeds
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,6 +19,8 @@ coverage-summary.json
|
|||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
charting_library/
|
||||||
|
datafeeds/
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
src/utils/charting_library
|
||||||
|
src/utils/datafeeds
|
29
copy_charting_library_files.sh
Normal file
29
copy_charting_library_files.sh
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
remove_if_directory_exists() {
|
||||||
|
if [ -d "$1" ]; then rm -Rf "$1"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
BRANCH="master"
|
||||||
|
|
||||||
|
REPOSITORY="https://$TV_USERNAME:$TV_ACCESS_TOKEN@github.com/tradingview/charting_library/"
|
||||||
|
|
||||||
|
echo $REPOSITORY
|
||||||
|
|
||||||
|
LATEST_HASH=$(git ls-remote $REPOSITORY $BRANCH | grep -Eo '^[[:alnum:]]+')
|
||||||
|
|
||||||
|
remove_if_directory_exists "$LATEST_HASH"
|
||||||
|
|
||||||
|
git clone -q --depth 1 -b "$BRANCH" $REPOSITORY "$LATEST_HASH"
|
||||||
|
|
||||||
|
remove_if_directory_exists "public/static/charting_library"
|
||||||
|
remove_if_directory_exists "public/static/datafeeds"
|
||||||
|
remove_if_directory_exists "src/utils/charting_library"
|
||||||
|
remove_if_directory_exists "src/utils/datafeeds"
|
||||||
|
|
||||||
|
cp -r "$LATEST_HASH/charting_library" public/
|
||||||
|
cp -r "$LATEST_HASH/charting_library" src/utils/
|
||||||
|
cp -r "$LATEST_HASH/datafeeds" public/
|
||||||
|
cp -r "$LATEST_HASH/datafeeds" src/utils/
|
||||||
|
|
||||||
|
remove_if_directory_exists "$LATEST_HASH"
|
151
public/tradingview.css
Normal file
151
public/tradingview.css
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
:root {
|
||||||
|
--tv-background: #220e1d;
|
||||||
|
--tv-menu-background: #31142a !important;
|
||||||
|
--tv-menu-text: rgba(255, 255, 255, 0.3) !important;
|
||||||
|
--tv-menu-text-hover: rgba(255, 255, 255, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark:root {
|
||||||
|
--tv-color-pane-background: var(--tv-background);
|
||||||
|
--tv-color-platform-background: var(--tv-background);
|
||||||
|
--tv-color-toolbar-button-text: var(--tv-menu-text);
|
||||||
|
--tv-color-toolbar-button-text-hover: var(--tv-menu-text-hover);
|
||||||
|
--tv-color-toolbar-button-text-active: var(--tv-menu-text-hover);
|
||||||
|
--tv-color-toolbar-button-text-active-hover: var(--tv-menu-text-hover);
|
||||||
|
--tv-color-toolbar-button-background-hover: var(--tv-background);
|
||||||
|
--tv-color-toolbar-button-background-expanded: var(--tv-background);
|
||||||
|
--tv-color-toolbar-button-background-active: var(--tv-background);
|
||||||
|
--tv-color-toolbar-button-background-active-hover: var(--tv-background);
|
||||||
|
--tv-color-toolbar-toggle-button-background-active: rgba(255, 255, 255, 0.2);
|
||||||
|
--tv-color-toolbar-toggle-button-background-active-hover: rgba(255, 255, 255, 0.2);
|
||||||
|
--tv-color-toolbar-divider-background: var(--tv-menu-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Favorited menus */
|
||||||
|
.tv-floating-toolbar__widget-wrapper > div {
|
||||||
|
background: var(--tv-menu-background) !important;
|
||||||
|
}
|
||||||
|
.tv-floating-toolbar__widget:hover,
|
||||||
|
.tv-favorited-drawings-toolbar__widget:hover *,
|
||||||
|
.tv-favorited-drawings-toolbar__widget:hover:before {
|
||||||
|
background: var(--tv-menu-background) !important;
|
||||||
|
color: var(--tv-menu-text-hover) !important;
|
||||||
|
border-color: var(--tv-menu-background) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating menu */
|
||||||
|
.floating-toolbar-react-widgets__button:hover,
|
||||||
|
.button-reABrhVR:hover:before,
|
||||||
|
.button-uO7HM85b.isInteractive-uO7HM85b:hover:before {
|
||||||
|
background: var(--tv-menu-background) !important;
|
||||||
|
color: var(--tv-menu-text-hover) !important;
|
||||||
|
border-color: var(--tv-menu-background) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout__area--left {
|
||||||
|
min-width: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[data-role='button']:hover,
|
||||||
|
/* Indiator dialog list items */
|
||||||
|
div[data-role="dialog-content"] div[data-role="list-item"]:hover,
|
||||||
|
/* Series left sidebar */
|
||||||
|
.active-a7Y2yl3G {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* General pop-up menus */
|
||||||
|
div[data-name='popup-menu-container'] div:not(.swatch-pNRFZrPx, .opacitySliderGradient-uujjxY8O),
|
||||||
|
/* Indicator dialog */
|
||||||
|
div[data-name='indicators-dialog'] *,
|
||||||
|
/* Layers dialog */
|
||||||
|
div[data-name="object-tree-dialog"] * ,
|
||||||
|
/* Layers dialog */
|
||||||
|
div[data-name="series-properties-dialog"],
|
||||||
|
/* Series left sidetabs */
|
||||||
|
.tab-a7Y2yl3G:hover,
|
||||||
|
.active-a7Y2yl3G,
|
||||||
|
/* Checkbox */
|
||||||
|
.check-bUw_gKIQ,
|
||||||
|
/* Close buton series popup */
|
||||||
|
.close-HS2PTQRJ:hover {
|
||||||
|
background: var(--tv-menu-background) !important;
|
||||||
|
outline: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* General toolbar popup list items */
|
||||||
|
.item-RhC5uhZw,
|
||||||
|
/* Emoji topbar items */
|
||||||
|
.wrapper-rSoA6gh6 svg,
|
||||||
|
.wrapper-rSoA6gh6.categories-TlKkLixs,
|
||||||
|
/* Emoji items */
|
||||||
|
.wrapper-yrezKVPX svg,
|
||||||
|
/* Indiator dialog list items */
|
||||||
|
div[data-role="dialog-content"] div[data-role="list-item"] span,
|
||||||
|
/* Layers dialog list item */
|
||||||
|
.wrap-G4AKrzja span,
|
||||||
|
/* Series proerties */
|
||||||
|
div[data-name="series-properties-dialog"],
|
||||||
|
/* Series left sidetabs */
|
||||||
|
.tab-a7Y2yl3G ,
|
||||||
|
/* Close buton series popup */
|
||||||
|
.close-HS2PTQRJ {
|
||||||
|
color: var(--tv-menu-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* General toolbar popup list items */
|
||||||
|
.item-RhC5uhZw:hover,
|
||||||
|
/* Sub headers for inteval popup */
|
||||||
|
.section-_8r4li9v:hover,
|
||||||
|
/* Emoji picker categories */
|
||||||
|
.wrapper-wawooJAf:hover,
|
||||||
|
/* Emoji topbar items */
|
||||||
|
.isActive-rSoA6gh6,
|
||||||
|
.isActive-rSoA6gh6.categories-TlKkLixs,
|
||||||
|
.wrapper-rSoA6gh6:hover svg,
|
||||||
|
.wrapper-rSoA6gh6.categories-TlKkLixs:hover,
|
||||||
|
/* Emoji items */
|
||||||
|
.isActive-yrezKVPX,
|
||||||
|
.wrapper-yrezKVPX:hover svg,
|
||||||
|
/* Indiator dialog list items */
|
||||||
|
div[data-role="dialog-content"] div[data-role="list-item"]:hover span,
|
||||||
|
/* Layers dialog list item */
|
||||||
|
.wrap-G4AKrzja:hover span,
|
||||||
|
/* Series left sidetabs */
|
||||||
|
.tab-a7Y2yl3G:hover,
|
||||||
|
/* Series left sidetabs */
|
||||||
|
.active-a7Y2yl3G,
|
||||||
|
/* Close buton series popup */
|
||||||
|
.close-HS2PTQRJ:hover {
|
||||||
|
cursor: pointer !important;
|
||||||
|
color: var(--tv-menu-text-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top and bottom scroll indicator for toolbar */
|
||||||
|
.scrollBot-g7ay5OPA,
|
||||||
|
.scrollTop-g7ay5OPA {
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--tv-menu-background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox */
|
||||||
|
.check-bUw_gKIQ {
|
||||||
|
border-color: var(--tv-menu-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons for series popup */
|
||||||
|
.variant-secondary-OvB35Th_,
|
||||||
|
.variant-primary-OvB35Th_ {
|
||||||
|
border-color: var(--tv-menu-text) !important;
|
||||||
|
color: var(--tv-menu-text) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
.variant-secondary-OvB35Th_:hover,
|
||||||
|
.variant-primary-OvB35Th_:hover {
|
||||||
|
border-color: var(--tv-menu-text-hover) !important;
|
||||||
|
color: var(--tv-menu-text-hover) !important;
|
||||||
|
background: none !important;
|
||||||
|
}
|
@ -17,7 +17,8 @@ export default function Card(props: Props) {
|
|||||||
id={props.id}
|
id={props.id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.className,
|
props.className,
|
||||||
'relative isolate max-w-full rounded-base',
|
'flex flex-col',
|
||||||
|
'relative isolate max-w-full overflow-hidden rounded-base',
|
||||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
259
src/components/Trade/TradeChart/OsmosisTheGraphDataFeed.ts
Normal file
259
src/components/Trade/TradeChart/OsmosisTheGraphDataFeed.ts
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import {
|
||||||
|
Bar,
|
||||||
|
ErrorCallback,
|
||||||
|
HistoryCallback,
|
||||||
|
IDatafeedChartApi,
|
||||||
|
LibrarySymbolInfo,
|
||||||
|
OnReadyCallback,
|
||||||
|
PeriodParams,
|
||||||
|
ResolutionString,
|
||||||
|
ResolveCallback,
|
||||||
|
} from 'utils/charting_library'
|
||||||
|
import { ENV } from 'constants/env'
|
||||||
|
import { getAssetByDenom, getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
import { defaultSymbolInfo } from 'components/Trade/TradeChart/constants'
|
||||||
|
|
||||||
|
interface BarQueryData {
|
||||||
|
close: string
|
||||||
|
high: string
|
||||||
|
low: string
|
||||||
|
open: string
|
||||||
|
timestamp: string
|
||||||
|
volume: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PAIR_SEPARATOR = '<>'
|
||||||
|
|
||||||
|
export class OsmosisTheGraphDataFeed implements IDatafeedChartApi {
|
||||||
|
candlesEndpoint = ENV.CANDLES_ENDPOINT
|
||||||
|
debug = false
|
||||||
|
exchangeName = 'Osmosis'
|
||||||
|
baseDecimals: number = 6
|
||||||
|
baseDenom: string = 'uosmo'
|
||||||
|
batchSize = 1000
|
||||||
|
enabledMarketAssetDenoms: string[] = []
|
||||||
|
pairs: string[] = []
|
||||||
|
pairsWithData: string[] = []
|
||||||
|
intervals: { [key: string]: string } = {
|
||||||
|
'15': '15m',
|
||||||
|
'30': '30m',
|
||||||
|
'60': '1h',
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedPools: string[] = []
|
||||||
|
supportedResolutions = ['15', '30', '60'] as ResolutionString[]
|
||||||
|
|
||||||
|
constructor(debug = false, baseDecimals: number, baseDenom: string) {
|
||||||
|
if (debug) console.log('Start TheGraph charting library datafeed')
|
||||||
|
this.debug = debug
|
||||||
|
this.baseDecimals = baseDecimals
|
||||||
|
this.baseDenom = baseDenom
|
||||||
|
const enabledMarketAssets = getEnabledMarketAssets()
|
||||||
|
this.enabledMarketAssetDenoms = enabledMarketAssets.map((asset) => asset.denom)
|
||||||
|
this.supportedPools = enabledMarketAssets
|
||||||
|
.map((asset) => asset.poolId?.toString())
|
||||||
|
.filter((poolId) => typeof poolId === 'string') as string[]
|
||||||
|
this.getAllPairs()
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllPairs() {
|
||||||
|
const assets = getEnabledMarketAssets()
|
||||||
|
const pairs: Set<string> = new Set()
|
||||||
|
assets.forEach((asset1) => {
|
||||||
|
assets.forEach((asset2) => {
|
||||||
|
if (asset1.symbol === asset2.symbol) return
|
||||||
|
pairs.add(`${asset1.denom}${PAIR_SEPARATOR}${asset2.denom}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.pairs = Array.from(pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPairsWithData() {
|
||||||
|
const query = `
|
||||||
|
{
|
||||||
|
pairs(first: ${this.batchSize},
|
||||||
|
orderBy: symbol,
|
||||||
|
orderDirection: asc,
|
||||||
|
where: {
|
||||||
|
baseAsset_in: ${JSON.stringify(this.enabledMarketAssetDenoms)},
|
||||||
|
quoteAsset_in: ${JSON.stringify(this.enabledMarketAssetDenoms)},
|
||||||
|
poolId_in: ${JSON.stringify(this.supportedPools)}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
baseAsset
|
||||||
|
quoteAsset
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
return fetch(this.candlesEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((json) => {
|
||||||
|
this.pairsWithData = json.data.pairs.map(
|
||||||
|
(pair: { baseAsset: string; quoteAsset: string }) => {
|
||||||
|
return `${pair.baseAsset}${PAIR_SEPARATOR}${pair.quoteAsset}`
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (this.debug) console.error(err)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onReady(callback: OnReadyCallback) {
|
||||||
|
const configurationData = {
|
||||||
|
supported_resolutions: this.supportedResolutions,
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this.getPairsWithData()
|
||||||
|
callback(configurationData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveSymbol(pairName: string, onResolve: ResolveCallback, onError: ErrorCallback) {
|
||||||
|
setTimeout(() =>
|
||||||
|
onResolve({
|
||||||
|
...defaultSymbolInfo,
|
||||||
|
currency_code: pairName.split(PAIR_SEPARATOR)[0],
|
||||||
|
original_currency_code: pairName.split(PAIR_SEPARATOR)[1],
|
||||||
|
full_name: pairName,
|
||||||
|
description: pairName,
|
||||||
|
ticker: pairName,
|
||||||
|
exchange: this.exchangeName,
|
||||||
|
listed_exchange: this.exchangeName,
|
||||||
|
supported_resolutions: this.supportedResolutions,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBars(
|
||||||
|
symbolInfo: LibrarySymbolInfo,
|
||||||
|
resolution: ResolutionString,
|
||||||
|
periodParams: PeriodParams,
|
||||||
|
onResult: HistoryCallback,
|
||||||
|
): Promise<void> {
|
||||||
|
const interval = this.intervals[resolution]
|
||||||
|
|
||||||
|
let pair1 = symbolInfo.full_name
|
||||||
|
let pair2: string = ''
|
||||||
|
|
||||||
|
if (!this.pairsWithData.includes(pair1)) {
|
||||||
|
if (this.debug) console.log('Pair does not have data, need to combine with 2nd pair')
|
||||||
|
|
||||||
|
const [buyAssetDenom, sellAssetDenom] = pair1.split(PAIR_SEPARATOR)
|
||||||
|
|
||||||
|
pair1 = `${buyAssetDenom}${PAIR_SEPARATOR}${this.baseDenom}`
|
||||||
|
pair2 = `${this.baseDenom}${PAIR_SEPARATOR}${sellAssetDenom}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const pair1Bars = this.queryBarData(
|
||||||
|
pair1.split(PAIR_SEPARATOR)[0],
|
||||||
|
pair1.split(PAIR_SEPARATOR)[1],
|
||||||
|
interval,
|
||||||
|
)
|
||||||
|
|
||||||
|
let pair2Bars: Promise<Bar[]> | null = null
|
||||||
|
|
||||||
|
if (pair2) {
|
||||||
|
pair2Bars = this.queryBarData(
|
||||||
|
pair2.split(PAIR_SEPARATOR)[0],
|
||||||
|
pair2.split(PAIR_SEPARATOR)[1],
|
||||||
|
interval,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([pair1Bars, pair2Bars]).then(([pair1Bars, pair2Bars]) => {
|
||||||
|
let bars = pair1Bars
|
||||||
|
if (pair2Bars) {
|
||||||
|
bars = this.combineBars(pair1Bars, pair2Bars)
|
||||||
|
}
|
||||||
|
onResult(bars)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryBarData(quote: string, base: string, interval: string): Promise<Bar[]> {
|
||||||
|
const query = `
|
||||||
|
{
|
||||||
|
candles(
|
||||||
|
first: ${this.batchSize},
|
||||||
|
where: {
|
||||||
|
interval: "${interval}",
|
||||||
|
quote: "${quote}",
|
||||||
|
base: "${base}"
|
||||||
|
}) {
|
||||||
|
timestamp
|
||||||
|
open
|
||||||
|
high
|
||||||
|
low
|
||||||
|
close
|
||||||
|
volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
return fetch(this.candlesEndpoint, {
|
||||||
|
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, base)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (this.debug) console.error(err)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveBarData(bars: BarQueryData[], base: string) {
|
||||||
|
const assetDecimals = getAssetByDenom(base)?.decimals || 6
|
||||||
|
const additionalDecimals = assetDecimals - this.baseDecimals
|
||||||
|
|
||||||
|
return bars.map((bar) => ({
|
||||||
|
time: BN(bar.timestamp).multipliedBy(1000).toNumber(),
|
||||||
|
close: BN(bar.close).shiftedBy(additionalDecimals).toNumber(),
|
||||||
|
open: BN(bar.open).shiftedBy(additionalDecimals).toNumber(),
|
||||||
|
high: BN(bar.high).shiftedBy(additionalDecimals).toNumber(),
|
||||||
|
low: BN(bar.low).shiftedBy(additionalDecimals).toNumber(),
|
||||||
|
volume: BN(bar.volume).shiftedBy(additionalDecimals).toNumber(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
combineBars(pair1Bars: Bar[], pair2Bars: Bar[]): Bar[] {
|
||||||
|
const bars: Bar[] = []
|
||||||
|
|
||||||
|
pair1Bars.forEach((pair1Bar) => {
|
||||||
|
const pair2Bar = pair2Bars.find((pair2Bar) => pair2Bar.time == pair1Bar.time)
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return bars
|
||||||
|
}
|
||||||
|
|
||||||
|
searchSymbols(): void {
|
||||||
|
// Don't allow to search for symbols
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeBars(): void {
|
||||||
|
// TheGraph doesn't support websockets yet
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeBars(listenerGuid: string): void {
|
||||||
|
// TheGraph doesn't support websockets yet
|
||||||
|
}
|
||||||
|
}
|
112
src/components/Trade/TradeChart/TVChartContainer.tsx
Normal file
112
src/components/Trade/TradeChart/TVChartContainer.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChartingLibraryWidgetOptions,
|
||||||
|
IChartingLibraryWidget,
|
||||||
|
ResolutionString,
|
||||||
|
Timezone,
|
||||||
|
widget,
|
||||||
|
} from 'utils/charting_library'
|
||||||
|
import Card from 'components/Card'
|
||||||
|
import {
|
||||||
|
OsmosisTheGraphDataFeed,
|
||||||
|
PAIR_SEPARATOR,
|
||||||
|
} from 'components/Trade/TradeChart/OsmosisTheGraphDataFeed'
|
||||||
|
import useStore from 'store'
|
||||||
|
import { disabledFeatures, enabledFeatures, overrides } from 'components/Trade/TradeChart/constants'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
buyAsset: Asset
|
||||||
|
sellAsset: Asset
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TVChartContainer = (props: Props) => {
|
||||||
|
const chartContainerRef = useRef<HTMLDivElement>() as React.MutableRefObject<HTMLInputElement>
|
||||||
|
const widgetRef = useRef<IChartingLibraryWidget>()
|
||||||
|
const defaultSymbol = useRef<string>(
|
||||||
|
`${props.buyAsset.denom}${PAIR_SEPARATOR}${props.sellAsset.denom}`,
|
||||||
|
)
|
||||||
|
const baseCurrency = useStore((s) => s.baseCurrency)
|
||||||
|
const dataFeed = useMemo(
|
||||||
|
() => new OsmosisTheGraphDataFeed(false, baseCurrency.decimals, baseCurrency.denom),
|
||||||
|
[baseCurrency],
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||||
|
symbol: defaultSymbol.current,
|
||||||
|
datafeed: dataFeed,
|
||||||
|
interval: '1h' as ResolutionString,
|
||||||
|
library_path: '/charting_library/',
|
||||||
|
locale: 'en',
|
||||||
|
time_scale: {
|
||||||
|
min_bar_spacing: 12,
|
||||||
|
},
|
||||||
|
toolbar_bg: '#220E1D',
|
||||||
|
disabled_features: disabledFeatures,
|
||||||
|
enabled_features: enabledFeatures,
|
||||||
|
charts_storage_api_version: '1.1',
|
||||||
|
client_id: 'sample-implementation',
|
||||||
|
timezone: 'Etc/UTC' as Timezone,
|
||||||
|
user_id: 'not-set',
|
||||||
|
fullscreen: false,
|
||||||
|
autosize: true,
|
||||||
|
container: chartContainerRef.current,
|
||||||
|
custom_css_url: '/tradingview.css',
|
||||||
|
settings_overrides: {
|
||||||
|
'paneProperties.background': '#220E1D',
|
||||||
|
'paneProperties.backgroundType': 'solid',
|
||||||
|
'paneProperties.vertGridProperties.color': '#220E1D',
|
||||||
|
'paneProperties.horzGridProperties.color': '#220E1D',
|
||||||
|
'mainSeriesProperties.candleStyle.upColor': '#3DAE9A',
|
||||||
|
'mainSeriesProperties.candleStyle.downColor': '#AE3D3D',
|
||||||
|
'mainSeriesProperties.candleStyle.borderColor': '#232834',
|
||||||
|
'mainSeriesProperties.candleStyle.borderUpColor': '#3DAE9A',
|
||||||
|
'mainSeriesProperties.candleStyle.borderDownColor': '#AE3D3D',
|
||||||
|
'mainSeriesProperties.candleStyle.wickUpColor': '#3DAE9A',
|
||||||
|
'mainSeriesProperties.candleStyle.wickDownColor': '#AE3D3D',
|
||||||
|
'mainSeriesProperties.candleStyle.barColorsOnPrevClose': false,
|
||||||
|
'scalesProperties.textColor': 'rgba(255, 255, 255, 0.3)',
|
||||||
|
'paneProperties.legendProperties.showSeriesTitle': false,
|
||||||
|
'paneProperties.legendProperties.showVolume': false,
|
||||||
|
'paneProperties.legendProperties.showStudyValues': false,
|
||||||
|
'paneProperties.legendProperties.showStudyTitles': false,
|
||||||
|
'scalesProperties.axisHighlightColor': '#381730',
|
||||||
|
'linetooltrendline.color': 'rgba( 21, 153, 128, 1)',
|
||||||
|
'linetooltrendline.linewidth': 10,
|
||||||
|
},
|
||||||
|
overrides,
|
||||||
|
loading_screen: {
|
||||||
|
backgroundColor: '#220E1D',
|
||||||
|
foregroundColor: 'rgba(255, 255, 255, 0.3)',
|
||||||
|
},
|
||||||
|
theme: 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const tvWidget = new widget(widgetOptions)
|
||||||
|
|
||||||
|
tvWidget.onChartReady(() => {
|
||||||
|
widgetRef.current = tvWidget
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
tvWidget.remove()
|
||||||
|
}
|
||||||
|
}, [dataFeed, defaultSymbol])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (widgetRef?.current) {
|
||||||
|
widgetRef.current.setSymbol(
|
||||||
|
`${props.sellAsset.denom}${PAIR_SEPARATOR}${props.buyAsset.denom}`,
|
||||||
|
widgetRef.current.chart().resolution() || ('1h' as ResolutionString),
|
||||||
|
() => {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [props.buyAsset.denom, props.sellAsset.denom])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title='Trading Chart' contentClassName='px-0.5 pb-0.5 h-full '>
|
||||||
|
<div ref={chartContainerRef} className='h-full' />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
49
src/components/Trade/TradeChart/constants.ts
Normal file
49
src/components/Trade/TradeChart/constants.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
ChartingLibraryFeatureset,
|
||||||
|
LibrarySymbolInfo,
|
||||||
|
ResolutionString,
|
||||||
|
SeriesFormat,
|
||||||
|
Timezone,
|
||||||
|
} from 'utils/charting_library/charting_library'
|
||||||
|
|
||||||
|
export const disabledFeatures: ChartingLibraryFeatureset[] = [
|
||||||
|
'timeframes_toolbar',
|
||||||
|
'go_to_date',
|
||||||
|
'header_compare',
|
||||||
|
'header_saveload',
|
||||||
|
'popup_hints',
|
||||||
|
'header_symbol_search',
|
||||||
|
'symbol_info',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const enabledFeatures: ChartingLibraryFeatureset[] = [
|
||||||
|
'timezone_menu',
|
||||||
|
'header_settings',
|
||||||
|
'use_localstorage_for_settings',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const overrides = {
|
||||||
|
'linetooltrendline.linecolor': 'rgba(255, 255, 255, 0.8)',
|
||||||
|
'linetooltrendline.linewidth': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultSymbolInfo: LibrarySymbolInfo = {
|
||||||
|
currency_code: '',
|
||||||
|
original_currency_code: '',
|
||||||
|
full_name: '',
|
||||||
|
description: '',
|
||||||
|
ticker: '',
|
||||||
|
name: 'Osmosis',
|
||||||
|
exchange: 'Osmosis',
|
||||||
|
listed_exchange: 'Osmosis',
|
||||||
|
type: 'AMM',
|
||||||
|
session: '24x7',
|
||||||
|
minmov: 1,
|
||||||
|
pricescale: 100000,
|
||||||
|
timezone: 'Etc/UTC' as Timezone,
|
||||||
|
has_intraday: true,
|
||||||
|
has_daily: true,
|
||||||
|
has_weekly_and_monthly: true,
|
||||||
|
format: 'price' as SeriesFormat,
|
||||||
|
supported_resolutions: ['15'] as ResolutionString[],
|
||||||
|
}
|
29
src/components/Trade/TradeChart/index.tsx
Normal file
29
src/components/Trade/TradeChart/index.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import Script from 'next/script'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
const TVChartContainer = dynamic(
|
||||||
|
() => import('components/Trade/TradeChart/TVChartContainer').then((mod) => mod.TVChartContainer),
|
||||||
|
{ ssr: false },
|
||||||
|
)
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
buyAsset: Asset
|
||||||
|
sellAsset: Asset
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TradeChart(props: Props) {
|
||||||
|
const [isScriptReady, setIsScriptReady] = useState(true)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
src='/datafeeds/udf/dist/bundle.js'
|
||||||
|
strategy='lazyOnload'
|
||||||
|
onReady={() => {
|
||||||
|
setIsScriptReady(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isScriptReady && <TVChartContainer buyAsset={props.buyAsset} sellAsset={props.sellAsset} />}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -2,7 +2,8 @@ import AssetImage from 'components/AssetImage'
|
|||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import { StarFilled, StarOutlined } from 'components/Icons'
|
import { StarFilled, StarOutlined } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { FAVORITE_ASSETS } from 'constants/localStore'
|
import { FAVORITE_ASSETS_KEY } from 'constants/localStore'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
@ -13,22 +14,21 @@ interface Props {
|
|||||||
|
|
||||||
export default function AssetItem(props: Props) {
|
export default function AssetItem(props: Props) {
|
||||||
const asset = props.asset
|
const asset = props.asset
|
||||||
|
const [favoriteAssetsDenoms, setFavoriteAssetsDenoms] = useLocalStorage<string[]>(
|
||||||
|
FAVORITE_ASSETS_KEY,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
const favoriteAssets: string[] = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
|
|
||||||
if (favoriteAssets) {
|
if (!favoriteAssetsDenoms.includes(asset.denom)) {
|
||||||
if (favoriteAssets.includes(asset.denom)) {
|
setFavoriteAssetsDenoms([...favoriteAssetsDenoms, asset.denom])
|
||||||
localStorage.setItem(
|
return
|
||||||
FAVORITE_ASSETS,
|
|
||||||
JSON.stringify(favoriteAssets.filter((item: string) => item !== asset.denom)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(FAVORITE_ASSETS, JSON.stringify([...favoriteAssets, asset.denom]))
|
|
||||||
}
|
|
||||||
window.dispatchEvent(new Event('storage'))
|
|
||||||
}
|
}
|
||||||
|
setFavoriteAssetsDenoms(favoriteAssetsDenoms.filter((item: string) => item !== asset.denom))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='border-b border-white/10 hover:bg-black/10'>
|
<li className='border-b border-white/10 hover:bg-black/10'>
|
||||||
<button
|
<button
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { SwapIcon } from 'components/Icons'
|
import { SwapIcon } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
|
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
|
||||||
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
|
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
|
||||||
import { ASSETS } from 'constants/assets'
|
|
||||||
|
|
||||||
export default function AssetSelector() {
|
interface Props {
|
||||||
|
buyAsset: Asset
|
||||||
|
sellAsset: Asset
|
||||||
|
onChangeBuyAsset: (asset: Asset) => void
|
||||||
|
onChangeSellAsset: (asset: Asset) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AssetSelector(props: Props) {
|
||||||
|
const { buyAsset, sellAsset, onChangeBuyAsset, onChangeSellAsset } = props
|
||||||
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
|
const [overlayState, setOverlayState] = useState<OverlayState>('closed')
|
||||||
const [buyAsset, setBuyAsset] = useState(ASSETS[0])
|
const [cachedBuyAsset, setCachedBuyAsset] = useState<Asset>(props.buyAsset)
|
||||||
const [sellAsset, setSellAsset] = useState(ASSETS[1])
|
const [cachedSellAsset, setCachedSellAsset] = useState<Asset>(props.sellAsset)
|
||||||
|
|
||||||
function handleSwapAssets() {
|
const handleSwapAssets = useCallback(() => {
|
||||||
setBuyAsset(sellAsset)
|
onChangeBuyAsset(sellAsset)
|
||||||
setSellAsset(buyAsset)
|
onChangeSellAsset(buyAsset)
|
||||||
}
|
}, [onChangeBuyAsset, onChangeSellAsset, sellAsset, buyAsset])
|
||||||
|
|
||||||
const handleChangeBuyAsset = useCallback(
|
const handleChangeBuyAsset = useCallback((asset: Asset) => {
|
||||||
(asset: Asset) => {
|
setCachedBuyAsset(asset)
|
||||||
setBuyAsset(asset)
|
setOverlayState('sell')
|
||||||
setOverlayState('sell')
|
}, [])
|
||||||
},
|
|
||||||
[setBuyAsset],
|
const handleChangeSellAsset = useCallback((asset: Asset) => {
|
||||||
)
|
setCachedSellAsset(asset)
|
||||||
|
setOverlayState('closed')
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleChangeSellAsset = useCallback(
|
|
||||||
(asset: Asset) => {
|
|
||||||
setSellAsset(asset)
|
|
||||||
setOverlayState('closed')
|
|
||||||
},
|
|
||||||
[setSellAsset],
|
|
||||||
)
|
|
||||||
const handleChangeState = useCallback(
|
const handleChangeState = useCallback(
|
||||||
(state: OverlayState) => {
|
(state: OverlayState) => {
|
||||||
setOverlayState(state)
|
setOverlayState(state)
|
||||||
@ -38,22 +40,29 @@ export default function AssetSelector() {
|
|||||||
[setOverlayState],
|
[setOverlayState],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (overlayState === 'closed') {
|
||||||
|
onChangeBuyAsset(cachedBuyAsset)
|
||||||
|
onChangeSellAsset(cachedSellAsset)
|
||||||
|
}
|
||||||
|
}, [onChangeBuyAsset, onChangeSellAsset, overlayState, cachedBuyAsset, cachedSellAsset])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
|
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
|
||||||
<Text size='sm'>Buy</Text>
|
<Text size='sm'>Buy</Text>
|
||||||
<Text size='sm' className='col-start-3'>
|
<Text size='sm' className='col-start-3'>
|
||||||
Sell
|
Sell
|
||||||
</Text>
|
</Text>
|
||||||
<AssetButton onClick={() => setOverlayState('buy')} asset={buyAsset} />
|
<AssetButton onClick={() => setOverlayState('buy')} asset={props.buyAsset} />
|
||||||
<button onClick={handleSwapAssets}>
|
<button onClick={handleSwapAssets}>
|
||||||
<SwapIcon className='mx-2 w-4 place-self-center' />
|
<SwapIcon className='mx-2 w-4 place-self-center' />
|
||||||
</button>
|
</button>
|
||||||
<AssetButton onClick={() => setOverlayState('sell')} asset={sellAsset} />
|
<AssetButton onClick={() => setOverlayState('sell')} asset={props.sellAsset} />
|
||||||
<AssetOverlay
|
<AssetOverlay
|
||||||
state={overlayState}
|
state={overlayState}
|
||||||
onChangeState={handleChangeState}
|
onChangeState={handleChangeState}
|
||||||
buyAsset={buyAsset}
|
buyAsset={cachedBuyAsset}
|
||||||
sellAsset={sellAsset}
|
sellAsset={cachedSellAsset}
|
||||||
onChangeBuyAsset={handleChangeBuyAsset}
|
onChangeBuyAsset={handleChangeBuyAsset}
|
||||||
onChangeSellAsset={handleChangeSellAsset}
|
onChangeSellAsset={handleChangeSellAsset}
|
||||||
/>
|
/>
|
||||||
|
@ -5,7 +5,14 @@ import Divider from 'components/Divider'
|
|||||||
import RangeInput from 'components/RangeInput'
|
import RangeInput from 'components/RangeInput'
|
||||||
import AssetSelector from 'components/Trade/TradeModule/AssetSelector'
|
import AssetSelector from 'components/Trade/TradeModule/AssetSelector'
|
||||||
|
|
||||||
export default function TradeModule() {
|
interface Props {
|
||||||
|
buyAsset: Asset
|
||||||
|
sellAsset: Asset
|
||||||
|
onChangeBuyAsset: (asset: Asset) => void
|
||||||
|
onChangeSellAsset: (asset: Asset) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TradeModule(props: Props) {
|
||||||
const [value, setValue] = useState(0)
|
const [value, setValue] = useState(0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,7 +23,12 @@ export default function TradeModule() {
|
|||||||
'row-span-2 h-full',
|
'row-span-2 h-full',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<AssetSelector />
|
<AssetSelector
|
||||||
|
buyAsset={props.buyAsset}
|
||||||
|
sellAsset={props.sellAsset}
|
||||||
|
onChangeBuyAsset={props.onChangeBuyAsset}
|
||||||
|
onChangeSellAsset={props.onChangeSellAsset}
|
||||||
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<RangeInput
|
<RangeInput
|
||||||
max={4000}
|
max={4000}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import { Suspense } from 'react'
|
|
||||||
|
|
||||||
import Card from 'components/Card'
|
|
||||||
import Loading from 'components/Loading'
|
|
||||||
import Text from 'components/Text'
|
|
||||||
|
|
||||||
function Content() {
|
|
||||||
return <Text size='sm'>Chart view</Text>
|
|
||||||
}
|
|
||||||
|
|
||||||
function Fallback() {
|
|
||||||
return <Loading className='h-4 w-50' />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TradingView() {
|
|
||||||
return (
|
|
||||||
<Card className='h-full bg-white/5' title='Trading View' contentClassName='px-4 py-6'>
|
|
||||||
<Suspense fallback={<Fallback />}>
|
|
||||||
<Content />
|
|
||||||
</Suspense>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
@ -30,6 +30,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isMarket: true,
|
isMarket: true,
|
||||||
isDisplayCurrency: true,
|
isDisplayCurrency: true,
|
||||||
isAutoLendEnabled: true,
|
isAutoLendEnabled: true,
|
||||||
|
poolId: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'stATOM',
|
symbol: 'stATOM',
|
||||||
@ -43,6 +44,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
isMarket: !IS_TESTNET,
|
isMarket: !IS_TESTNET,
|
||||||
isDisplayCurrency: !IS_TESTNET,
|
isDisplayCurrency: !IS_TESTNET,
|
||||||
|
poolId: 803,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'WBTC.axl',
|
symbol: 'WBTC.axl',
|
||||||
@ -56,6 +58,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
isMarket: !IS_TESTNET,
|
isMarket: !IS_TESTNET,
|
||||||
isDisplayCurrency: !IS_TESTNET,
|
isDisplayCurrency: !IS_TESTNET,
|
||||||
|
poolId: 712,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'WETH.axl',
|
symbol: 'WETH.axl',
|
||||||
@ -69,6 +72,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isEnabled: !IS_TESTNET,
|
isEnabled: !IS_TESTNET,
|
||||||
isMarket: !IS_TESTNET,
|
isMarket: !IS_TESTNET,
|
||||||
isDisplayCurrency: !IS_TESTNET,
|
isDisplayCurrency: !IS_TESTNET,
|
||||||
|
poolId: 704,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'MARS',
|
symbol: 'MARS',
|
||||||
@ -80,10 +84,10 @@ export const ASSETS: Asset[] = [
|
|||||||
color: '#dd5b65',
|
color: '#dd5b65',
|
||||||
logo: '/tokens/mars.svg',
|
logo: '/tokens/mars.svg',
|
||||||
decimals: 6,
|
decimals: 6,
|
||||||
poolId: IS_TESTNET ? 768 : 907,
|
|
||||||
hasOraclePrice: true,
|
hasOraclePrice: true,
|
||||||
isMarket: false,
|
isMarket: false,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
|
poolId: 907,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'USDC.axl',
|
symbol: 'USDC.axl',
|
||||||
@ -100,6 +104,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isMarket: true,
|
isMarket: true,
|
||||||
isDisplayCurrency: true,
|
isDisplayCurrency: true,
|
||||||
isStable: true,
|
isStable: true,
|
||||||
|
poolId: 678,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'USDC.n',
|
symbol: 'USDC.n',
|
||||||
|
@ -7,6 +7,7 @@ interface EnvironmentVariables {
|
|||||||
ADDRESS_RED_BANK: string
|
ADDRESS_RED_BANK: string
|
||||||
ADDRESS_SWAPPER: string
|
ADDRESS_SWAPPER: string
|
||||||
ADDRESS_ZAPPER: string
|
ADDRESS_ZAPPER: string
|
||||||
|
CANDLES_ENDPOINT: string
|
||||||
CHAIN_ID: string
|
CHAIN_ID: string
|
||||||
NETWORK: string
|
NETWORK: string
|
||||||
URL_GQL: string
|
URL_GQL: string
|
||||||
@ -26,6 +27,7 @@ export const ENV: EnvironmentVariables = {
|
|||||||
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK || '',
|
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK || '',
|
||||||
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER || '',
|
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER || '',
|
||||||
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER || '',
|
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER || '',
|
||||||
|
CANDLES_ENDPOINT: process.env.NEXT_PUBLIC_CANDLES_ENDPOINT || '',
|
||||||
CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID || '',
|
CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID || '',
|
||||||
NETWORK: process.env.NEXT_PUBLIC_NETWORK || '',
|
NETWORK: process.env.NEXT_PUBLIC_NETWORK || '',
|
||||||
URL_GQL: process.env.NEXT_PUBLIC_GQL || '',
|
URL_GQL: process.env.NEXT_PUBLIC_GQL || '',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export const PREFERRED_ASSET_KEY = 'favouriteAsset'
|
export const PREFERRED_ASSET_KEY = 'favouriteAsset'
|
||||||
export const DISPLAY_CURRENCY_KEY = 'displayCurrency'
|
export const DISPLAY_CURRENCY_KEY = 'displayCurrency'
|
||||||
export const REDUCE_MOTION_KEY = 'reduceMotion'
|
export const REDUCE_MOTION_KEY = 'reduceMotion'
|
||||||
export const FAVORITE_ASSETS = 'favoriteAssets'
|
export const FAVORITE_ASSETS_KEY = 'favoriteAssets'
|
||||||
export const LEND_ASSETS_KEY = 'lendAssets'
|
export const LEND_ASSETS_KEY = 'lendAssets'
|
||||||
export const AUTO_LEND_ENABLED_ACCOUNT_IDS_KEY = 'autoLendEnabledAccountIds'
|
export const AUTO_LEND_ENABLED_ACCOUNT_IDS_KEY = 'autoLendEnabledAccountIds'
|
||||||
export const SLIPPAGE_KEY = 'slippage'
|
export const SLIPPAGE_KEY = 'slippage'
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { ASSETS } from 'constants/assets'
|
import { ASSETS } from 'constants/assets'
|
||||||
import { FAVORITE_ASSETS } from 'constants/localStore'
|
import { FAVORITE_ASSETS_KEY } from 'constants/localStore'
|
||||||
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
|
||||||
|
import useLocalStorage from './useLocalStorage'
|
||||||
|
|
||||||
export default function useAssets() {
|
export default function useAssets() {
|
||||||
const [assets, setAssets] = useState<Asset[]>(ASSETS)
|
const [assets, setAssets] = useState<Asset[]>(ASSETS)
|
||||||
|
const [favoriteAssetsDenoms] = useLocalStorage<string[]>(FAVORITE_ASSETS_KEY, [])
|
||||||
|
|
||||||
const getFavoriteAssets = useCallback(() => {
|
const getFavoriteAssets = useCallback(() => {
|
||||||
const favoriteAssets = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
|
const assets = getEnabledMarketAssets()
|
||||||
const assets = ASSETS.map((asset) => ({
|
.map((asset) => ({
|
||||||
...asset,
|
...asset,
|
||||||
isFavorite: favoriteAssets.includes(asset.denom),
|
isFavorite: favoriteAssetsDenoms.includes(asset.denom),
|
||||||
})).sort((a, b) => {
|
}))
|
||||||
if (a.isFavorite && !b.isFavorite) return -1
|
.sort((a, b) => +b.isFavorite - +a.isFavorite)
|
||||||
if (!a.isFavorite && b.isFavorite) return 1
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
|
|
||||||
setAssets(assets)
|
setAssets(assets)
|
||||||
}, [])
|
}, [favoriteAssetsDenoms])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getFavoriteAssets()
|
getFavoriteAssets()
|
||||||
|
@ -1,12 +1,24 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
import OrderBook from 'components/Trade/OrderBook'
|
import OrderBook from 'components/Trade/OrderBook'
|
||||||
|
import TradeChart from 'components/Trade/TradeChart'
|
||||||
import TradeModule from 'components/Trade/TradeModule'
|
import TradeModule from 'components/Trade/TradeModule'
|
||||||
import TradingView from 'components/Trade/TradingView'
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
|
||||||
export default function TradePage() {
|
export default function TradePage() {
|
||||||
|
const enabledMarketAssets = getEnabledMarketAssets()
|
||||||
|
const [buyAsset, setBuyAsset] = useState(enabledMarketAssets[0])
|
||||||
|
const [sellAsset, setSellAsset] = useState(enabledMarketAssets[1])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
|
<div className='grid h-full w-full grid-cols-[346px_auto] gap-4'>
|
||||||
<TradeModule />
|
<TradeModule
|
||||||
<TradingView />
|
buyAsset={buyAsset}
|
||||||
|
sellAsset={sellAsset}
|
||||||
|
onChangeBuyAsset={setBuyAsset}
|
||||||
|
onChangeSellAsset={setSellAsset}
|
||||||
|
/>
|
||||||
|
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
|
||||||
<OrderBook />
|
<OrderBook />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user