* feat: generate new nx application * feat: add env variables & render a headline * feat: add cypress projectId and delete unused files * feat: render LP grid * feat: create liquidity provision lib * feat: liquidity provision calculate volume * feat: add volume change, generate types * feat: add EnvironmentProvider * feat: add LP health bar * feat: liquidity provision health * feat: liquidity provider dashboard healthbars * feat: liquidity provider dashnoard - add auction trigger * feat: liquidity provider dashboard - display multiple fees * feat: liquidity provision provider refactor * feat: liquidity provision provider refactor * feat: liquidity provision dashboard - add router * feat: liquidity provision open details in new window * feat: liquidity provision details page * feat: liquidity provision details volume * feat: liquidity provision formatting * feat: move market candle providers * feat: liquidity provision styles * feat: add liquidity provision status * feat: liquidity provision details * feat: liquidity move colors * feat: liquidty provision details * feat: fix merge * Feat/lp health bar redesign (#1903) * feat: liquidity provision details page * feat: liquidity provision details * feat: liquidity health bar redesign * feat: fix merge * feat: health bar redesign * feat: add vega colors
This commit is contained in:
parent
5cf6c11d36
commit
0d69ffb4a8
@ -1,15 +1,27 @@
|
|||||||
|
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useRoutes } from 'react-router-dom';
|
||||||
|
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||||
|
import { createClient } from './lib/apollo-client';
|
||||||
|
|
||||||
import '../styles.scss';
|
import '../styles.scss';
|
||||||
import { Header } from './components/header';
|
import { Navbar } from './components/navbar';
|
||||||
import { Intro } from './components/intro';
|
|
||||||
import { MarketList } from './components/market-list';
|
import { routerConfig } from './routes/router-config';
|
||||||
|
|
||||||
|
const AppRouter = () => useRoutes(routerConfig);
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
|
<EnvironmentProvider>
|
||||||
|
<ThemeContext.Provider value="light">
|
||||||
|
<NetworkLoader createClient={createClient}>
|
||||||
<div className="max-h-full min-h-full bg-white">
|
<div className="max-h-full min-h-full bg-white">
|
||||||
<Header />
|
<Navbar />
|
||||||
<Intro />
|
<AppRouter />
|
||||||
<MarketList />
|
|
||||||
</div>
|
</div>
|
||||||
|
</NetworkLoader>
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
</EnvironmentProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import { Intro } from './intro';
|
||||||
|
import { MarketList } from './market-list';
|
||||||
|
|
||||||
|
export function Dashboard() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="px-16 pt-20 pb-12 bg-greys-light-100">
|
||||||
|
<div className="max-w-screen-xl mx-auto">
|
||||||
|
<h1 className="font-alpha uppercase text-5xl mb-8">
|
||||||
|
{t('Top liquidity opportunities')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Intro />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-16 py-6">
|
||||||
|
<div className="max-w-screen-xl mx-auto">
|
||||||
|
<MarketList />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './dashboard';
|
@ -5,19 +5,19 @@ import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
|||||||
const LINKS = {
|
const LINKS = {
|
||||||
testnet: [
|
testnet: [
|
||||||
{
|
{
|
||||||
label: 'Understand how liquidity fees are calculated',
|
label: 'Learn about liquidity fees',
|
||||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#resources',
|
url: 'https://docs.vega.xyz/docs/testnet/tutorials/providing-liquidity#resources',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'How to provide liquidity',
|
label: 'Provide liquidity',
|
||||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#overview',
|
url: 'https://docs.vega.xyz/docs/testnet/tutorials/providing-liquidity#overview',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'How to view existing liquidity provisions',
|
label: 'View your liquidity provisions',
|
||||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#viewing-existing-liquidity-provisions',
|
url: 'https://docs.vega.xyz/docs/testnet/tutorials/providing-liquidity#viewing-existing-liquidity-provisions',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'How to amend or remove liquidity',
|
label: 'Amend or remove liquidity',
|
||||||
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#amending-a-liquidity-commitment',
|
url: 'https://docs.vega.xyz/testnet/tutorials/providing-liquidity#amending-a-liquidity-commitment',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -29,12 +29,11 @@ type Network = 'testnet' | 'mainnet';
|
|||||||
|
|
||||||
export const Intro = ({ network = 'testnet' }: { network?: Network }) => {
|
export const Intro = ({ network = 'testnet' }: { network?: Network }) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx-6 my-6 px-6 py-6 bg-neutral-100" data-testid="intro">
|
<div>
|
||||||
<h2 className="text-xl font-medium mb-1">
|
<p className="font-alpha text-2xl font-medium mb-2">
|
||||||
{t('Become a liquidity provider')}
|
{t(
|
||||||
</h2>
|
'Become a liquidity provider and earn a cut of the fees paid during trading.'
|
||||||
<p className="text-base mb-2">
|
)}
|
||||||
{t('Earn a cut of the fees paid by price takers during trading.')}
|
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<ul className="flex flex-wrap">
|
<ul className="flex flex-wrap">
|
@ -0,0 +1,171 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
|
import type {
|
||||||
|
ValueFormatterParams,
|
||||||
|
GetRowIdParams,
|
||||||
|
RowClickedEvent,
|
||||||
|
} from 'ag-grid-community';
|
||||||
|
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||||
|
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||||
|
import { t, addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Icon, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { Market } from '@vegaprotocol/liquidity';
|
||||||
|
import {
|
||||||
|
useMarketsLiquidity,
|
||||||
|
formatWithAsset,
|
||||||
|
displayChange,
|
||||||
|
} from '@vegaprotocol/liquidity';
|
||||||
|
import type { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { HealthBar } from '../../health-bar';
|
||||||
|
import { Grid } from '../../grid';
|
||||||
|
import { HealthDialog } from '../../health-dialog';
|
||||||
|
import { Status } from '../../status';
|
||||||
|
|
||||||
|
export const MarketList = () => {
|
||||||
|
const { data, error, loading } = useMarketsLiquidity();
|
||||||
|
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
|
||||||
|
|
||||||
|
const localData = data?.markets;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncRenderer loading={loading} error={error} data={localData}>
|
||||||
|
<div
|
||||||
|
className="grow w-full"
|
||||||
|
style={{ minHeight: 500, overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
gridOptions={{
|
||||||
|
onRowClicked: ({ data }: RowClickedEvent) => {
|
||||||
|
window.open(
|
||||||
|
`/markets/${data.id}`,
|
||||||
|
'_blank',
|
||||||
|
'noopener,noreferrer'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rowData={localData}
|
||||||
|
defaultColDef={{
|
||||||
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
unSortIcon: true,
|
||||||
|
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||||
|
}}
|
||||||
|
getRowId={getRowId}
|
||||||
|
isRowClickable
|
||||||
|
>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Market (futures)')}
|
||||||
|
field="tradableInstrument.instrument.name"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
data: Market;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="leading-3">{value}</span>
|
||||||
|
<span className="leading-3">
|
||||||
|
{
|
||||||
|
data?.tradableInstrument?.instrument?.product
|
||||||
|
?.settlementAsset?.symbol
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
minWidth={100}
|
||||||
|
flex="1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Volume (24h)')}
|
||||||
|
field="dayVolume"
|
||||||
|
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||||
|
`${addDecimalsFormatNumber(
|
||||||
|
value,
|
||||||
|
data.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.decimals
|
||||||
|
)} (${displayChange(data.volumeChange)})`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Committed bond/stake')}
|
||||||
|
field="liquidityCommitted"
|
||||||
|
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||||
|
formatWithAsset(
|
||||||
|
value,
|
||||||
|
data.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Status')}
|
||||||
|
field="tradingMode"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
value: MarketTradingMode;
|
||||||
|
data: Market;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Status trigger={data.data?.trigger} tradingMode={value} />
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AgGridColumn
|
||||||
|
headerComponent={() => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{t('Health')}</span>{' '}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsHealthDialogOpen(true)}
|
||||||
|
aria-label={t('open tooltip')}
|
||||||
|
>
|
||||||
|
<Icon name="info-sign" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
field="tradingMode"
|
||||||
|
cellRenderer={({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
value: MarketTradingMode;
|
||||||
|
data: Market;
|
||||||
|
}) => (
|
||||||
|
<HealthBar
|
||||||
|
status={value}
|
||||||
|
target={data.target}
|
||||||
|
decimals={
|
||||||
|
data.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.decimals
|
||||||
|
}
|
||||||
|
levels={data.feeLevels}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
sortable={false}
|
||||||
|
cellStyle={{ overflow: 'unset' }}
|
||||||
|
/>
|
||||||
|
<AgGridColumn headerName={t('Est. return / APY')} field="apy" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<HealthDialog
|
||||||
|
isOpen={isHealthDialogOpen}
|
||||||
|
onChange={() => {
|
||||||
|
setIsHealthDialogOpen(!isHealthDialogOpen);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AsyncRenderer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,107 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
t,
|
||||||
|
useDataProvider,
|
||||||
|
makeDerivedDataProvider,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getFeeLevels,
|
||||||
|
sumLiquidityCommitted,
|
||||||
|
marketLiquidityDataProvider,
|
||||||
|
liquidityProvisionsDataProvider,
|
||||||
|
} from '@vegaprotocol/liquidity';
|
||||||
|
import type { MarketLpQuery } from '@vegaprotocol/liquidity';
|
||||||
|
|
||||||
|
import { Market } from './market';
|
||||||
|
import { Header } from './header';
|
||||||
|
import { LPProvidersGrid } from './providers';
|
||||||
|
|
||||||
|
const formatMarket = (data: MarketLpQuery) => {
|
||||||
|
return {
|
||||||
|
name: data?.market?.tradableInstrument.instrument.name,
|
||||||
|
symbol:
|
||||||
|
data?.market?.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.symbol,
|
||||||
|
settlementAsset:
|
||||||
|
data?.market?.tradableInstrument.instrument.product.settlementAsset,
|
||||||
|
targetStake: data?.market?.data?.targetStake,
|
||||||
|
tradingMode: data?.market?.data?.marketTradingMode,
|
||||||
|
trigger: data?.market?.data?.trigger,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lpDataProvider = makeDerivedDataProvider(
|
||||||
|
[marketLiquidityDataProvider, liquidityProvisionsDataProvider],
|
||||||
|
([market, providers]) => ({
|
||||||
|
market: { ...formatMarket(market) },
|
||||||
|
liquidityProviders: providers || [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const useMarketDetails = (marketId: string | undefined) => {
|
||||||
|
const { data, loading, error } = useDataProvider({
|
||||||
|
dataProvider: lpDataProvider,
|
||||||
|
noUpdate: true,
|
||||||
|
variables: useMemo(() => ({ marketId }), [marketId]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const liquidityProviders = data?.liquidityProviders || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
name: data?.market?.name,
|
||||||
|
symbol: data?.market?.symbol,
|
||||||
|
liquidityProviders: liquidityProviders,
|
||||||
|
feeLevels: getFeeLevels(liquidityProviders),
|
||||||
|
comittedLiquidity: sumLiquidityCommitted(liquidityProviders) || 0,
|
||||||
|
settlementAsset: data?.market?.settlementAsset || {},
|
||||||
|
targetStake: data?.market?.targetStake || '0',
|
||||||
|
tradingMode: data?.market.tradingMode,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
loading: loading,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Detail = () => {
|
||||||
|
const { marketId } = useParams<{ marketId: string }>();
|
||||||
|
const { data, loading, error } = useMarketDetails(marketId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||||
|
<div className="px-16 pt-14 pb-12 bg-greys-light-100">
|
||||||
|
<div className="max-w-screen-xl mx-auto">
|
||||||
|
<Header name={data.name} symbol={data.symbol} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-16">
|
||||||
|
<div className="max-w-screen-xl mx-auto">
|
||||||
|
<div className="py-12">
|
||||||
|
{marketId && (
|
||||||
|
<Market
|
||||||
|
marketId={marketId}
|
||||||
|
feeLevels={data.feeLevels}
|
||||||
|
comittedLiquidity={data.comittedLiquidity}
|
||||||
|
settlementAsset={data.settlementAsset}
|
||||||
|
targetStake={data.targetStake}
|
||||||
|
tradingMode={data.tradingMode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="font-alpha text-2xl mb-4">
|
||||||
|
{t('Current Liquidity Provision')}
|
||||||
|
</h2>
|
||||||
|
<LPProvidersGrid
|
||||||
|
liquidityProviders={data.liquidityProviders}
|
||||||
|
settlementAsset={data.settlementAsset}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AsyncRenderer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
export const Header = ({
|
||||||
|
name,
|
||||||
|
symbol,
|
||||||
|
}: {
|
||||||
|
name?: string;
|
||||||
|
symbol?: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-6">
|
||||||
|
<Link to="/">
|
||||||
|
<Icon name="chevron-left" className="mr-2" />
|
||||||
|
<span className="underline font-alpha text-lg font-medium">
|
||||||
|
{t('Liquidity opportunities')}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<h1 className="font-alpha text-5xl mb-6">{name}</h1>
|
||||||
|
<p className="font-alpha text-4xl">{symbol}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './detail';
|
@ -0,0 +1 @@
|
|||||||
|
export * from './last-24h-volume';
|
@ -0,0 +1,110 @@
|
|||||||
|
import { useState, useMemo, useRef, useCallback } from 'react';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import {
|
||||||
|
useYesterday,
|
||||||
|
useDataProvider,
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
import { Interval } from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
calcDayVolume,
|
||||||
|
getChange,
|
||||||
|
displayChange,
|
||||||
|
} from '@vegaprotocol/liquidity';
|
||||||
|
|
||||||
|
import type { Candle } from '@vegaprotocol/market-list';
|
||||||
|
import { marketCandlesProvider } from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
|
const DEBOUNCE_UPDATE_TIME = 500;
|
||||||
|
|
||||||
|
export const Last24hVolume = ({
|
||||||
|
marketId,
|
||||||
|
decimals,
|
||||||
|
}: {
|
||||||
|
marketId: string;
|
||||||
|
decimals: number;
|
||||||
|
}) => {
|
||||||
|
const [candleVolume, setCandleVolume] = useState<string>();
|
||||||
|
const [volumeChange, setVolumeChange] = useState<string>(' - ');
|
||||||
|
|
||||||
|
const yesterday = useYesterday();
|
||||||
|
|
||||||
|
const yTimestamp = useMemo(() => {
|
||||||
|
return new Date(yesterday).toISOString();
|
||||||
|
}, [yesterday]);
|
||||||
|
|
||||||
|
const variables = useMemo(
|
||||||
|
() => ({
|
||||||
|
marketId: marketId,
|
||||||
|
interval: Interval.INTERVAL_I1H,
|
||||||
|
since: yTimestamp,
|
||||||
|
}),
|
||||||
|
[marketId, yTimestamp]
|
||||||
|
);
|
||||||
|
|
||||||
|
const variables24hAgo = useMemo(
|
||||||
|
() => ({
|
||||||
|
marketId: marketId,
|
||||||
|
interval: Interval.INTERVAL_I1D,
|
||||||
|
since: yTimestamp,
|
||||||
|
}),
|
||||||
|
[marketId, yTimestamp]
|
||||||
|
);
|
||||||
|
|
||||||
|
const throttledSetCandles = useRef(
|
||||||
|
throttle((data: Candle[]) => {
|
||||||
|
setCandleVolume(calcDayVolume(data));
|
||||||
|
}, DEBOUNCE_UPDATE_TIME)
|
||||||
|
).current;
|
||||||
|
|
||||||
|
const update = useCallback(
|
||||||
|
({ data }: { data: Candle[] }) => {
|
||||||
|
throttledSetCandles(data);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[throttledSetCandles]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error } = useDataProvider<Candle[], Candle>({
|
||||||
|
dataProvider: marketCandlesProvider,
|
||||||
|
variables: variables,
|
||||||
|
update,
|
||||||
|
skip: !marketId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const throttledSetVolumeChange = useRef(
|
||||||
|
throttle((candles: Candle[]) => {
|
||||||
|
const candle24hAgo = candles?.[0];
|
||||||
|
setVolumeChange(getChange(data || [], candle24hAgo?.close));
|
||||||
|
}, DEBOUNCE_UPDATE_TIME)
|
||||||
|
).current;
|
||||||
|
|
||||||
|
const updateCandle24hAgo = useCallback(
|
||||||
|
({ data }: { data: Candle[] }) => {
|
||||||
|
throttledSetVolumeChange(data);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[throttledSetVolumeChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
useDataProvider<Candle[], Candle>({
|
||||||
|
dataProvider: marketCandlesProvider,
|
||||||
|
update: updateCandle24hAgo,
|
||||||
|
variables: variables24hAgo,
|
||||||
|
skip: !marketId || !data,
|
||||||
|
updateOnInit: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span className="text-3xl">
|
||||||
|
{!error && candleVolume
|
||||||
|
? addDecimalsFormatNumber(candleVolume, decimals)
|
||||||
|
: '0'}{' '}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg text-greys-light-400">
|
||||||
|
({displayChange(volumeChange)})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './market';
|
@ -0,0 +1,118 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { formatWithAsset } from '@vegaprotocol/liquidity';
|
||||||
|
|
||||||
|
import type { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
|
||||||
|
import { HealthBar } from '../../health-bar';
|
||||||
|
import { HealthDialog } from '../../health-dialog';
|
||||||
|
import { Last24hVolume } from '../last-24h-volume';
|
||||||
|
import { Status } from '../../status';
|
||||||
|
|
||||||
|
interface Levels {
|
||||||
|
fee: string;
|
||||||
|
commitmentAmount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface settlementAsset {
|
||||||
|
symbol?: string;
|
||||||
|
decimals?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Market = ({
|
||||||
|
marketId,
|
||||||
|
feeLevels,
|
||||||
|
comittedLiquidity,
|
||||||
|
settlementAsset,
|
||||||
|
targetStake,
|
||||||
|
tradingMode,
|
||||||
|
trigger,
|
||||||
|
}: {
|
||||||
|
marketId: string;
|
||||||
|
feeLevels: Levels[];
|
||||||
|
comittedLiquidity: number;
|
||||||
|
targetStake: string;
|
||||||
|
settlementAsset?: settlementAsset;
|
||||||
|
tradingMode?: MarketTradingMode;
|
||||||
|
trigger?: AuctionTrigger;
|
||||||
|
}) => {
|
||||||
|
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="border border-greys-light-200 rounded-2xl px-2 py-6">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr
|
||||||
|
className="text-sm text-greys-light-400 text-left font-alpha"
|
||||||
|
style={{ fontFeatureSettings: "'liga' off, 'calt' off" }}
|
||||||
|
>
|
||||||
|
<th className="font-medium px-4">{t('Volume (24h)')}</th>
|
||||||
|
<th className="font-medium px-4">{t('Commited Liquidity')}</th>
|
||||||
|
<th className="font-medium px-4">{t('Status')}</th>
|
||||||
|
<th className="font-medium flex items-center px-4">
|
||||||
|
<span>{t('Health')}</span>{' '}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsHealthDialogOpen(true)}
|
||||||
|
aria-label={t('open tooltip')}
|
||||||
|
className="flex ml-1"
|
||||||
|
>
|
||||||
|
<Icon name="info-sign" />
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<th className="font-medium">{t('Est. APY')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4">
|
||||||
|
<div>
|
||||||
|
{marketId && settlementAsset?.decimals && (
|
||||||
|
<Last24hVolume
|
||||||
|
marketId={marketId}
|
||||||
|
decimals={settlementAsset.decimals}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4">
|
||||||
|
<span className="text-3xl">
|
||||||
|
{comittedLiquidity && settlementAsset
|
||||||
|
? formatWithAsset(`${comittedLiquidity}`, settlementAsset)
|
||||||
|
: '0'}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4">
|
||||||
|
<Status
|
||||||
|
trigger={trigger}
|
||||||
|
tradingMode={tradingMode}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="px-4">
|
||||||
|
{tradingMode && settlementAsset?.decimals && feeLevels && (
|
||||||
|
<HealthBar
|
||||||
|
status={tradingMode}
|
||||||
|
target={targetStake}
|
||||||
|
decimals={settlementAsset.decimals}
|
||||||
|
levels={feeLevels}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-4">
|
||||||
|
<span className="text-3xl"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HealthDialog
|
||||||
|
isOpen={isHealthDialogOpen}
|
||||||
|
onChange={() => {
|
||||||
|
setIsHealthDialogOpen(!isHealthDialogOpen);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './providers';
|
@ -0,0 +1,77 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
import { AgGridColumn } from 'ag-grid-react';
|
||||||
|
|
||||||
|
import type { GetRowIdParams } from 'ag-grid-community';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import type { LiquidityProvisionFieldsFragment } from '@vegaprotocol/liquidity';
|
||||||
|
import { formatWithAsset } from '@vegaprotocol/liquidity';
|
||||||
|
|
||||||
|
import { Grid } from '../../grid';
|
||||||
|
|
||||||
|
const formatToHours = ({ value }: { value?: string | null }) => {
|
||||||
|
if (!value) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
const MS_IN_HOUR = 1000 * 60 * 60;
|
||||||
|
const created = new Date(value).getTime();
|
||||||
|
const now = new Date().getTime();
|
||||||
|
return `${Math.round(Math.abs(now - created) / MS_IN_HOUR)}h`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LPProvidersGrid = ({
|
||||||
|
liquidityProviders,
|
||||||
|
settlementAsset,
|
||||||
|
}: {
|
||||||
|
liquidityProviders: LiquidityProvisionFieldsFragment[];
|
||||||
|
settlementAsset: {
|
||||||
|
decimals?: number;
|
||||||
|
symbol?: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const getRowId = useCallback(({ data }: GetRowIdParams) => data.party.id, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
rowData={liquidityProviders}
|
||||||
|
defaultColDef={{
|
||||||
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
unSortIcon: true,
|
||||||
|
cellClass: ['flex', 'flex-col', 'justify-center'],
|
||||||
|
}}
|
||||||
|
getRowId={getRowId}
|
||||||
|
rowHeight={92}
|
||||||
|
>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('LPs')}
|
||||||
|
field="party.id"
|
||||||
|
flex="1"
|
||||||
|
minWidth={100}
|
||||||
|
/>
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Time in market')}
|
||||||
|
valueFormatter={formatToHours}
|
||||||
|
field="createdAt"
|
||||||
|
/>
|
||||||
|
<AgGridColumn headerName={t('Galps')} field="Galps" />
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('committed bond/stake')}
|
||||||
|
field="commitmentAmount"
|
||||||
|
valueFormatter={({ value }: { value?: string | null }) =>
|
||||||
|
value ? formatWithAsset(value, settlementAsset) : '0'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<AgGridColumn headerName={t('Margin Req.')} field="margin" />
|
||||||
|
<AgGridColumn headerName={t('24h Fees')} field="fees" />
|
||||||
|
<AgGridColumn
|
||||||
|
headerName={t('Fee level')}
|
||||||
|
valueFormatter={({ value }: { value?: string | null }) => `${value}%`}
|
||||||
|
field="fee"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AgGridColumn headerName={t('APY')} field="apy" />
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
.ag-theme-alpine {
|
||||||
|
--ag-line-height: 24px;
|
||||||
|
--ag-row-hover-color: transparent;
|
||||||
|
--ag-header-background-color: transparent;
|
||||||
|
--ag-odd-row-background-color: transparent;
|
||||||
|
--ag-header-foreground-color: #626262;
|
||||||
|
--ag-secondary-foreground-color: #626262;
|
||||||
|
--ag-font-size: 16px;
|
||||||
|
--ag-background-color: transparent;
|
||||||
|
--ag-range-selection-border-color: transparent;
|
||||||
|
|
||||||
|
font-family: AlphaLyrae, Helvetica Neue, -apple-system, BlinkMacSystemFont,
|
||||||
|
Segoe UI, Roboto, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||||
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
font-feature-settings: 'liga' off, 'calt' off;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-cell {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-header {
|
||||||
|
border-bottom: 1px solid #a7a7a7;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-root-wrapper {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-header-row {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-row {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #bfccd6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine .ag-root-wrapper-body.ag-layout-normal {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ag-theme-alpine.row-hover .ag-row:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useRef, useCallback, useEffect } from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { AgGridReact } from 'ag-grid-react';
|
||||||
|
import type {
|
||||||
|
AgGridReactProps,
|
||||||
|
AgReactUiProps,
|
||||||
|
AgGridReact as AgGridReactType,
|
||||||
|
} from 'ag-grid-react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import 'ag-grid-community/dist/styles/ag-grid.css';
|
||||||
|
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
||||||
|
|
||||||
|
import './grid.scss';
|
||||||
|
|
||||||
|
type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||||
|
isRowClickable?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||||
|
const gridRef = useRef<AgGridReactType | null>(null);
|
||||||
|
|
||||||
|
const resizeGrid = useCallback(() => {
|
||||||
|
gridRef.current?.api?.sizeColumnsToFit();
|
||||||
|
}, [gridRef]);
|
||||||
|
|
||||||
|
const handleOnGridReady = useCallback(() => {
|
||||||
|
resizeGrid();
|
||||||
|
}, [resizeGrid]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', resizeGrid);
|
||||||
|
return () => window.removeEventListener('resize', resizeGrid);
|
||||||
|
}, [resizeGrid]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AgGridReact
|
||||||
|
className={classNames('ag-theme-alpine h-full font-alpha', {
|
||||||
|
'row-hover': isRowClickable,
|
||||||
|
})}
|
||||||
|
rowHeight={92}
|
||||||
|
ref={gridRef}
|
||||||
|
onGridReady={handleOnGridReady}
|
||||||
|
suppressRowClickSelection
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AgGridReact>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './grid';
|
@ -1,12 +0,0 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
export const Header = () => {
|
|
||||||
return (
|
|
||||||
<div className="flex items-stretch px-6 py-6" data-testid="header">
|
|
||||||
<h1 className="text-3xl">{t('Top liquidity opportunities')}</h1>
|
|
||||||
<div className="flex items-center gap-2 ml-auto relative z-10">
|
|
||||||
{t('Network switcher')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,18 +1,19 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
import type { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import { t, addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
import { t, addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
const marketTradingModeStyle = {
|
import { getColorForStatus } from '../../lib/utils';
|
||||||
[MarketTradingMode.TRADING_MODE_CONTINUOUS]: '#00a88a',
|
|
||||||
[MarketTradingMode.TRADING_MODE_MONITORING_AUCTION]: '#fb8e7f',
|
|
||||||
[MarketTradingMode.TRADING_MODE_OPENING_AUCTION]: '#68e2e4',
|
|
||||||
[MarketTradingMode.TRADING_MODE_BATCH_AUCTION]: 'batch',
|
|
||||||
[MarketTradingMode.TRADING_MODE_NO_TRADING]: 'none',
|
|
||||||
};
|
|
||||||
|
|
||||||
const COPY_CLASS = 'text-[8px] leading-[1.2em] font-medium';
|
import { Indicator } from '../indicator';
|
||||||
|
|
||||||
|
const Remainder = () => (
|
||||||
|
<div className="bg-greys-light-200 h-[inherit] relative flex-1"></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const COPY_CLASS =
|
||||||
|
'text-sm font-medium whitespace-nowrap text-white font-alpha';
|
||||||
|
|
||||||
const Tooltip = ({
|
const Tooltip = ({
|
||||||
children,
|
children,
|
||||||
@ -24,27 +25,13 @@ const Tooltip = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-0 left-1/2 -translate-x-2/4 -translate-y-[120%] border border-[#bfccd6] py-0.5 px-2 flex-col z-10 bg-white group-hover:flex min-w-[65px]',
|
'absolute top-0 left-1/2 -translate-x-2/4 -translate-y-[80%] p-2 z-10 bg-greys-light-400 group-hover:flex rounded',
|
||||||
{
|
{
|
||||||
flex: isExpanded,
|
flex: isExpanded,
|
||||||
hidden: !isExpanded,
|
hidden: !isExpanded,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
className="absolute w-0 h-0 translate-y-full translate-x-2/4 left-[calc(50% - 8px)] -bottom-px border-4"
|
|
||||||
style={{
|
|
||||||
left: 'calc(50% - 8px)',
|
|
||||||
borderColor: '#bfccd6 transparent transparent transparent',
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
left: 'calc(50% - 8px)',
|
|
||||||
borderColor: 'white transparent transparent transparent',
|
|
||||||
}}
|
|
||||||
className="absolute bottom-0 w-0 h-0 translate-y-full translate-x-2/4 left-[calc(50% - 8px)] border-4"
|
|
||||||
></div>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -62,18 +49,21 @@ const Target = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-0 left-1/2 -translate-x-2/4 px-1.5 group'
|
'absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-1/2 px-1.5 group'
|
||||||
)}
|
)}
|
||||||
style={{ left: `${targetPercent}%` }}
|
style={{ left: `${targetPercent}%` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames('health-target w-0.5 h-8 bg-black', {
|
className={classNames(
|
||||||
'h-[72px]': isLarge,
|
'health-target w-0.5 bg-black group-hover:scale-x-150 group-hover:scale-y-108',
|
||||||
})}
|
{
|
||||||
>
|
'h-6': !isLarge,
|
||||||
|
'h-12': isLarge,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,14 +71,14 @@ const Level = ({
|
|||||||
children,
|
children,
|
||||||
commitmentAmount,
|
commitmentAmount,
|
||||||
total,
|
total,
|
||||||
index,
|
backgroundColor,
|
||||||
status,
|
opacity,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
index: number;
|
|
||||||
status: MarketTradingMode;
|
|
||||||
commitmentAmount: number;
|
commitmentAmount: number;
|
||||||
total: number;
|
total: number;
|
||||||
|
backgroundColor: string;
|
||||||
|
opacity: number;
|
||||||
}) => {
|
}) => {
|
||||||
const width = new BigNumber(commitmentAmount)
|
const width = new BigNumber(commitmentAmount)
|
||||||
.div(total)
|
.div(total)
|
||||||
@ -97,26 +87,26 @@ const Level = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(`relative h-[inherit] w-full group`)}
|
className={classNames(`relative h-[inherit] w-full group min-w-[1px]`)}
|
||||||
style={{
|
style={{
|
||||||
width: `${width}%`,
|
width: `${width}%`,
|
||||||
opacity: 1 - 0.1 * index,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="relative w-full h-[inherit]"
|
className="relative w-full h-[inherit] group-hover:scale-y-150"
|
||||||
style={{
|
style={{
|
||||||
opacity: 1 - 0.1 * index,
|
opacity,
|
||||||
backgroundColor: marketTradingModeStyle[status],
|
backgroundColor,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Full = () => (
|
const Full = () => (
|
||||||
<div className="bg-neutral-100 w-full h-[inherit] absolute bottom-0 left-0"></div>
|
<div className="bg-transparent w-full h-[inherit] absolute bottom-0 left-0"></div>
|
||||||
);
|
);
|
||||||
|
|
||||||
interface Levels {
|
interface Levels {
|
||||||
@ -126,7 +116,7 @@ interface Levels {
|
|||||||
|
|
||||||
export const HealthBar = ({
|
export const HealthBar = ({
|
||||||
status,
|
status,
|
||||||
target,
|
target = '0',
|
||||||
decimals,
|
decimals,
|
||||||
levels,
|
levels,
|
||||||
size = 'small',
|
size = 'small',
|
||||||
@ -151,6 +141,7 @@ export const HealthBar = ({
|
|||||||
targetNumber * 2 >= committedNumber ? targetNumber * 2 : committedNumber;
|
targetNumber * 2 >= committedNumber ? targetNumber * 2 : committedNumber;
|
||||||
const targetPercent = (targetNumber / total) * 100;
|
const targetPercent = (targetNumber / total) * 100;
|
||||||
const isLarge = size === 'large';
|
const isLarge = size === 'large';
|
||||||
|
const backgroundColor = getColorForStatus(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@ -168,36 +159,50 @@ export const HealthBar = ({
|
|||||||
>
|
>
|
||||||
<Full />
|
<Full />
|
||||||
|
|
||||||
<div className="health-bars h-[inherit] flex w-full">
|
<div className="health-bars h-[inherit] flex w-full gap-0.5">
|
||||||
{levels.map((p, index) => {
|
{levels.map((p, index) => {
|
||||||
const { commitmentAmount, fee } = p;
|
const { commitmentAmount, fee } = p;
|
||||||
|
const prevLevel = levels[index - 1]?.commitmentAmount;
|
||||||
|
const opacity = 1 - 0.2 * index;
|
||||||
return (
|
return (
|
||||||
<Level
|
<Level
|
||||||
status={status}
|
|
||||||
commitmentAmount={commitmentAmount}
|
commitmentAmount={commitmentAmount}
|
||||||
index={index}
|
|
||||||
total={total}
|
total={total}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
opacity={opacity}
|
||||||
>
|
>
|
||||||
<Tooltip isExpanded={isExpanded}>
|
<Tooltip isExpanded={isExpanded}>
|
||||||
|
<div className="mt-1.5 inline-flex">
|
||||||
|
<Indicator status={status} opacity={opacity} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
<span className={COPY_CLASS}>
|
<span className={COPY_CLASS}>
|
||||||
{fee}% {t('Fee')}
|
{fee}% {t('Fee')}
|
||||||
</span>
|
</span>
|
||||||
<span className={COPY_CLASS}>
|
<span className={classNames(COPY_CLASS, 'opacity-60')}>
|
||||||
{addDecimalsFormatNumber(commitmentAmount, decimals)}
|
{prevLevel
|
||||||
|
? addDecimalsFormatNumber(prevLevel, decimals)
|
||||||
|
: '0'}{' '}
|
||||||
|
- {addDecimalsFormatNumber(commitmentAmount, decimals)}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Level>
|
</Level>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{(total !== committedNumber || levels.length === 0) && (
|
||||||
|
<Remainder />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Target targetPercent={targetPercent} isLarge={isLarge}>
|
<Target targetPercent={targetPercent} isLarge={isLarge}>
|
||||||
<Tooltip isExpanded={isExpanded}>
|
<Tooltip isExpanded={isExpanded}>
|
||||||
<span className={COPY_CLASS}>{t('Target stake')}</span>
|
<div className="mt-1.5 inline-flex">
|
||||||
|
<Indicator />
|
||||||
|
</div>
|
||||||
<span className={COPY_CLASS}>
|
<span className={COPY_CLASS}>
|
||||||
{addDecimalsFormatNumber(target, decimals)}
|
{t('Target stake')} {addDecimalsFormatNumber(target, decimals)}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Target>
|
</Target>
|
@ -0,0 +1 @@
|
|||||||
|
export * from './health-bar';
|
@ -1,8 +1,9 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { HealthBar } from './health-bar';
|
import { HealthBar } from '../health-bar';
|
||||||
|
|
||||||
interface HealthDialogProps {
|
interface HealthDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -58,36 +59,48 @@ const ROWS = [
|
|||||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||||
return (
|
return (
|
||||||
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
||||||
<h1 className="text-xl mb-4 pr-2 font-bold" data-testid="dialog-title">
|
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase liga-0-calt-0">
|
||||||
{t('Health')}
|
{t('Health')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl mb-4">
|
<p className="text-lg font-medium font-alpha mb-8 liga-0-calt-0">
|
||||||
{t(
|
{t(
|
||||||
'Market health is a representation of market and liquidity status and how close that market is to moving from one fee level to another.'
|
'Market health is a representation of market and liquidity status and how close that market is to moving from one fee level to another.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table className="table-fixed">
|
<table className="table-fixed">
|
||||||
<thead>
|
<thead className="border-b border-greys-light-300">
|
||||||
<th className="w-1/2 text-left">{t('Market status')}</th>
|
<th className="w-1/2 text-left font-medium font-alpha text-base pb-4 uppercase liga-0-calt-0">
|
||||||
<th className="w-1/2 text-left">{t('Liquidity status')}</th>
|
{t('Market status')}
|
||||||
|
</th>
|
||||||
|
<th className="w-1/2 text-lef font-medium font-alpha text-base pb-4 uppercase liga-0-calt-0">
|
||||||
|
{t('Liquidity status')}
|
||||||
|
</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ROWS.map((r) => {
|
{ROWS.map((r, index) => {
|
||||||
|
const isFirstRow = index === 0;
|
||||||
return (
|
return (
|
||||||
<tr key={r.key}>
|
<tr key={r.key}>
|
||||||
<td className="pr-4 py-10">
|
<td
|
||||||
<h2 className="font-bold text-base">{t(r.title)}</h2>
|
className={classNames('pr-4 pb-10', { 'pt-8': isFirstRow })}
|
||||||
<p className="text-base">{t(r.copy)}</p>
|
>
|
||||||
|
<h2 className="font-medium font-alpha uppercase text-base liga-0-calt-0">
|
||||||
|
{t(r.title)}
|
||||||
|
</h2>
|
||||||
|
<p className="font-medium font-alpha text-lg liga-0-calt-0">
|
||||||
|
{t(r.copy)}
|
||||||
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-10">
|
<td
|
||||||
|
className={classNames('pl-4 pb-10', { 'pt-8': isFirstRow })}
|
||||||
|
>
|
||||||
<HealthBar
|
<HealthBar
|
||||||
size="large"
|
size="large"
|
||||||
levels={r.data.levels}
|
levels={r.data.levels}
|
||||||
status={r.data.status}
|
status={r.data.status}
|
||||||
target={r.data.target}
|
target={r.data.target}
|
||||||
decimals={r.data.decimals}
|
decimals={r.data.decimals}
|
||||||
isExpanded
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
@ -0,0 +1 @@
|
|||||||
|
export * from './health-dialog';
|
@ -0,0 +1 @@
|
|||||||
|
export * from './indicator';
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { getColorForStatus } from '../../lib/utils';
|
||||||
|
|
||||||
|
export const Indicator = ({
|
||||||
|
status,
|
||||||
|
opacity,
|
||||||
|
}: {
|
||||||
|
status?: MarketTradingMode;
|
||||||
|
opacity?: number;
|
||||||
|
}) => {
|
||||||
|
const backgroundColor = status ? getColorForStatus(status) : undefined;
|
||||||
|
return (
|
||||||
|
<div className="inline-block w-2 h-2 mr-1 rounded-full bg-white overflow-hidden shrink-0">
|
||||||
|
<div
|
||||||
|
className="h-full bg-black"
|
||||||
|
style={{
|
||||||
|
opacity,
|
||||||
|
backgroundColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,189 +0,0 @@
|
|||||||
import { useCallback, useRef, useEffect, useState } from 'react';
|
|
||||||
import { AgGridReact, AgGridColumn } from 'ag-grid-react';
|
|
||||||
import type { AgGridReact as AgGridReactType } from 'ag-grid-react';
|
|
||||||
import type {
|
|
||||||
GroupCellRendererParams,
|
|
||||||
ValueFormatterParams,
|
|
||||||
GetRowIdParams,
|
|
||||||
} from 'ag-grid-community';
|
|
||||||
import 'ag-grid-community/dist/styles/ag-grid.css';
|
|
||||||
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
|
|
||||||
import { formatNumber, t } from '@vegaprotocol/react-helpers';
|
|
||||||
import { useMarketsLiquidity } from '@vegaprotocol/liquidity';
|
|
||||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import type { Market } from '@vegaprotocol/liquidity';
|
|
||||||
import { formatWithAsset } from '@vegaprotocol/liquidity';
|
|
||||||
import {
|
|
||||||
MarketTradingModeMapping,
|
|
||||||
MarketTradingMode,
|
|
||||||
AuctionTrigger,
|
|
||||||
AuctionTriggerMapping,
|
|
||||||
} from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
import { HealthBar } from './health-bar';
|
|
||||||
import { HealthDialog } from './health-dialog';
|
|
||||||
import './market-list.scss';
|
|
||||||
|
|
||||||
const displayValue = (value: string) => {
|
|
||||||
return parseFloat(value) > 0 ? `+${value}` : value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const marketNameCellRenderer = ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
value: string;
|
|
||||||
data: Market;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span style={{ lineHeight: '12px' }}>{value}</span>
|
|
||||||
<span style={{ lineHeight: '12px' }}>
|
|
||||||
{data?.tradableInstrument?.instrument?.product?.settlementAsset?.symbol}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const healthCellRenderer = ({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
value: MarketTradingMode;
|
|
||||||
data: Market;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<HealthBar
|
|
||||||
status={value}
|
|
||||||
target={data.target}
|
|
||||||
decimals={
|
|
||||||
data.tradableInstrument.instrument.product.settlementAsset.decimals
|
|
||||||
}
|
|
||||||
levels={data.feeLevels}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MarketList = () => {
|
|
||||||
const { data, error, loading } = useMarketsLiquidity();
|
|
||||||
const [isHealthDialogOpen, setIsHealthDialogOpen] = useState(false);
|
|
||||||
const gridRef = useRef<AgGridReactType | null>(null);
|
|
||||||
|
|
||||||
const getRowId = useCallback(({ data }: GetRowIdParams) => data.id, []);
|
|
||||||
|
|
||||||
const handleOnGridReady = useCallback(() => {
|
|
||||||
gridRef.current?.api?.sizeColumnsToFit();
|
|
||||||
}, [gridRef]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('resize', handleOnGridReady);
|
|
||||||
return () => window.removeEventListener('resize', handleOnGridReady);
|
|
||||||
}, [handleOnGridReady]);
|
|
||||||
|
|
||||||
if (loading) return <p>Loading...</p>;
|
|
||||||
if (error) return <p>Error :( </p>;
|
|
||||||
|
|
||||||
const localData = data?.markets;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="px-6 py-6 grow"
|
|
||||||
data-testid="market-list"
|
|
||||||
style={{ minHeight: 500, overflow: 'hidden' }}
|
|
||||||
>
|
|
||||||
<AgGridReact
|
|
||||||
rowData={localData}
|
|
||||||
className="ag-theme-alpine h-full"
|
|
||||||
defaultColDef={{
|
|
||||||
resizable: true,
|
|
||||||
sortable: true,
|
|
||||||
unSortIcon: true,
|
|
||||||
cellClass: ['flex', 'flex-col', 'justify-center'],
|
|
||||||
}}
|
|
||||||
getRowId={getRowId}
|
|
||||||
rowHeight={92}
|
|
||||||
ref={gridRef}
|
|
||||||
>
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Market (futures)')}
|
|
||||||
field="tradableInstrument.instrument.name"
|
|
||||||
cellRenderer={marketNameCellRenderer}
|
|
||||||
minWidth={100}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Volume (24h)')}
|
|
||||||
field="dayVolume"
|
|
||||||
cellRenderer={({ value, data }: GroupCellRendererParams) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{formatNumber(value)} ({displayValue(data.volumeChange)})
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Committed bond/stake')}
|
|
||||||
field="liquidityCommitted"
|
|
||||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
|
||||||
formatWithAsset(
|
|
||||||
value,
|
|
||||||
data.tradableInstrument.instrument.product.settlementAsset
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AgGridColumn
|
|
||||||
headerName={t('Status')}
|
|
||||||
field="tradingMode"
|
|
||||||
valueFormatter={({
|
|
||||||
value,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
value: MarketTradingMode;
|
|
||||||
data: Market;
|
|
||||||
}) => {
|
|
||||||
return value ===
|
|
||||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
|
||||||
data.data?.trigger &&
|
|
||||||
data.data.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
|
||||||
? `${MarketTradingModeMapping[value]}
|
|
||||||
- ${AuctionTriggerMapping[data.data.trigger]}`
|
|
||||||
: MarketTradingModeMapping[value];
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AgGridColumn
|
|
||||||
headerComponent={() => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<span>{t('Health')}</span>{' '}
|
|
||||||
<button
|
|
||||||
onClick={() => setIsHealthDialogOpen(true)}
|
|
||||||
aria-label={t('open tooltip')}
|
|
||||||
>
|
|
||||||
<Icon name="info-sign" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
field="tradingMode"
|
|
||||||
cellRenderer={healthCellRenderer}
|
|
||||||
sortable={false}
|
|
||||||
cellStyle={{ overflow: 'unset' }}
|
|
||||||
/>
|
|
||||||
<AgGridColumn headerName={t('Est. return / APY')} field="apy" />
|
|
||||||
</AgGridReact>
|
|
||||||
|
|
||||||
<HealthDialog
|
|
||||||
isOpen={isHealthDialogOpen}
|
|
||||||
onChange={() => {
|
|
||||||
setIsHealthDialogOpen(!isHealthDialogOpen);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1 @@
|
|||||||
|
export * from './navbar';
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { VegaLogo } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
export const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<div className="px-8 py-4 flex items-stretch border-b border-greys-light-200">
|
||||||
|
<div className="flex gap-4 mr-4 items-center h-full">
|
||||||
|
<Link to="/">
|
||||||
|
<VegaLogo />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 ml-auto"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './status';
|
@ -0,0 +1,45 @@
|
|||||||
|
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MarketTradingModeMapping,
|
||||||
|
MarketTradingMode,
|
||||||
|
AuctionTrigger,
|
||||||
|
AuctionTriggerMapping,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { Indicator } from '../indicator';
|
||||||
|
|
||||||
|
export const Status = ({
|
||||||
|
tradingMode,
|
||||||
|
trigger,
|
||||||
|
size = 'small',
|
||||||
|
}: {
|
||||||
|
tradingMode?: MarketTradingMode;
|
||||||
|
trigger?: AuctionTrigger;
|
||||||
|
size?: 'small' | 'large';
|
||||||
|
}) => {
|
||||||
|
const getStatus = () => {
|
||||||
|
if (!tradingMode) return '';
|
||||||
|
if (tradingMode === MarketTradingMode.TRADING_MODE_MONITORING_AUCTION) {
|
||||||
|
if (trigger && trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED) {
|
||||||
|
return `${MarketTradingModeMapping[tradingMode]} - ${AuctionTriggerMapping[trigger]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MarketTradingModeMapping[tradingMode];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('inline-flex whitespace-normal', {
|
||||||
|
'text-base': size === 'large',
|
||||||
|
'text-sm': size === 'small',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Lozenge className="border border-greys-light-300 bg-greys-light-100 flex items-center">
|
||||||
|
<Indicator status={tradingMode} />
|
||||||
|
{getStatus()}
|
||||||
|
</Lozenge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
12
apps/liquidity-provision-dashboard/src/app/lib/utils.tsx
Normal file
12
apps/liquidity-provision-dashboard/src/app/lib/utils.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
const marketTradingModeStyle = {
|
||||||
|
[MarketTradingMode.TRADING_MODE_CONTINUOUS]: '#00D46E',
|
||||||
|
[MarketTradingMode.TRADING_MODE_MONITORING_AUCTION]: '#CF0064',
|
||||||
|
[MarketTradingMode.TRADING_MODE_OPENING_AUCTION]: '#0046CD',
|
||||||
|
[MarketTradingMode.TRADING_MODE_BATCH_AUCTION]: '#CF0064',
|
||||||
|
[MarketTradingMode.TRADING_MODE_NO_TRADING]: '#CF0064',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getColorForStatus = (status: MarketTradingMode) =>
|
||||||
|
marketTradingModeStyle[status];
|
@ -0,0 +1 @@
|
|||||||
|
export * from './router-config';
|
@ -0,0 +1,25 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import { Dashboard } from '../components/dashboard';
|
||||||
|
import { Detail } from '../components/detail';
|
||||||
|
|
||||||
|
export const ROUTES = {
|
||||||
|
MARKETS: 'markets',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const routerConfig = [
|
||||||
|
{ path: '/', element: <Dashboard />, icon: '' },
|
||||||
|
{
|
||||||
|
path: ROUTES.MARKETS,
|
||||||
|
name: 'Markets',
|
||||||
|
text: t('Markets'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ':marketId',
|
||||||
|
element: <Detail />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
icon: 'trade',
|
||||||
|
isNavItem: true,
|
||||||
|
},
|
||||||
|
];
|
@ -1,8 +1,6 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
|
||||||
import { createClient } from './app/lib/apollo-client';
|
|
||||||
|
|
||||||
import App from './app/app';
|
import App from './app/app';
|
||||||
|
|
||||||
@ -11,12 +9,8 @@ const root = rootElement && createRoot(rootElement);
|
|||||||
|
|
||||||
root?.render(
|
root?.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<EnvironmentProvider>
|
<BrowserRouter>
|
||||||
<ThemeContext.Provider value="light">
|
|
||||||
<NetworkLoader createClient={createClient}>
|
|
||||||
<App />
|
<App />
|
||||||
</NetworkLoader>
|
</BrowserRouter>
|
||||||
</ThemeContext.Provider>
|
|
||||||
</EnvironmentProvider>
|
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,19 @@ module.exports = {
|
|||||||
...createGlobPatternsForDependencies(__dirname),
|
...createGlobPatternsForDependencies(__dirname),
|
||||||
],
|
],
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme,
|
theme: {
|
||||||
|
...theme,
|
||||||
|
colors: {
|
||||||
|
...theme.colors,
|
||||||
|
greys: {
|
||||||
|
light: {
|
||||||
|
100: '#F0F0F0',
|
||||||
|
200: '#D2D2D2',
|
||||||
|
300: '#A7A7A7',
|
||||||
|
400: '#626262',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [vegaCustomClasses, vegaCustomClassesLite],
|
plugins: [vegaCustomClasses, vegaCustomClassesLite],
|
||||||
};
|
};
|
||||||
|
@ -24,9 +24,11 @@ query MarketLp($marketId: ID!) {
|
|||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
marketTradingMode
|
||||||
suppliedStake
|
suppliedStake
|
||||||
openInterest
|
openInterest
|
||||||
targetStake
|
targetStake
|
||||||
|
trigger
|
||||||
marketValueProxy
|
marketValueProxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export type MarketLpQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type MarketLpQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, name: string, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } } } }, data?: { __typename?: 'MarketData', suppliedStake?: string | null, openInterest: string, targetStake?: string | null, marketValueProxy: string, market: { __typename?: 'Market', id: string } } | null } | null };
|
export type MarketLpQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, name: string, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number } } } }, data?: { __typename?: 'MarketData', marketTradingMode: Types.MarketTradingMode, suppliedStake?: string | null, openInterest: string, targetStake?: string | null, trigger: Types.AuctionTrigger, marketValueProxy: string, market: { __typename?: 'Market', id: string } } | null } | null };
|
||||||
|
|
||||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: string, updatedAt?: string | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: string, updatedAt?: string | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||||
|
|
||||||
@ -97,9 +97,11 @@ export const MarketLpDocument = gql`
|
|||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
marketTradingMode
|
||||||
suppliedStake
|
suppliedStake
|
||||||
openInterest
|
openInterest
|
||||||
targetStake
|
targetStake
|
||||||
|
trigger
|
||||||
marketValueProxy
|
marketValueProxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ import {
|
|||||||
} from './utils/liquidity-utils';
|
} from './utils/liquidity-utils';
|
||||||
import type { Provider, LiquidityProvisionMarket } from './utils';
|
import type { Provider, LiquidityProvisionMarket } from './utils';
|
||||||
|
|
||||||
interface FeeLevels {
|
export interface FeeLevels {
|
||||||
commitmentAmount: number;
|
commitmentAmount: number;
|
||||||
fee: string;
|
fee: string;
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,15 @@ import type { MarketNodeFragment } from './../__generated__/MarketsLiquidity';
|
|||||||
export type LiquidityProvisionMarket = MarketNodeFragment;
|
export type LiquidityProvisionMarket = MarketNodeFragment;
|
||||||
|
|
||||||
export interface Provider {
|
export interface Provider {
|
||||||
commitmentAmount: string;
|
commitmentAmount: string | undefined;
|
||||||
fee: string;
|
fee: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sumLiquidityCommitted = (
|
export const sumLiquidityCommitted = (
|
||||||
providers: Array<{ commitmentAmount: string }>
|
providers: Array<{ commitmentAmount: string | undefined }>
|
||||||
) => {
|
) => {
|
||||||
return providers
|
return providers
|
||||||
? providers.reduce((total: number, { commitmentAmount }) => {
|
? providers.reduce((total: number, { commitmentAmount = '0' }) => {
|
||||||
return total + parseInt(commitmentAmount, 10);
|
return total + parseInt(commitmentAmount, 10);
|
||||||
}, 0)
|
}, 0)
|
||||||
: 0;
|
: 0;
|
||||||
@ -23,15 +23,14 @@ export const sumLiquidityCommitted = (
|
|||||||
export const formatWithAsset = (
|
export const formatWithAsset = (
|
||||||
value: string,
|
value: string,
|
||||||
settlementAsset: {
|
settlementAsset: {
|
||||||
decimals: number;
|
decimals?: number;
|
||||||
symbol: string;
|
symbol?: string;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const formattedValue = addDecimalsFormatNumber(
|
const { decimals, symbol } = settlementAsset;
|
||||||
value,
|
const formattedValue = decimals
|
||||||
settlementAsset.decimals
|
? addDecimalsFormatNumber(value, decimals)
|
||||||
);
|
: value;
|
||||||
const symbol = settlementAsset.symbol;
|
|
||||||
return `${formattedValue} ${symbol}`;
|
return `${formattedValue} ${symbol}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,6 +47,10 @@ export const getCandle24hAgo = (
|
|||||||
return candles24hAgo.find((c) => c.marketId === marketId)?.candles?.[0];
|
return candles24hAgo.find((c) => c.marketId === marketId)?.candles?.[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const displayChange = (value: string) => {
|
||||||
|
return parseFloat(value) > 0 ? `+${value}` : value;
|
||||||
|
};
|
||||||
|
|
||||||
export const EMPTY_VALUE = ' - ';
|
export const EMPTY_VALUE = ' - ';
|
||||||
export const getChange = (candles: (Candle | null)[], lastClose?: string) => {
|
export const getChange = (candles: (Candle | null)[], lastClose?: string) => {
|
||||||
const firstCandle = candles.find((item) => item?.open);
|
const firstCandle = candles.find((item) => item?.open);
|
||||||
@ -81,7 +84,7 @@ export const calcDayVolume = (candles: Array<{ volume: string }> = []) => {
|
|||||||
|
|
||||||
export const getFeeLevels = (providers: Provider[]) => {
|
export const getFeeLevels = (providers: Provider[]) => {
|
||||||
const lp = providers.reduce((total: { [x: string]: number }, current) => {
|
const lp = providers.reduce((total: { [x: string]: number }, current) => {
|
||||||
const { fee, commitmentAmount } = current;
|
const { fee = '0', commitmentAmount = '0' } = current;
|
||||||
const ca = parseInt(commitmentAmount, 10);
|
const ca = parseInt(commitmentAmount, 10);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -7,6 +7,9 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
|
|||||||
'.calt': {
|
'.calt': {
|
||||||
fontFeatureSettings: "'calt'",
|
fontFeatureSettings: "'calt'",
|
||||||
},
|
},
|
||||||
|
'.liga-0-calt-0': {
|
||||||
|
fontFeatureSettings: "'liga' 0, 'calt' 0",
|
||||||
|
},
|
||||||
'.syntax-highlighter-wrapper .hljs': {
|
'.syntax-highlighter-wrapper .hljs': {
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
|
fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace",
|
||||||
|
Loading…
Reference in New Issue
Block a user