Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/components/SearchDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
</template>
<template v-else>
<i class="mr4" :class="`icon-${market.exchange}`"></i>
{{ market.pair }}
{{ market.displayPair || market.pair }}
</template>
</button>
</template>
Expand Down Expand Up @@ -332,7 +332,7 @@
:class="'icon-' + market.exchange"
></td>
<td v-text="market.exchange"></td>
<td v-text="market.pair"></td>
<td v-text="market.displayPair || market.pair"></td>
<td v-text="market.type"></td>
<td class="text-center">
<i
Expand Down Expand Up @@ -408,6 +408,7 @@ import {
getExchangeSymbols,
ensureIndexedProducts,
parseMarket,
productMatchesSearchQuery,
stripStableQuote
} from '@/services/productsService'
import ToggableSection from '@/components/framework/ToggableSection.vue'
Expand Down Expand Up @@ -662,7 +663,7 @@ export default {
.filter(
product =>
selection.indexOf(product.id) === -1 &&
queryFilter.test(product.local)
productMatchesSearchQuery(product, queryFilter)
)
.reduce((groups, product) => {
let localPair
Expand Down Expand Up @@ -711,7 +712,8 @@ export default {
} else {
return this.filteredProducts.filter(
product =>
selection.indexOf(product.id) === -1 && queryFilter.test(product.id)
selection.indexOf(product.id) === -1 &&
productMatchesSearchQuery(product, queryFilter)
)
}
},
Expand Down
7 changes: 6 additions & 1 deletion src/components/chart/MarketsOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
@click.stop.prevent
/>
<div></div>
<span>{{ market }}</span>
<span>{{ formatMarket(market) }}</span>
</label>
</div>
</div>
Expand All @@ -57,6 +57,7 @@
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { formatMarketForDisplay } from '@/services/productsService'

@Component({
name: 'MarketsOverlay',
Expand Down Expand Up @@ -103,6 +104,10 @@ export default class MarketsOverlay extends Vue {
toggleMarkets(type) {
this.$store.dispatch(this.paneId + '/toggleMarkets', { type })
}

formatMarket(marketId: string) {
return formatMarketForDisplay(marketId)
}
}
</script>

Expand Down
4 changes: 3 additions & 1 deletion src/components/settings/Exchange.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
<div class="form-group" v-if="markets.length">
<div>
<div v-for="market in markets" :key="market.id" class="d-flex">
<div class="-fill -center">{{ market.pair }}</div>
<div class="-fill -center">
{{ market.displayPair || market.pair }}
</div>
</div>
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion src/components/trades/TradesSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,11 @@ export default class TradesSettings extends Vue {
const multiplier = (this.$store.state[this.paneId] as TradesPaneState)
.multipliers[marketKey]

const product = this.$store.state.panes.marketsListeners[marketKey]

return {
exchange,
pair,
pair: product?.displayPair || pair,
multiplier: !isNaN(multiplier) ? multiplier : 1,
identifier: marketKey
}
Expand Down
14 changes: 9 additions & 5 deletions src/components/trades/tradesFeed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import audioService, { AudioFunction } from '@/services/audioService'
import gifsService from '@/services/gifsService'
import { formatAmount, formatMarketPrice } from '@/services/productsService'
import {
formatAmount,
formatMarketPrice,
getMarketDisplayPair
} from '@/services/productsService'
import store from '@/store'
import { SlippageMode, Trade } from '@/types/types'
import {
Expand Down Expand Up @@ -301,10 +305,10 @@ export default class TradesFeed {
let pairName = ''

if (this.showPairs) {
pairName = `<div class="trade__pair">${trade.pair.replace(
'_',
' '
)}</div>`
pairName = `<div class="trade__pair">${getMarketDisplayPair(
trade.exchange,
trade.pair
).replace('_', ' ')}</div>`
}

return `<li class="trade -${trade.exchange} -${trade.side} -level-${
Expand Down
112 changes: 107 additions & 5 deletions src/services/productsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const REVERSE_MATCH_REGEX = /(\w+)[^a-z0-9]/i
const COMMON_FUTURES_SUFFIX_REGEX = /[HUZ_-]\d{2}/
const PARSE_MARKET_REGEX = /([^:]*):(.*)/
const BITUNIX_PERP_REGEX = /[A-Z]/
const HYPERLIQUID_SPOT_REGEX = /^@|\//

const stablecoins = [
'USDT',
Expand Down Expand Up @@ -83,6 +84,8 @@ const promisesOfProducts = {}

export const indexedProducts = {}

const hyperliquidSpotPairLabels: { [pair: string]: string } = {}

export const marketDecimals = {}

/**
Expand Down Expand Up @@ -291,6 +294,14 @@ export async function getExchangeSymbols(
const data = await requestExchangeProductsData(exchangeId, forceFetch)

if (data) {
if (
exchangeId === 'HYPERLIQUID' &&
!Array.isArray(data) &&
data.spotPairLabels
) {
Object.assign(hyperliquidSpotPairLabels, data.spotPairLabels)
}

if (Array.isArray(data)) {
symbols = data
} else {
Expand All @@ -309,6 +320,62 @@ export function parseMarket(market: string) {
return market.match(PARSE_MARKET_REGEX).slice(1, 3)
}

function finalizeProduct(product) {
if (product.exchange === 'HYPERLIQUID' && product.type === 'spot') {
const label = hyperliquidSpotPairLabels[product.pair]

if (label) {
product.displayPair = label
} else if (product.pair.includes('/')) {
product.displayPair = product.pair
} else if (product.base && product.quote) {
product.displayPair = `${product.base}/${product.quote}`
}
}

return product
}

export function getMarketDisplayPair(
exchangeId: string,
pair: string,
product?: { displayPair?: string; type?: string }
) {
if (product?.displayPair) {
return product.displayPair
}

if (exchangeId === 'HYPERLIQUID') {
const label = hyperliquidSpotPairLabels[pair]

if (label) {
return label
}
}

return pair
}

export function formatMarketForDisplay(marketId: string) {
const [exchange, pair] = parseMarket(marketId)

if (!exchange || !pair) {
return marketId
}

return `${exchange}:${getMarketDisplayPair(exchange, pair)}`
}

export function productMatchesSearchQuery(product, queryFilter: RegExp) {
return (
queryFilter.test(product.id) ||
queryFilter.test(product.local) ||
queryFilter.test(product.base) ||
queryFilter.test(product.quote) ||
(product.displayPair && queryFilter.test(product.displayPair))
)
}

export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
const id = exchangeId + ':' + symbol

Expand All @@ -319,10 +386,13 @@ export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
} else if (
exchangeId === 'BINANCE_FUTURES' ||
exchangeId === 'DYDX' ||
exchangeId === 'HYPERLIQUID' ||
exchangeId === 'ASTER'
) {
type = 'perp'
} else if (exchangeId === 'MEXC' && UNDERSCORE_REGEX.test(symbol)) {
type = 'perp'
} else if (exchangeId === 'HYPERLIQUID') {
type = HYPERLIQUID_SPOT_REGEX.test(symbol) ? 'spot' : 'perp'
} else if (exchangeId === 'COINBASE' && COINBASE_INTX_REGEX.test(symbol)) {
type = 'perp'
} else if (exchangeId === 'BITFINEX' && BITFINEX_PERP_REGEX.test(symbol)) {
Expand All @@ -331,8 +401,6 @@ export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
type = 'future'
} else if (exchangeId === 'BITMART' && !UNDERSCORE_REGEX.test(symbol)) {
type = 'perp'
} else if (exchangeId === 'MEXC' && UNDERSCORE_REGEX.test(symbol)) {
type = 'perp'
} else if (exchangeId === 'HUOBI' && DASH_REGEX.test(symbol)) {
type = 'perp'
} else if (
Expand Down Expand Up @@ -384,6 +452,10 @@ export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
localSymbol = localSymbol.replace(KUCOIN_SUFFIX_REGEX, '')
} else if (exchangeId === 'COINBASE' && type === 'perp') {
localSymbol = localSymbol.replace(COINBASE_INTX_REGEX, '')
} else if (exchangeId === 'HYPERLIQUID' && type === 'spot') {
if (hyperliquidSpotPairLabels[symbol]) {
localSymbol = hyperliquidSpotPairLabels[symbol]
}
} else if (exchangeId === 'HYPERLIQUID') {
localSymbol = localSymbol.replace(/^k/, '') + 'USD'
} else if (exchangeId === 'PHEMEX') {
Expand Down Expand Up @@ -425,6 +497,36 @@ export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
}
}
}
if (!match && exchangeId === 'HYPERLIQUID' && type === 'spot') {
const label = hyperliquidSpotPairLabels[symbol]

if (label && label.includes('/')) {
const [base, quote] = label.split('/')

return finalizeProduct({
id,
base,
quote,
pair: symbol,
local: noStable ? stripStablePair(base + quote) : base + quote,
exchange: exchangeId,
type
})
}

if (HYPERLIQUID_SPOT_REGEX.test(symbol)) {
return finalizeProduct({
id,
base: symbol,
quote: 'USDC',
pair: symbol,
local: symbol,
exchange: exchangeId,
type
})
}
}

if (!match) {
return null
}
Expand All @@ -446,15 +548,15 @@ export function getMarketProduct(exchangeId, symbol, noStable?: boolean) {
localSymbolAlpha = base + quote
}

return {
return finalizeProduct({
id,
base,
quote,
pair: symbol,
local: localSymbolAlpha,
exchange: exchangeId,
type
}
})
}

export async function getApiSupportedMarkets() {
Expand Down
1 change: 1 addition & 0 deletions src/store/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Product {
base: string
quote: string
local: string
displayPair?: string
}

export interface ListenedProduct extends Product {
Expand Down
Loading