From 75e7cea32a472e8abc4761f2bb402c76eacac8bf Mon Sep 17 00:00:00 2001 From: Art Date: Tue, 6 Feb 2024 12:31:12 +0100 Subject: [PATCH 01/17] fix(trading): trading view on multiple tabs (#5748) --- libs/trading-view/src/lib/trading-view.tsx | 5 +++-- libs/trading-view/src/lib/use-datafeed.ts | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/libs/trading-view/src/lib/trading-view.tsx b/libs/trading-view/src/lib/trading-view.tsx index f673aca28..846794945 100644 --- a/libs/trading-view/src/lib/trading-view.tsx +++ b/libs/trading-view/src/lib/trading-view.tsx @@ -41,16 +41,17 @@ export const TradingView = ({ const chartContainerRef = useRef(null); const widgetRef = useRef(); - const datafeed = useDatafeed(); - const prevMarketId = usePrevious(marketId); const prevTheme = usePrevious(theme); + const datafeed = useDatafeed(marketId); + useEffect(() => { // Widget already created if (widgetRef.current !== undefined) { // Update the symbol if changed if (marketId !== prevMarketId) { + datafeed.setSymbol(marketId); widgetRef.current.setSymbol( marketId, (interval ? interval : '15') as TVResolutionString, diff --git a/libs/trading-view/src/lib/use-datafeed.ts b/libs/trading-view/src/lib/use-datafeed.ts index 7fe30d314..1021f7b0b 100644 --- a/libs/trading-view/src/lib/use-datafeed.ts +++ b/libs/trading-view/src/lib/use-datafeed.ts @@ -44,14 +44,22 @@ const configurationData: DatafeedConfiguration = { supported_resolutions: supportedResolutions as ResolutionString[], } as const; -export const useDatafeed = () => { +// HACK: local handle for market id +let requestedSymbol: string | undefined = undefined; + +export const useDatafeed = (marketId: string) => { const hasHistory = useRef(false); const subRef = useRef(); const client = useApolloClient(); const datafeed = useMemo(() => { - const feed: IBasicDataFeed = { + const feed: IBasicDataFeed & { setSymbol: (symbol: string) => void } = { + setSymbol: (symbol: string) => { + // re-setting the symbol so it could be consumed by `resolveSymbol` + requestedSymbol = symbol; + }, onReady: (callback) => { + requestedSymbol = marketId; setTimeout(() => callback(configurationData)); }, @@ -68,7 +76,7 @@ export const useDatafeed = () => { const result = await client.query({ query: SymbolDocument, variables: { - marketId, + marketId: requestedSymbol || marketId, }, }); @@ -242,7 +250,7 @@ export const useDatafeed = () => { }; return feed; - }, [client]); + }, [client, marketId]); useEffect(() => { return () => { From d6084e75a02252aceb9e64a4d6a20053d3ceb40f Mon Sep 17 00:00:00 2001 From: Art Date: Tue, 6 Feb 2024 14:40:14 +0100 Subject: [PATCH 02/17] fix(environment): pick best node (#5752) --- .../node-switcher/row-data.spec.tsx | 14 --- .../src/components/node-switcher/row-data.tsx | 16 +-- .../src/hooks/use-environment.spec.ts | 18 ++- libs/environment/src/hooks/use-environment.ts | 107 +++++++++++++----- libs/environment/src/utils/time.spec.ts | 22 ++++ libs/environment/src/utils/time.ts | 25 ++++ 6 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 libs/environment/src/utils/time.spec.ts create mode 100644 libs/environment/src/utils/time.ts diff --git a/libs/environment/src/components/node-switcher/row-data.spec.tsx b/libs/environment/src/components/node-switcher/row-data.spec.tsx index ba58feb57..fea946089 100644 --- a/libs/environment/src/components/node-switcher/row-data.spec.tsx +++ b/libs/environment/src/components/node-switcher/row-data.spec.tsx @@ -23,7 +23,6 @@ import { SUBSCRIPTION_TIMEOUT, useNodeBasicStatus, useNodeSubscriptionStatus, - useResponseTime, } from './row-data'; import { BLOCK_THRESHOLD, RowData } from './row-data'; import { CUSTOM_NODE_KEY } from '../../types'; @@ -162,19 +161,6 @@ describe('useNodeBasicStatus', () => { }); }); -describe('useResponseTime', () => { - it('returns response time when url is valid', () => { - const { result } = renderHook(() => - useResponseTime('https://localhost:1234') - ); - expect(result.current.responseTime).toBe(50); - }); - it('does not return response time when url is invalid', () => { - const { result } = renderHook(() => useResponseTime('nope')); - expect(result.current.responseTime).toBeUndefined(); - }); -}); - describe('RowData', () => { const props = { id: '0', diff --git a/libs/environment/src/components/node-switcher/row-data.tsx b/libs/environment/src/components/node-switcher/row-data.tsx index 6680e8aa0..f4dca0d49 100644 --- a/libs/environment/src/components/node-switcher/row-data.tsx +++ b/libs/environment/src/components/node-switcher/row-data.tsx @@ -1,4 +1,3 @@ -import { isValidUrl } from '@vegaprotocol/utils'; import { TradingRadio } from '@vegaprotocol/ui-toolkit'; import { useEffect, useState } from 'react'; import { CUSTOM_NODE_KEY } from '../../types'; @@ -8,6 +7,7 @@ import { } from '../../utils/__generated__/NodeCheck'; import { LayoutCell } from './layout-cell'; import { useT } from '../../use-t'; +import { useResponseTime } from '../../utils/time'; export const POLL_INTERVAL = 1000; export const SUBSCRIPTION_TIMEOUT = 3000; @@ -108,20 +108,6 @@ export const useNodeBasicStatus = () => { }; }; -export const useResponseTime = (url: string, trigger?: unknown) => { - const [responseTime, setResponseTime] = useState(); - useEffect(() => { - if (!isValidUrl(url)) return; - if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment - const requestUrl = new URL(url); - const requests = window.performance.getEntriesByName(requestUrl.href); - const { duration } = - (requests.length && requests[requests.length - 1]) || {}; - setResponseTime(duration); - }, [url, trigger]); - return { responseTime }; -}; - export const RowData = ({ id, url, diff --git a/libs/environment/src/hooks/use-environment.spec.ts b/libs/environment/src/hooks/use-environment.spec.ts index cec5fc598..1e3ab9e03 100644 --- a/libs/environment/src/hooks/use-environment.spec.ts +++ b/libs/environment/src/hooks/use-environment.spec.ts @@ -10,6 +10,7 @@ import { getUserEnabledFeatureFlags, setUserEnabledFeatureFlag, } from './use-environment'; +import { canMeasureResponseTime, measureResponseTime } from '../utils/time'; const noop = () => { /* no op*/ @@ -17,6 +18,10 @@ const noop = () => { jest.mock('@vegaprotocol/apollo-client'); jest.mock('zustand'); +jest.mock('../utils/time'); + +const mockCanMeasureResponseTime = canMeasureResponseTime as jest.Mock; +const mockMeasureResponseTime = measureResponseTime as jest.Mock; const mockCreateClient = createClient as jest.Mock; const createDefaultMockClient = () => { @@ -155,6 +160,14 @@ describe('useEnvironment', () => { const fastNode = 'https://api.n01.foo.vega.xyz'; const fastWait = 1000; const nodes = [slowNode, fastNode]; + + mockCanMeasureResponseTime.mockImplementation(() => true); + mockMeasureResponseTime.mockImplementation((url: string) => { + if (url === slowNode) return slowWait; + if (url === fastNode) return fastWait; + return Infinity; + }); + // @ts-ignore: typscript doesn't recognise the mock implementation global.fetch.mockImplementation(setupFetch({ hosts: nodes })); @@ -168,7 +181,7 @@ describe('useEnvironment', () => { statistics: { chainId: 'chain-id', blockHeight: '100', - vegaTime: new Date().toISOString(), + vegaTime: new Date(1).toISOString(), }, }, }); @@ -196,7 +209,8 @@ describe('useEnvironment', () => { expect(result.current.nodes).toEqual(nodes); }); - jest.runAllTimers(); + jest.advanceTimersByTime(2000); + // jest.runAllTimers(); await waitFor(() => { expect(result.current.status).toEqual('success'); diff --git a/libs/environment/src/hooks/use-environment.ts b/libs/environment/src/hooks/use-environment.ts index 893ac5f1a..e79b7b779 100644 --- a/libs/environment/src/hooks/use-environment.ts +++ b/libs/environment/src/hooks/use-environment.ts @@ -19,6 +19,9 @@ import { compileErrors } from '../utils/compile-errors'; import { envSchema } from '../utils/validate-environment'; import { tomlConfigSchema } from '../utils/validate-configuration'; import uniq from 'lodash/uniq'; +import orderBy from 'lodash/orderBy'; +import first from 'lodash/first'; +import { canMeasureResponseTime, measureResponseTime } from '../utils/time'; type Client = ReturnType; type ClientCollection = { @@ -38,8 +41,17 @@ export type EnvStore = Env & Actions; const VERSION = 1; export const STORAGE_KEY = `vega_url_${VERSION}`; + +const QUERY_TIMEOUT = 3000; const SUBSCRIPTION_TIMEOUT = 3000; +const raceAgainst = (timeout: number): Promise => + new Promise((resolve) => { + setTimeout(() => { + resolve(false); + }, timeout); + }); + /** * Fetch and validate a vega node configuration */ @@ -64,53 +76,88 @@ const fetchConfig = async (url?: string) => { const findNode = async (clients: ClientCollection): Promise => { const tests = Object.entries(clients).map((args) => testNode(...args)); try { - const url = await Promise.any(tests); - return url; - } catch { + const nodes = await Promise.all(tests); + const responsiveNodes = nodes + .filter(([, q, s]) => q && s) + .map(([url, q]) => { + return { + url, + ...q, + }; + }); + + // more recent and faster at the top + const ordered = orderBy( + responsiveNodes, + [(n) => n.blockHeight, (n) => n.vegaTime, (n) => n.responseTime], + ['desc', 'desc', 'asc'] + ); + + const best = first(ordered); + return best ? best.url : null; + } catch (err) { // All tests rejected, no suitable node found return null; } }; +type Maybe = T | false; +type QueryTestResult = { + blockHeight: number; + vegaTime: Date; + responseTime: number; +}; +type SubscriptionTestResult = true; +type NodeTestResult = [ + /** url */ + string, + Maybe, + Maybe +]; /** * Test a node for suitability for connection */ const testNode = async ( url: string, client: Client -): Promise => { +): Promise => { const results = await Promise.all([ - // these promises will only resolve with true/false - testQuery(client), + testQuery(client, url), testSubscription(client), ]); - if (results[0] && results[1]) { - return url; - } - - const message = `Tests failed for node: ${url}`; - console.warn(message); - - // throwing here will mean this tests is ignored and a different - // node that hopefully does resolve will fulfill the Promise.any - throw new Error(message); + return [url, ...results]; }; /** * Run a test query on a client */ -const testQuery = async (client: Client) => { - try { - const result = await client.query({ - query: NodeCheckDocument, - }); - if (!result || result.error) { - return false; - } - return true; - } catch (err) { - return false; - } +const testQuery = ( + client: Client, + url: string +): Promise> => { + const test: Promise> = new Promise((resolve) => + client + .query({ + query: NodeCheckDocument, + }) + .then((result) => { + if (result && !result.error) { + const res = { + blockHeight: Number(result.data.statistics.blockHeight), + vegaTime: new Date(result.data.statistics.vegaTime), + // only after a request has been sent we can retrieve the response time + responseTime: canMeasureResponseTime(url) + ? measureResponseTime(url) || Infinity + : Infinity, + } as QueryTestResult; + resolve(res); + } else { + resolve(false); + } + }) + .catch(() => resolve(false)) + ); + return Promise.race([test, raceAgainst(QUERY_TIMEOUT)]); }; /** @@ -118,7 +165,9 @@ const testQuery = async (client: Client) => { * that takes longer than SUBSCRIPTION_TIMEOUT ms to respond * is deemed a failure */ -const testSubscription = (client: Client) => { +const testSubscription = ( + client: Client +): Promise> => { return new Promise((resolve) => { const sub = client .subscribe({ diff --git a/libs/environment/src/utils/time.spec.ts b/libs/environment/src/utils/time.spec.ts new file mode 100644 index 000000000..9d56c52b6 --- /dev/null +++ b/libs/environment/src/utils/time.spec.ts @@ -0,0 +1,22 @@ +import { renderHook } from '@testing-library/react'; +import { useResponseTime } from './time'; + +const mockResponseTime = 50; +global.performance.getEntriesByName = jest.fn().mockReturnValue([ + { + duration: mockResponseTime, + }, +]); + +describe('useResponseTime', () => { + it('returns response time when url is valid', () => { + const { result } = renderHook(() => + useResponseTime('https://localhost:1234') + ); + expect(result.current.responseTime).toBe(50); + }); + it('does not return response time when url is invalid', () => { + const { result } = renderHook(() => useResponseTime('nope')); + expect(result.current.responseTime).toBeUndefined(); + }); +}); diff --git a/libs/environment/src/utils/time.ts b/libs/environment/src/utils/time.ts new file mode 100644 index 000000000..6e135eab9 --- /dev/null +++ b/libs/environment/src/utils/time.ts @@ -0,0 +1,25 @@ +import { isValidUrl } from '@vegaprotocol/utils'; +import { useEffect, useState } from 'react'; + +export const useResponseTime = (url: string, trigger?: unknown) => { + const [responseTime, setResponseTime] = useState(); + useEffect(() => { + if (!canMeasureResponseTime(url)) return; + const duration = measureResponseTime(url); + setResponseTime(duration); + }, [url, trigger]); + return { responseTime }; +}; + +export const canMeasureResponseTime = (url: string) => { + if (!isValidUrl(url)) return false; + if (typeof window.performance.getEntriesByName !== 'function') return false; + return true; +}; + +export const measureResponseTime = (url: string) => { + const requestUrl = new URL(url); + const requests = window.performance.getEntriesByName(requestUrl.href); + const { duration } = (requests.length && requests[requests.length - 1]) || {}; + return duration; +}; From bf70dc33ecc6b21c0e7a7139ad2dc4bd826850e1 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:41:48 +0200 Subject: [PATCH 03/17] chore(trading): back merge main to develop (#5749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthew Russell Co-authored-by: Edd Co-authored-by: Bartłomiej Głownia Co-authored-by: Art --- README.md | 10 +++++----- apps/trading/components/settings/settings.tsx | 1 + nx.json | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9e657a93a..45ce92595 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The front-end monorepo provides a toolkit for building apps that interact with V This repository is managed using [Nx](https://nx.dev). -# 🔎 Applications in this repo +## 🔎 Applications in this repo ### [Block explorer](./apps/explorer) @@ -30,7 +30,7 @@ Hosting for static content being shared across apps, for example fonts. The utility dApp for validators wishing to add or remove themselves as a signer of the multisig contract. -# 🧱 Libraries in this repo +## 🧱 Libraries in this repo ### [UI toolkit](./libs/ui-toolkit) @@ -53,7 +53,7 @@ A utility library for connecting to the Ethereum network and interacting with Ve Generic react helpers that can be used across multiple applications, along with other utilities. -# 💻 Develop +## 💻 Develop ### Set up @@ -103,7 +103,7 @@ In CI linting, formatting and also run. These checks can be seen in the [CI work Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more. -# 🐋 Hosting a console +## 🐋 Hosting a console To host a console there are two possible build scenarios for running the frontends: nx performed **outside** or **inside** docker build. For specific build instructions follow [build instructions](#build-instructions). @@ -226,6 +226,6 @@ Note: The script is only needed if capsule was built for first time or fresh. To vega wallet service run -n DV --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet ``` -# 📑 License +## 📑 License [MIT](./LICENSE) diff --git a/apps/trading/components/settings/settings.tsx b/apps/trading/components/settings/settings.tsx index 66b1999a0..4648906a4 100644 --- a/apps/trading/components/settings/settings.tsx +++ b/apps/trading/components/settings/settings.tsx @@ -81,6 +81,7 @@ export const Settings = () => { intent={Intent.Primary} onClick={() => { localStorage.clear(); + sessionStorage.clear(); window.location.reload(); }} > diff --git a/nx.json b/nx.json index 4175f6a04..a3284e7a6 100644 --- a/nx.json +++ b/nx.json @@ -11,7 +11,9 @@ "echo $NX_VEGA_URL", "echo $NX_TENDERMINT_URL", "echo $NX_TENDERMINT_WEBSOCKET_URL", - "echo $NX_ETHEREUM_PROVIDER_URL" + "echo $NX_ETHEREUM_PROVIDER_URL", + "echo $NX_CHARTING_LIBRARY_PATH", + "echo $NX_CHARTING_LIBRARY_HASH" ] } } From be38813a33308b3df01af442d031e0f6122a4761 Mon Sep 17 00:00:00 2001 From: Madalina Raicu Date: Tue, 6 Feb 2024 21:52:32 +0000 Subject: [PATCH 04/17] feat(trading): show total value of the valume traded in last 24hrs --- .../market/market-header-stats.tsx | 4 ++ .../markets/market-list-table.tsx | 4 +- .../client-pages/markets/use-column-defs.tsx | 32 +++++++++++-- .../last-24h-volume/last-24h-volume.tsx | 38 ++++++++++++--- .../market-info/market-info-panels.tsx | 1 + libs/markets/src/lib/market-utils.spec.tsx | 29 ++++++++++++ libs/markets/src/lib/market-utils.ts | 47 ++++++++++++++++++- 7 files changed, 142 insertions(+), 13 deletions(-) diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index e477a4e19..38952d49e 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -19,6 +19,7 @@ import { useFundingRate, useMarketTradingMode, useExternalTwap, + getQuoteName, } from '@vegaprotocol/markets'; import { MarketState as State } from '@vegaprotocol/types'; import { HeaderStat } from '../../components/header'; @@ -41,6 +42,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const asset = getAsset(market); + const quoteUnit = getQuoteName(market); return ( <> @@ -60,6 +62,8 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { ()( ); export const MarketListTable = (props: Props) => { - const columnDefs = useColumnDefs(); + const columnDefs = useMarketsColumnDefs(); const gridStore = useMarketsStore((store) => store.gridStore); const updateGridStore = useMarketsStore((store) => store.updateGridStore); diff --git a/apps/trading/client-pages/markets/use-column-defs.tsx b/apps/trading/client-pages/markets/use-column-defs.tsx index 06b55a43e..5617b2833 100644 --- a/apps/trading/client-pages/markets/use-column-defs.tsx +++ b/apps/trading/client-pages/markets/use-column-defs.tsx @@ -7,21 +7,31 @@ import type { } from '@vegaprotocol/datagrid'; import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid'; import * as Schema from '@vegaprotocol/types'; -import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils'; +import { + addDecimalsFormatNumber, + formatNumber, + toBigNum, +} from '@vegaprotocol/utils'; import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import type { + MarketFieldsFragment, MarketMaybeWithData, MarketMaybeWithDataAndCandles, } from '@vegaprotocol/markets'; import { MarketActionsDropdown } from './market-table-actions'; -import { calcCandleVolume, getAsset } from '@vegaprotocol/markets'; +import { + calcCandleVolume, + calcCandleVolumePrice, + getAsset, + getQuoteName, +} from '@vegaprotocol/markets'; import { MarketCodeCell } from './market-code-cell'; import { useT } from '../../lib/use-t'; const { MarketTradingMode, AuctionTrigger } = Schema; -export const useColumnDefs = () => { +export const useMarketsColumnDefs = () => { const t = useT(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return useMemo( @@ -158,11 +168,25 @@ export const useColumnDefs = () => { }: ValueFormatterParams) => { const candles = data?.candles; const vol = candles ? calcCandleVolume(candles) : '0'; + const quoteName = getQuoteName(data as MarketFieldsFragment); + const volPrice = + candles && + calcCandleVolumePrice( + candles, + data.decimalPlaces, + data.positionDecimalPlaces + ); + const volume = data && vol && vol !== '0' ? addDecimalsFormatNumber(vol, data.positionDecimalPlaces) : '0.00'; - return volume; + const volumePrice = + volPrice && formatNumber(volPrice, data?.decimalPlaces); + + return volumePrice + ? `${volume} (${volumePrice} ${quoteName})` + : volume; }, }, { diff --git a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx index 6cd5860cd..7cf631a6e 100644 --- a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx +++ b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx @@ -1,5 +1,9 @@ -import { calcCandleVolume } from '../../market-utils'; -import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; +import { calcCandleVolume, calcCandleVolumePrice } from '../../market-utils'; +import { + addDecimalsFormatNumber, + formatNumber, + isNumeric, +} from '@vegaprotocol/utils'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { useCandles } from '../../hooks'; import { useT } from '../../use-t'; @@ -9,13 +13,17 @@ interface Props { positionDecimalPlaces?: number; formatDecimals?: number; initialValue?: string; + marketDecimals?: number; + quoteUnit?: string; } export const Last24hVolume = ({ marketId, + marketDecimals, positionDecimalPlaces, formatDecimals, initialValue, + quoteUnit, }: Props) => { const t = useT(); const { oneDayCandles, fiveDaysCandles } = useCandles({ @@ -28,6 +36,11 @@ export const Last24hVolume = ({ (!oneDayCandles || oneDayCandles?.length === 0) ) { const candleVolume = calcCandleVolume(fiveDaysCandles); + const candleVolumePrice = calcCandleVolumePrice( + fiveDaysCandles, + marketDecimals, + positionDecimalPlaces + ); const candleVolumeValue = candleVolume && isNumeric(positionDecimalPlaces) ? addDecimalsFormatNumber( @@ -42,8 +55,8 @@ export const Last24hVolume = ({
{t( - '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}', - { candleVolumeValue } + '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} ({{candleVolumePrice}} {{quoteUnit}})', + { candleVolumeValue, candleVolumePrice, quoteUnit } )}
@@ -57,10 +70,18 @@ export const Last24hVolume = ({ ? calcCandleVolume(oneDayCandles) : initialValue; + const candleVolumePrice = oneDayCandles + ? calcCandleVolumePrice( + oneDayCandles, + marketDecimals, + positionDecimalPlaces + ) + : initialValue; + return ( @@ -70,7 +91,12 @@ export const Last24hVolume = ({ positionDecimalPlaces, formatDecimals ) - : '-'} + : '-'}{' '} + ( + {candleVolumePrice && isNumeric(positionDecimalPlaces) + ? formatNumber(candleVolumePrice, formatDecimals) + : '-'}{' '} + {quoteUnit}) ); diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.tsx index a6c3b0e6c..fe3c3acbe 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.tsx @@ -155,6 +155,7 @@ export const MarketVolumeInfoPanel = ({ market }: MarketInfoProps) => { ), openInterest: dash(data?.openInterest), diff --git a/libs/markets/src/lib/market-utils.spec.tsx b/libs/markets/src/lib/market-utils.spec.tsx index 2bb759f81..88d2d6fba 100644 --- a/libs/markets/src/lib/market-utils.spec.tsx +++ b/libs/markets/src/lib/market-utils.spec.tsx @@ -1,6 +1,7 @@ import * as Schema from '@vegaprotocol/types'; import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider'; import { + calcCandleVolumePrice, calcTradedFactor, filterAndSortMarkets, sumFeesFactors, @@ -145,3 +146,31 @@ describe('sumFeesFactors', () => { ).toEqual(0.6); }); }); + +describe('calcCandleVolumePrice', () => { + it('calculates the volume price', () => { + const candles = [ + { + volume: '1000', + high: '100', + low: '10', + open: '15', + close: '90', + periodStart: '2022-05-18T13:08:27.693537312Z', + }, + { + volume: '1000', + high: '100', + low: '10', + open: '15', + close: '90', + periodStart: '2022-05-18T14:08:27.693537312Z', + }, + ]; + const marketDecimals = 3; + const positionDecimalPlaces = 2; + expect( + calcCandleVolumePrice(candles, marketDecimals, positionDecimalPlaces) + ).toEqual('2'); + }); +}); diff --git a/libs/markets/src/lib/market-utils.ts b/libs/markets/src/lib/market-utils.ts index f0b93d109..36f5f4d2b 100644 --- a/libs/markets/src/lib/market-utils.ts +++ b/libs/markets/src/lib/market-utils.ts @@ -1,4 +1,8 @@ -import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils'; +import { + addDecimal, + formatNumberPercentage, + toBigNum, +} from '@vegaprotocol/utils'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import BigNumber from 'bignumber.js'; import orderBy from 'lodash/orderBy'; @@ -147,10 +151,51 @@ export const calcCandleHigh = (candles: Candle[]): string | undefined => { .toString(); }; +/** + * The total number of contracts traded in the last 24 hours. + * + * @param candles + * @returns the volume of a given set of candles + */ export const calcCandleVolume = (candles: Candle[]): string | undefined => candles && candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0'); +/** + * The total number of contracts traded in the last 24 hours. (Total value of contracts traded in the last 24 hours) + * The volume is calculated as the sum of the product of the volume and the high price of each candle. + * The result is formatted using positionDecimalPlaces to account for the position size. + * The result is formatted using marketDecimals to account for the market precision. + * + * @param candles + * @param marketDecimals + * @param positionDecimalPlaces + * @returns the volume (in quote price) of a given set of candles + */ +export const calcCandleVolumePrice = ( + candles: Candle[], + marketDecimals: number = 1, + positionDecimalPlaces: number = 1 +): string | undefined => + candles && + candles.reduce( + (acc, c) => + new BigNumber(acc) + .plus( + BigNumber(addDecimal(c.volume, positionDecimalPlaces)).times( + addDecimal(c.high, marketDecimals) + ) + ) + .toString(), + '0' + ); + +/** + * Calculates the traded factor of a given market. + * + * @param m + * @returns + */ export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => { const volume = Number(calcCandleVolume(m.candles || []) || 0); const price = m.data?.markPrice ? Number(m.data.markPrice) : 0; From 3a8b40d7a51793cb066990fd76ebc1db715fdebf Mon Sep 17 00:00:00 2001 From: Madalina Raicu Date: Tue, 6 Feb 2024 22:12:33 +0000 Subject: [PATCH 05/17] Revert "feat(trading): show total value of the valume traded in last 24hrs" This reverts commit be38813a33308b3df01af442d031e0f6122a4761. --- .../market/market-header-stats.tsx | 4 -- .../markets/market-list-table.tsx | 4 +- .../client-pages/markets/use-column-defs.tsx | 32 ++----------- .../last-24h-volume/last-24h-volume.tsx | 38 +++------------ .../market-info/market-info-panels.tsx | 1 - libs/markets/src/lib/market-utils.spec.tsx | 29 ------------ libs/markets/src/lib/market-utils.ts | 47 +------------------ 7 files changed, 13 insertions(+), 142 deletions(-) diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index 38952d49e..e477a4e19 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -19,7 +19,6 @@ import { useFundingRate, useMarketTradingMode, useExternalTwap, - getQuoteName, } from '@vegaprotocol/markets'; import { MarketState as State } from '@vegaprotocol/types'; import { HeaderStat } from '../../components/header'; @@ -42,7 +41,6 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const asset = getAsset(market); - const quoteUnit = getQuoteName(market); return ( <> @@ -62,8 +60,6 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { ()( ); export const MarketListTable = (props: Props) => { - const columnDefs = useMarketsColumnDefs(); + const columnDefs = useColumnDefs(); const gridStore = useMarketsStore((store) => store.gridStore); const updateGridStore = useMarketsStore((store) => store.updateGridStore); diff --git a/apps/trading/client-pages/markets/use-column-defs.tsx b/apps/trading/client-pages/markets/use-column-defs.tsx index 5617b2833..06b55a43e 100644 --- a/apps/trading/client-pages/markets/use-column-defs.tsx +++ b/apps/trading/client-pages/markets/use-column-defs.tsx @@ -7,31 +7,21 @@ import type { } from '@vegaprotocol/datagrid'; import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid'; import * as Schema from '@vegaprotocol/types'; -import { - addDecimalsFormatNumber, - formatNumber, - toBigNum, -} from '@vegaprotocol/utils'; +import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils'; import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import type { - MarketFieldsFragment, MarketMaybeWithData, MarketMaybeWithDataAndCandles, } from '@vegaprotocol/markets'; import { MarketActionsDropdown } from './market-table-actions'; -import { - calcCandleVolume, - calcCandleVolumePrice, - getAsset, - getQuoteName, -} from '@vegaprotocol/markets'; +import { calcCandleVolume, getAsset } from '@vegaprotocol/markets'; import { MarketCodeCell } from './market-code-cell'; import { useT } from '../../lib/use-t'; const { MarketTradingMode, AuctionTrigger } = Schema; -export const useMarketsColumnDefs = () => { +export const useColumnDefs = () => { const t = useT(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return useMemo( @@ -168,25 +158,11 @@ export const useMarketsColumnDefs = () => { }: ValueFormatterParams) => { const candles = data?.candles; const vol = candles ? calcCandleVolume(candles) : '0'; - const quoteName = getQuoteName(data as MarketFieldsFragment); - const volPrice = - candles && - calcCandleVolumePrice( - candles, - data.decimalPlaces, - data.positionDecimalPlaces - ); - const volume = data && vol && vol !== '0' ? addDecimalsFormatNumber(vol, data.positionDecimalPlaces) : '0.00'; - const volumePrice = - volPrice && formatNumber(volPrice, data?.decimalPlaces); - - return volumePrice - ? `${volume} (${volumePrice} ${quoteName})` - : volume; + return volume; }, }, { diff --git a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx index 7cf631a6e..6cd5860cd 100644 --- a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx +++ b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx @@ -1,9 +1,5 @@ -import { calcCandleVolume, calcCandleVolumePrice } from '../../market-utils'; -import { - addDecimalsFormatNumber, - formatNumber, - isNumeric, -} from '@vegaprotocol/utils'; +import { calcCandleVolume } from '../../market-utils'; +import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { useCandles } from '../../hooks'; import { useT } from '../../use-t'; @@ -13,17 +9,13 @@ interface Props { positionDecimalPlaces?: number; formatDecimals?: number; initialValue?: string; - marketDecimals?: number; - quoteUnit?: string; } export const Last24hVolume = ({ marketId, - marketDecimals, positionDecimalPlaces, formatDecimals, initialValue, - quoteUnit, }: Props) => { const t = useT(); const { oneDayCandles, fiveDaysCandles } = useCandles({ @@ -36,11 +28,6 @@ export const Last24hVolume = ({ (!oneDayCandles || oneDayCandles?.length === 0) ) { const candleVolume = calcCandleVolume(fiveDaysCandles); - const candleVolumePrice = calcCandleVolumePrice( - fiveDaysCandles, - marketDecimals, - positionDecimalPlaces - ); const candleVolumeValue = candleVolume && isNumeric(positionDecimalPlaces) ? addDecimalsFormatNumber( @@ -55,8 +42,8 @@ export const Last24hVolume = ({
{t( - '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} ({{candleVolumePrice}} {{quoteUnit}})', - { candleVolumeValue, candleVolumePrice, quoteUnit } + '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}', + { candleVolumeValue } )}
@@ -70,18 +57,10 @@ export const Last24hVolume = ({ ? calcCandleVolume(oneDayCandles) : initialValue; - const candleVolumePrice = oneDayCandles - ? calcCandleVolumePrice( - oneDayCandles, - marketDecimals, - positionDecimalPlaces - ) - : initialValue; - return ( @@ -91,12 +70,7 @@ export const Last24hVolume = ({ positionDecimalPlaces, formatDecimals ) - : '-'}{' '} - ( - {candleVolumePrice && isNumeric(positionDecimalPlaces) - ? formatNumber(candleVolumePrice, formatDecimals) - : '-'}{' '} - {quoteUnit}) + : '-'} ); diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.tsx index fe3c3acbe..a6c3b0e6c 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.tsx @@ -155,7 +155,6 @@ export const MarketVolumeInfoPanel = ({ market }: MarketInfoProps) => { ), openInterest: dash(data?.openInterest), diff --git a/libs/markets/src/lib/market-utils.spec.tsx b/libs/markets/src/lib/market-utils.spec.tsx index 88d2d6fba..2bb759f81 100644 --- a/libs/markets/src/lib/market-utils.spec.tsx +++ b/libs/markets/src/lib/market-utils.spec.tsx @@ -1,7 +1,6 @@ import * as Schema from '@vegaprotocol/types'; import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider'; import { - calcCandleVolumePrice, calcTradedFactor, filterAndSortMarkets, sumFeesFactors, @@ -146,31 +145,3 @@ describe('sumFeesFactors', () => { ).toEqual(0.6); }); }); - -describe('calcCandleVolumePrice', () => { - it('calculates the volume price', () => { - const candles = [ - { - volume: '1000', - high: '100', - low: '10', - open: '15', - close: '90', - periodStart: '2022-05-18T13:08:27.693537312Z', - }, - { - volume: '1000', - high: '100', - low: '10', - open: '15', - close: '90', - periodStart: '2022-05-18T14:08:27.693537312Z', - }, - ]; - const marketDecimals = 3; - const positionDecimalPlaces = 2; - expect( - calcCandleVolumePrice(candles, marketDecimals, positionDecimalPlaces) - ).toEqual('2'); - }); -}); diff --git a/libs/markets/src/lib/market-utils.ts b/libs/markets/src/lib/market-utils.ts index 36f5f4d2b..f0b93d109 100644 --- a/libs/markets/src/lib/market-utils.ts +++ b/libs/markets/src/lib/market-utils.ts @@ -1,8 +1,4 @@ -import { - addDecimal, - formatNumberPercentage, - toBigNum, -} from '@vegaprotocol/utils'; +import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import BigNumber from 'bignumber.js'; import orderBy from 'lodash/orderBy'; @@ -151,51 +147,10 @@ export const calcCandleHigh = (candles: Candle[]): string | undefined => { .toString(); }; -/** - * The total number of contracts traded in the last 24 hours. - * - * @param candles - * @returns the volume of a given set of candles - */ export const calcCandleVolume = (candles: Candle[]): string | undefined => candles && candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0'); -/** - * The total number of contracts traded in the last 24 hours. (Total value of contracts traded in the last 24 hours) - * The volume is calculated as the sum of the product of the volume and the high price of each candle. - * The result is formatted using positionDecimalPlaces to account for the position size. - * The result is formatted using marketDecimals to account for the market precision. - * - * @param candles - * @param marketDecimals - * @param positionDecimalPlaces - * @returns the volume (in quote price) of a given set of candles - */ -export const calcCandleVolumePrice = ( - candles: Candle[], - marketDecimals: number = 1, - positionDecimalPlaces: number = 1 -): string | undefined => - candles && - candles.reduce( - (acc, c) => - new BigNumber(acc) - .plus( - BigNumber(addDecimal(c.volume, positionDecimalPlaces)).times( - addDecimal(c.high, marketDecimals) - ) - ) - .toString(), - '0' - ); - -/** - * Calculates the traded factor of a given market. - * - * @param m - * @returns - */ export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => { const volume = Number(calcCandleVolume(m.candles || []) || 0); const price = m.data?.markPrice ? Number(m.data.markPrice) : 0; From 532ad3a4b9f3eafa444a9ae7806a20e36b931ea4 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:07:24 +0200 Subject: [PATCH 06/17] feat(trading): show value of the volume traded in the last 24hrs (#5766) Co-authored-by: bwallacee --- .../market/market-header-stats.tsx | 4 ++ .../markets/market-list-table.tsx | 4 +- .../client-pages/markets/use-column-defs.tsx | 32 +++++++++++-- .../market_lifecycle/test_market_lifecycle.py | 2 +- .../last-24h-volume/last-24h-volume.tsx | 38 ++++++++++++--- .../market-info/market-info-panels.tsx | 1 + libs/markets/src/lib/market-utils.spec.tsx | 29 ++++++++++++ libs/markets/src/lib/market-utils.ts | 47 ++++++++++++++++++- 8 files changed, 143 insertions(+), 14 deletions(-) diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index e477a4e19..38952d49e 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -19,6 +19,7 @@ import { useFundingRate, useMarketTradingMode, useExternalTwap, + getQuoteName, } from '@vegaprotocol/markets'; import { MarketState as State } from '@vegaprotocol/types'; import { HeaderStat } from '../../components/header'; @@ -41,6 +42,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const asset = getAsset(market); + const quoteUnit = getQuoteName(market); return ( <> @@ -60,6 +62,8 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { ()( ); export const MarketListTable = (props: Props) => { - const columnDefs = useColumnDefs(); + const columnDefs = useMarketsColumnDefs(); const gridStore = useMarketsStore((store) => store.gridStore); const updateGridStore = useMarketsStore((store) => store.updateGridStore); diff --git a/apps/trading/client-pages/markets/use-column-defs.tsx b/apps/trading/client-pages/markets/use-column-defs.tsx index 06b55a43e..5617b2833 100644 --- a/apps/trading/client-pages/markets/use-column-defs.tsx +++ b/apps/trading/client-pages/markets/use-column-defs.tsx @@ -7,21 +7,31 @@ import type { } from '@vegaprotocol/datagrid'; import { COL_DEFS, SetFilter } from '@vegaprotocol/datagrid'; import * as Schema from '@vegaprotocol/types'; -import { addDecimalsFormatNumber, toBigNum } from '@vegaprotocol/utils'; +import { + addDecimalsFormatNumber, + formatNumber, + toBigNum, +} from '@vegaprotocol/utils'; import { ButtonLink, Tooltip } from '@vegaprotocol/ui-toolkit'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import type { + MarketFieldsFragment, MarketMaybeWithData, MarketMaybeWithDataAndCandles, } from '@vegaprotocol/markets'; import { MarketActionsDropdown } from './market-table-actions'; -import { calcCandleVolume, getAsset } from '@vegaprotocol/markets'; +import { + calcCandleVolume, + calcCandleVolumePrice, + getAsset, + getQuoteName, +} from '@vegaprotocol/markets'; import { MarketCodeCell } from './market-code-cell'; import { useT } from '../../lib/use-t'; const { MarketTradingMode, AuctionTrigger } = Schema; -export const useColumnDefs = () => { +export const useMarketsColumnDefs = () => { const t = useT(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); return useMemo( @@ -158,11 +168,25 @@ export const useColumnDefs = () => { }: ValueFormatterParams) => { const candles = data?.candles; const vol = candles ? calcCandleVolume(candles) : '0'; + const quoteName = getQuoteName(data as MarketFieldsFragment); + const volPrice = + candles && + calcCandleVolumePrice( + candles, + data.decimalPlaces, + data.positionDecimalPlaces + ); + const volume = data && vol && vol !== '0' ? addDecimalsFormatNumber(vol, data.positionDecimalPlaces) : '0.00'; - return volume; + const volumePrice = + volPrice && formatNumber(volPrice, data?.decimalPlaces); + + return volumePrice + ? `${volume} (${volumePrice} ${quoteName})` + : volume; }, }, { diff --git a/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py b/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py index 638c88c83..7df845583 100644 --- a/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py +++ b/apps/trading/e2e/tests/market_lifecycle/test_market_lifecycle.py @@ -37,7 +37,7 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page): # 6002-MDET-004 expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)0.00%0.00") # 6002-MDET-005 - expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-") + expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)- (- BTC)") # 6002-MDET-008 expect(page.get_by_test_id("market-settlement-asset")).to_have_text( "Settlement assettDAI" diff --git a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx index 6cd5860cd..7cf631a6e 100644 --- a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx +++ b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx @@ -1,5 +1,9 @@ -import { calcCandleVolume } from '../../market-utils'; -import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; +import { calcCandleVolume, calcCandleVolumePrice } from '../../market-utils'; +import { + addDecimalsFormatNumber, + formatNumber, + isNumeric, +} from '@vegaprotocol/utils'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; import { useCandles } from '../../hooks'; import { useT } from '../../use-t'; @@ -9,13 +13,17 @@ interface Props { positionDecimalPlaces?: number; formatDecimals?: number; initialValue?: string; + marketDecimals?: number; + quoteUnit?: string; } export const Last24hVolume = ({ marketId, + marketDecimals, positionDecimalPlaces, formatDecimals, initialValue, + quoteUnit, }: Props) => { const t = useT(); const { oneDayCandles, fiveDaysCandles } = useCandles({ @@ -28,6 +36,11 @@ export const Last24hVolume = ({ (!oneDayCandles || oneDayCandles?.length === 0) ) { const candleVolume = calcCandleVolume(fiveDaysCandles); + const candleVolumePrice = calcCandleVolumePrice( + fiveDaysCandles, + marketDecimals, + positionDecimalPlaces + ); const candleVolumeValue = candleVolume && isNumeric(positionDecimalPlaces) ? addDecimalsFormatNumber( @@ -42,8 +55,8 @@ export const Last24hVolume = ({
{t( - '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}', - { candleVolumeValue } + '24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} ({{candleVolumePrice}} {{quoteUnit}})', + { candleVolumeValue, candleVolumePrice, quoteUnit } )}
@@ -57,10 +70,18 @@ export const Last24hVolume = ({ ? calcCandleVolume(oneDayCandles) : initialValue; + const candleVolumePrice = oneDayCandles + ? calcCandleVolumePrice( + oneDayCandles, + marketDecimals, + positionDecimalPlaces + ) + : initialValue; + return ( @@ -70,7 +91,12 @@ export const Last24hVolume = ({ positionDecimalPlaces, formatDecimals ) - : '-'} + : '-'}{' '} + ( + {candleVolumePrice && isNumeric(positionDecimalPlaces) + ? formatNumber(candleVolumePrice, formatDecimals) + : '-'}{' '} + {quoteUnit}) ); diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.tsx index a6c3b0e6c..fe3c3acbe 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.tsx @@ -155,6 +155,7 @@ export const MarketVolumeInfoPanel = ({ market }: MarketInfoProps) => { ), openInterest: dash(data?.openInterest), diff --git a/libs/markets/src/lib/market-utils.spec.tsx b/libs/markets/src/lib/market-utils.spec.tsx index 2bb759f81..88d2d6fba 100644 --- a/libs/markets/src/lib/market-utils.spec.tsx +++ b/libs/markets/src/lib/market-utils.spec.tsx @@ -1,6 +1,7 @@ import * as Schema from '@vegaprotocol/types'; import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider'; import { + calcCandleVolumePrice, calcTradedFactor, filterAndSortMarkets, sumFeesFactors, @@ -145,3 +146,31 @@ describe('sumFeesFactors', () => { ).toEqual(0.6); }); }); + +describe('calcCandleVolumePrice', () => { + it('calculates the volume price', () => { + const candles = [ + { + volume: '1000', + high: '100', + low: '10', + open: '15', + close: '90', + periodStart: '2022-05-18T13:08:27.693537312Z', + }, + { + volume: '1000', + high: '100', + low: '10', + open: '15', + close: '90', + periodStart: '2022-05-18T14:08:27.693537312Z', + }, + ]; + const marketDecimals = 3; + const positionDecimalPlaces = 2; + expect( + calcCandleVolumePrice(candles, marketDecimals, positionDecimalPlaces) + ).toEqual('2'); + }); +}); diff --git a/libs/markets/src/lib/market-utils.ts b/libs/markets/src/lib/market-utils.ts index f0b93d109..36f5f4d2b 100644 --- a/libs/markets/src/lib/market-utils.ts +++ b/libs/markets/src/lib/market-utils.ts @@ -1,4 +1,8 @@ -import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils'; +import { + addDecimal, + formatNumberPercentage, + toBigNum, +} from '@vegaprotocol/utils'; import { MarketState, MarketTradingMode } from '@vegaprotocol/types'; import BigNumber from 'bignumber.js'; import orderBy from 'lodash/orderBy'; @@ -147,10 +151,51 @@ export const calcCandleHigh = (candles: Candle[]): string | undefined => { .toString(); }; +/** + * The total number of contracts traded in the last 24 hours. + * + * @param candles + * @returns the volume of a given set of candles + */ export const calcCandleVolume = (candles: Candle[]): string | undefined => candles && candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0'); +/** + * The total number of contracts traded in the last 24 hours. (Total value of contracts traded in the last 24 hours) + * The volume is calculated as the sum of the product of the volume and the high price of each candle. + * The result is formatted using positionDecimalPlaces to account for the position size. + * The result is formatted using marketDecimals to account for the market precision. + * + * @param candles + * @param marketDecimals + * @param positionDecimalPlaces + * @returns the volume (in quote price) of a given set of candles + */ +export const calcCandleVolumePrice = ( + candles: Candle[], + marketDecimals: number = 1, + positionDecimalPlaces: number = 1 +): string | undefined => + candles && + candles.reduce( + (acc, c) => + new BigNumber(acc) + .plus( + BigNumber(addDecimal(c.volume, positionDecimalPlaces)).times( + addDecimal(c.high, marketDecimals) + ) + ) + .toString(), + '0' + ); + +/** + * Calculates the traded factor of a given market. + * + * @param m + * @returns + */ export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => { const volume = Number(calcCandleVolume(m.candles || []) || 0); const price = m.data?.markPrice ? Number(m.data.markPrice) : 0; From 46e2965fa24973507562679c7a4efe44d7b48c98 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 8 Feb 2024 12:25:16 +0000 Subject: [PATCH 07/17] chore(trading): fix test teardown (#5769) --- .github/workflows/console-test-run.yml | 2 +- apps/trading/e2e/.env | 2 +- apps/trading/e2e/conftest.py | 29 ++- .../e2e/tests/deal_ticket/test_basic.py | 8 +- .../e2e/tests/deal_ticket/test_stop_order.py | 9 +- ...test_trading_deal_ticket_submit_account.py | 7 +- apps/trading/e2e/tests/fees/test_fees.py | 17 +- .../e2e/tests/get_started/test_get_started.py | 9 +- .../iceberg_orders/test_iceberg_orders.py | 53 +++--- .../test_liquidity_provision.py | 9 +- .../e2e/tests/market/test_closed_markets.py | 9 +- .../e2e/tests/market/test_market_info.py | 9 +- .../e2e/tests/market/test_markets_proposed.py | 8 +- ...itoring_auction_price_volatility_market.py | 8 +- .../e2e/tests/navigation/test_navigation.py | 9 +- .../e2e/tests/order/test_order_status.py | 7 +- .../e2e/tests/orderbook/test_orderbook.py | 9 +- .../e2e/tests/positions/test_collateral.py | 8 +- .../e2e/tests/referrals/test_referrals.py | 8 +- .../trading/e2e/tests/rewards/test_rewards.py | 8 +- .../rewards/test_rewards_activity_tier_0.py | 176 ++++++++++++++++++ .../e2e/tests/settings/test_settings.py | 9 +- apps/trading/e2e/tests/teams/test_teams.py | 14 +- 23 files changed, 324 insertions(+), 103 deletions(-) create mode 100644 apps/trading/e2e/tests/rewards/test_rewards_activity_tier_0.py diff --git a/.github/workflows/console-test-run.yml b/.github/workflows/console-test-run.yml index 9943ab54c..27573fa84 100644 --- a/.github/workflows/console-test-run.yml +++ b/.github/workflows/console-test-run.yml @@ -205,7 +205,7 @@ jobs: # run tests #---------------------------------------------- - name: Run tests - run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 1 --dist loadfile --durations=45 + run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45 working-directory: apps/trading/e2e #---------------------------------------------- # upload traces diff --git a/apps/trading/e2e/.env b/apps/trading/e2e/.env index 4c307ec14..e35db6531 100644 --- a/apps/trading/e2e/.env +++ b/apps/trading/e2e/.env @@ -1,3 +1,3 @@ CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest -VEGA_VERSION=v0.74.0-preview.8 +VEGA_VERSION=v0.74.0-preview.10 LOCAL_SERVER=false diff --git a/apps/trading/e2e/conftest.py b/apps/trading/e2e/conftest.py index 8c3cafb3b..1574db6e9 100644 --- a/apps/trading/e2e/conftest.py +++ b/apps/trading/e2e/conftest.py @@ -111,6 +111,7 @@ def init_vega(request=None): f"Container {container.id} started", extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")}, ) + vega.container = container yield vega except APIError as e: logger.info(f"Container creation failed.") @@ -177,10 +178,34 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe @pytest.fixture def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) + yield vega_instance + +def cleanup_container(vega_instance): + try: + # Attempt to stop the container if it's still running + if vega_instance.container.status == 'running': + print(f"Stopping container {vega_instance.container.id}") + vega_instance.container.stop() + else: + print(f"Container {vega_instance.container.id} is not running.") + except docker.errors.NotFound: + print(f"Container {vega_instance.container.id} not found, may have been stopped and removed.") + except Exception as e: + print(f"Error during cleanup: {str(e)}") + + try: + # Attempt to remove the container + vega_instance.container.remove() + print(f"Container {vega_instance.container.id} removed.") + except docker.errors.NotFound: + print(f"Container {vega_instance.container.id} not found, may have been removed.") + except Exception as e: + print(f"Error during container removal: {str(e)}") + @pytest.fixture def page(vega, browser, request): with init_page(vega, browser, request) as page_instance: diff --git a/apps/trading/e2e/tests/deal_ticket/test_basic.py b/apps/trading/e2e/tests/deal_ticket/test_basic.py index 21691e139..0a97dff71 100644 --- a/apps/trading/e2e/tests/deal_ticket/test_basic.py +++ b/apps/trading/e2e/tests/deal_ticket/test_basic.py @@ -2,7 +2,7 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from datetime import datetime, timedelta -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market from actions.utils import wait_for_toast_confirmation @@ -17,8 +17,10 @@ expire = "expire" @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/deal_ticket/test_stop_order.py b/apps/trading/e2e/tests/deal_ticket/test_stop_order.py index 34cbdc84b..6ae055806 100644 --- a/apps/trading/e2e/tests/deal_ticket/test_stop_order.py +++ b/apps/trading/e2e/tests/deal_ticket/test_stop_order.py @@ -3,7 +3,7 @@ from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from actions.vega import submit_order from datetime import datetime, timedelta -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market stop_order_btn = "order-type-Stop" @@ -259,9 +259,10 @@ def test_submit_stop_limit_order_cancel( class TestStopOcoValidation: @pytest.fixture(scope="class") - def vega(self, request): - with init_vega(request) as vega: - yield vega + def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="class") def continuous_market(self, vega): diff --git a/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py b/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py index 971c730c2..82a2f2b17 100644 --- a/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py +++ b/apps/trading/e2e/tests/deal_ticket/test_trading_deal_ticket_submit_account.py @@ -2,7 +2,7 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from actions.utils import change_keys -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market order_size = "order-size" @@ -14,8 +14,9 @@ deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button" @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/fees/test_fees.py b/apps/trading/e2e/tests/fees/test_fees.py index b8976869a..f6bd79a75 100644 --- a/apps/trading/e2e/tests/fees/test_fees.py +++ b/apps/trading/e2e/tests/fees/test_fees.py @@ -4,7 +4,7 @@ from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from actions.vega import submit_order from wallet_config import MM_WALLET -from conftest import init_vega, init_page, auth_setup +from conftest import init_vega, init_page, auth_setup, cleanup_container from actions.utils import next_epoch, change_keys, forward_time from fixtures.market import market_exists, setup_continuous_market @@ -82,31 +82,36 @@ def market_ids(): @pytest.fixture(scope="module") def vega_volume_discount_tier_1(request): with init_vega(request) as vega_volume_discount_tier_1: - yield vega_volume_discount_tier_1 + request.addfinalizer(lambda: cleanup_container(vega_volume_discount_tier_1)) # Register the cleanup function + yield vega_volume_discount_tier_1 @pytest.fixture(scope="module") def vega_volume_discount_tier_2(request): with init_vega(request) as vega_volume_discount_tier_2: - yield vega_volume_discount_tier_2 + request.addfinalizer(lambda: cleanup_container(vega_volume_discount_tier_2)) # Register the cleanup function + yield vega_volume_discount_tier_2 @pytest.fixture(scope="module") def vega_referral_discount_tier_1(request): with init_vega(request) as vega_referral_discount_tier_1: - yield vega_referral_discount_tier_1 + request.addfinalizer(lambda: cleanup_container(vega_referral_discount_tier_1)) # Register the cleanup function + yield vega_referral_discount_tier_1 @pytest.fixture(scope="module") def vega_referral_discount_tier_2(request): with init_vega(request) as vega_referral_discount_tier_2: - yield vega_referral_discount_tier_2 + request.addfinalizer(lambda: cleanup_container(vega_referral_discount_tier_2)) # Register the cleanup function + yield vega_referral_discount_tier_2 @pytest.fixture(scope="module") def vega_referral_and_volume_discount(request): with init_vega(request) as vega_referral_and_volume_discount: - yield vega_referral_and_volume_discount + request.addfinalizer(lambda: cleanup_container(vega_referral_and_volume_discount)) # Register the cleanup function + yield vega_referral_and_volume_discount @pytest.fixture diff --git a/apps/trading/e2e/tests/get_started/test_get_started.py b/apps/trading/e2e/tests/get_started/test_get_started.py index b18e72c0a..478e2e045 100644 --- a/apps/trading/e2e/tests/get_started/test_get_started.py +++ b/apps/trading/e2e/tests/get_started/test_get_started.py @@ -3,7 +3,7 @@ from playwright.sync_api import expect, Page import json from vega_sim.null_service import VegaServiceNull from fixtures.market import setup_simple_market -from conftest import init_vega +from conftest import init_vega, cleanup_container from actions.vega import submit_order from wallet_config import MM_WALLET, TERMINATE_WALLET, wallets import logging @@ -12,9 +12,10 @@ logger = logging.getLogger() @pytest.fixture(scope="class") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="class") diff --git a/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py b/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py index 139d9e834..2d6b139e0 100644 --- a/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py +++ b/apps/trading/e2e/tests/iceberg_orders/test_iceberg_orders.py @@ -2,8 +2,6 @@ import pytest from playwright.sync_api import expect, Page from vega_sim.null_service import VegaServiceNull from actions.vega import submit_order -from conftest import init_vega -from fixtures.market import setup_continuous_market from wallet_config import MM_WALLET2 def hover_and_assert_tooltip(page: Page, element_text): @@ -11,39 +9,30 @@ def hover_and_assert_tooltip(page: Page, element_text): element.hover() expect(page.get_by_role("tooltip")).to_be_visible() -class TestIcebergOrdersValidations: - @pytest.fixture(scope="class") - def vega(self, request): - with init_vega(request) as vega: - yield vega - @pytest.fixture(scope="class") - def continuous_market(self, vega): - return setup_continuous_market(vega) +@pytest.mark.usefixtures("auth", "risk_accepted") +def test_iceberg_submit(continuous_market, vega: VegaServiceNull, page: Page): + page.goto(f"/#/markets/{continuous_market}") + page.get_by_test_id("iceberg").click() + page.get_by_test_id("order-peak-size").type("2") + page.get_by_test_id("order-minimum-size").type("1") + page.get_by_test_id("order-size").type("3") + page.get_by_test_id("order-price").type("107") + page.get_by_test_id("place-order").click() - @pytest.mark.usefixtures("auth", "risk_accepted") - def test_iceberg_submit(self, continuous_market, vega: VegaServiceNull, page: Page): - page.goto(f"/#/markets/{continuous_market}") - page.get_by_test_id("iceberg").click() - page.get_by_test_id("order-peak-size").type("2") - page.get_by_test_id("order-minimum-size").type("1") - page.get_by_test_id("order-size").type("3") - page.get_by_test_id("order-price").type("107") - page.get_by_test_id("place-order").click() + expect(page.get_by_test_id("toast-content")).to_have_text( + "Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer" + ) - expect(page.get_by_test_id("toast-content")).to_have_text( - "Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer" - ) - - vega.wait_fn(1) - vega.wait_for_total_catchup() - expect(page.get_by_test_id("toast-content")).to_have_text( - "Order filledYour transaction has been confirmedView in block explorerSubmit order - filledBTC:DAI_2023+3 @ 107.00 tDAI" - ) - page.get_by_test_id("All").click() - expect( - (page.get_by_role("row").locator('[col-id="type"]')).nth(1) - ).to_have_text("Limit (Iceberg)") + vega.wait_fn(1) + vega.wait_for_total_catchup() + expect(page.get_by_test_id("toast-content")).to_have_text( + "Order filledYour transaction has been confirmedView in block explorerSubmit order - filledBTC:DAI_2023+3 @ 107.00 tDAI" + ) + page.get_by_test_id("All").click() + expect( + (page.get_by_role("row").locator('[col-id="type"]')).nth(1) + ).to_have_text("Limit (Iceberg)") @pytest.mark.usefixtures("auth", "risk_accepted") def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page): diff --git a/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py b/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py index d798046a1..803f8e993 100644 --- a/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py +++ b/apps/trading/e2e/tests/liquidity_provision/test_liquidity_provision.py @@ -1,15 +1,16 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market from actions.utils import next_epoch, truncate_middle, change_keys - @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/market/test_closed_markets.py b/apps/trading/e2e/tests/market/test_closed_markets.py index 88067ec43..876fdfc9a 100644 --- a/apps/trading/e2e/tests/market/test_closed_markets.py +++ b/apps/trading/e2e/tests/market/test_closed_markets.py @@ -4,14 +4,15 @@ import vega_sim.api.governance as governance from vega_sim.null_service import VegaServiceNull from playwright.sync_api import Page, expect from fixtures.market import setup_continuous_market -from conftest import init_vega +from conftest import init_vega, cleanup_container from actions.utils import next_epoch @pytest.fixture(scope="class") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="class") diff --git a/apps/trading/e2e/tests/market/test_market_info.py b/apps/trading/e2e/tests/market/test_market_info.py index fdba2423d..e729efdf0 100644 --- a/apps/trading/e2e/tests/market/test_market_info.py +++ b/apps/trading/e2e/tests/market/test_market_info.py @@ -3,15 +3,16 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from fixtures.market import setup_continuous_market -from conftest import init_page, init_vega, risk_accepted_setup +from conftest import init_page, init_vega, risk_accepted_setup, cleanup_container market_title_test_id = "accordion-title" @pytest.fixture(scope="module") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/market/test_markets_proposed.py b/apps/trading/e2e/tests/market/test_markets_proposed.py index dfca5f830..b1a48a67c 100644 --- a/apps/trading/e2e/tests/market/test_markets_proposed.py +++ b/apps/trading/e2e/tests/market/test_markets_proposed.py @@ -3,7 +3,7 @@ import vega_sim.api.governance as governance import re from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_simple_market from wallet_config import MM_WALLET @@ -13,8 +13,10 @@ col_market_id = '[col-id="market"] [data-testid="stack-cell-primary"]' @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py b/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py index 107e77887..d4287e747 100644 --- a/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py +++ b/apps/trading/e2e/tests/market/test_monitoring_auction_price_volatility_market.py @@ -3,7 +3,7 @@ from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull from actions.vega import submit_order from fixtures.market import setup_simple_market -from conftest import init_vega +from conftest import init_vega, cleanup_container from actions.utils import wait_for_toast_confirmation, change_keys from wallet_config import MM_WALLET, MM_WALLET2 @@ -15,8 +15,10 @@ COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value" @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/navigation/test_navigation.py b/apps/trading/e2e/tests/navigation/test_navigation.py index a4bfa8614..0044c105e 100644 --- a/apps/trading/e2e/tests/navigation/test_navigation.py +++ b/apps/trading/e2e/tests/navigation/test_navigation.py @@ -1,13 +1,14 @@ import pytest from playwright.sync_api import Page, expect, Locator -from conftest import init_page, init_vega +from conftest import init_page, init_vega, cleanup_container @pytest.fixture(scope="module") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance # we can reuse single page instance in all tests diff --git a/apps/trading/e2e/tests/order/test_order_status.py b/apps/trading/e2e/tests/order/test_order_status.py index 659ccb96c..5e11fbd23 100644 --- a/apps/trading/e2e/tests/order/test_order_status.py +++ b/apps/trading/e2e/tests/order/test_order_status.py @@ -2,7 +2,7 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.service import PeggedOrder from vega_sim.null_service import VegaServiceNull -from conftest import auth_setup, init_page, init_vega, risk_accepted_setup +from conftest import auth_setup, init_page, init_vega, risk_accepted_setup, cleanup_container from fixtures.market import setup_continuous_market, setup_simple_market from actions.utils import wait_for_toast_confirmation @@ -11,8 +11,9 @@ order_tab = "tab-orders" @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="module", autouse=True) diff --git a/apps/trading/e2e/tests/orderbook/test_orderbook.py b/apps/trading/e2e/tests/orderbook/test_orderbook.py index 518fd4989..698292a3d 100644 --- a/apps/trading/e2e/tests/orderbook/test_orderbook.py +++ b/apps/trading/e2e/tests/orderbook/test_orderbook.py @@ -2,15 +2,16 @@ import pytest from playwright.sync_api import Page, expect from typing import List from actions.vega import submit_order, submit_liquidity, submit_multiple_orders -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_simple_market from wallet_config import MM_WALLET, MM_WALLET2 @pytest.fixture(scope="module") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/positions/test_collateral.py b/apps/trading/e2e/tests/positions/test_collateral.py index 5867b37b3..8fa86dfe0 100644 --- a/apps/trading/e2e/tests/positions/test_collateral.py +++ b/apps/trading/e2e/tests/positions/test_collateral.py @@ -1,7 +1,7 @@ import pytest from playwright.sync_api import Page, expect from vega_sim.null_service import VegaServiceNull -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market TOOLTIP_LABEL = "margin-health-tooltip-label" @@ -11,8 +11,10 @@ COL_ID_USED = ".ag-center-cols-container [col-id='used'] .ag-cell-value" @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/referrals/test_referrals.py b/apps/trading/e2e/tests/referrals/test_referrals.py index 1f81cae07..53f276df7 100644 --- a/apps/trading/e2e/tests/referrals/test_referrals.py +++ b/apps/trading/e2e/tests/referrals/test_referrals.py @@ -1,7 +1,7 @@ import pytest from playwright.sync_api import Page from vega_sim.null_service import VegaServiceNull -from conftest import init_vega +from conftest import init_vega, cleanup_container from fixtures.market import setup_continuous_market, setup_simple_market from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text from actions.vega import submit_order, submit_liquidity @@ -14,8 +14,10 @@ BUY_ORDERS = [[1, 106], [1, 107], [1, 108]] @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + @pytest.fixture(scope="module") diff --git a/apps/trading/e2e/tests/rewards/test_rewards.py b/apps/trading/e2e/tests/rewards/test_rewards.py index ea465ca73..df49fb13d 100644 --- a/apps/trading/e2e/tests/rewards/test_rewards.py +++ b/apps/trading/e2e/tests/rewards/test_rewards.py @@ -2,7 +2,7 @@ import pytest import vega_sim.proto.vega as vega_protos from playwright.sync_api import Page, expect -from conftest import init_vega, init_page, auth_setup +from conftest import init_vega, init_page, auth_setup, cleanup_container from fixtures.market import setup_continuous_market, market_exists from actions.utils import next_epoch, change_keys from wallet_config import MM_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D @@ -46,36 +46,42 @@ def market_ids(): @pytest.fixture(scope="module") def vega_activity_tier_0(request): with init_vega(request) as vega_activity_tier_0: + request.addfinalizer(lambda: cleanup_container(vega_activity_tier_0)) # Register the cleanup function yield vega_activity_tier_0 @pytest.fixture(scope="module") def vega_hoarder_tier_0(request): with init_vega(request) as vega_hoarder_tier_0: + request.addfinalizer(lambda: cleanup_container(vega_hoarder_tier_0)) # Register the cleanup function yield vega_hoarder_tier_0 @pytest.fixture(scope="module") def vega_combo_tier_0(request): with init_vega(request) as vega_combo_tier_0: + request.addfinalizer(lambda: cleanup_container(vega_combo_tier_0)) # Register the cleanup function yield vega_combo_tier_0 @pytest.fixture(scope="module") def vega_activity_tier_1(request): with init_vega(request) as vega_activity_tier_1: + request.addfinalizer(lambda: cleanup_container(vega_activity_tier_1)) # Register the cleanup function yield vega_activity_tier_1 @pytest.fixture(scope="module") def vega_hoarder_tier_1(request): with init_vega(request) as vega_hoarder_tier_1: + request.addfinalizer(lambda: cleanup_container(vega_hoarder_tier_1)) # Register the cleanup function yield vega_hoarder_tier_1 @pytest.fixture(scope="module") def vega_combo_tier_1(request): with init_vega(request) as vega_combo_tier_1: + request.addfinalizer(lambda: cleanup_container(vega_combo_tier_1)) # Register the cleanup function yield vega_combo_tier_1 diff --git a/apps/trading/e2e/tests/rewards/test_rewards_activity_tier_0.py b/apps/trading/e2e/tests/rewards/test_rewards_activity_tier_0.py new file mode 100644 index 000000000..119672e6f --- /dev/null +++ b/apps/trading/e2e/tests/rewards/test_rewards_activity_tier_0.py @@ -0,0 +1,176 @@ +import pytest +import vega_sim.proto.vega as vega_protos +from playwright.sync_api import Page, expect +from conftest import init_vega, init_page, auth_setup, risk_accepted_setup, cleanup_container +from fixtures.market import setup_continuous_market +from actions.utils import next_epoch, change_keys, create_and_faucet_wallet +from wallet_config import MM_WALLET, WalletConfig +from vega_sim.null_service import VegaServiceNull + +# region Constants +ACTIVITY = "activity" +HOARDER = "hoarder" +COMBO = "combo" + +REWARDS_URL = "/#/rewards" + +# test IDs +COMBINED_MULTIPLIERS = "combined-multipliers" +TOTAL_REWARDS = "total-rewards" +PRICE_TAKING_COL_ID = '[col-id="priceTaking"]' +TOTAL_COL_ID = '[col-id="total"]' +ROW = "row" +STREAK_REWARD_MULTIPLIER_VALUE = "streak-reward-multiplier-value" +HOARDER_REWARD_MULTIPLIER_VALUE = "hoarder-reward-multiplier-value" +HOARDER_BONUS_TOTAL_HOARDED = "hoarder-bonus-total-hoarded" +EARNED_BY_ME_BUTTON = "earned-by-me-button" +TRANSFER_AMOUNT = "transfer-amount" +EPOCH_STREAK = "epoch-streak" + +# endregion + +# Keys +PARTY_A = "PARTY_A" +PARTY_B = "PARTY_B" +PARTY_C = "PARTY_C" +PARTY_D = "PARTY_D" + + +@pytest.fixture(scope="module") +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance + + + +@pytest.fixture(scope="module") +def page(vega, browser, request): + with init_page(vega, browser, request) as page: + risk_accepted_setup(page) + auth_setup(vega, page) + page.goto(REWARDS_URL) + change_keys(page, vega, PARTY_B) + yield page + + +@pytest.fixture(scope="module", autouse=True) +def setup_market_with_reward_program(vega: VegaServiceNull): + tDAI_market = setup_continuous_market(vega) + PARTY_A, PARTY_B, PARTY_C, PARTY_D = keys(vega) + tDAI_asset_id = vega.find_asset_id(symbol="tDAI") + vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000) + vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000) + vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000) + vega.mint(key_name=PARTY_D.name, asset=tDAI_asset_id, amount=100000) + next_epoch(vega=vega) + + vega.update_network_parameter( + proposal_key=MM_WALLET.name, + parameter="rewards.activityStreak.benefitTiers", + new_value=ACTIVITY_STREAKS, + ) + print("update_network_parameter activity done") + next_epoch(vega=vega) + + tDAI_asset_id = vega.find_asset_id(symbol="tDAI") + vega.update_network_parameter( + MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id + ) + + next_epoch(vega=vega) + vega.recurring_transfer( + from_key_name=PARTY_A.name, + from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL, + to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES, + asset=tDAI_asset_id, + reference="reward", + asset_for_metric=tDAI_asset_id, + metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID, + amount=100, + factor=1.0, + ) + vega.submit_order( + trading_key=PARTY_B.name, + market_id=tDAI_market, + order_type="TYPE_MARKET", + time_in_force="TIME_IN_FORCE_IOC", + side="SIDE_BUY", + volume=1, + ) + vega.submit_order( + trading_key=PARTY_A.name, + market_id=tDAI_market, + order_type="TYPE_MARKET", + time_in_force="TIME_IN_FORCE_IOC", + side="SIDE_BUY", + volume=1, + ) + + vega.wait_for_total_catchup() + + next_epoch(vega=vega) + return tDAI_market, tDAI_asset_id + + +ACTIVITY_STREAKS = """ +{ + "tiers": [ + { + "minimum_activity_streak": 2, + "reward_multiplier": "2.0", + "vesting_multiplier": "1.1" + } + ] +} +""" + + +def keys(vega): + PARTY_A = WalletConfig("PARTY_A", "PARTY_A") + create_and_faucet_wallet(vega=vega, wallet=PARTY_A) + PARTY_B = WalletConfig("PARTY_B", "PARTY_B") + create_and_faucet_wallet(vega=vega, wallet=PARTY_B) + PARTY_C = WalletConfig("PARTY_C", "PARTY_C") + create_and_faucet_wallet(vega=vega, wallet=PARTY_C) + PARTY_D = WalletConfig("PARTY_D", "PARTY_D") + create_and_faucet_wallet(vega=vega, wallet=PARTY_D) + return PARTY_A, PARTY_B, PARTY_C, PARTY_D + + +@pytest.mark.xdist_group(name="test_rewards_activity_tier_0") +def test_network_reward_pot( + page: Page, +): + expect(page.get_by_test_id(TOTAL_REWARDS)).to_have_text("50.00 tDAI") + + +@pytest.mark.xdist_group(name="test_rewards_activity_tier_0") +def test_reward_multiplier( + page: Page, +): + expect(page.get_by_test_id(COMBINED_MULTIPLIERS)).to_have_text("1x") + expect(page.get_by_test_id(STREAK_REWARD_MULTIPLIER_VALUE)).to_have_text("1x") + expect(page.get_by_test_id(HOARDER_REWARD_MULTIPLIER_VALUE)).to_have_text("1x") + + +@pytest.mark.xdist_group(name="test_rewards_activity_tier_0") +def test_activity_streak( + page: Page, +): + expect(page.get_by_test_id(EPOCH_STREAK)).to_have_text( + "Active trader: 1 epochs so far " + ) + + +@pytest.mark.xdist_group(name="test_rewards_activity_tier_0") +def test_reward_history( + page: Page, +): + page.locator('[name="fromEpoch"]').fill("1") + expect((page.get_by_role(ROW).locator(PRICE_TAKING_COL_ID)).nth(1)).to_have_text( + "100.00100.00%" + ) + expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text("100.00") + page.get_by_test_id(EARNED_BY_ME_BUTTON).click() + expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text("50.00") diff --git a/apps/trading/e2e/tests/settings/test_settings.py b/apps/trading/e2e/tests/settings/test_settings.py index d929ccc32..ea052bc40 100644 --- a/apps/trading/e2e/tests/settings/test_settings.py +++ b/apps/trading/e2e/tests/settings/test_settings.py @@ -1,12 +1,13 @@ import pytest from playwright.sync_api import expect, Page -from conftest import init_vega +from conftest import init_vega, cleanup_container @pytest.fixture(scope="module") -def vega(): - with init_vega() as vega: - yield vega +def vega(request): + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.mark.usefixtures("risk_accepted") diff --git a/apps/trading/e2e/tests/teams/test_teams.py b/apps/trading/e2e/tests/teams/test_teams.py index a22102ac3..56559d3ec 100644 --- a/apps/trading/e2e/tests/teams/test_teams.py +++ b/apps/trading/e2e/tests/teams/test_teams.py @@ -2,17 +2,17 @@ import pytest from playwright.sync_api import expect, Page import vega_sim.proto.vega as vega_protos from vega_sim.null_service import VegaServiceNull -from conftest import init_vega +from conftest import init_vega, cleanup_container from actions.utils import next_epoch, change_keys from fixtures.market import setup_continuous_market from conftest import auth_setup, init_page, init_vega, risk_accepted_setup from wallet_config import PARTY_A, PARTY_B, PARTY_C, PARTY_D, MM_WALLET - @pytest.fixture(scope="module") def vega(request): - with init_vega(request) as vega: - yield vega + with init_vega(request) as vega_instance: + request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function + yield vega_instance @pytest.fixture(scope="module") @@ -237,12 +237,12 @@ def test_team_page_headline(team_page: Page, setup_teams_and_games): expect(team_page.get_by_test_id("team-name")).to_have_text(team_name) expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4") - expect(team_page.get_by_test_id("total-games-stat")).to_have_text("2") + expect(team_page.get_by_test_id("total-games-stat")).to_have_text("1") # TODO this still seems wrong as its always 0 expect(team_page.get_by_test_id("total-volume-stat")).to_have_text("0") - expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("214") + expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("78") def test_switch_teams(team_page: Page, vega: VegaServiceNull): @@ -271,7 +271,7 @@ def test_leaderboard(competitions_page: Page, setup_teams_and_games): # FIXME: the numbers are different we need to clarify this with the backend # expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160") - expect(competitions_page.get_by_test_id("games-1")).to_have_text("2") + expect(competitions_page.get_by_test_id("games-1")).to_have_text("1") # TODO still odd that this is 0 expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-") From 76c07992d3422fa9ffc757625317812eb71461e0 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:24:48 +0200 Subject: [PATCH 08/17] feat(trading): update mobile layout (#5718) Co-authored-by: Matthew Russell --- apps/explorer/src/assets/manifest.json | 4 +- apps/governance/src/assets/manifest.json | 4 +- apps/static/src/index.html | 2 +- apps/trading/assets/manifest.json | 16 ++- .../market/market-header-stats.tsx | 3 +- .../client-pages/market/trade-panels.tsx | 113 +++++++++------ .../layouts/layout-with-sidebar.tsx | 12 +- .../trading/components/market-header/index.ts | 1 + .../market-header/mobile-market-header.tsx | 133 ++++++++++++++++++ apps/trading/components/navbar/index.tsx | 1 - apps/trading/components/navbar/nav-header.tsx | 81 ----------- apps/trading/components/navbar/navbar.tsx | 10 +- apps/trading/pages/_app.page.tsx | 15 +- apps/trading/pages/_document.page.tsx | 7 +- apps/trading/pages/client-router.tsx | 11 +- apps/trading/public/manifest.json | 22 +++ .../last-24h-price-change.tsx | 7 +- 17 files changed, 267 insertions(+), 175 deletions(-) create mode 100644 apps/trading/components/market-header/mobile-market-header.tsx delete mode 100644 apps/trading/components/navbar/nav-header.tsx create mode 100644 apps/trading/public/manifest.json diff --git a/apps/explorer/src/assets/manifest.json b/apps/explorer/src/assets/manifest.json index 949569331..4cbc76d39 100644 --- a/apps/explorer/src/assets/manifest.json +++ b/apps/explorer/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "short_name": "Explorer VEGA", + "name": "Vega Protocol - Explorer", "icons": [ { "src": "favicon.ico", diff --git a/apps/governance/src/assets/manifest.json b/apps/governance/src/assets/manifest.json index 949569331..4779dbc73 100644 --- a/apps/governance/src/assets/manifest.json +++ b/apps/governance/src/assets/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "short_name": "Governance VEGA", + "name": "Vega Protocol - Governance", "icons": [ { "src": "favicon.ico", diff --git a/apps/static/src/index.html b/apps/static/src/index.html index 2ab0a6f71..b49517237 100644 --- a/apps/static/src/index.html +++ b/apps/static/src/index.html @@ -4,7 +4,7 @@ - Vega Protocol static asseets + Vega Protocol static assets diff --git a/apps/trading/assets/manifest.json b/apps/trading/assets/manifest.json index 949569331..37981d3d3 100644 --- a/apps/trading/assets/manifest.json +++ b/apps/trading/assets/manifest.json @@ -1,6 +1,12 @@ { - "short_name": "Mainnet Stats", - "name": "Vega Mainnet statistics", + "name": "Vega Protocol - Trading", + "short_name": "Console", + "description": "Vega Protocol - Trading dApp", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "theme_color": "#000000", + "background_color": "#ffffff", "icons": [ { "src": "favicon.ico", @@ -12,9 +18,5 @@ "type": "image/png", "sizes": "192x192" } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + ] } diff --git a/apps/trading/client-pages/market/market-header-stats.tsx b/apps/trading/client-pages/market/market-header-stats.tsx index 38952d49e..c9ee0dee6 100644 --- a/apps/trading/client-pages/market/market-header-stats.tsx +++ b/apps/trading/client-pages/market/market-header-stats.tsx @@ -56,6 +56,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { -} /> @@ -112,7 +113,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => { heading={`${t('Funding Rate')} / ${t('Countdown')}`} testId="market-funding" > -
+
diff --git a/apps/trading/client-pages/market/trade-panels.tsx b/apps/trading/client-pages/market/trade-panels.tsx index 870a87509..759a56364 100644 --- a/apps/trading/client-pages/market/trade-panels.tsx +++ b/apps/trading/client-pages/market/trade-panels.tsx @@ -3,7 +3,6 @@ import { type Market } from '@vegaprotocol/markets'; // TODO: handle oracle banner // import { OracleBanner } from '@vegaprotocol/markets'; import { useState } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; import classNames from 'classnames'; import { Popover, @@ -12,21 +11,21 @@ import { VegaIconNames, } from '@vegaprotocol/ui-toolkit'; import { useT } from '../../lib/use-t'; -import { MarketBanner } from '../../components/market-banner'; import { ErrorBoundary } from '../../components/error-boundary'; import { type TradingView } from './trade-views'; import { TradingViews } from './trade-views'; - interface TradePanelsProps { market: Market; pinnedAsset?: PinnedAsset; } export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { - const [view, setView] = useState('chart'); - const viewCfg = TradingViews[view]; + const [topView, setTopView] = useState('chart'); + const topViewCfg = TradingViews[topView]; + const [bottomView, setBottomView] = useState('positions'); + const bottomViewCfg = TradingViews[bottomView]; - const renderView = () => { + const renderView = (view: TradingView) => { const Component = TradingViews[view].component; if (!Component) { @@ -39,12 +38,13 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { // so watch out for clashes in props return ( - ; + ); }; - const renderMenu = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const renderMenu = (viewCfg: any) => { if ('menu' in viewCfg || 'settings' in viewCfg) { return (
@@ -69,55 +69,80 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { }; return ( -
-
- -
-
{renderMenu()}
-
- - {({ width, height }) => ( -
- {renderView()} -
- )} -
-
-
- {Object.keys(TradingViews) - // filter to control available views for the current market - // eg only perps should get the funding views - .filter((_key) => { - const key = _key as TradingView; - const perpOnlyViews = ['funding', 'fundingPayments']; +
+
+
+ {['chart', 'orderbook', 'trades', 'liquidity', 'fundingPayments'] + // filter to control available views for the current market + // e.g. only perpetuals should get the funding views + .filter((_key) => { + const key = _key as TradingView; + const perpOnlyViews = ['funding', 'fundingPayments']; + + if ( + market?.tradableInstrument.instrument.product.__typename === + 'Perpetual' + ) { + return true; + } + + if (perpOnlyViews.includes(key)) { + return false; + } - if ( - market?.tradableInstrument.instrument.product.__typename === - 'Perpetual' - ) { return true; - } + }) + .map((_key) => { + const key = _key as TradingView; + const isActive = topView === key; + return ( + { + setTopView(key); + }} + /> + ); + })} +
+
+
{renderMenu(topViewCfg)}
+
{renderView(topView)}
+
+
- if (perpOnlyViews.includes(key)) { - return false; - } - - return true; - }) - .map((_key) => { +
+
+ {[ + 'positions', + 'activeOrders', + 'closedOrders', + 'rejectedOrders', + 'orders', + 'stopOrders', + 'collateral', + 'fills', + ].map((_key) => { const key = _key as TradingView; - const isActive = view === key; + const isActive = bottomView === key; return ( { - setView(key); + setBottomView(key); }} /> ); })} +
+
+
{renderMenu(bottomViewCfg)}
+
{renderView(bottomView)}
+
); @@ -157,7 +182,7 @@ const useViewLabel = (view: TradingView) => { depth: t('Depth'), liquidity: t('Liquidity'), funding: t('Funding'), - fundingPayments: t('Funding Payments'), + fundingPayments: t('Funding'), orderbook: t('Orderbook'), trades: t('Trades'), positions: t('Positions'), diff --git a/apps/trading/components/layouts/layout-with-sidebar.tsx b/apps/trading/components/layouts/layout-with-sidebar.tsx index eec996706..937113c62 100644 --- a/apps/trading/components/layouts/layout-with-sidebar.tsx +++ b/apps/trading/components/layouts/layout-with-sidebar.tsx @@ -3,7 +3,6 @@ import { Outlet } from 'react-router-dom'; import { Sidebar, SidebarContent, useSidebar } from '../sidebar'; import classNames from 'classnames'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; - export const LayoutWithSidebar = ({ header, sidebar, @@ -27,10 +26,13 @@ export const LayoutWithSidebar = ({
{header}
diff --git a/apps/trading/components/market-header/index.ts b/apps/trading/components/market-header/index.ts index fe42898da..54e3fbc3e 100644 --- a/apps/trading/components/market-header/index.ts +++ b/apps/trading/components/market-header/index.ts @@ -1 +1,2 @@ export * from './market-header'; +export * from './mobile-market-header'; diff --git a/apps/trading/components/market-header/mobile-market-header.tsx b/apps/trading/components/market-header/mobile-market-header.tsx new file mode 100644 index 000000000..3e2438882 --- /dev/null +++ b/apps/trading/components/market-header/mobile-market-header.tsx @@ -0,0 +1,133 @@ +import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; +import { MarketSelector } from '../market-selector'; +import { + Last24hPriceChange, + useMarket, + useMarketList, +} from '@vegaprotocol/markets'; +import { useParams } from 'react-router-dom'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import { useState } from 'react'; +import { useT } from '../../lib/use-t'; +import classNames from 'classnames'; +import { MarketHeaderStats } from '../../client-pages/market/market-header-stats'; +import { MarketMarkPrice } from '../market-mark-price'; +/** + * This is only rendered for the mobile navigation + */ +export const MobileMarketHeader = () => { + const t = useT(); + const { marketId } = useParams(); + const { data } = useMarket(marketId); + const [openMarket, setOpenMarket] = useState(false); + const [openPrice, setOpenPrice] = useState(false); + + // Ensure that markets are kept cached so opening the list + // shows all markets instantly + useMarketList(); + + if (!marketId) return null; + + return ( +
+ { + setOpenMarket(x); + }} + trigger={ +

+ {data + ? data.tradableInstrument.instrument.code + : t('Select market')} + + + +

+ } + > + setOpenMarket(false)} + /> +
+ { + setOpenPrice(x); + }} + trigger={ + + {data && ( + <> + + + + + + + + + )} + + } + > + {data && ( +
+ +
+ )} +
+
+ ); +}; + +export interface PopoverProps extends PopoverPrimitive.PopoverProps { + trigger: React.ReactNode | string; +} + +export const FullScreenPopover = ({ + trigger, + children, + open, + onOpenChange, +}: PopoverProps) => { + return ( + + + {trigger} + + + + {children} + + + + ); +}; diff --git a/apps/trading/components/navbar/index.tsx b/apps/trading/components/navbar/index.tsx index 376d1c7c5..f5899d036 100644 --- a/apps/trading/components/navbar/index.tsx +++ b/apps/trading/components/navbar/index.tsx @@ -1,2 +1 @@ export * from './navbar'; -export * from './nav-header'; diff --git a/apps/trading/components/navbar/nav-header.tsx b/apps/trading/components/navbar/nav-header.tsx deleted file mode 100644 index 0715900a7..000000000 --- a/apps/trading/components/navbar/nav-header.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; -import { MarketSelector } from '../market-selector'; -import { useMarket, useMarketList } from '@vegaprotocol/markets'; -import { useParams } from 'react-router-dom'; -import * as PopoverPrimitive from '@radix-ui/react-popover'; -import { useState } from 'react'; -import { useT } from '../../lib/use-t'; -import classNames from 'classnames'; - -/** - * This is only rendered for the mobile navigation - */ -export const NavHeader = () => { - const t = useT(); - const { marketId } = useParams(); - const { data } = useMarket(marketId); - const [open, setOpen] = useState(false); - - // Ensure that markets are kept cached so opening the list - // shows all markets instantly - useMarketList(); - - if (!marketId) return null; - - return ( - { - setOpen(x); - }} - trigger={ -

- {data ? data.tradableInstrument.instrument.code : t('Select market')} - - - -

- } - > - setOpen(false)} - /> -
- ); -}; - -export interface PopoverProps extends PopoverPrimitive.PopoverProps { - trigger: React.ReactNode | string; -} - -export const FullScreenPopover = ({ - trigger, - children, - open, - onOpenChange, -}: PopoverProps) => { - return ( - - - {trigger} - - - - {children} - - - - ); -}; diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index b304df2d4..476e5465f 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -34,13 +34,7 @@ import { supportedLngs } from '../../lib/i18n'; type MenuState = 'wallet' | 'nav' | null; type Theme = 'system' | 'yellow'; -export const Navbar = ({ - children, - theme = 'system', -}: { - children?: ReactNode; - theme?: Theme; -}) => { +export const Navbar = ({ theme = 'system' }: { theme?: Theme }) => { const i18n = useI18n(); const t = useT(); // menu state for small screens @@ -75,8 +69,6 @@ export const Navbar = ({ > - {/* Left section */} -
{children}
{/* Used to show header in nav on mobile */}
setMenu(null)} /> diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index e2273fc90..e6913f4d2 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -14,7 +14,7 @@ import './styles.css'; import { usePageTitleStore } from '../stores'; import DialogsContainer from './dialogs-container'; import ToastsManager from './toasts-manager'; -import { HashRouter, useLocation, Route, Routes } from 'react-router-dom'; +import { HashRouter, useLocation } from 'react-router-dom'; import { Bootstrapper } from '../components/bootstrapper'; import { AnnouncementBanner } from '../components/banner'; import { Navbar } from '../components/navbar'; @@ -25,9 +25,7 @@ import { ProtocolUpgradeProposalNotification, } from '@vegaprotocol/proposals'; import { ViewingBanner } from '../components/viewing-banner'; -import { NavHeader } from '../components/navbar/nav-header'; import { Telemetry } from '../components/telemetry'; -import { Routes as AppRoutes } from '../lib/links'; import { SSRLoader } from './ssr-loader'; import { PartyActiveOrdersHandler } from './party-active-orders-handler'; import { MaybeConnectEagerly } from './maybe-connect-eagerly'; @@ -73,16 +71,7 @@ function AppBody({ Component }: AppProps) { <div className={gridClasses}> <AnnouncementBanner /> - <Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'}> - <Routes> - <Route - path={AppRoutes.MARKETS} - // render nothing for markets/all, otherwise markets/:marketId will match with markets/all - element={null} - /> - <Route path={AppRoutes.MARKET} element={<NavHeader />} /> - </Routes> - </Navbar> + <Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} /> <div data-testid="banners"> <ProtocolUpgradeProposalNotification mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING} diff --git a/apps/trading/pages/_document.page.tsx b/apps/trading/pages/_document.page.tsx index 80e28f715..4ddd0fa58 100644 --- a/apps/trading/pages/_document.page.tsx +++ b/apps/trading/pages/_document.page.tsx @@ -24,11 +24,14 @@ export default function Document() { {/* scripts */} <script src="/theme-setter.js" type="text/javascript" async /> + + {/* manifest */} + <link rel="manifest" href="/apps/trading/public/manifest.json" /> </Head> <Html> <body - // Nextjs will set body to display none until js runs. Because the entire app is client rendered - // and delivered via ipfs we override this to show a server side render loading animation until the + // Next.js will set body to display none until js runs. Because the entire app is client rendered + // and delivered via IPFS we override this to show a server side render loading animation until the // js is downloaded and react takes over rendering style={{ display: 'block' }} className="bg-white dark:bg-vega-cdark-900 text-default font-alpha" diff --git a/apps/trading/pages/client-router.tsx b/apps/trading/pages/client-router.tsx index 832e9a7cc..5db2ad224 100644 --- a/apps/trading/pages/client-router.tsx +++ b/apps/trading/pages/client-router.tsx @@ -23,7 +23,7 @@ import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-bo import { compact } from 'lodash'; import { useFeatureFlags } from '@vegaprotocol/environment'; import { LiquidityHeader } from '../components/liquidity-header'; -import { MarketHeader } from '../components/market-header'; +import { MarketHeader, MobileMarketHeader } from '../components/market-header'; import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar'; import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar'; import { MarketsSidebar } from '../client-pages/markets/markets-sidebar'; @@ -33,6 +33,7 @@ import { CompetitionsTeams } from '../client-pages/competitions/competitions-tea import { CompetitionsTeam } from '../client-pages/competitions/competitions-team'; import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team'; import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; // These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM // Using dynamic imports is a workaround for this until pennant is published as ESM @@ -50,6 +51,9 @@ const NotFound = () => { export const useRouterConfig = (): RouteObject[] => { const featureFlags = useFeatureFlags((state) => state.flags); + const { screenSize } = useScreenDimensions(); + const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize); + const marketHeader = largeScreen ? <MarketHeader /> : <MobileMarketHeader />; const routeConfig = compact([ { index: true, @@ -151,10 +155,7 @@ export const useRouterConfig = (): RouteObject[] => { { path: 'markets/*', element: ( - <LayoutWithSidebar - header={<MarketHeader />} - sidebar={<MarketsSidebar />} - /> + <LayoutWithSidebar header={marketHeader} sidebar={<MarketsSidebar />} /> ), children: [ { diff --git a/apps/trading/public/manifest.json b/apps/trading/public/manifest.json new file mode 100644 index 000000000..77abf4cc7 --- /dev/null +++ b/apps/trading/public/manifest.json @@ -0,0 +1,22 @@ +{ + "name": "Vega Protocol - Trading", + "short_name": "Console", + "description": "Vega Protocol - Trading dApp", + "start_url": "/", + "display": "standalone", + "orientation": "portrait", + "theme_color": "#000000", + "background_color": "#ffffff", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "cover.png", + "type": "image/png", + "sizes": "192x192" + } + ] +} diff --git a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx index 4a1afafab..828841bf4 100644 --- a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx +++ b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx @@ -18,12 +18,15 @@ interface Props { initialValue?: string[]; isHeader?: boolean; noUpdate?: boolean; + // render prop for no price change + fallback?: React.ReactNode; } export const Last24hPriceChange = ({ marketId, decimalPlaces, initialValue, + fallback, }: Props) => { const t = useT(); const { oneDayCandles, error, fiveDaysCandles } = useCandles({ @@ -48,13 +51,13 @@ export const Last24hPriceChange = ({ </span> } > - <span>-</span> + <span>{fallback}</span> </Tooltip> ); } if (error || !isNumeric(decimalPlaces)) { - return <span>-</span>; + return <span>{fallback}</span>; } const candles = oneDayCandles?.map((c) => c.close) || initialValue || []; From 5ddcb613e226b149bcbf7af6753b1e80a0e08f01 Mon Sep 17 00:00:00 2001 From: Art <artur@vegaprotocol.io> Date: Thu, 8 Feb 2024 15:07:48 +0100 Subject: [PATCH 09/17] chore(trading): create and update team form traversing (#5764) --- .../competitions/competitions-create-team.tsx | 42 +++++++++---- .../competitions/competitions-update-team.tsx | 63 ++++++++++++++++++- .../components/competitions/team-avatar.tsx | 24 ++++++- libs/i18n/src/locales/en/trading.json | 6 +- 4 files changed, 119 insertions(+), 16 deletions(-) diff --git a/apps/trading/client-pages/competitions/competitions-create-team.tsx b/apps/trading/client-pages/competitions/competitions-create-team.tsx index 21661fef3..19710ffef 100644 --- a/apps/trading/client-pages/competitions/competitions-create-team.tsx +++ b/apps/trading/client-pages/competitions/competitions-create-team.tsx @@ -1,5 +1,10 @@ -import { useSearchParams } from 'react-router-dom'; -import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit'; +import { Link, useSearchParams } from 'react-router-dom'; +import { + Intent, + TradingAnchorButton, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { useT } from '../../lib/use-t'; @@ -30,6 +35,19 @@ export const CompetitionsCreateTeam = () => { <LayoutWithGradient> <div className="mx-auto md:w-2/3 max-w-xl"> <Box className="flex flex-col gap-4"> + <Link + to={Links.COMPETITIONS()} + className="text-xs inline-flex items-center gap-1 group" + > + <VegaIcon + name={VegaIconNames.CHEVRON_LEFT} + size={12} + className="text-vega-clight-100 dark:text-vega-cdark-100" + />{' '} + <span className="group-hover:underline"> + {t('Go back to the competitions')} + </span> + </Link> <h1 className="calt text-2xl lg:text-3xl xl:text-4xl"> {isSolo ? t('Create solo team') : t('Create a team')} </h1> @@ -78,15 +96,17 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => { <p className="text-sm">{t('Team creation transaction successful')}</p> {code && ( <> - <p className="text-sm"> - Your team ID is:{' '} - <span - className="font-mono break-all" - data-testid="team-id-display" - > - {code} - </span> - </p> + <dl> + <dt className="text-sm">{t('Your team ID:')}</dt> + <dl> + <span + className="font-mono break-all bg-rainbow bg-clip-text text-transparent text-2xl" + data-testid="team-id-display" + > + {code} + </span> + </dl> + </dl> <TradingAnchorButton href={Links.COMPETITIONS_TEAM(code)} intent={Intent.Info} diff --git a/apps/trading/client-pages/competitions/competitions-update-team.tsx b/apps/trading/client-pages/competitions/competitions-update-team.tsx index f0dae7c88..f8bd9c2d1 100644 --- a/apps/trading/client-pages/competitions/competitions-update-team.tsx +++ b/apps/trading/client-pages/competitions/competitions-update-team.tsx @@ -3,7 +3,14 @@ import { usePageTitle } from '../../lib/hooks/use-page-title'; import { Box } from '../../components/competitions/box'; import { useT } from '../../lib/use-t'; import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; -import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; +import { + Intent, + Loader, + Splash, + TradingAnchorButton, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; import { RainbowButton } from '../../components/rainbow-button'; import { Link, Navigate, useParams } from 'react-router-dom'; import { Links } from '../../lib/links'; @@ -11,6 +18,7 @@ import { useReferralSetTransaction } from '../../lib/hooks/use-referral-set-tran import { type FormFields, TeamForm, TransactionType } from './team-form'; import { useTeam } from '../../lib/hooks/use-team'; import { LayoutWithGradient } from '../../components/layouts-inner'; +import { useEffect, useState } from 'react'; export const CompetitionsUpdateTeam = () => { const t = useT(); @@ -29,6 +37,19 @@ export const CompetitionsUpdateTeam = () => { <LayoutWithGradient> <div className="mx-auto md:w-2/3 max-w-xl"> <Box className="flex flex-col gap-4"> + <Link + to={Links.COMPETITIONS_TEAM(teamId)} + className="text-xs inline-flex items-center gap-1 group" + > + <VegaIcon + name={VegaIconNames.CHEVRON_LEFT} + size={12} + className="text-vega-clight-100 dark:text-vega-cdark-100" + />{' '} + <span className="group-hover:underline"> + {t('Go back to the team profile')} + </span> + </Link> <h1 className="calt text-2xl lg:text-3xl xl:text-5xl"> {t('Update a team')} </h1> @@ -57,7 +78,8 @@ const UpdateTeamFormContainer = ({ pubKey: string; }) => { const t = useT(); - const { team, loading, error } = useTeam(teamId, pubKey); + const [refetching, setRefetching] = useState<boolean>(false); + const { team, loading, error, refetch } = useTeam(teamId, pubKey); const { err, status, onSubmit } = useReferralSetTransaction({ onSuccess: () => { @@ -65,7 +87,15 @@ const UpdateTeamFormContainer = ({ }, }); - if (loading) { + // refetch when saved + useEffect(() => { + if (refetch && status === 'confirmed') { + refetch(); + setRefetching(true); + } + }, [refetch, status]); + + if (loading && !refetching) { return <Loader size="small" />; } if (error) { @@ -84,6 +114,33 @@ const UpdateTeamFormContainer = ({ return <Navigate to={Links.COMPETITIONS_TEAM(teamId)} />; } + if (status === 'confirmed') { + return ( + <div + className="flex flex-col items-start gap-2" + data-testid="team-creation-success-message" + > + <p className="text-sm"> + <VegaIcon + name={VegaIconNames.TICK} + size={18} + className="text-vega-green-500" + />{' '} + {t('Changes successfully saved to your team.')} + </p> + + <TradingAnchorButton + href={Links.COMPETITIONS_TEAM(teamId)} + intent={Intent.Info} + size="small" + data-testid="view-team-button" + > + {t('View team')} + </TradingAnchorButton> + </div> + ); + } + const defaultValues: FormFields = { id: team.teamId, name: team.name, diff --git a/apps/trading/components/competitions/team-avatar.tsx b/apps/trading/components/competitions/team-avatar.tsx index 7e8be0ecb..2b50a51ba 100644 --- a/apps/trading/components/competitions/team-avatar.tsx +++ b/apps/trading/components/competitions/team-avatar.tsx @@ -1,4 +1,6 @@ +import { isValidUrl } from '@vegaprotocol/utils'; import classNames from 'classnames'; +import { useEffect, useState } from 'react'; const NUM_AVATARS = 20; const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png'; @@ -11,6 +13,26 @@ export const getFallbackAvatar = (teamId: string) => { return AVATAR_PATHNAME_PATTERN.replace('{id}', avatarId); }; +const useAvatar = (teamId: string, url: string) => { + const fallback = getFallbackAvatar(teamId); + const [avatar, setAvatar] = useState<string>(fallback); + + useEffect(() => { + if (!isValidUrl(url)) return; + fetch(url, { cache: 'force-cache' }) + .then((response) => { + if (response.ok) { + setAvatar(url); + } + }) + .catch(() => { + /** noop */ + }); + }); + + return avatar; +}; + export const TeamAvatar = ({ teamId, imgUrl, @@ -22,7 +44,7 @@ export const TeamAvatar = ({ alt?: string; size?: 'large' | 'small'; }) => { - const img = imgUrl && imgUrl.length > 0 ? imgUrl : getFallbackAvatar(teamId); + const img = useAvatar(teamId, imgUrl); return ( // eslint-disable-next-line @next/next/no-img-element <img diff --git a/libs/i18n/src/locales/en/trading.json b/libs/i18n/src/locales/en/trading.json index 82a2e0aea..d3d5174fc 100644 --- a/libs/i18n/src/locales/en/trading.json +++ b/libs/i18n/src/locales/en/trading.json @@ -446,5 +446,9 @@ "Choose a team": "Choose a team", "Join a team": "Join a team", "Solo team / lone wolf": "Solo team / lone wolf", - "Choose a team to get involved": "Choose a team to get involved" + "Choose a team to get involved": "Choose a team to get involved", + "Go back to the team's profile": "Go back to the team's profile", + "Go back to the competitions": "Go back to the competitions", + "Your team ID:": "Your team ID:", + "Changes successfully saved to your team.": "Changes successfully saved to your team." } From b953de953a1452d70da2eb5c2ded978d00f96c0d Mon Sep 17 00:00:00 2001 From: Edd <edd@vega.xyz> Date: Thu, 8 Feb 2024 19:06:38 +0000 Subject: [PATCH 10/17] feat(explorer): update party profile tx (#5719) --- .../proposal/tx-update-party-profile.tsx | 46 +++++++++++++++++++ .../txs/details/tx-details-wrapper.tsx | 3 ++ .../src/app/components/txs/tx-filter.tsx | 2 + 3 files changed, 51 insertions(+) create mode 100644 apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx diff --git a/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx b/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx new file mode 100644 index 000000000..c9bd63a2b --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/proposal/tx-update-party-profile.tsx @@ -0,0 +1,46 @@ +import { t } from '@vegaprotocol/i18n'; +import { TxDetailsShared } from '../shared/tx-details-shared'; +import { TableWithTbody } from '../../../table'; +import type { components } from '../../../../../types/explorer'; + +import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response'; +import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response'; +import { TableCell, TableRow } from '../../../table'; + +type Update = components['schemas']['v1UpdatePartyProfile']; + +interface TxDetailsUpdatePartyProfileProps { + txData: BlockExplorerTransactionResult | undefined; + pubKey: string | undefined; + blockData: TendermintBlocksResponse | undefined; +} + +/** + * Party profiles can be an alias and arbitrary key/values pairs. + * This component displays the alias, if any, but not the metadata. When there is + * some wider usage, we can decide how to render it. For now, it's available in the + * full TX details. + */ +export const TxDetailsUpdatePartyProfile = ({ + txData, + pubKey, + blockData, +}: TxDetailsUpdatePartyProfileProps) => { + if (!txData?.command.updatePartyProfile) { + return <>{t('Awaiting Block Explorer transaction details')}</>; + } + + const update: Update = txData.command.updatePartyProfile; + + return ( + <TableWithTbody className="mb-8" allowWrap={true}> + <TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} /> + {update.alias && ( + <TableRow modifier="bordered"> + <TableCell>{t('New alias')}</TableCell> + <TableCell>{update.alias}</TableCell> + </TableRow> + )} + </TableWithTbody> + ); +}; diff --git a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx index 9f7343700..937dcd8d1 100644 --- a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx @@ -34,6 +34,7 @@ import { TxDetailsUpdateReferralSet } from './tx-update-referral-set'; import { TxDetailsJoinTeam } from './tx-join-team'; import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode'; import { TxBatchProposal } from './tx-batch-proposal'; +import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile'; interface TxDetailsWrapperProps { txData: BlockExplorerTransactionResult | undefined; @@ -139,6 +140,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) { return TxDetailsUpdateMarginMode; case 'Batch Proposal': return TxBatchProposal; + case 'Update Party Profile': + return TxDetailsUpdatePartyProfile; default: return TxDetailsGeneric; } diff --git a/apps/explorer/src/app/components/txs/tx-filter.tsx b/apps/explorer/src/app/components/txs/tx-filter.tsx index 290bc9625..96233834f 100644 --- a/apps/explorer/src/app/components/txs/tx-filter.tsx +++ b/apps/explorer/src/app/components/txs/tx-filter.tsx @@ -44,6 +44,7 @@ export type FilterOption = | 'Submit Order' | 'Transfer Funds' | 'Undelegate' + | 'Update Party Profile' | 'Update Referral Set' | 'Update Margin Mode' | 'Validator Heartbeat' @@ -79,6 +80,7 @@ export const filterOptions: Record<string, FilterOption[]> = { 'Apply Referral Code', 'Create Referral Set', 'Join Team', + 'Update Party Profile', 'Update Referral Set', ], 'External Data': ['Chain Event', 'Submit Oracle Data'], From 0a3b1cadba315471bf9a47fa828c255b4c03e36b Mon Sep 17 00:00:00 2001 From: Art <artur@vegaprotocol.io> Date: Fri, 9 Feb 2024 10:00:28 +0100 Subject: [PATCH 11/17] fix(trading): keep leaderboard rank when filtering (#5775) --- .../components/competitions/competitions-leaderboard.tsx | 4 ++-- apps/trading/lib/hooks/use-teams.tsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/trading/components/competitions/competitions-leaderboard.tsx b/apps/trading/components/competitions/competitions-leaderboard.tsx index cc1cfc25b..40e87d39b 100644 --- a/apps/trading/components/competitions/competitions-leaderboard.tsx +++ b/apps/trading/components/competitions/competitions-leaderboard.tsx @@ -33,9 +33,9 @@ export const CompetitionsLeaderboard = ({ { name: 'status', displayName: t('Status') }, { name: 'volume', displayName: t('Volume') }, ]} - data={data.map((td, i) => { + data={data.map((td) => { // leaderboard place or medal - let rank: number | React.ReactNode = i + 1; + let rank: number | React.ReactNode = td.rank; if (rank === 1) rank = <Rank variant="gold" />; if (rank === 2) rank = <Rank variant="silver" />; if (rank === 3) rank = <Rank variant="bronze" />; diff --git a/apps/trading/lib/hooks/use-teams.tsx b/apps/trading/lib/hooks/use-teams.tsx index d71ae8652..dc02837cc 100644 --- a/apps/trading/lib/hooks/use-teams.tsx +++ b/apps/trading/lib/hooks/use-teams.tsx @@ -36,7 +36,9 @@ export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => { ...stats.find((s) => s.teamId === t.teamId), })); - return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc'); + return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc').map( + (d, i) => ({ ...d, rank: i + 1 }) + ); }, [teams, stats]); return { From c5a27dc6a20cbab6f524c430d99a49d4032ef2dc Mon Sep 17 00:00:00 2001 From: Edd <edd@vega.xyz> Date: Fri, 9 Feb 2024 10:26:08 +0000 Subject: [PATCH 12/17] chore(trading): switch eth provider URL (#5779) --- apps/trading/.env.mainnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/trading/.env.mainnet b/apps/trading/.env.mainnet index afb43a660..a37ccdf44 100644 --- a/apps/trading/.env.mainnet +++ b/apps/trading/.env.mainnet @@ -1,4 +1,4 @@ -NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a +NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.rpc.grove.city/v1/af6a2d529a11f8158bc8ca2a NX_ETHERSCAN_URL=https://etherscan.io NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613 From 41fd14dd00863e9be2c7f469743e5c1201d62e3b Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:30:24 +0200 Subject: [PATCH 13/17] feat(trading): mobile layout and buttons (#5751) Co-authored-by: Matthew Russell <mattrussell36@gmail.com> --- .../client-pages/markets/mobile-buttons.tsx | 235 ++++++++++++++++++ .../portfolio/portfolio-sidebar.tsx | 26 ++ .../layouts/layout-with-sidebar.tsx | 2 +- apps/trading/components/sidebar/sidebar.tsx | 85 +++++-- apps/trading/pages/client-router.tsx | 31 ++- .../src/components/dialog/dialog.tsx | 2 +- .../trading-dropdown/actions-dropdown.tsx | 19 ++ .../src/components/trading-dropdown/index.ts | 2 +- 8 files changed, 366 insertions(+), 36 deletions(-) create mode 100644 apps/trading/client-pages/markets/mobile-buttons.tsx diff --git a/apps/trading/client-pages/markets/mobile-buttons.tsx b/apps/trading/client-pages/markets/mobile-buttons.tsx new file mode 100644 index 000000000..4e3118235 --- /dev/null +++ b/apps/trading/client-pages/markets/mobile-buttons.tsx @@ -0,0 +1,235 @@ +import { Route, Routes } from 'react-router-dom'; +import { + Intent, + MobileActionsDropdown, + Tooltip, + TradingButton, + TradingDropdownItem, + VegaIcon, + VegaIconNames, +} from '@vegaprotocol/ui-toolkit'; +import { type BarView, ViewType, useSidebar } from '../../components/sidebar'; +import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; +import { useT } from '../../lib/use-t'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; +import { useEffect } from 'react'; +import classNames from 'classnames'; +import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; + +const ViewInitializer = () => { + const currentRouteId = useGetCurrentRouteId(); + const { setViews, getView } = useSidebar(); + const view = getView(currentRouteId); + const { screenSize } = useScreenDimensions(); + const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize); + useEffect(() => { + if (largeScreen && view === undefined) { + setViews({ type: ViewType.Order }, currentRouteId); + } + }, [setViews, view, currentRouteId, largeScreen]); + return null; +}; + +export const MarketsMobileSidebar = () => { + const t = useT(); + const currentRouteId = useGetCurrentRouteId(); + const { pubKeys, isReadOnly } = useVegaWallet(); + const openVegaWalletDialog = useVegaWalletDialogStore( + (store) => store.openVegaWalletDialog + ); + + return ( + <Routes> + <Route + path=":marketId" + element={ + <> + <ViewInitializer /> + <div className="grid grid-cols-3 grow md:grow-0 md:flex lg:flex-col items-center gap-2 lg:gap-4 p-1"> + {!pubKeys || isReadOnly ? ( + <> + <TradingButton + intent={Intent.Primary} + size="medium" + onClick={() => { + openVegaWalletDialog(); + }} + > + {t('Connect')} + </TradingButton> + <MobileButton + view={ViewType.Order} + tooltip={t('Trade')} + routeId={currentRouteId} + /> + <MobileBarActionsDropdown currentRouteId={currentRouteId} /> + </> + ) : ( + <> + <MobileButton + view={ViewType.Order} + tooltip={t('Trade')} + routeId={currentRouteId} + /> + <MobileButton + view={ViewType.Deposit} + tooltip={t('Deposit')} + routeId={currentRouteId} + /> + <MobileBarActionsDropdown currentRouteId={currentRouteId} /> + </> + )} + </div> + </> + } + /> + </Routes> + ); +}; + +export const MobileButton = ({ + view, + tooltip: label, + disabled = false, + onClick, + routeId, +}: { + view?: ViewType; + tooltip: string; + disabled?: boolean; + onClick?: () => void; + routeId: string; +}) => { + const { setViews, getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(routeId); + const onSelect = (view: BarView['type']) => { + if (view === currView?.type) { + setViews(null, routeId); + } else { + setViews({ type: view }, routeId); + } + }; + + const buttonClasses = classNames( + 'flex items-center p-1 rounded', + 'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500', + { + 'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500': + !view || view !== currView?.type, + 'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black': + view && view === currView?.type, + } + ); + + return ( + <Tooltip description={label} align="center" side="right" sideOffset={10}> + <TradingButton + className={buttonClasses} + data-testid={view} + onClick={onClick || (() => onSelect(view as BarView['type']))} + disabled={disabled} + > + {label} + </TradingButton> + </Tooltip> + ); +}; + +export const MobileDropdownItem = ({ + view, + icon, + tooltip, + disabled = false, + onClick, + routeId, +}: { + view?: ViewType; + icon: VegaIconNames; + tooltip: string; + disabled?: boolean; + onClick?: () => void; + routeId: string; +}) => { + const { setViews, getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(routeId); + const onSelect = (view: BarView['type']) => { + if (view === currView?.type) { + setViews(null, routeId); + } else { + setViews({ type: view }, routeId); + } + }; + + const buttonClasses = classNames( + 'flex items-center p-1 rounded', + 'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500', + { + 'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500': + !view || view !== currView?.type, + 'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black': + view && view === currView?.type, + } + ); + + return ( + <Tooltip description={tooltip} align="center" side="right" sideOffset={10}> + <TradingDropdownItem + className={buttonClasses} + data-testid={view} + onClick={onClick || (() => onSelect(view as BarView['type']))} + disabled={disabled} + > + <VegaIcon name={icon} size={20} /> + {tooltip} + </TradingDropdownItem> + </Tooltip> + ); +}; + +export const MobileBarActionsDropdown = ({ + currentRouteId, +}: { + currentRouteId: string; +}) => { + const t = useT(); + return ( + <MobileActionsDropdown> + <MobileDropdownItem + view={ViewType.Deposit} + icon={VegaIconNames.DEPOSIT} + tooltip={t('Deposit')} + routeId={currentRouteId} + /> + <MobileDropdownItem + view={ViewType.Withdraw} + icon={VegaIconNames.WITHDRAW} + tooltip={t('Withdraw')} + routeId={currentRouteId} + /> + <MobileDropdownItem + view={ViewType.Transfer} + icon={VegaIconNames.TRANSFER} + tooltip={t('Transfer')} + routeId={currentRouteId} + /> + <MobileDropdownItem + view={ViewType.Info} + icon={VegaIconNames.BREAKDOWN} + tooltip={t('Market specification')} + routeId={currentRouteId} + /> + <MobileDropdownItem + view={ViewType.Settings} + icon={VegaIconNames.COG} + tooltip={t('Settings')} + routeId={currentRouteId} + /> + </MobileActionsDropdown> + ); +}; diff --git a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx index dc390c6eb..14461ebe3 100644 --- a/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx +++ b/apps/trading/client-pages/portfolio/portfolio-sidebar.tsx @@ -2,6 +2,7 @@ import { VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { SidebarButton, ViewType } from '../../components/sidebar'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useT } from '../../lib/use-t'; +import { MobileButton } from '../markets/mobile-buttons'; export const PortfolioSidebar = () => { const t = useT(); @@ -30,3 +31,28 @@ export const PortfolioSidebar = () => { </> ); }; + +export const PortfolioMobileSidebar = () => { + const t = useT(); + const currentRouteId = useGetCurrentRouteId(); + + return ( + <div className="grid grid-cols-3 grow md:grow-0 md:flex lg:flex-col items-center gap-2 lg:gap-4 p-1"> + <MobileButton + view={ViewType.Deposit} + tooltip={t('Deposit')} + routeId={currentRouteId} + /> + <MobileButton + view={ViewType.Withdraw} + tooltip={t('Withdraw')} + routeId={currentRouteId} + /> + <MobileButton + view={ViewType.Transfer} + tooltip={t('Transfer')} + routeId={currentRouteId} + /> + </div> + ); +}; diff --git a/apps/trading/components/layouts/layout-with-sidebar.tsx b/apps/trading/components/layouts/layout-with-sidebar.tsx index 937113c62..3ecb36113 100644 --- a/apps/trading/components/layouts/layout-with-sidebar.tsx +++ b/apps/trading/components/layouts/layout-with-sidebar.tsx @@ -16,7 +16,7 @@ export const LayoutWithSidebar = ({ const sidebarOpen = sidebarView !== null; const gridClasses = classNames( 'h-full relative z-0 grid', - 'grid-rows-[min-content_1fr_40px]', + 'grid-rows-[min-content_1fr_50px]', 'lg:grid-rows-[min-content_1fr]', 'lg:grid-cols-[1fr_280px_40px]', 'xxxl:grid-cols-[1fr_320px_40px]' diff --git a/apps/trading/components/sidebar/sidebar.tsx b/apps/trading/components/sidebar/sidebar.tsx index b54854ae0..628e574d6 100644 --- a/apps/trading/components/sidebar/sidebar.tsx +++ b/apps/trading/components/sidebar/sidebar.tsx @@ -17,6 +17,7 @@ import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useT } from '../../lib/use-t'; import { ErrorBoundary } from '../error-boundary'; +import { useScreenDimensions } from '@vegaprotocol/react-helpers'; export enum ViewType { Order = 'Order', @@ -26,9 +27,10 @@ export enum ViewType { Transfer = 'Transfer', Settings = 'Settings', ViewAs = 'ViewAs', + Close = 'Close', } -type SidebarView = +export type BarView = | { type: ViewType.Deposit; assetId?: string; @@ -49,6 +51,9 @@ type SidebarView = } | { type: ViewType.Settings; + } + | { + type: ViewType.Close; }; export const Sidebar = ({ options }: { options?: ReactNode }) => { @@ -57,26 +62,52 @@ export const Sidebar = ({ options }: { options?: ReactNode }) => { const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1'; const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen); const { pubKeys } = useVegaWallet(); + const { isMobile } = useScreenDimensions(); + const { getView } = useSidebar((store) => ({ + setViews: store.setViews, + getView: store.getView, + })); + const currView = getView(currentRouteId); return ( - <div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar"> - {options && <nav className={navClasses}>{options}</nav>} - <nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}> - <SidebarButton - view={ViewType.ViewAs} - onClick={() => { - setViewAsDialogOpen(true); - }} - icon={VegaIconNames.EYE} - tooltip={t('View as party')} - disabled={Boolean(pubKeys)} - routeId={currentRouteId} - /> - <SidebarButton - view={ViewType.Settings} - icon={VegaIconNames.COG} - tooltip={t('Settings')} - routeId={currentRouteId} - /> + <div className="flex h-full lg:flex-col gap-1" data-testid="sidebar"> + {options && ( + <nav className={classNames(navClasses, 'flex grow')}>{options}</nav> + )} + <nav + className={classNames( + navClasses, + 'ml-auto lg:mt-auto lg:ml-0 shrink-0' + )} + > + {!isMobile ? ( + <> + <SidebarButton + view={ViewType.ViewAs} + onClick={() => { + setViewAsDialogOpen(true); + }} + icon={VegaIconNames.EYE} + tooltip={t('View as party')} + disabled={Boolean(pubKeys)} + routeId={currentRouteId} + /> + <SidebarButton + view={ViewType.Settings} + icon={VegaIconNames.COG} + tooltip={t('Settings')} + routeId={currentRouteId} + /> + </> + ) : ( + currView && ( + <SidebarButton + view={ViewType.Close} + icon={VegaIconNames.ARROW_LEFT} + tooltip={t('Back')} + routeId={currentRouteId} + /> + ) + )} <NodeHealthContainer /> </nav> </div> @@ -103,7 +134,7 @@ export const SidebarButton = ({ getView: store.getView, })); const currView = getView(routeId); - const onSelect = (view: SidebarView['type']) => { + const onSelect = (view: BarView['type']) => { if (view === currView?.type) { setViews(null, routeId); } else { @@ -133,7 +164,7 @@ export const SidebarButton = ({ <button className={buttonClasses} data-testid={view} - onClick={onClick || (() => onSelect(view as SidebarView['type']))} + onClick={onClick || (() => onSelect(view as BarView['type']))} disabled={disabled} > <VegaIcon name={icon} size={20} /> @@ -180,6 +211,10 @@ export const SidebarContent = () => { } } + if (view.type === ViewType.Close) { + return <CloseSidebar />; + } + if (view.type === ViewType.Info) { if (params.marketId) { return ( @@ -267,9 +302,9 @@ const CloseSidebar = () => { }; export const useSidebar = create<{ - views: { [key: string]: SidebarView | null }; - setViews: (view: SidebarView | null, routeId: string) => void; - getView: (routeId: string) => SidebarView | null | undefined; + views: { [key: string]: BarView | null }; + setViews: (view: BarView | null, routeId: string) => void; + getView: (routeId: string) => BarView | null | undefined; }>()((set, get) => ({ views: {}, setViews: (x, routeId) => diff --git a/apps/trading/pages/client-router.tsx b/apps/trading/pages/client-router.tsx index 5db2ad224..e5eb49688 100644 --- a/apps/trading/pages/client-router.tsx +++ b/apps/trading/pages/client-router.tsx @@ -24,7 +24,10 @@ import { compact } from 'lodash'; import { useFeatureFlags } from '@vegaprotocol/environment'; import { LiquidityHeader } from '../components/liquidity-header'; import { MarketHeader, MobileMarketHeader } from '../components/market-header'; -import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar'; +import { + PortfolioMobileSidebar, + PortfolioSidebar, +} from '../client-pages/portfolio/portfolio-sidebar'; import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar'; import { MarketsSidebar } from '../client-pages/markets/markets-sidebar'; import { useT } from '../lib/use-t'; @@ -33,9 +36,10 @@ import { CompetitionsTeams } from '../client-pages/competitions/competitions-tea import { CompetitionsTeam } from '../client-pages/competitions/competitions-team'; import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team'; import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team'; +import { MarketsMobileSidebar } from '../client-pages/markets/mobile-buttons'; import { useScreenDimensions } from '@vegaprotocol/react-helpers'; -// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM +// These must remain dynamically imported as pennant cannot be compiled by Next.js due to ESM // Using dynamic imports is a workaround for this until pennant is published as ESM const MarketPage = lazy(() => import('../client-pages/market')); const Portfolio = lazy(() => import('../client-pages/portfolio')); @@ -54,6 +58,17 @@ export const useRouterConfig = (): RouteObject[] => { const { screenSize } = useScreenDimensions(); const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize); const marketHeader = largeScreen ? <MarketHeader /> : <MobileMarketHeader />; + const marketsSidebar = largeScreen ? ( + <MarketsSidebar /> + ) : ( + <MarketsMobileSidebar /> + ); + const portfolioSidebar = largeScreen ? ( + <PortfolioSidebar /> + ) : ( + <PortfolioMobileSidebar /> + ); + const routeConfig = compact([ { index: true, @@ -70,7 +85,7 @@ export const useRouterConfig = (): RouteObject[] => { featureFlags.REFERRALS ? { path: AppRoutes.REFERRALS, - element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />, + element: <LayoutWithSidebar sidebar={portfolioSidebar} />, children: [ { element: ( @@ -103,7 +118,7 @@ export const useRouterConfig = (): RouteObject[] => { featureFlags.TEAM_COMPETITION ? { path: AppRoutes.COMPETITIONS, - element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />, + element: <LayoutWithSidebar sidebar={portfolioSidebar} />, children: [ // pages with planets and stars { @@ -134,7 +149,7 @@ export const useRouterConfig = (): RouteObject[] => { : undefined, { path: 'fees/*', - element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />, + element: <LayoutWithSidebar sidebar={portfolioSidebar} />, children: [ { index: true, @@ -144,7 +159,7 @@ export const useRouterConfig = (): RouteObject[] => { }, { path: 'rewards/*', - element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />, + element: <LayoutWithSidebar sidebar={portfolioSidebar} />, children: [ { index: true, @@ -155,7 +170,7 @@ export const useRouterConfig = (): RouteObject[] => { { path: 'markets/*', element: ( - <LayoutWithSidebar header={marketHeader} sidebar={<MarketsSidebar />} /> + <LayoutWithSidebar header={marketHeader} sidebar={marketsSidebar} /> ), children: [ { @@ -176,7 +191,7 @@ export const useRouterConfig = (): RouteObject[] => { }, { path: 'portfolio/*', - element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />, + element: <LayoutWithSidebar sidebar={portfolioSidebar} />, children: [ { index: true, diff --git a/libs/ui-toolkit/src/components/dialog/dialog.tsx b/libs/ui-toolkit/src/components/dialog/dialog.tsx index 26836c54e..3af38e51f 100644 --- a/libs/ui-toolkit/src/components/dialog/dialog.tsx +++ b/libs/ui-toolkit/src/components/dialog/dialog.tsx @@ -38,7 +38,7 @@ export function Dialog({ ); const wrapperClasses = classNames( // Dimensions - 'w-screen sm:max-w-[90vw] p-4 md:p-8', + 'max-w-[95vw] sm:max-w-[90vw] p-4 md:p-8', // Need to apply background and text colors again as content is rendered in a portal 'dark:bg-black bg-white dark:text-white', getIntentBorder(intent), diff --git a/libs/ui-toolkit/src/components/trading-dropdown/actions-dropdown.tsx b/libs/ui-toolkit/src/components/trading-dropdown/actions-dropdown.tsx index fd73c6e38..c67533a58 100644 --- a/libs/ui-toolkit/src/components/trading-dropdown/actions-dropdown.tsx +++ b/libs/ui-toolkit/src/components/trading-dropdown/actions-dropdown.tsx @@ -1,4 +1,5 @@ import { VegaIcon, VegaIconNames } from '../icon'; +import { TradingButton } from '../trading-button'; import { TradingDropdown, TradingDropdownContent, @@ -15,6 +16,16 @@ export const ActionsDropdownTrigger = () => { ); }; +export const MobileActionsDropdownTrigger = () => { + return ( + <TradingDropdownTrigger data-testid="dropdown-menu"> + <TradingButton size="medium"> + <VegaIcon name={VegaIconNames.KEBAB} /> + </TradingButton> + </TradingDropdownTrigger> + ); +}; + type ActionMenuContentProps = React.ComponentProps< typeof TradingDropdownContent >; @@ -26,3 +37,11 @@ export const ActionsDropdown = (props: ActionMenuContentProps) => { </TradingDropdown> ); }; + +export const MobileActionsDropdown = (props: ActionMenuContentProps) => { + return ( + <TradingDropdown trigger={<MobileActionsDropdownTrigger />}> + <TradingDropdownContent {...props} side="bottom" align="end" /> + </TradingDropdown> + ); +}; diff --git a/libs/ui-toolkit/src/components/trading-dropdown/index.ts b/libs/ui-toolkit/src/components/trading-dropdown/index.ts index bea80fad8..4d7a93d1e 100644 --- a/libs/ui-toolkit/src/components/trading-dropdown/index.ts +++ b/libs/ui-toolkit/src/components/trading-dropdown/index.ts @@ -1,2 +1,2 @@ -export * from './trading-dropdown'; export * from './actions-dropdown'; +export * from './trading-dropdown'; From a21feea6994e830d78217370e13de656c6c4d8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= <bglownia@gmail.com> Date: Fri, 9 Feb 2024 11:31:15 +0100 Subject: [PATCH 14/17] chore(utils): improve formatNumber to keep precision (#5761) --- .../referrals/hooks/use-referral-program.ts | 6 +- .../referrals/referral-statistics.tsx | 8 +- .../competitions/competitions-leaderboard.tsx | 5 +- libs/liquidity/src/lib/liquidity-table.tsx | 12 +-- libs/market-depth/src/lib/depth-chart.tsx | 7 +- libs/utils/src/lib/format/number.spec.ts | 26 +----- libs/utils/src/lib/format/number.ts | 92 ++++++++++++------- libs/utils/src/lib/format/range.spec.ts | 24 ++--- 8 files changed, 92 insertions(+), 88 deletions(-) diff --git a/apps/trading/client-pages/referrals/hooks/use-referral-program.ts b/apps/trading/client-pages/referrals/hooks/use-referral-program.ts index e530d6f05..dffb01a2c 100644 --- a/apps/trading/client-pages/referrals/hooks/use-referral-program.ts +++ b/apps/trading/client-pages/referrals/hooks/use-referral-program.ts @@ -1,4 +1,4 @@ -import { getNumberFormat } from '@vegaprotocol/utils'; +import { formatNumber } from '@vegaprotocol/utils'; import sortBy from 'lodash/sortBy'; import omit from 'lodash/omit'; import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram'; @@ -107,9 +107,7 @@ export const useReferralProgram = () => { discountFactor: Number(t.referralDiscountFactor), discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%', minimumVolume: Number(t.minimumRunningNotionalTakerVolume), - volume: getNumberFormat(0).format( - Number(t.minimumRunningNotionalTakerVolume) - ), + volume: formatNumber(t.minimumRunningNotionalTakerVolume, 0), epochs: Number(t.minimumEpochs), }; }); diff --git a/apps/trading/client-pages/referrals/referral-statistics.tsx b/apps/trading/client-pages/referrals/referral-statistics.tsx index 245871d8e..38cc15b5b 100644 --- a/apps/trading/client-pages/referrals/referral-statistics.tsx +++ b/apps/trading/client-pages/referrals/referral-statistics.tsx @@ -14,9 +14,9 @@ import { import { useVegaWallet } from '@vegaprotocol/wallet'; import { addDecimalsFormatNumber, + formatNumber, getDateFormat, getDateTimeFormat, - getNumberFormat, getUserLocale, removePaginationWrapper, } from '@vegaprotocol/utils'; @@ -323,7 +323,7 @@ export const Statistics = ({ } description={<QUSDTooltip />} > - {getNumberFormat(0).format(Number(totalCommissionValue))} + {formatNumber(totalCommissionValue, 0)} </StatTile> ); @@ -563,8 +563,8 @@ export const RefereesTable = ({ ) .map((r) => ({ ...r, - volume: getNumberFormat(0).format(r.volume), - commission: getNumberFormat(0).format(r.commission), + volume: formatNumber(r.volume, 0), + commission: formatNumber(r.commission, 0), })) .reverse()} /> diff --git a/apps/trading/components/competitions/competitions-leaderboard.tsx b/apps/trading/components/competitions/competitions-leaderboard.tsx index 40e87d39b..5a3291cc3 100644 --- a/apps/trading/components/competitions/competitions-leaderboard.tsx +++ b/apps/trading/components/competitions/competitions-leaderboard.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; import { Splash } from '@vegaprotocol/ui-toolkit'; -import { getNumberFormat } from '@vegaprotocol/utils'; +import { formatNumber } from '@vegaprotocol/utils'; import { type useTeams } from '../../lib/hooks/use-teams'; import { useT } from '../../lib/use-t'; import { Table } from '../table'; @@ -15,8 +15,7 @@ export const CompetitionsLeaderboard = ({ }) => { const t = useT(); - const num = (n?: number | string) => - !n ? '-' : getNumberFormat(0).format(Number(n)); + const num = (n?: number | string) => (!n ? '-' : formatNumber(n, 0)); if (!data || data.length === 0) { return <Splash>{t('Could not find any teams')}</Splash>; diff --git a/libs/liquidity/src/lib/liquidity-table.tsx b/libs/liquidity/src/lib/liquidity-table.tsx index 523297efb..db3b4dd44 100644 --- a/libs/liquidity/src/lib/liquidity-table.tsx +++ b/libs/liquidity/src/lib/liquidity-table.tsx @@ -94,7 +94,7 @@ export const LiquidityTable = ({ return `${addDecimalsFormatNumberQuantum( value, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 )}`; }; @@ -165,7 +165,7 @@ export const LiquidityTable = ({ return `${addDecimalsFormatNumberQuantum( newValue, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 )}`; }; @@ -227,7 +227,7 @@ export const LiquidityTable = ({ addDecimalsFormatNumberQuantum( pendingCommitmentAmount, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 ); if ( @@ -238,7 +238,7 @@ export const LiquidityTable = ({ addDecimalsFormatNumberQuantum( currentCommitmentAmount, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 ); return ( @@ -286,7 +286,7 @@ export const LiquidityTable = ({ addDecimalsFormatNumberQuantum( pendingCommitmentAmount, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 ); if ( @@ -297,7 +297,7 @@ export const LiquidityTable = ({ addDecimalsFormatNumberQuantum( currentCommitmentAmount, assetDecimalPlaces ?? 0, - quantum ?? 0 + quantum ?? 1 ); return ( diff --git a/libs/market-depth/src/lib/depth-chart.tsx b/libs/market-depth/src/lib/depth-chart.tsx index 0b7cc79ea..c370e06aa 100644 --- a/libs/market-depth/src/lib/depth-chart.tsx +++ b/libs/market-depth/src/lib/depth-chart.tsx @@ -1,7 +1,7 @@ import { DepthChart } from 'pennant'; import throttle from 'lodash/throttle'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; -import { addDecimal, getNumberFormat } from '@vegaprotocol/utils'; +import { addDecimal, formatNumber } from '@vegaprotocol/utils'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/data-provider'; import { marketDepthProvider } from './market-depth-provider'; @@ -216,13 +216,12 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => { const volumeFormat = useCallback( (volume: number) => - getNumberFormat(market?.positionDecimalPlaces || 0).format(volume), + formatNumber(volume, market?.positionDecimalPlaces || 0), [market?.positionDecimalPlaces] ); const priceFormat = useCallback( - (price: number) => - getNumberFormat(market?.decimalPlaces || 0).format(price), + (price: number) => formatNumber(price, market?.decimalPlaces || 0), [market?.decimalPlaces] ); diff --git a/libs/utils/src/lib/format/number.spec.ts b/libs/utils/src/lib/format/number.spec.ts index 121d00547..7e1ace053 100644 --- a/libs/utils/src/lib/format/number.spec.ts +++ b/libs/utils/src/lib/format/number.spec.ts @@ -23,6 +23,7 @@ describe('number utils', () => { { v: new BigNumber(123000), d: 1, o: '12,300.0' }, { v: new BigNumber(123001), d: 2, o: '1,230.01' }, { v: new BigNumber(123001000), d: 2, o: '1,230,010.00' }, + { v: '100000000000000000001', d: 18, o: '100.000000000000000001' }, ])( 'formats with addDecimalsFormatNumber given number correctly', ({ v, d, o }) => { @@ -31,27 +32,10 @@ describe('number utils', () => { ); it.each([ - { v: new BigNumber(123000), d: 5, o: '1.23', q: 0.1 }, - { v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 }, - { v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 }, - { v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 }, - { v: new BigNumber(123001), d: 2, o: '1,230.01', q: 100 }, - { v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 }, - { v: new BigNumber(123001), d: 2, o: '1,230.01', q: 1 }, - { - v: BigNumber('123456789123456789'), - d: 10, - o: '12,345,678.91234568', - q: '0.00003846', - }, - { - v: BigNumber('123456789123456789'), - d: 10, - o: '12,345,678.91234568', - q: '1', - }, - // USDT / USDC - { v: new BigNumber(12345678), d: 6, o: '12.35', q: 1000000 }, + { v: '1234000000000000000', d: 18, q: '1000000000000000000', o: '1.23' }, //vega + { v: '1235000000000000000', d: 18, q: '1000000000000000000', o: '1.24' }, //vega + { v: '1230012', d: 6, q: '1000000', o: '1.23' }, // USDT + { v: '1234560000000000000', d: 18, q: '500000000000000', o: '1.2346' }, // WEth ])( 'formats with addDecimalsFormatNumberQuantum given number correctly', ({ v, d, o, q }) => { diff --git a/libs/utils/src/lib/format/number.ts b/libs/utils/src/lib/format/number.ts index 90d41974f..7d407e050 100644 --- a/libs/utils/src/lib/format/number.ts +++ b/libs/utils/src/lib/format/number.ts @@ -1,5 +1,4 @@ import { BigNumber } from 'bignumber.js'; -import isNil from 'lodash/isNil'; import memoize from 'lodash/memoize'; import { getUserLocale } from '../get-user-locale'; @@ -53,36 +52,36 @@ export function removeDecimal( return new BigNumber(value || 0).times(times).toFixed(0); } -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat -export const getNumberFormat = memoize((digits: number) => { - if (isNil(digits) || digits < 0) { - return new Intl.NumberFormat(getUserLocale()); - } - return new Intl.NumberFormat(getUserLocale(), { - minimumFractionDigits: Math.min(Math.max(0, digits), MIN_FRACTION_DIGITS), - maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS), - }); -}); - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat -export const getFixedNumberFormat = memoize((digits: number) => { - if (isNil(digits) || digits < 0) { - return new Intl.NumberFormat(getUserLocale()); - } - return new Intl.NumberFormat(getUserLocale(), { - minimumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS), - maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS), - }); -}); - export const getDecimalSeparator = memoize( () => - getNumberFormat(1) + new Intl.NumberFormat(getUserLocale()) .formatToParts(1.1) - .find((part) => part.type === 'decimal')?.value + .find((part) => part.type === 'decimal')?.value ?? '.' ); -/** formatNumber will format the number with fixed decimals +export const getGroupFormat = memoize(() => { + const parts = new Intl.NumberFormat(getUserLocale()).formatToParts( + 100000000000.1 + ); + const groupSeparator = parts.find((part) => part.type === 'group')?.value; + const groupSize = + (groupSeparator && + parts.reverse().find((part) => part.type === 'integer')?.value.length) || + 0; + return { + groupSize, + groupSeparator, + }; +}); + +const getFormat = memoize(() => ({ + decimalSeparator: getDecimalSeparator(), + ...getGroupFormat(), +})); + +/** + * formatNumber will format the number with maximum number of decimals + * trailing zeros are removed but min(MIN_FRACTION_DIGITS, formatDecimals) decimal places will be kept * @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN * @param formatDecimals - number of decimals to use */ @@ -90,7 +89,23 @@ export const formatNumber = ( rawValue: string | number | BigNumber, formatDecimals = 0 ) => { - return getNumberFormat(formatDecimals).format(Number(rawValue)); + const decimalPlaces = Math.min( + Math.max(0, formatDecimals), + MAX_FRACTION_DIGITS + ); + const format = getFormat(); + const formatted = new BigNumber(rawValue).toFormat(decimalPlaces, format); + // if there are no decimal places just return formatted value + if (!decimalPlaces) { + return formatted; + } + // minimum number of decimal places to keep when removing trailing zeros + const minimumFractionDigits = Math.min(decimalPlaces, MIN_FRACTION_DIGITS); + const parts = formatted.split(format.decimalSeparator); + parts[1] = (parts[1] || '') + .replace(/0+$/, '') + .padEnd(minimumFractionDigits, '0'); + return parts.join(format.decimalSeparator); }; /** formatNumberFixed will format the number with fixed decimals @@ -101,7 +116,10 @@ export const formatNumberFixed = ( rawValue: string | number | BigNumber, formatDecimals = 0 ) => { - return getFixedNumberFormat(formatDecimals).format(Number(rawValue)); + return new BigNumber(rawValue).toFormat( + Math.min(Math.max(0, formatDecimals), MAX_FRACTION_DIGITS), + getFormat() + ); }; export const quantumDecimalPlaces = ( @@ -131,9 +149,14 @@ export const addDecimalsFormatNumberQuantum = ( if (isNaN(Number(quantum))) { return addDecimalsFormatNumber(rawValue, decimalPlaces); } - const quantumValue = addDecimal(quantum, decimalPlaces); - const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue))); - return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP)); + const numberDP = Math.ceil( + Math.abs(Math.log10(toBigNum(quantum, decimalPlaces).toNumber())) + ); + return addDecimalsFormatNumber( + rawValue, + decimalPlaces, + Math.max(MIN_FRACTION_DIGITS, numberDP) + ); }; export const addDecimalsFormatNumber = ( @@ -141,9 +164,10 @@ export const addDecimalsFormatNumber = ( decimalPlaces: number, formatDecimals: number = decimalPlaces ) => { - const x = addDecimal(rawValue, decimalPlaces); - - return formatNumber(x, formatDecimals); + return formatNumber( + new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimalPlaces)), + formatDecimals + ); }; export const addDecimalsFixedFormatNumber = ( diff --git a/libs/utils/src/lib/format/range.spec.ts b/libs/utils/src/lib/format/range.spec.ts index 5730c2fbc..5af45f802 100644 --- a/libs/utils/src/lib/format/range.spec.ts +++ b/libs/utils/src/lib/format/range.spec.ts @@ -10,24 +10,24 @@ describe('formatValue', () => { { v: '123456789123456789', d: 10, - o: '12,345,678.91234568', + o: '12,345,678.9123456789', }, ])('formats values correctly', ({ v, d, o }) => { expect(formatValue(v, d)).toStrictEqual(o); }); it.each([ - { v: 123000, d: 5, o: '1.23', q: '0.1' }, - { v: 123000, d: 3, o: '123.00', q: '0.1' }, - { v: 123000, d: 1, o: '12,300.00', q: '0.1' }, - { v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' }, + { v: 123000, d: 5, o: '1.23', q: '1' }, + { v: 123000, d: 3, o: '123.00', q: '1' }, + { v: 123000, d: 1, o: '12,300.00', q: '1' }, + { v: 123001000, d: 2, o: '1,230,010.00', q: '1' }, { v: 123001, d: 2, o: '1,230.01', q: '100' }, - { v: 123001, d: 2, o: '1,230.01', q: '0.1' }, + { v: 123001, d: 2, o: '1,230.01', q: '1' }, { v: '123456789123456789', d: 10, - o: '12,345,678.91234568', - q: '0.00003846', + o: '12,345,678.91235', + q: '384600', }, ])( 'formats with formatValue with quantum given number correctly', @@ -42,15 +42,15 @@ describe('formatRange', () => { min: 123000, max: 12300011111, d: 5, - o: '1.23 - 123,000.11111', - q: '0.1', + o: '1.23 - 123,000.11', + q: '1000', }, { min: 123000, max: 12300011111, d: 3, - o: '123.00 - 12,300,011.111', - q: '0.1', + o: '123.00 - 12,300,011.11', + q: '100', }, { min: 123000, From 3ed2ec88d778b79bf8508326ba92181db8c69731 Mon Sep 17 00:00:00 2001 From: Art <artur@vegaprotocol.io> Date: Fri, 9 Feb 2024 11:32:54 +0100 Subject: [PATCH 15/17] chore(explorer): remove voting column from proposal table (#5776) --- .../src/integration/proposal.cy.js | 8 ---- .../components/proposals/proposals-table.tsx | 43 +------------------ 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/apps/explorer-e2e/src/integration/proposal.cy.js b/apps/explorer-e2e/src/integration/proposal.cy.js index ef03e28b3..3d1568141 100644 --- a/apps/explorer-e2e/src/integration/proposal.cy.js +++ b/apps/explorer-e2e/src/integration/proposal.cy.js @@ -24,10 +24,6 @@ context('Proposal page', { tags: '@smoke' }, function () { cy.get_element_by_col_id('title').should('have.text', proposalTitle); cy.get_element_by_col_id('type').should('have.text', 'NewMarket'); cy.get_element_by_col_id('state').should('have.text', 'Enacted'); - cy.getByTestId('vote-progress').should('be.visible'); - cy.getByTestId('vote-progress-bar-for') - .invoke('attr', 'style') - .should('eq', 'width: 100%;'); cy.get('[col-id="cDate"]') .invoke('text') .should('match', dateTimeRegex); @@ -73,10 +69,6 @@ context('Proposal page', { tags: '@smoke' }, function () { 'have.text', 'Waiting for Node Vote' ); - cy.getByTestId('vote-progress').should('be.visible'); - cy.getByTestId('vote-progress-bar-against') - .invoke('attr', 'style') - .should('eq', 'width: 100%;'); cy.get('[col-id="cDate"]') .invoke('text') .should('match', dateTimeRegex); diff --git a/apps/explorer/src/app/components/proposals/proposals-table.tsx b/apps/explorer/src/app/components/proposals/proposals-table.tsx index 88d31a697..6999008eb 100644 --- a/apps/explorer/src/app/components/proposals/proposals-table.tsx +++ b/apps/explorer/src/app/components/proposals/proposals-table.tsx @@ -1,5 +1,4 @@ import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals'; -import { VoteProgress } from '@vegaprotocol/proposals'; import { type AgGridReact } from 'ag-grid-react'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AgGrid } from '@vegaprotocol/datagrid'; @@ -12,12 +11,7 @@ import { type ColDef } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community'; import { getDateTimeFormat } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; -import { - NetworkParams, - useNetworkParams, -} from '@vegaprotocol/network-parameters'; import { ProposalStateMapping } from '@vegaprotocol/types'; -import BigNumber from 'bignumber.js'; import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment'; import { BREAKPOINT_MD } from '../../config/breakpoints'; import { JsonViewerDialog } from '../dialogs/json-viewer-dialog'; @@ -31,15 +25,7 @@ type ProposalsTableProps = { data: ProposalListFieldsFragment[] | null; }; export const ProposalsTable = ({ data }: ProposalsTableProps) => { - const { params } = useNetworkParams([ - NetworkParams.governance_proposal_market_requiredMajority, - ]); const tokenLink = useLinks(DApp.Governance); - const requiredMajorityPercentage = useMemo(() => { - const requiredMajority = - params?.governance_proposal_market_requiredMajority ?? 1; - return new BigNumber(requiredMajority).times(100); - }, [params?.governance_proposal_market_requiredMajority]); const gridRef = useRef<AgGridReact>(null); useLayoutEffect(() => { @@ -90,33 +76,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => { return value ? ProposalStateMapping[value] : '-'; }, }, - { - colId: 'voting', - maxWidth: 100, - hide: window.innerWidth <= BREAKPOINT_MD, - headerName: t('Voting'), - cellRenderer: ({ - data, - }: VegaICellRendererParams<ProposalListFieldsFragment>) => { - if (data) { - const yesTokens = new BigNumber(data.votes.yes.totalTokens); - const noTokens = new BigNumber(data.votes.no.totalTokens); - const totalTokensVoted = yesTokens.plus(noTokens); - const yesPercentage = totalTokensVoted.isZero() - ? new BigNumber(0) - : yesTokens.multipliedBy(100).dividedBy(totalTokensVoted); - return ( - <div className="flex h-full items-center justify-center pt-2 uppercase"> - <VoteProgress - threshold={requiredMajorityPercentage} - progress={yesPercentage} - /> - </div> - ); - } - return '-'; - }, - }, { colId: 'cDate', maxWidth: 150, @@ -184,7 +143,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => { }, }, ], - [requiredMajorityPercentage, tokenLink] + [tokenLink] ); return ( <> From 44189591fc9994f009b0d09a223bfac06b6008c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= <bglownia@gmail.com> Date: Fri, 9 Feb 2024 13:55:17 +0100 Subject: [PATCH 16/17] feat(accounts): get trasfer fee from estimateTransferFee API (#5721) --- libs/accounts/src/lib/TransferFee.graphql | 18 +++ .../src/lib/__generated__/TransferFee.ts | 63 ++++++++ libs/accounts/src/lib/transfer-container.tsx | 2 - libs/accounts/src/lib/transfer-form.spec.tsx | 139 ++++++++++-------- libs/accounts/src/lib/transfer-form.tsx | 80 ++++++---- libs/i18n/src/locales/en/accounts.json | 1 - specs/1003-TRAN-transfer.md | 2 - 7 files changed, 209 insertions(+), 96 deletions(-) create mode 100644 libs/accounts/src/lib/TransferFee.graphql create mode 100644 libs/accounts/src/lib/__generated__/TransferFee.ts diff --git a/libs/accounts/src/lib/TransferFee.graphql b/libs/accounts/src/lib/TransferFee.graphql new file mode 100644 index 000000000..49d5e9d84 --- /dev/null +++ b/libs/accounts/src/lib/TransferFee.graphql @@ -0,0 +1,18 @@ +query TransferFee( + $fromAccount: ID! + $fromAccountType: AccountType! + $toAccount: ID! + $amount: String! + $assetId: String! +) { + estimateTransferFee( + fromAccount: $fromAccount + fromAccountType: $fromAccountType + toAccount: $toAccount + amount: $amount + assetId: $assetId + ) { + fee + discount + } +} diff --git a/libs/accounts/src/lib/__generated__/TransferFee.ts b/libs/accounts/src/lib/__generated__/TransferFee.ts new file mode 100644 index 000000000..22ad02662 --- /dev/null +++ b/libs/accounts/src/lib/__generated__/TransferFee.ts @@ -0,0 +1,63 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type TransferFeeQueryVariables = Types.Exact<{ + fromAccount: Types.Scalars['ID']; + fromAccountType: Types.AccountType; + toAccount: Types.Scalars['ID']; + amount: Types.Scalars['String']; + assetId: Types.Scalars['String']; +}>; + + +export type TransferFeeQuery = { __typename?: 'Query', estimateTransferFee?: { __typename?: 'EstimatedTransferFee', fee: string, discount: string } | null }; + + +export const TransferFeeDocument = gql` + query TransferFee($fromAccount: ID!, $fromAccountType: AccountType!, $toAccount: ID!, $amount: String!, $assetId: String!) { + estimateTransferFee( + fromAccount: $fromAccount + fromAccountType: $fromAccountType + toAccount: $toAccount + amount: $amount + assetId: $assetId + ) { + fee + discount + } +} + `; + +/** + * __useTransferFeeQuery__ + * + * To run a query within a React component, call `useTransferFeeQuery` and pass it any options that fit your needs. + * When your component renders, `useTransferFeeQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useTransferFeeQuery({ + * variables: { + * fromAccount: // value for 'fromAccount' + * fromAccountType: // value for 'fromAccountType' + * toAccount: // value for 'toAccount' + * amount: // value for 'amount' + * assetId: // value for 'assetId' + * }, + * }); + */ +export function useTransferFeeQuery(baseOptions: Apollo.QueryHookOptions<TransferFeeQuery, TransferFeeQueryVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery<TransferFeeQuery, TransferFeeQueryVariables>(TransferFeeDocument, options); + } +export function useTransferFeeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TransferFeeQuery, TransferFeeQueryVariables>) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery<TransferFeeQuery, TransferFeeQueryVariables>(TransferFeeDocument, options); + } +export type TransferFeeQueryHookResult = ReturnType<typeof useTransferFeeQuery>; +export type TransferFeeLazyQueryHookResult = ReturnType<typeof useTransferFeeLazyQuery>; +export type TransferFeeQueryResult = Apollo.QueryResult<TransferFeeQuery, TransferFeeQueryVariables>; \ No newline at end of file diff --git a/libs/accounts/src/lib/transfer-container.tsx b/libs/accounts/src/lib/transfer-container.tsx index a02f07f94..84ab0d1d1 100644 --- a/libs/accounts/src/lib/transfer-container.tsx +++ b/libs/accounts/src/lib/transfer-container.tsx @@ -25,7 +25,6 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => { const t = useT(); const { pubKey, pubKeys, isReadOnly } = useVegaWallet(); const { params } = useNetworkParams([ - NetworkParams.transfer_fee_factor, NetworkParams.transfer_minTransferQuantumMultiple, ]); @@ -72,7 +71,6 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => { pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null} isReadOnly={isReadOnly} assetId={assetId} - feeFactor={params.transfer_fee_factor} minQuantumMultiple={params.transfer_minTransferQuantumMultiple} submitTransfer={transfer} accounts={sortedAccounts} diff --git a/libs/accounts/src/lib/transfer-form.spec.tsx b/libs/accounts/src/lib/transfer-form.spec.tsx index 369e52ced..d1eb6d738 100644 --- a/libs/accounts/src/lib/transfer-form.spec.tsx +++ b/libs/accounts/src/lib/transfer-form.spec.tsx @@ -15,6 +15,30 @@ import { } from './transfer-form'; import { AccountType, AccountTypeMapping } from '@vegaprotocol/types'; import { removeDecimal } from '@vegaprotocol/utils'; +import type { TransferFeeQuery } from './__generated__/TransferFee'; + +const feeFactor = 0.001; +const mockUseTransferFeeQuery = jest.fn( + ({ + variables: { amount }, + }: { + variables: { amount: string }; + }): { data: TransferFeeQuery } => { + return { + data: { + estimateTransferFee: { + discount: '0', + fee: (Number(amount) * feeFactor).toFixed(), + }, + }, + }; + } +); + +jest.mock('./__generated__/TransferFee', () => ({ + useTransferFeeQuery: (props: { variables: { amount: string } }) => + mockUseTransferFeeQuery(props), +})); describe('TransferForm', () => { const renderComponent = (props: TransferFormProps) => { @@ -56,7 +80,6 @@ describe('TransferForm', () => { const props = { pubKey, pubKeys: [pubKey, '2'.repeat(64)], - feeFactor: '0.001', submitTransfer: jest.fn(), accounts: [ { @@ -79,7 +102,6 @@ describe('TransferForm', () => { pubKey, 'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce', ], - feeFactor: '0.001', submitTransfer: jest.fn(), accounts: [], minQuantumMultiple: '1', @@ -96,10 +118,6 @@ describe('TransferForm', () => { }); it.each([ - { - targetText: 'Transfer fee', - tooltipText: /transfer\.fee\.factor/, - }, { targetText: 'Amount to be transferred', tooltipText: /without the fee/, @@ -109,9 +127,6 @@ describe('TransferForm', () => { tooltipText: /total amount taken from your account/, }, ])('Tooltip for "$targetText" shows', async (o) => { - // 1003-TRAN-015 - // 1003-TRAN-016 - // 1003-TRAN-017 // 1003-TRAN-018 // 1003-TRAN-019 renderComponent(props); @@ -124,6 +139,10 @@ describe('TransferForm', () => { // Select asset await selectAsset(asset); + await userEvent.selectOptions( + screen.getByLabelText('From account'), + `${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}` + ); // set valid amount const amountInput = screen.getByLabelText('Amount'); await userEvent.type(amountInput, amount); @@ -214,9 +233,7 @@ describe('TransferForm', () => { // set valid amount await userEvent.clear(amountInput); await userEvent.type(amountInput, amount); - expect(screen.getByTestId('transfer-fee')).toHaveTextContent( - new BigNumber(props.feeFactor).times(amount).toFixed() - ); + expect(screen.getByTestId('transfer-fee')).toHaveTextContent('1'); await submit(); @@ -385,47 +402,44 @@ describe('TransferForm', () => { }); }); }); - describe('IncludeFeesCheckbox', () => { - it('validates fields when checkbox is not checked', async () => { - renderComponent(props); - // check current pubkey not shown - const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key'); - const pubKeyOptions = ['', pubKey, props.pubKeys[1]]; - expect(keySelect.children).toHaveLength(pubKeyOptions.length); - expect(Array.from(keySelect.options).map((o) => o.value)).toEqual( - pubKeyOptions - ); + it('validates fields', async () => { + renderComponent(props); - await submit(); - expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value + // check current pubkey not shown + const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key'); + const pubKeyOptions = ['', pubKey, props.pubKeys[1]]; + expect(keySelect.children).toHaveLength(pubKeyOptions.length); + expect(Array.from(keySelect.options).map((o) => o.value)).toEqual( + pubKeyOptions + ); - // Select a pubkey - await userEvent.selectOptions( - screen.getByLabelText('To Vega key'), - props.pubKeys[1] - ); + await submit(); + expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value - // Select asset - await selectAsset(asset); + // Select a pubkey + await userEvent.selectOptions( + screen.getByLabelText('To Vega key'), + props.pubKeys[1] + ); - await userEvent.selectOptions( - screen.getByLabelText('From account'), - `${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}` - ); + // Select asset + await selectAsset(asset); - const amountInput = screen.getByLabelText('Amount'); + await userEvent.selectOptions( + screen.getByLabelText('From account'), + `${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}` + ); - await userEvent.type(amountInput, amount); - const expectedFee = new BigNumber(amount) - .times(props.feeFactor) - .toFixed(); - const total = new BigNumber(amount).plus(expectedFee).toFixed(); - // 1003-TRAN-021 - expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee); - expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount); - expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total); - }); + const amountInput = screen.getByLabelText('Amount'); + + await userEvent.type(amountInput, amount); + const expectedFee = new BigNumber(amount).times(feeFactor).toFixed(); + const total = new BigNumber(amount).plus(expectedFee).toFixed(); + // 1003-TRAN-021 + expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee); + expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount); + expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total); }); describe('AddressField', () => { @@ -457,24 +471,29 @@ describe('TransferForm', () => { describe('TransferFee', () => { const props = { - amount: '200', - feeFactor: '0.001', - fee: '0.2', - transferAmount: '200', - decimals: 8, + amount: '20000', + discount: '0', + fee: '20', + decimals: 2, }; - it('calculates and renders the transfer fee', () => { + it('calculates and renders amounts and fee', () => { render(<TransferFee {...props} />); + expect(screen.queryByTestId('discount')).not.toBeInTheDocument(); + expect(screen.getByTestId('transfer-fee')).toHaveTextContent('0.2'); + expect(screen.getByTestId('transfer-amount')).toHaveTextContent('200.00'); + expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent( + '200.20' + ); + }); - const expected = new BigNumber(props.amount) - .times(props.feeFactor) - .toFixed(); - const total = new BigNumber(props.amount).plus(expected).toFixed(); - expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected); - expect(screen.getByTestId('transfer-amount')).toHaveTextContent( - props.amount + it('calculates and renders amounts, fee and discount', () => { + render(<TransferFee {...props} discount="10" />); + expect(screen.getByTestId('discount')).toHaveTextContent('0.1'); + expect(screen.getByTestId('transfer-fee')).toHaveTextContent('0.2'); + expect(screen.getByTestId('transfer-amount')).toHaveTextContent('200.00'); + expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent( + '200.10' ); - expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total); }); }); }); diff --git a/libs/accounts/src/lib/transfer-form.tsx b/libs/accounts/src/lib/transfer-form.tsx index d69f8dfde..a44697273 100644 --- a/libs/accounts/src/lib/transfer-form.tsx +++ b/libs/accounts/src/lib/transfer-form.tsx @@ -4,8 +4,9 @@ import { useRequired, useVegaPublicKey, addDecimal, - formatNumber, toBigNum, + removeDecimal, + addDecimalsFormatNumber, } from '@vegaprotocol/utils'; import { useT } from './use-t'; import { @@ -21,10 +22,11 @@ import type { Transfer } from '@vegaprotocol/wallet'; import { normalizeTransfer } from '@vegaprotocol/wallet'; import BigNumber from 'bignumber.js'; import type { ReactNode } from 'react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { AssetOption, Balance } from '@vegaprotocol/assets'; import { AccountType, AccountTypeMapping } from '@vegaprotocol/types'; +import { useTransferFeeQuery } from './__generated__/TransferFee'; interface FormFields { toVegaKey: string; @@ -51,7 +53,6 @@ export interface TransferFormProps { asset: Asset; }>; assetId?: string; - feeFactor: string | null; minQuantumMultiple: string | null; submitTransfer: (transfer: Transfer) => void; } @@ -61,7 +62,6 @@ export const TransferForm = ({ pubKeys, isReadOnly, assetId: initialAssetId, - feeFactor, submitTransfer, accounts, minQuantumMultiple, @@ -136,11 +136,22 @@ export const TransferForm = ({ // Max amount given selected asset and from account const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0); + const normalizedAmount = + (amount && asset && removeDecimal(amount, asset.decimals)) || '0'; - const fee = useMemo( - () => feeFactor && new BigNumber(feeFactor).times(amount).toString(), - [amount, feeFactor] - ); + const transferFeeQuery = useTransferFeeQuery({ + variables: { + fromAccount: pubKey || '', + fromAccountType: accountType || AccountType.ACCOUNT_TYPE_GENERAL, + amount: normalizedAmount, + assetId: asset?.id || '', + toAccount: selectedPubKey, + }, + skip: !pubKey || !amount || !asset || !selectedPubKey || fromVested, + }); + const transferFee = transferFeeQuery.loading + ? transferFeeQuery.data || transferFeeQuery.previousData + : transferFeeQuery.data; const onSubmit = useCallback( (fields: FormFields) => { @@ -432,12 +443,14 @@ export const TransferForm = ({ </TradingInputError> )} </TradingFormGroup> - {amount && fee && ( + {(transferFee?.estimateTransferFee || fromVested) && amount && asset && ( <TransferFee - amount={amount} - feeFactor={feeFactor} - fee={fromVested ? '0' : fee} - decimals={asset?.decimals} + amount={normalizedAmount} + fee={fromVested ? '0' : transferFee?.estimateTransferFee?.fee} + discount={ + fromVested ? '0' : transferFee?.estimateTransferFee?.discount + } + decimals={asset.decimals} /> )} <TradingButton type="submit" fill={true} disabled={isReadOnly}> @@ -449,39 +462,44 @@ export const TransferForm = ({ export const TransferFee = ({ amount, - feeFactor, fee, + discount, decimals, }: { amount: string; - feeFactor: string | null; fee?: string; - decimals?: number; + discount?: string; + decimals: number; }) => { const t = useT(); - if (!feeFactor || !amount || !fee) return null; - if (isNaN(Number(feeFactor)) || isNaN(Number(amount)) || isNaN(Number(fee))) { + if (!amount || !fee) return null; + if (isNaN(Number(amount)) || isNaN(Number(fee))) { return null; } - const totalValue = new BigNumber(amount).plus(fee).toString(); + const totalValue = ( + BigInt(amount) + + BigInt(fee) - + BigInt(discount || '0') + ).toString(); return ( <div className="mb-4 flex flex-col gap-2 text-xs"> <div className="flex flex-wrap items-center justify-between gap-1"> - <Tooltip - description={t( - `The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}`, - { feeFactor } - )} - > - <div>{t('Transfer fee')}</div> - </Tooltip> - + <div>{t('Transfer fee')}</div> <div data-testid="transfer-fee" className="text-muted"> - {formatNumber(fee, decimals)} + {addDecimalsFormatNumber(fee, decimals)} </div> </div> + {discount && discount !== '0' && ( + <div className="flex flex-wrap items-center justify-between gap-1"> + <div>{t('Discount')}</div> + <div data-testid="discount" className="text-muted"> + {addDecimalsFormatNumber(discount, decimals)} + </div> + </div> + )} + <div className="flex flex-wrap items-center justify-between gap-1"> <Tooltip description={t( @@ -492,7 +510,7 @@ export const TransferFee = ({ </Tooltip> <div data-testid="transfer-amount" className="text-muted"> - {formatNumber(amount, decimals)} + {addDecimalsFormatNumber(amount, decimals)} </div> </div> <div className="flex flex-wrap items-center justify-between gap-1"> @@ -505,7 +523,7 @@ export const TransferFee = ({ </Tooltip> <div data-testid="total-transfer-fee" className="text-muted"> - {formatNumber(totalValue, decimals)} + {addDecimalsFormatNumber(totalValue, decimals)} </div> </div> </div> diff --git a/libs/i18n/src/locales/en/accounts.json b/libs/i18n/src/locales/en/accounts.json index cface53cb..8f5259e75 100644 --- a/libs/i18n/src/locales/en/accounts.json +++ b/libs/i18n/src/locales/en/accounts.json @@ -35,7 +35,6 @@ "The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.", "The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.", "The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)", - "The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}": "The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}", "To account": "To account", "To Vega key": "To Vega key", "Total": "Total", diff --git a/specs/1003-TRAN-transfer.md b/specs/1003-TRAN-transfer.md index b6d08f33f..9f014c3de 100644 --- a/specs/1003-TRAN-transfer.md +++ b/specs/1003-TRAN-transfer.md @@ -40,8 +40,6 @@ ## Transfer -- **Must** display tooltip for "Transfer fee when hovered over.(<a name="1003-TRAN-017" href="#1003-TRAN-017">1003-TRAN-017</a>) - - **Must** display tooltip for "Amount to be transferred" when hovered over.(<a name="1003-TRAN-018" href="#1003-TRAN-018">1003-TRAN-018</a>) - **Must** display tooltip for "Total amount (with fee)" when hovered over.(<a name="1003-TRAN-019" href="#1003-TRAN-019">1003-TRAN-019</a>) From 0d850bd8b9fc52e08bc6d0a4daf729f0a2093c67 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:44:15 +0200 Subject: [PATCH 17/17] feat(trading): add LP fee settings (#5773) Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> --- .../competitions/games-container.tsx | 38 ++++++++++- .../rewards-container/active-rewards.tsx | 2 +- apps/trading/e2e/tests/teams/test_teams.py | 2 +- libs/environment/src/hooks/use-links.ts | 1 + libs/i18n/src/locales/en/trading.json | 1 + libs/markets/src/lib/__generated__/markets.ts | 8 ++- .../components/market-info/MarketInfo.graphql | 4 ++ .../market-info/__generated__/MarketInfo.ts | 6 +- .../market-info/market-info-accordion.tsx | 6 ++ .../market-info/market-info-panels.tsx | 41 ++++++++++++ .../market-info/tooltip-mapping.tsx | 5 +- .../oracle-full-profile.tsx | 2 +- libs/markets/src/lib/markets.graphql | 4 ++ libs/markets/src/lib/markets.mock.ts | 5 ++ libs/types/src/__generated__/types.ts | 64 +++++++++++++++++-- libs/types/src/global-types-mappings.ts | 39 ++++++++--- 16 files changed, 203 insertions(+), 25 deletions(-) diff --git a/apps/trading/components/competitions/games-container.tsx b/apps/trading/components/competitions/games-container.tsx index e59f9c576..9d6053942 100644 --- a/apps/trading/components/competitions/games-container.tsx +++ b/apps/trading/components/competitions/games-container.tsx @@ -1,6 +1,11 @@ import { type TransferNode } from '@vegaprotocol/types'; -import { ActiveRewardCard } from '../rewards-container/active-rewards'; +import { + ActiveRewardCard, + isActiveReward, +} from '../rewards-container/active-rewards'; import { useT } from '../../lib/use-t'; +import { useAssetsMapProvider } from '@vegaprotocol/assets'; +import { useMarketsMapProvider } from '@vegaprotocol/markets'; export const GamesContainer = ({ data, @@ -10,8 +15,35 @@ export const GamesContainer = ({ currentEpoch: number; }) => { const t = useT(); + // Re-load markets and assets in the games container to ensure that the + // the cards are updated (not grayed out) when the user navigates to the games page + const { data: assets } = useAssetsMapProvider(); + const { data: markets } = useMarketsMapProvider(); - if (!data || data.length === 0) { + const enrichedTransfers = data + .filter((node) => isActiveReward(node, currentEpoch)) + .map((node) => { + if (node.transfer.kind.__typename !== 'RecurringTransfer') { + return node; + } + + const asset = + assets && + assets[ + node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || '' + ]; + + const marketsInScope = + node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map( + (id) => markets && markets[id] + ); + + return { ...node, asset, markets: marketsInScope }; + }); + + if (!enrichedTransfers || !enrichedTransfers.length) return null; + + if (!enrichedTransfers || enrichedTransfers.length === 0) { return ( <p className="mb-6 text-muted"> {t('There are currently no games available.')} @@ -21,7 +53,7 @@ export const GamesContainer = ({ return ( <div className="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> - {data.map((game, i) => { + {enrichedTransfers.map((game, i) => { // TODO: Remove `kind` prop from ActiveRewardCard const { transfer } = game; if ( diff --git a/apps/trading/components/rewards-container/active-rewards.tsx b/apps/trading/components/rewards-container/active-rewards.tsx index 232efa97f..03ea7c171 100644 --- a/apps/trading/components/rewards-container/active-rewards.tsx +++ b/apps/trading/components/rewards-container/active-rewards.tsx @@ -468,7 +468,7 @@ export const ActiveRewardCard = ({ } </div> {dispatchStrategy?.dispatchMetric && ( - <span className="text-muted text-sm h-[2rem]"> + <span className="text-muted text-sm h-[3rem]"> {t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])} </span> )} diff --git a/apps/trading/e2e/tests/teams/test_teams.py b/apps/trading/e2e/tests/teams/test_teams.py index 56559d3ec..073660f4e 100644 --- a/apps/trading/e2e/tests/teams/test_teams.py +++ b/apps/trading/e2e/tests/teams/test_teams.py @@ -288,7 +288,7 @@ def test_game_card(competitions_page: Page): expect(game_1.get_by_test_id("distribution-strategy") ).to_have_text("Pro rata") expect(game_1.get_by_test_id("dispatch-metric-info") - ).to_have_text("Price maker fees paid • ") + ).to_have_text("Price maker fees paid • tDAI") expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs") expect(game_1.get_by_test_id("scope")).to_have_text("In team") expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00") diff --git a/libs/environment/src/hooks/use-links.ts b/libs/environment/src/hooks/use-links.ts index bd85af8ad..f3a893156 100644 --- a/libs/environment/src/hooks/use-links.ts +++ b/libs/environment/src/hooks/use-links.ts @@ -86,6 +86,7 @@ export const DocsLinks = VEGA_DOCS_URL POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`, QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`, REFERRALS: `${VEGA_DOCS_URL}/tutorials/proposals/referral-program-proposal`, + LIQUIDITY_FEE_PERCENTAGE: `${VEGA_DOCS_URL}/concepts/liquidity/rewards-penalties#determining-the-liquidity-fee-percentage`, } : undefined; diff --git a/libs/i18n/src/locales/en/trading.json b/libs/i18n/src/locales/en/trading.json index d3d5174fc..507950f49 100644 --- a/libs/i18n/src/locales/en/trading.json +++ b/libs/i18n/src/locales/en/trading.json @@ -110,6 +110,7 @@ "Fills": "Fills", "Final commission rate": "Final commission rate", "Find out more": "Find out more", + "For more info, visit the documentation": "For more info, visit the documentation", "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.": "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.", "From epoch": "From epoch", "Fully decentralised high performance peer-to-network trading.": "Fully decentralised high performance peer-to-network trading.", diff --git a/libs/markets/src/lib/__generated__/markets.ts b/libs/markets/src/lib/__generated__/markets.ts index b7216e707..cea34aedf 100644 --- a/libs/markets/src/lib/__generated__/markets.ts +++ b/libs/markets/src/lib/__generated__/markets.ts @@ -4,12 +4,12 @@ import { gql } from '@apollo/client'; import { FutureFragmentDoc, PerpetualFragmentDoc } from '../components/market-info/__generated__/MarketInfo'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } }; +export type MarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string }, liquidityFeeSettings?: { __typename?: 'LiquidityFeeSettings', feeConstant?: string | null, method: Types.LiquidityFeeMethod } | null }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } }; export type MarketsQueryVariables = Types.Exact<{ [key: string]: never; }>; -export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null }; +export type MarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, parentMarketID?: string | null, successorMarketID?: string | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string }, liquidityFeeSettings?: { __typename?: 'LiquidityFeeSettings', feeConstant?: string | null, method: Types.LiquidityFeeMethod } | null }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any } } }> } | null }; export const MarketFieldsFragmentDoc = gql` fragment MarketFields on Market { @@ -26,6 +26,10 @@ export const MarketFieldsFragmentDoc = gql` infrastructureFee liquidityFee } + liquidityFeeSettings { + feeConstant + method + } } tradableInstrument { instrument { diff --git a/libs/markets/src/lib/components/market-info/MarketInfo.graphql b/libs/markets/src/lib/components/market-info/MarketInfo.graphql index 61bcdadd7..6b4771ad9 100644 --- a/libs/markets/src/lib/components/market-info/MarketInfo.graphql +++ b/libs/markets/src/lib/components/market-info/MarketInfo.graphql @@ -171,6 +171,10 @@ query MarketInfo($marketId: ID!) { infrastructureFee liquidityFee } + liquidityFeeSettings { + feeConstant + method + } } priceMonitoringSettings { parameters { diff --git a/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts b/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts index 7724d2422..6424a7158 100644 --- a/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts +++ b/libs/markets/src/lib/components/market-info/__generated__/MarketInfo.ts @@ -16,7 +16,7 @@ export type MarketInfoQueryVariables = Types.Exact<{ }>; -export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, linearSlippageFactor: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, liquiditySLAParameters?: { __typename?: 'LiquiditySLAParameters', priceRange: string, commitmentMinTimeFraction: string, performanceHysteresisEpochs: number, slaCompetitionFactor: string } | null, liquidationStrategy?: { __typename?: 'LiquidationStrategy', disposalTimeStep: number, disposalFraction: string, fullDisposalSize: number, maxFractionConsumed: string } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null }; +export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, linearSlippageFactor: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string }, liquidityFeeSettings?: { __typename?: 'LiquidityFeeSettings', feeConstant?: string | null, method: Types.LiquidityFeeMethod } | null }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, liquiditySLAParameters?: { __typename?: 'LiquiditySLAParameters', priceRange: string, commitmentMinTimeFraction: string, performanceHysteresisEpochs: number, slaCompetitionFactor: string } | null, liquidationStrategy?: { __typename?: 'LiquidationStrategy', disposalTimeStep: number, disposalFraction: string, fullDisposalSize: number, maxFractionConsumed: string } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'Perpetual', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, address: string, args?: Array<string> | null, method: string, requiredConfirmations: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } | { __typename: 'DataSourceSpecConfigurationTimeTrigger', triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null>, conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } | { __typename?: 'Spot' } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null }; export const DataSourceFilterFragmentDoc = gql` fragment DataSourceFilter on Filter { @@ -196,6 +196,10 @@ export const MarketInfoDocument = gql` infrastructureFee liquidityFee } + liquidityFeeSettings { + feeConstant + method + } } priceMonitoringSettings { parameters { diff --git a/libs/markets/src/lib/components/market-info/market-info-accordion.tsx b/libs/markets/src/lib/components/market-info/market-info-accordion.tsx index dba5edff1..72b73917a 100644 --- a/libs/markets/src/lib/components/market-info/market-info-accordion.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-accordion.tsx @@ -28,6 +28,7 @@ import { InsurancePoolInfoPanel, KeyDetailsInfoPanel, LiquidationStrategyInfoPanel, + LiquidityFeesSettings, LiquidityInfoPanel, LiquidityMonitoringParametersInfoPanel, LiquidityPriceRangeInfoPanel, @@ -300,6 +301,11 @@ export const MarketInfoAccordion = ({ } content={<LiquiditySLAParametersInfoPanel market={market} />} /> + <AccordionItem + itemId="lp-fee-settings" + title={t('Liquidity fee settings')} + content={<LiquidityFeesSettings market={market} />} + /> <AccordionItem itemId="liquidity" title={t('Liquidity')} diff --git a/libs/markets/src/lib/components/market-info/market-info-panels.tsx b/libs/markets/src/lib/components/market-info/market-info-panels.tsx index fe3c3acbe..bdda1db01 100644 --- a/libs/markets/src/lib/components/market-info/market-info-panels.tsx +++ b/libs/markets/src/lib/components/market-info/market-info-panels.tsx @@ -44,6 +44,8 @@ import type { } from '@vegaprotocol/types'; import { ConditionOperatorMapping, + LiquidityFeeMethodMapping, + LiquidityFeeMethodMappingDescription, MarketStateMapping, MarketTradingModeMapping, } from '@vegaprotocol/types'; @@ -54,6 +56,7 @@ import { TOKEN_PROPOSAL, useEnvironment, useLinks, + DocsLinks, } from '@vegaprotocol/environment'; import type { Provider } from '../../oracle-schema'; import { OracleBasicProfile } from '../../components/oracle-basic-profile'; @@ -110,6 +113,44 @@ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => { ); }; +export const LiquidityFeesSettings = ({ market }: MarketInfoProps) => { + const t = useT(); + return ( + <> + <MarketInfoTable + data={{ + feeConstant: market.fees.liquidityFeeSettings?.feeConstant, + method: market.fees.liquidityFeeSettings && ( + <Tooltip + description={ + LiquidityFeeMethodMappingDescription[ + market.fees.liquidityFeeSettings?.method + ] + } + > + <span> + { + LiquidityFeeMethodMapping[ + market.fees.liquidityFeeSettings?.method + ] + } + </span> + </Tooltip> + ), + }} + /> + <p className="text-xs"> + <ExternalLink + href={DocsLinks?.LIQUIDITY_FEE_PERCENTAGE} + className="mt-2" + > + {t('Fore more info, visit the documentation')} + </ExternalLink> + </p> + </> + ); +}; + export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => { const t = useT(); const assetSymbol = getAsset(market).symbol; diff --git a/libs/markets/src/lib/components/market-info/tooltip-mapping.tsx b/libs/markets/src/lib/components/market-info/tooltip-mapping.tsx index 981c6c868..82c7363c6 100644 --- a/libs/markets/src/lib/components/market-info/tooltip-mapping.tsx +++ b/libs/markets/src/lib/components/market-info/tooltip-mapping.tsx @@ -16,7 +16,6 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => { infrastructureFee: t( 'Fees paid to validators as a reward for running the infrastructure of the network.' ), - markPrice: t( 'A concept derived from traditional markets. It is a calculated value for the ‘current market price’ on a market.' ), @@ -154,5 +153,9 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => { minProbabilityOfTradingLPOrders: t( 'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.' ), + method: t(`The method used to calculate the market's liquidity fee.`), + feeConstant: t( + 'The constant liquidity fee used when using the constant fee method .' + ), }; }; diff --git a/libs/markets/src/lib/components/oracle-full-profile/oracle-full-profile.tsx b/libs/markets/src/lib/components/oracle-full-profile/oracle-full-profile.tsx index 9f9f3f074..2318ccdf9 100644 --- a/libs/markets/src/lib/components/oracle-full-profile/oracle-full-profile.tsx +++ b/libs/markets/src/lib/components/oracle-full-profile/oracle-full-profile.tsx @@ -240,7 +240,7 @@ export const OracleFullProfile = ({ <div className="font-alpha calt dark:text-vega-light-300 text-vega-dark-300 mb-2 grid grid-cols-4 gap-1 uppercase"> <div className="col-span-1">{t('Market')}</div> <div className="col-span-1">{t('Status')}</div> - <div className="col-span-1">{t('Specifications')}</div> + <div className="col-span-2">{t('Specifications')}</div> </div> <div className="max-h-60 overflow-auto"> {oracleMarkets?.map((market) => ( diff --git a/libs/markets/src/lib/markets.graphql b/libs/markets/src/lib/markets.graphql index b33dc3dae..11544e120 100644 --- a/libs/markets/src/lib/markets.graphql +++ b/libs/markets/src/lib/markets.graphql @@ -12,6 +12,10 @@ fragment MarketFields on Market { infrastructureFee liquidityFee } + liquidityFeeSettings { + feeConstant + method + } } tradableInstrument { instrument { diff --git a/libs/markets/src/lib/markets.mock.ts b/libs/markets/src/lib/markets.mock.ts index 93d923338..faa8a13d6 100644 --- a/libs/markets/src/lib/markets.mock.ts +++ b/libs/markets/src/lib/markets.mock.ts @@ -52,6 +52,11 @@ export const createMarketFragment = ( infrastructureFee: '', liquidityFee: '', }, + liquidityFeeSettings: { + __typename: 'LiquidityFeeSettings', + method: Schema.LiquidityFeeMethod.METHOD_MARGINAL_COST, + feeConstant: '', + }, }, tradableInstrument: { instrument: { diff --git a/libs/types/src/__generated__/types.ts b/libs/types/src/__generated__/types.ts index 5b147778c..cd5ee37a2 100644 --- a/libs/types/src/__generated__/types.ts +++ b/libs/types/src/__generated__/types.ts @@ -14,6 +14,32 @@ export type Scalars = { Timestamp: any; }; +/** Margins for a hypothetical position not related to any existing party */ +export type AbstractMarginLevels = { + __typename?: 'AbstractMarginLevels'; + /** Asset for the current margins */ + asset: Asset; + /** + * If the margin of the party is greater than this level, then collateral will be released from the margin account into + * the general account of the party for the given asset. + */ + collateralReleaseLevel: Scalars['String']; + /** This is the minimum margin required for a party to place a new order on the network, expressed as unsigned integer */ + initialLevel: Scalars['String']; + /** Minimal margin for the position to be maintained in the network (unsigned integer) */ + maintenanceLevel: Scalars['String']; + /** Margin factor, only relevant for isolated margin mode, else 0 */ + marginFactor: Scalars['String']; + /** Margin mode of the party, cross margin or isolated margin */ + marginMode: MarginMode; + /** Market in which the margin is required for this party */ + market: Market; + /** When in isolated margin, the required order margin level, otherwise, 0 */ + orderMarginLevel: Scalars['String']; + /** If the margin is between maintenance and search, the network will initiate a collateral search, expressed as unsigned integer */ + searchLevel: Scalars['String']; +}; + /** An account record */ export type AccountBalance = { __typename?: 'AccountBalance'; @@ -359,6 +385,8 @@ export enum AuctionTrigger { export type BatchProposal = { __typename?: 'BatchProposal'; + /** Terms of all the proposals in the batch */ + batchTerms?: Maybe<BatchProposalTerms>; /** RFC3339Nano time and date when the proposal reached the network */ datetime: Scalars['Timestamp']; /** Details of the rejection reason */ @@ -389,10 +417,10 @@ export type BatchProposal = { votes: ProposalVotes; }; -/** The rationale for the proposal */ +/** The terms for the batch proposal */ export type BatchProposalTerms = { __typename?: 'BatchProposalTerms'; - /** Actual changes being introduced by the proposal - actions the proposal triggers if passed and enacted. */ + /** Actual changes being introduced by the batch proposal - actions the proposal triggers if passed and enacted. */ changes: Array<Maybe<BatchProposalTermsChange>>; /** * RFC3339Nano time and date when voting closes for this proposal. @@ -531,6 +559,22 @@ export type CompositePriceConfiguration = { decayWeight: Scalars['String']; }; +export type CompositePriceSource = { + __typename?: 'CompositePriceSource'; + /** The source of the price */ + PriceSource: Scalars['String']; + /** The last time the price source was updated in RFC3339Nano */ + lastUpdated: Scalars['Timestamp']; + /** The current value of the composite source price */ + price: Scalars['String']; +}; + +export type CompositePriceState = { + __typename?: 'CompositePriceState'; + /** Underlying state of the composite price */ + priceSources?: Maybe<Array<CompositePriceSource>>; +}; + export enum CompositePriceType { /** Composite price is set to the last trade (legacy) */ COMPOSITE_PRICE_TYPE_LAST_TRADE = 'COMPOSITE_PRICE_TYPE_LAST_TRADE', @@ -2165,9 +2209,9 @@ export type MarginEdge = { export type MarginEstimate = { __typename?: 'MarginEstimate'; /** Margin level estimate assuming no slippage */ - bestCase: MarginLevels; + bestCase: AbstractMarginLevels; /** Margin level estimate assuming slippage cap is applied */ - worstCase: MarginLevels; + worstCase: AbstractMarginLevels; }; /** Margins for a given a party */ @@ -2439,6 +2483,8 @@ export type MarketData = { liquidityProviderSla?: Maybe<Array<LiquidityProviderSLA>>; /** The mark price (an unsigned integer) */ markPrice: Scalars['String']; + /** State of the underlying internal composite price */ + markPriceState?: Maybe<CompositePriceState>; /** The methodology used for the calculation of the mark price */ markPriceType: CompositePriceType; /** Market of the associated mark price */ @@ -3053,6 +3099,8 @@ export type ObservableMarketData = { liquidityProviderSla?: Maybe<Array<ObservableLiquidityProviderSLA>>; /** The mark price (an unsigned integer) */ markPrice: Scalars['String']; + /** State of the underlying internal composite price */ + markPriceState?: Maybe<CompositePriceState>; /** The methodology used to calculated mark price */ markPriceType: CompositePriceType; /** The market growth factor for the last market time window */ @@ -4021,6 +4069,8 @@ export type PerpetualData = { fundingRate?: Maybe<Scalars['String']>; /** Internal composite price used as input to the internal VWAP */ internalCompositePrice: Scalars['String']; + /** The internal state of the underlying internal composite price */ + internalCompositePriceState?: Maybe<CompositePriceState>; /** The methodology used to calculated internal composite price for perpetual markets */ internalCompositePriceType: CompositePriceType; /** Time-weighted average price calculated from data points for this period from the internal data source. */ @@ -4031,6 +4081,8 @@ export type PerpetualData = { seqNum: Scalars['Int']; /** Time at which the funding period started */ startTime: Scalars['Timestamp']; + /** The last value from the external oracle */ + underlyingIndexPrice: Scalars['String']; }; export type PerpetualProduct = { @@ -4328,7 +4380,7 @@ export type ProposalDetail = { __typename?: 'ProposalDetail'; /** Batch proposal ID that is provided by Vega once proposal reaches the network */ batchId?: Maybe<Scalars['ID']>; - /** Terms of the proposal for a batch proposal */ + /** Terms of all the proposals in the batch */ batchTerms?: Maybe<BatchProposalTerms>; /** RFC3339Nano time and date when the proposal reached the Vega network */ datetime: Scalars['Timestamp']; @@ -4354,7 +4406,7 @@ export type ProposalDetail = { requiredParticipation: Scalars['String']; /** State of the proposal */ state: ProposalState; - /** Terms of the proposal for proposal */ + /** Terms of the proposal */ terms?: Maybe<ProposalTerms>; }; diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index 084308487..60cf886c3 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -1,12 +1,13 @@ -import type { - ConditionOperator, - EntityScope, - GovernanceTransferKind, - GovernanceTransferType, - IndividualScope, - PeggedReference, - ProposalChange, - TransferStatus, +import { + type LiquidityFeeMethod, + type ConditionOperator, + type EntityScope, + type GovernanceTransferKind, + type GovernanceTransferType, + type IndividualScope, + type PeggedReference, + type ProposalChange, + type TransferStatus, } from './__generated__/types'; import type { AccountType } from './__generated__/types'; import type { @@ -734,3 +735,23 @@ export const ProposalProductTypeShortName: Record<ProposalProductType, string> = SpotProduct: 'Spot', PerpetualProduct: 'Perp', }; + +export const LiquidityFeeMethodMapping: { [e in LiquidityFeeMethod]: string } = + { + /** Fee is set by the market to a constant value irrespective of any liquidity provider's nominated fee */ + METHOD_CONSTANT: 'Constant', + /** Fee is smallest value of all bids, such that liquidity providers with nominated fees less than or equal to this value still have sufficient commitment to fulfil the market's target stake. */ + METHOD_MARGINAL_COST: 'Marginal cost', + METHOD_UNSPECIFIED: 'Unspecified', + /** Fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment */ + METHOD_WEIGHTED_AVERAGE: 'Weighted average', + }; + +export const LiquidityFeeMethodMappingDescription: { + [e in LiquidityFeeMethod]: string; +} = { + METHOD_CONSTANT: `This liquidity fee is a constant value, set in the market parameters, and overrides the liquidity providers' nominated fees.`, + METHOD_MARGINAL_COST: `This liquidity fee factor is determined by sorting all LP fee bids from lowest to highest, with LPs' commitments tallied up to the point of fulfilling the market's target stake. The last LP's bid becomes the fee factor.`, + METHOD_UNSPECIFIED: 'Unspecified', + METHOD_WEIGHTED_AVERAGE: `This liquidity fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment.`, +};