diff --git a/apps/token/src/hooks/use-network-param.tsx b/apps/token/src/hooks/use-network-param.tsx deleted file mode 100644 index a8536ccdb..000000000 --- a/apps/token/src/hooks/use-network-param.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { gql } from '@apollo/client'; - -import type { NetworkParams } from './__generated__/NetworkParams'; - -export const NETWORK_PARAMS_QUERY = gql` - query NetworkParams { - networkParameters { - key - value - } - } -`; - -export function useNetworkParam(params: string[]) { - const { data, loading, error } = useQuery( - NETWORK_PARAMS_QUERY - ); - const foundParams = data?.networkParameters - ?.filter((p) => params.includes(p.key)) - .sort((a, b) => params.indexOf(a.key) - params.indexOf(b.key)); - return { - data: foundParams ? foundParams.map((f) => f.value) : null, - loading, - error, - }; -} diff --git a/apps/token/src/routes/governance/hooks/use-vote-information.ts b/apps/token/src/routes/governance/hooks/use-vote-information.ts index a62860f7d..634c2b4ca 100644 --- a/apps/token/src/routes/governance/hooks/use-vote-information.ts +++ b/apps/token/src/routes/governance/hooks/use-vote-information.ts @@ -1,8 +1,8 @@ +import { useNetworkParams } from '@vegaprotocol/react-helpers'; import React from 'react'; import { NetworkParams } from '../../../config'; import { useAppState } from '../../../contexts/app-state/app-state-context'; -import { useNetworkParam } from '../../../hooks/use-network-param'; import { BigNumber } from '../../../lib/bignumber'; import { addDecimal } from '../../../lib/decimals'; import type { @@ -16,7 +16,7 @@ const useProposalNetworkParams = ({ }: { proposal: Proposals_proposals; }) => { - const { data, loading } = useNetworkParam([ + const { data, loading } = useNetworkParams([ NetworkParams.GOV_UPDATE_MARKET_REQUIRED_MAJORITY, NetworkParams.GOV_UPDATE_MARKET_REQUIRED_PARTICIPATION, NetworkParams.GOV_NEW_MARKET_REQUIRED_MAJORITY, diff --git a/apps/token/src/routes/rewards/home/index.tsx b/apps/token/src/routes/rewards/home/index.tsx index d3a2368cc..aa6beb2aa 100644 --- a/apps/token/src/routes/rewards/home/index.tsx +++ b/apps/token/src/routes/rewards/home/index.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client'; import { Button, Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; import { formatDistance } from 'date-fns'; -// @ts-ignore No types availabel for duration-js +// @ts-ignore No types available for duration-js import Duration from 'duration-js'; import gql from 'graphql-tag'; import React from 'react'; @@ -15,10 +15,10 @@ import { AppStateActionType, useAppState, } from '../../../contexts/app-state/app-state-context'; -import { useNetworkParam } from '../../../hooks/use-network-param'; import type { Rewards } from './__generated__/Rewards'; import { RewardInfo } from './reward-info'; import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useNetworkParams } from '@vegaprotocol/react-helpers'; export const REWARDS_QUERY = gql` query Rewards($partyId: ID!) { @@ -77,7 +77,7 @@ export const RewardsIndex = () => { data: rewardAssetData, loading: rewardAssetLoading, error: rewardAssetError, - } = useNetworkParam([ + } = useNetworkParams([ NetworkParams.REWARD_ASSET, NetworkParams.REWARD_PAYOUT_DURATION, ]); diff --git a/apps/token/src/routes/staking/staking-form.tsx b/apps/token/src/routes/staking/staking-form.tsx index 21e7fd7ed..666639328 100644 --- a/apps/token/src/routes/staking/staking-form.tsx +++ b/apps/token/src/routes/staking/staking-form.tsx @@ -7,7 +7,6 @@ import { useNavigate } from 'react-router-dom'; import { TokenInput } from '../../components/token-input'; import { NetworkParams } from '../../config'; import { useAppState } from '../../contexts/app-state/app-state-context'; -import { useNetworkParam } from '../../hooks/use-network-param'; import { useSearchParams } from '../../hooks/use-search-params'; import { BigNumber } from '../../lib/bignumber'; import { addDecimal, removeDecimal } from '../../lib/decimals'; @@ -31,6 +30,7 @@ import type { UndelegateSubmissionBody, } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useNetworkParam } from '@vegaprotocol/react-helpers'; export const PARTY_DELEGATIONS_QUERY = gql` query PartyDelegations($partyId: ID!) { @@ -103,9 +103,9 @@ export const StakingForm = ({ setAmount(''); }, [action, setAmount]); - const { data } = useNetworkParam([ - NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT, - ]); + const { data } = useNetworkParam( + NetworkParams.VALIDATOR_DELEGATION_MIN_AMOUNT + ); const minTokensWithDecimals = React.useMemo(() => { const minTokens = new BigNumber(data && data.length === 1 ? data[0] : ''); diff --git a/apps/trading-e2e/src/integration/market-info.cy.ts b/apps/trading-e2e/src/integration/market-info.cy.ts index fafa62721..c02477b1f 100644 --- a/apps/trading-e2e/src/integration/market-info.cy.ts +++ b/apps/trading-e2e/src/integration/market-info.cy.ts @@ -1,4 +1,8 @@ -import { MarketState } from '@vegaprotocol/types'; +import { + MarketState, + MarketStateMapping, + MarketTradingModeMapping, +} from '@vegaprotocol/types'; import { mockTradingPage } from '../support/trading'; const marketInfoBtn = 'Info'; @@ -51,8 +55,12 @@ describe('market info is displayed', () => { validateMarketDataRow(0, 'Name', 'ETHBTC Quarterly (30 Jun 2022)'); validateMarketDataRow(1, 'Decimal Places', '2'); validateMarketDataRow(2, 'Position Decimal Places', '0'); - validateMarketDataRow(3, 'Trading Mode', 'Trading mode continuous'); - validateMarketDataRow(4, 'State', 'STATE_ACTIVE'); + validateMarketDataRow( + 3, + 'Trading Mode', + MarketTradingModeMapping.TRADING_MODE_CONTINUOUS + ); + validateMarketDataRow(4, 'State', MarketStateMapping.STATE_ACTIVE); validateMarketDataRow(5, 'Market ID', 'market-0'); }); diff --git a/apps/trading-e2e/src/integration/markets.cy.ts b/apps/trading-e2e/src/integration/markets.cy.ts index a8c811745..a4736732b 100644 --- a/apps/trading-e2e/src/integration/markets.cy.ts +++ b/apps/trading-e2e/src/integration/markets.cy.ts @@ -10,6 +10,7 @@ describe('markets table', () => { it('renders markets correctly', () => { cy.visit('/'); + cy.wait('@Market'); cy.wait('@MarketList'); cy.get('[data-testid^="market-link-"]') .should('not.be.empty') diff --git a/apps/trading-e2e/src/integration/withdraw.cy.ts b/apps/trading-e2e/src/integration/withdraw.cy.ts index b13b78e74..895a1171d 100644 --- a/apps/trading-e2e/src/integration/withdraw.cy.ts +++ b/apps/trading-e2e/src/integration/withdraw.cy.ts @@ -1,5 +1,6 @@ import { aliasQuery } from '@vegaprotocol/cypress'; import { connectEthereumWallet } from '../support/ethereum-wallet'; +import { generateAccounts } from '../support/mocks/generate-accounts'; import { generateNetworkParameters } from '../support/mocks/generate-network-parameters'; import { generateWithdrawFormQuery } from '../support/mocks/generate-withdraw-page-query'; import { generateWithdrawals } from '../support/mocks/generate-withdrawals'; @@ -20,6 +21,7 @@ describe('withdraw', () => { aliasQuery(req, 'Withdrawals', generateWithdrawals()); aliasQuery(req, 'NetworkParamsQuery', generateNetworkParameters()); aliasQuery(req, 'WithdrawFormQuery', generateWithdrawFormQuery()); + aliasQuery(req, 'Accounts', generateAccounts()); }); cy.visit('/portfolio'); diff --git a/apps/trading-e2e/src/support/mocks/generate-accounts.ts b/apps/trading-e2e/src/support/mocks/generate-accounts.ts index 3c6d4e36e..4639708a1 100644 --- a/apps/trading-e2e/src/support/mocks/generate-accounts.ts +++ b/apps/trading-e2e/src/support/mocks/generate-accounts.ts @@ -71,6 +71,18 @@ export const generateAccounts = ( decimals: 5, }, }, + { + __typename: 'Account', + type: AccountType.ACCOUNT_TYPE_GENERAL, + balance: '100000000', + market: null, + asset: { + __typename: 'Asset', + id: 'asset-0', + symbol: 'AST0', + decimals: 5, + }, + }, ], }, }; diff --git a/apps/trading-e2e/src/support/mocks/generate-market-info-query.ts b/apps/trading-e2e/src/support/mocks/generate-market-info-query.ts index 39238e0b0..709075397 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market-info-query.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market-info-query.ts @@ -86,7 +86,9 @@ export const generateMarketInfoQuery = ( id: '54b78c1b877e106842ae156332ccec740ad98d6bad43143ac6a029501dd7c6e0', }, markPrice: '5749', - indicativeVolume: '0', + suppliedStake: '56767', + marketValueProxy: '677678', + targetStake: '56789', bestBidVolume: '5', bestOfferVolume: '1', bestStaticBidVolume: '5', @@ -146,6 +148,7 @@ export const generateMarketInfoQuery = ( id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', symbol: 'tBTC', name: 'tBTC TEST', + decimals: 1, }, oracleSpecForSettlementPrice: { __typename: 'OracleSpec', diff --git a/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts b/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts index 510ec1ae4..d97801af2 100644 --- a/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts +++ b/apps/trading-e2e/src/support/mocks/generate-withdraw-page-query.ts @@ -1,66 +1,69 @@ import { AccountType } from '@vegaprotocol/types'; +import type { + WithdrawFormQuery, + WithdrawFormQuery_assetsConnection_edges, + WithdrawFormQuery_party_accounts, + WithdrawFormQuery_party_withdrawals, +} from '@vegaprotocol/withdraws'; import merge from 'lodash/merge'; import type { PartialDeep } from 'type-fest'; export const generateWithdrawFormQuery = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - override?: PartialDeep -) => { + override?: PartialDeep +): WithdrawFormQuery => { + const withdrawal: WithdrawFormQuery_party_withdrawals = { + id: 'withdrawal-0', + txHash: null, + __typename: 'Withdrawal', + }; + const account: WithdrawFormQuery_party_accounts = { + type: AccountType.ACCOUNT_TYPE_GENERAL, + balance: '100000000', + asset: { + __typename: 'Asset', + id: 'asset-0', + symbol: 'AST0', + }, + __typename: 'Account', + }; + const assetEdge1: WithdrawFormQuery_assetsConnection_edges = { + node: { + id: 'asset-0', + symbol: 'AST0', + name: 'Asset 0', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + __typename: 'AssetEdge', + }; + const assetEdge2: WithdrawFormQuery_assetsConnection_edges = { + node: { + id: 'asset-1', + symbol: 'AST1', + name: 'Asset 1', + decimals: 5, + source: { + __typename: 'ERC20', + contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81', + }, + __typename: 'Asset', + }, + __typename: 'AssetEdge', + }; const defaultResult = { party: { id: 'party-0', - withdrawals: [ - { - id: 'withdrawal-0', - txHash: null, - __typename: 'Withdrawal', - }, - ], - accounts: [ - { - type: AccountType.ACCOUNT_TYPE_GENERAL, - balance: '100000000', - asset: { - __typename: 'Asset', - id: 'asset-0', - symbol: 'AST0', - }, - __typename: 'Account', - }, - ], + withdrawals: [withdrawal], + accounts: [account], __typename: 'Party', }, assetsConnection: { - edges: [ - { - node: { - id: 'asset-0', - symbol: 'AST0', - name: 'Asset 0', - decimals: 5, - source: { - __typename: 'ERC20', - contractAddress: '0x5E4b9aDA947130Fc320a144cd22bC1641e5c9d81', - }, - __typename: 'Asset', - }, - __typename: 'AssetEdge', - }, - { - node: { - id: 'asset-1', - symbol: 'AST1', - name: 'Asset 1', - decimals: 5, - source: { - __typename: 'ERC20', - contractAddress: '0x444b9aDA947130Fc320a144cd22bC1641e5c9d81', - }, - __typename: 'Asset', - }, - __typename: 'AssetEdge', - }, - ], + __typename: 'AssetsConnection', + edges: [assetEdge1, assetEdge2], }, }; diff --git a/apps/trading/components/header/header.tsx b/apps/trading/components/header/header.tsx new file mode 100644 index 000000000..7eaaa0d6e --- /dev/null +++ b/apps/trading/components/header/header.tsx @@ -0,0 +1,53 @@ +import type { ReactElement, ReactNode } from 'react'; +import { Children } from 'react'; +import { cloneElement } from 'react'; + +interface TradeMarketHeaderProps { + title: ReactNode; + children: ReactElement[]; +} + +export const Header = ({ title, children }: TradeMarketHeaderProps) => { + return ( +
+
+
+ {title} +
+
+ {Children.map(children, (child, index) => { + return cloneElement(child, { + id: `header-stat-${index}`, + }); + })} +
+
+
+ ); +}; + +export const HeaderStat = ({ + children, + heading, + id, +}: { + children: ReactNode; + heading: string; + id?: string; +}) => { + const itemClass = + 'min-w-min w-[120px] whitespace-nowrap pb-3 px-4 border-l border-neutral-300 dark:border-neutral-700'; + const itemHeading = 'text-neutral-400'; + + return ( +
+
+ {heading} +
+
{children}
+
+ ); +}; diff --git a/apps/trading/components/header/index.ts b/apps/trading/components/header/index.ts new file mode 100644 index 000000000..677ca79d4 --- /dev/null +++ b/apps/trading/components/header/index.ts @@ -0,0 +1 @@ +export * from './header'; diff --git a/apps/trading/components/trading-mode-tooltip/trading-mode-tooltip.tsx b/apps/trading/components/trading-mode-tooltip/trading-mode-tooltip.tsx index e06923785..414a158dc 100644 --- a/apps/trading/components/trading-mode-tooltip/trading-mode-tooltip.tsx +++ b/apps/trading/components/trading-mode-tooltip/trading-mode-tooltip.tsx @@ -10,9 +10,8 @@ import type { Market_market } from '../../pages/markets/__generated__/Market'; type MarketDataGridProps = { grid: { - label: string; + label: string | ReactNode; value?: ReactNode; - isEstimate?: boolean; }[]; }; @@ -20,12 +19,11 @@ const MarketDataGrid = ({ grid }: MarketDataGridProps) => { return ( <> {grid.map( - ({ label, value, isEstimate }, index) => + ({ label, value }, index) => value && (
{label} - - {isEstimate && {'~'}} + {value}
@@ -61,12 +59,14 @@ const compileGridData = (market: Market_market) => { } if (market.data?.auctionEnd) { + const endDate = getDateTimeFormat().format( + new Date(market.data.auctionEnd) + ); grid.push({ label: isLiquidityMonitoringAuction ? t('Est auction end') : t('Auction end'), - value: getDateTimeFormat().format(new Date(market.data.auctionEnd)), - isEstimate: isLiquidityMonitoringAuction ? true : false, + value: isLiquidityMonitoringAuction ? `~${endDate}` : endDate, }); } @@ -79,8 +79,11 @@ const compileGridData = (market: Market_market) => { if (isLiquidityMonitoringAuction && market.data?.suppliedStake) { grid.push({ - label: t('Current liquidity'), - // @TODO: link this to liquidity view when https://github.com/vegaprotocol/frontend-monorepo/issues/491 is done + label: ( + + {t('Current liquidity')} + + ), value: formatStake(market.data.suppliedStake, market), }); } @@ -88,22 +91,24 @@ const compileGridData = (market: Market_market) => { if (market.data?.indicativePrice) { grid.push({ label: t('Est uncrossing price'), - value: addDecimalsFormatNumber( - market.data.indicativePrice, - market.positionDecimalPlaces - ), - isEstimate: true, + value: + '~' + + addDecimalsFormatNumber( + market.data.indicativePrice, + market.positionDecimalPlaces + ), }); } if (market.data?.indicativeVolume) { grid.push({ label: t('Est uncrossing vol'), - value: addDecimalsFormatNumber( - market.data.indicativeVolume, - market.positionDecimalPlaces - ), - isEstimate: true, + value: + '~' + + addDecimalsFormatNumber( + market.data.indicativeVolume, + market.positionDecimalPlaces + ), }); } @@ -128,7 +133,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => { case MarketTradingMode.TRADING_MODE_OPENING_AUCTION: { return ( <> -

+

{t( 'This new market is in an opening auction to determine a fair mid-price before starting continuous trading.' @@ -150,7 +155,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => { case AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: { return ( <> -

+

{t( 'This market is in auction until it reaches sufficient liquidity.' @@ -170,7 +175,7 @@ export const TradingModeTooltip = ({ market }: TradingModeTooltipProps) => { case AuctionTrigger.AUCTION_TRIGGER_PRICE: { return ( <> -

+

{t('This market is in auction due to high price volatility.')} {' '} diff --git a/apps/trading/pages/liquidity/[marketId].page.tsx b/apps/trading/pages/liquidity/[marketId].page.tsx new file mode 100644 index 000000000..a79f9e383 --- /dev/null +++ b/apps/trading/pages/liquidity/[marketId].page.tsx @@ -0,0 +1,103 @@ +import { LiquidityTable, useLiquidityProvision } from '@vegaprotocol/liquidity'; +import { t } from '@vegaprotocol/react-helpers'; +import { LiquidityProvisionStatus } from '@vegaprotocol/types'; +import { AsyncRenderer, Tab, Tabs } from '@vegaprotocol/ui-toolkit'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import type { AgGridReact } from 'ag-grid-react'; +import { Header, HeaderStat } from '../../components/header'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useRef, useMemo } from 'react'; + +const LiquidityPage = ({ id }: { id?: string }) => { + const { query } = useRouter(); + const { keypair } = useVegaWallet(); + const gridRef = useRef(null); + + const partyId = keypair?.pub; + // Default to first marketId query item if found + const marketId = + id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId); + + const { + data: { liquidityProviders, suppliedStake, targetStake, code, symbol }, + loading, + error, + } = useLiquidityProvision({ marketId }); + + const myLpEdges = useMemo( + () => liquidityProviders.filter((e) => e.party === partyId), + [liquidityProviders, partyId] + ); + const activeEdges = useMemo( + () => + liquidityProviders.filter( + (e) => e.status === LiquidityProvisionStatus.STATUS_ACTIVE + ), + [liquidityProviders] + ); + const inactiveEdges = useMemo( + () => + liquidityProviders.filter( + (e) => e.status !== LiquidityProvisionStatus.STATUS_ACTIVE + ), + [liquidityProviders] + ); + + const enum LiquidityTabs { + Active = 'active', + Inactive = 'inactive', + MyLiquidityProvision = 'myLP', + } + + const getActiveDefaultId = () => { + if (myLpEdges?.length > 0) return LiquidityTabs.MyLiquidityProvision; + if (activeEdges?.length) return LiquidityTabs.Active; + else if (inactiveEdges?.length > 0) return LiquidityTabs.Inactive; + return LiquidityTabs.Active; + }; + + return ( + +

+
+ {`${code} ${t('liquidity provision')}`} + + } + > + +
{`${targetStake} ${symbol}`}
+
+ +
{`${suppliedStake} ${symbol}`}
+
+ +
{marketId}
+
+
+ + + + + + + + + +
+ + ); +}; +LiquidityPage.getInitialProps = () => ({ + page: 'liquidity', +}); + +export default LiquidityPage; diff --git a/apps/trading/pages/liquidity/index.ts b/apps/trading/pages/liquidity/index.ts new file mode 100644 index 000000000..e02f4d096 --- /dev/null +++ b/apps/trading/pages/liquidity/index.ts @@ -0,0 +1 @@ +export * from './[marketId].page'; diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index 0bdbbd385..50b36ef2a 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -5,7 +5,7 @@ import { Interval } from '@vegaprotocol/types'; import { Splash } from '@vegaprotocol/ui-toolkit'; import debounce from 'lodash/debounce'; import { useRouter } from 'next/router'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { PageQueryContainer } from '../../components/page-query-container'; import { useGlobalStore } from '../../stores'; import { TradeGrid, TradePanels } from './trade-grid'; @@ -91,10 +91,19 @@ const MarketPage = ({ id }: { id?: string }) => { // Cache timestamp for yesterday to prevent full unmount of market page when // a rerender occurs - const [yTimestamp] = useState(() => { + const yTimestamp = useMemo(() => { const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; return new Date(yesterday * 1000).toISOString(); - }); + }, []); + + const variables = useMemo( + () => ({ + marketId: marketId || '', + interval: Interval.INTERVAL_I1H, + since: yTimestamp, + }), + [marketId, yTimestamp] + ); if (!marketId) { return ( @@ -109,11 +118,7 @@ const MarketPage = ({ id }: { id?: string }) => { query={MARKET_QUERY} data-testid="market" options={{ - variables: { - marketId, - interval: Interval.INTERVAL_I1H, - since: yTimestamp, - }, + variables, fetchPolicy: 'network-only', }} render={({ market }) => { diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index c8d8ec216..60a5d88f6 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -1,48 +1,47 @@ -import 'allotment/dist/style.css'; import { DealTicketContainer, MarketInfoContainer, } from '@vegaprotocol/deal-ticket'; import { OrderbookContainer } from '@vegaprotocol/market-depth'; -import { SelectMarketPopover } from '@vegaprotocol/market-list'; import { OrderListContainer } from '@vegaprotocol/orders'; import { FillsContainer } from '@vegaprotocol/fills'; import { PositionsContainer } from '@vegaprotocol/positions'; -import { - addDecimalsFormatNumber, - getDateFormat, - t, -} from '@vegaprotocol/react-helpers'; import { TradesContainer } from '@vegaprotocol/trades'; -import { - AuctionTrigger, - AuctionTriggerMapping, - MarketTradingMode, - MarketTradingModeMapping, -} from '@vegaprotocol/types'; import { LayoutPriority } from 'allotment'; import classNames from 'classnames'; import AutoSizer from 'react-virtualized-auto-sizer'; import { useState } from 'react'; import type { ReactNode } from 'react'; import type { Market_market } from './__generated__/Market'; -import type { CandleClose } from '@vegaprotocol/types'; -import { useGlobalStore } from '../../stores'; import { AccountsContainer } from '@vegaprotocol/accounts'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; -import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; -import { useEnvironment } from '@vegaprotocol/environment'; import { Tab, Tabs, + ResizableGrid, + ResizableGridPanel, + ButtonLink, + Tooltip, PriceCellChange, Link, - Tooltip, - ResizableGrid, - ButtonLink, - ResizableGridPanel, } from '@vegaprotocol/ui-toolkit'; +import { + addDecimalsFormatNumber, + getDateFormat, + t, +} from '@vegaprotocol/react-helpers'; +import { SelectMarketPopover } from '@vegaprotocol/market-list'; +import { useGlobalStore } from '../../stores'; +import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; +import { useEnvironment } from '@vegaprotocol/environment'; +import type { CandleClose } from '@vegaprotocol/types'; +import { + AuctionTrigger, + AuctionTriggerMapping, + MarketTradingMode, + MarketTradingModeMapping, +} from '@vegaprotocol/types'; import { TradingModeTooltip } from '../../components/trading-mode-tooltip'; const TradingViews = { diff --git a/libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts b/libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicketQuery.ts similarity index 100% rename from libs/deal-ticket/src/components/__generated__/DealTicketQuery.ts rename to libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicketQuery.ts diff --git a/libs/deal-ticket/src/components/__generated__/MarketNames.ts b/libs/deal-ticket/src/components/deal-ticket/__generated__/MarketNames.ts similarity index 100% rename from libs/deal-ticket/src/components/__generated__/MarketNames.ts rename to libs/deal-ticket/src/components/deal-ticket/__generated__/MarketNames.ts diff --git a/libs/deal-ticket/src/components/deal-ticket/__generated__/index.ts b/libs/deal-ticket/src/components/deal-ticket/__generated__/index.ts new file mode 100644 index 000000000..1a861aa78 --- /dev/null +++ b/libs/deal-ticket/src/components/deal-ticket/__generated__/index.ts @@ -0,0 +1,2 @@ +export * from './DealTicketQuery'; +export * from './MarketNames'; diff --git a/libs/deal-ticket/src/components/deal-ticket-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket-amount.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket-container.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket-limit-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx similarity index 93% rename from libs/deal-ticket/src/components/deal-ticket-limit-amount.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx index 2d5ba1cde..4d5e169ea 100644 --- a/libs/deal-ticket/src/components/deal-ticket-limit-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx @@ -48,7 +48,10 @@ export const DealTicketLimitAmount = ({ type="number" step={priceStep} data-testid="order-price" - {...register('price', { required: true, min: 0 })} + {...register('price', { + required: true, + min: 0, + })} /> diff --git a/libs/deal-ticket/src/components/deal-ticket-manager.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket-manager.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket-market-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket-market-amount.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket.spec.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx similarity index 100% rename from libs/deal-ticket/src/components/deal-ticket.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx diff --git a/libs/deal-ticket/src/components/expiry-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx similarity index 100% rename from libs/deal-ticket/src/components/expiry-selector.tsx rename to libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx diff --git a/libs/deal-ticket/src/components/deal-ticket/index.ts b/libs/deal-ticket/src/components/deal-ticket/index.ts new file mode 100644 index 000000000..97827ec50 --- /dev/null +++ b/libs/deal-ticket/src/components/deal-ticket/index.ts @@ -0,0 +1,12 @@ +export * from './__generated__'; +export * from './deal-ticket-amount'; +export * from './deal-ticket-container'; +export * from './deal-ticket-limit-amount'; +export * from './deal-ticket-manager'; +export * from './deal-ticket-market-amount'; +export * from './deal-ticket'; +export * from './expiry-selector'; +export * from './market-selector'; +export * from './side-selector'; +export * from './time-in-force-selector'; +export * from './type-selector'; diff --git a/libs/deal-ticket/src/components/market-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/market-selector.tsx similarity index 100% rename from libs/deal-ticket/src/components/market-selector.tsx rename to libs/deal-ticket/src/components/deal-ticket/market-selector.tsx diff --git a/libs/deal-ticket/src/components/side-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/side-selector.tsx similarity index 100% rename from libs/deal-ticket/src/components/side-selector.tsx rename to libs/deal-ticket/src/components/deal-ticket/side-selector.tsx diff --git a/libs/deal-ticket/src/components/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx similarity index 100% rename from libs/deal-ticket/src/components/time-in-force-selector.tsx rename to libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx diff --git a/libs/deal-ticket/src/components/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx similarity index 100% rename from libs/deal-ticket/src/components/type-selector.tsx rename to libs/deal-ticket/src/components/deal-ticket/type-selector.tsx diff --git a/libs/deal-ticket/src/components/index.ts b/libs/deal-ticket/src/components/index.ts index 740de3af5..93c181307 100644 --- a/libs/deal-ticket/src/components/index.ts +++ b/libs/deal-ticket/src/components/index.ts @@ -1,15 +1,2 @@ -export * from './__generated__/DealTicketQuery'; -export * from './__generated__/MarketInfoQuery'; -export * from './__generated__/MarketNames'; -export * from './deal-ticket-amount'; -export * from './deal-ticket-container'; -export * from './deal-ticket-limit-amount'; -export * from './deal-ticket-manager'; -export * from './deal-ticket-market-amount'; export * from './deal-ticket'; -export * from './expiry-selector'; -export * from './info-market'; -export * from './side-selector'; -export * from './time-in-force-selector'; -export * from './type-selector'; -export * from './market-selector'; +export * from './market-info'; diff --git a/libs/deal-ticket/src/components/__generated__/MarketInfoQuery.ts b/libs/deal-ticket/src/components/market-info/__generated__/MarketInfoQuery.ts similarity index 97% rename from libs/deal-ticket/src/components/__generated__/MarketInfoQuery.ts rename to libs/deal-ticket/src/components/market-info/__generated__/MarketInfoQuery.ts index 0193664e7..529fc790d 100644 --- a/libs/deal-ticket/src/components/__generated__/MarketInfoQuery.ts +++ b/libs/deal-ticket/src/components/market-info/__generated__/MarketInfoQuery.ts @@ -3,7 +3,7 @@ // @generated // This file was automatically generated and should not be edited. -import { Interval, MarketState, AccountType, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types"; +import { Interval, MarketState, MarketTradingMode, AccountType, AuctionTrigger } from "@vegaprotocol/types"; // ==================================================== // GraphQL query operation: MarketInfoQuery @@ -191,10 +191,6 @@ export interface MarketInfoQuery_market_data { * the mark price (an unsigned integer) */ markPrice: string; - /** - * indicative volume if the auction ended now, 0 if not in auction mode - */ - indicativeVolume: string; /** * the aggregated volume being bid at the best bid price. */ @@ -211,10 +207,6 @@ export interface MarketInfoQuery_market_data { * the aggregated volume being offered at the best static offer price, excluding pegged orders. */ bestStaticOfferVolume: string; - /** - * the sum of the size of all positions greater than 0. - */ - openInterest: string; /** * the highest price level on an order book for buy orders. */ @@ -227,6 +219,22 @@ export interface MarketInfoQuery_market_data { * what triggered an auction (if an auction was started) */ trigger: AuctionTrigger; + /** + * the sum of the size of all positions greater than 0. + */ + openInterest: string; + /** + * the supplied stake for the market + */ + suppliedStake: string | null; + /** + * the amount of stake targeted for this market + */ + targetStake: string | null; + /** + * the market value proxy + */ + marketValueProxy: string; /** * a list of valid price ranges per associated trigger */ @@ -287,6 +295,10 @@ export interface MarketInfoQuery_market_tradableInstrument_instrument_product_se * The full name of the asset (e.g: Great British Pound) */ name: string; + /** + * The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18 + */ + decimals: number; } export interface MarketInfoQuery_market_tradableInstrument_instrument_product_oracleSpecForSettlementPrice { @@ -454,14 +466,14 @@ export interface MarketInfoQuery_market { /** * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct * number denominated in the currency of the market. (uint64) - * + * * Examples: * Currency Balance decimalPlaces Real Balance * GBP 100 0 GBP 100 * GBP 100 2 GBP 1.00 * GBP 100 4 GBP 0.01 * GBP 1 4 GBP 0.0001 ( 0.01p ) - * + * * GBX (pence) 100 0 GBP 1.00 (100p ) * GBX (pence) 100 2 GBP 0.01 ( 1p ) * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) @@ -479,6 +491,10 @@ export interface MarketInfoQuery_market { * Current state of the market */ state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; /** * The proposal that initiated this market */ @@ -487,10 +503,6 @@ export interface MarketInfoQuery_market { * Get account for a party or market */ accounts: MarketInfoQuery_market_accounts[] | null; - /** - * Current mode of execution of the market - */ - tradingMode: MarketTradingMode; /** * Fees related data */ diff --git a/libs/deal-ticket/src/components/market-info/__generated__/index.ts b/libs/deal-ticket/src/components/market-info/__generated__/index.ts new file mode 100644 index 000000000..9807aed20 --- /dev/null +++ b/libs/deal-ticket/src/components/market-info/__generated__/index.ts @@ -0,0 +1 @@ +export * from './MarketInfoQuery'; diff --git a/libs/deal-ticket/src/components/market-info/index.ts b/libs/deal-ticket/src/components/market-info/index.ts new file mode 100644 index 000000000..0ffe69ca7 --- /dev/null +++ b/libs/deal-ticket/src/components/market-info/index.ts @@ -0,0 +1,3 @@ +export * from './__generated__'; +export * from './info-market-query'; +export * from './info-market'; diff --git a/libs/deal-ticket/src/components/market-info/info-key-value-table.tsx b/libs/deal-ticket/src/components/market-info/info-key-value-table.tsx new file mode 100644 index 000000000..52c9f9410 --- /dev/null +++ b/libs/deal-ticket/src/components/market-info/info-key-value-table.tsx @@ -0,0 +1,110 @@ +import { + t, + addDecimalsFormatNumber, + formatNumberPercentage, + formatNumber, +} from '@vegaprotocol/react-helpers'; +import { + KeyValueTableRow, + KeyValueTable, + Tooltip, +} from '@vegaprotocol/ui-toolkit'; +import BigNumber from 'bignumber.js'; +import startCase from 'lodash/startCase'; +import type { ReactNode } from 'react'; +import { tooltipMapping } from './tooltip-mapping'; + +interface RowProps { + field: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any; + decimalPlaces?: number; + asPercentage?: boolean; + unformatted?: boolean; + assetSymbol?: string; +} + +const Row = ({ + field, + value, + decimalPlaces, + asPercentage, + unformatted, + assetSymbol = '', +}: RowProps) => { + const isNumber = typeof value === 'number' || !isNaN(Number(value)); + const isPrimitive = typeof value === 'string' || isNumber; + const className = 'text-black dark:text-white text-ui !px-0 !font-normal'; + let formattedValue = value; + if (isNumber && !unformatted) { + if (decimalPlaces) { + formattedValue = `${addDecimalsFormatNumber( + value, + decimalPlaces + )} ${assetSymbol}`; + } else if (asPercentage && value) { + formattedValue = formatNumberPercentage(new BigNumber(value).times(100)); + } else { + formattedValue = `${formatNumber(Number(value))} ${assetSymbol}`; + } + } + if (isPrimitive) { + return ( + + +
{startCase(t(field))}
+
+ {formattedValue} +
+ ); + } + return null; +}; + +export interface MarketInfoTableProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + decimalPlaces?: number; + asPercentage?: boolean; + unformatted?: boolean; + omits?: string[]; + link?: ReactNode; + assetSymbol?: string; +} + +export const MarketInfoTable = ({ + data, + decimalPlaces, + asPercentage, + unformatted, + omits = ['__typename'], + link, + assetSymbol, +}: MarketInfoTableProps) => { + return ( + <> + + {Object.entries(data) + .filter(([key]) => !omits.includes(key)) + .map(([key, value]) => ( + + ))} + + {link} + + ); +}; diff --git a/libs/deal-ticket/src/components/info-market-query.ts b/libs/deal-ticket/src/components/market-info/info-market-query.ts similarity index 95% rename from libs/deal-ticket/src/components/info-market-query.ts rename to libs/deal-ticket/src/components/market-info/info-market-query.ts index 6c197b93a..35f9d63d0 100644 --- a/libs/deal-ticket/src/components/info-market-query.ts +++ b/libs/deal-ticket/src/components/market-info/info-market-query.ts @@ -8,6 +8,7 @@ export const MARKET_INFO_QUERY = gql` decimalPlaces positionDecimalPlaces state + tradingMode proposal { id rationale { @@ -49,15 +50,18 @@ export const MARKET_INFO_QUERY = gql` id } markPrice - indicativeVolume bestBidVolume bestOfferVolume bestStaticBidVolume bestStaticOfferVolume - openInterest bestBidPrice bestOfferPrice trigger + openInterest + suppliedStake + openInterest + targetStake + marketValueProxy priceMonitoringBounds { minValidPrice maxValidPrice @@ -94,6 +98,7 @@ export const MARKET_INFO_QUERY = gql` id symbol name + decimals } oracleSpecForSettlementPrice { id diff --git a/libs/deal-ticket/src/components/info-market.tsx b/libs/deal-ticket/src/components/market-info/info-market.tsx similarity index 52% rename from libs/deal-ticket/src/components/info-market.tsx rename to libs/deal-ticket/src/components/market-info/info-market.tsx index 074c2a385..89e65f22d 100644 --- a/libs/deal-ticket/src/components/info-market.tsx +++ b/libs/deal-ticket/src/components/market-info/info-market.tsx @@ -1,31 +1,23 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { ReactNode } from 'react'; import { useMemo } from 'react'; -import { - addDecimalsFormatNumber, - formatLabel, - formatNumber, - formatNumberPercentage, - t, -} from '@vegaprotocol/react-helpers'; -import { - KeyValueTable, - KeyValueTableRow, - AsyncRenderer, - Splash, - Accordion, - Tooltip, - Link, -} from '@vegaprotocol/ui-toolkit'; -import startCase from 'lodash/startCase'; +import { formatNumber, t } from '@vegaprotocol/react-helpers'; +import { AsyncRenderer, Splash, Accordion } from '@vegaprotocol/ui-toolkit'; import pick from 'lodash/pick'; -import omit from 'lodash/omit'; -import type { MarketInfoQuery, MarketInfoQuery_market } from './'; import BigNumber from 'bignumber.js'; import { useQuery } from '@apollo/client'; import { totalFees } from '@vegaprotocol/market-list'; -import { AccountType, Interval } from '@vegaprotocol/types'; +import { + AccountType, + Interval, + MarketStateMapping, + MarketTradingModeMapping, +} from '@vegaprotocol/types'; import { MARKET_INFO_QUERY } from './info-market-query'; +import type { + MarketInfoQuery, + MarketInfoQuery_market, + MarketInfoQuery_market_candles, +} from './__generated__/MarketInfoQuery'; +import { MarketInfoTable } from './info-key-value-table'; import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { generatePath } from 'react-router-dom'; @@ -42,11 +34,11 @@ export interface InfoProps { export const calcCandleVolume = ( m: MarketInfoQuery_market ): string | undefined => { - return m?.candles - ?.reduce((acc, c) => { + return m.candles + ?.reduce((acc: BigNumber, c: MarketInfoQuery_market_candles | null) => { return acc.plus(new BigNumber(c?.volume ?? 0)); - }, new BigNumber(0)) - .toString(); + }, new BigNumber(m.candles?.[0]?.volume ?? 0)) + ?.toString(); }; export interface MarketInfoContainerProps { @@ -57,10 +49,16 @@ export const MarketInfoContainer = ({ marketId }: MarketInfoContainerProps) => { const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; return new Date(yesterday * 1000).toISOString(); }, []); - - const { data, loading, error } = useQuery(MARKET_INFO_QUERY, { - variables: { marketId, interval: Interval.INTERVAL_I1H, since: yTimestamp }, - }); + const variables = useMemo( + () => ({ marketId, since: yTimestamp, interval: Interval.INTERVAL_I1H }), + [marketId, yTimestamp] + ); + const { data, loading, error } = useQuery( + MARKET_INFO_QUERY, + { + variables, + } + ); return ( data={data} loading={loading} error={error}> @@ -79,6 +77,8 @@ export const Info = ({ market }: InfoProps) => { const { VEGA_TOKEN_URL } = useEnvironment(); const headerClassName = 'uppercase text-lg'; const dayVolume = calcCandleVolume(market); + const assetSymbol = + market.tradableInstrument.instrument.product?.settlementAsset.symbol; const marketDataPanels = [ { title: t('Current fees'), @@ -137,28 +137,33 @@ export const Info = ({ market }: InfoProps) => { }, ...(market.accounts || []) .filter((a) => a.type === AccountType.ACCOUNT_TYPE_INSURANCE) - .map((a, i) => ({ + .map((a) => ({ title: t(`Insurance pool`), content: ( ), })), ]; - - const keyDetails = pick( - market, - 'name', - 'decimalPlaces', - 'positionDecimalPlaces', - 'tradingMode', - 'state', - 'id' as 'marketId' - ); + const { VEGA_EXPLORER_URL } = useEnvironment(); + const keyDetails = { + ...pick( + market, + 'name', + 'decimalPlaces', + 'positionDecimalPlaces', + 'tradingMode' + ), + state: MarketStateMapping[market.state], + }; const marketSpecPanels = [ { title: t('Key details'), @@ -166,10 +171,10 @@ export const Info = ({ market }: InfoProps) => { ), @@ -251,6 +256,12 @@ export const Info = ({ market }: InfoProps) => { ), })), + ...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({ + title: t(`Price monitoring bound ${i + 1}`), + content: ( + + ), + })), { title: t('Liquidity monitoring parameters'), content: ( @@ -263,6 +274,28 @@ export const Info = ({ market }: InfoProps) => { /> ), }, + { + title: t('Liquidity'), + content: ( + + {t('View liquidity provision table')} + + } + /> + ), + }, { title: t('Oracle'), content: ( @@ -276,6 +309,13 @@ export const Info = ({ market }: InfoProps) => { market.tradableInstrument.instrument.product .oracleSpecForTradingTermination.id, }} + link={ + + {t('View full oracle details')} + + } /> ), }, @@ -323,172 +363,3 @@ export const Info = ({ market }: InfoProps) => { ); }; - -const tooltipMapping: Record = { - makerFee: t( - 'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).' - ), - liquidityFee: t( - 'Liquidity portion of the fee is paid to market makers for providing liquidity, and is transferred to the market-maker fee pool for the market.' - ), - 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.' - ), - openInterest: t( - 'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).' - ), - indicativeVolume: t( - 'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).' - ), - bestBidVolume: t( - 'The aggregated volume being bid at the best bid price on the market.' - ), - bestOfferVolume: t( - 'The aggregated volume being offered at the best offer price on the market.' - ), - bestStaticBidVolume: t( - 'The aggregated volume being bid at the best static bid price on the market.' - ), - bestStaticOfferVolume: t( - 'The aggregated volume being offered at the best static offer price on the market.' - ), - - decimalPlaces: t('The smallest price increment on the book.'), - positionDecimalPlaces: t( - 'How big the smallest order / position on the market can be.' - ), - tradingMode: t('The trading mode the market is currently running.'), - state: t('The current state of the market'), - - base: t( - 'The first currency in a pair for a currency-based derivatives market.' - ), - quote: t( - 'The second currency in a pair for a currency-based derivatives market.' - ), - class: t( - 'The classification of the product. Examples: shares, commodities, crypto, FX.' - ), - sector: t( - 'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.' - ), - - short: t( - 'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.' - ), - long: t( - 'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.' - ), - - tau: ( - - {t('Projection horizon measured as a year fraction used in ')} - - {t('Expected Shortfall')} - - {t(' calculation when obtaining Risk Factor Long and Risk Factor Short')} - - ), - riskAversionParameter: ( - - {t('Probability level used in ')} - - {t('Expected Shortfall')} - - {t(' calculation when obtaining Risk Factor Long and Risk Factor Short')} - - ), - - horizonSecs: t('Time horizon of the price projection in seconds.'), - probability: t( - 'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.' - ), - auctionExtensionSecs: t( - 'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.' - ), - - triggeringRatio: t('The triggering ratio for entering liquidity auction.'), - timeWindow: t('The length of time over which open interest is measured.'), - scalingFactor: t( - 'The scaling between the liquidity demand estimate, based on open interest and target stake.' - ), -}; - -interface RowProps { - field: string; - value: any; - decimalPlaces?: number; - asPercentage?: boolean; - unformatted?: boolean; -} - -const Row = ({ - field, - value, - decimalPlaces, - asPercentage, - unformatted, -}: RowProps) => { - const isNumber = typeof value === 'number' || !isNaN(Number(value)); - const isPrimitive = typeof value === 'string' || isNumber; - if (isPrimitive) { - return ( - - -
{startCase(t(field))}
-
- - {isNumber && !unformatted - ? decimalPlaces - ? addDecimalsFormatNumber(value, decimalPlaces) - : asPercentage - ? formatNumberPercentage(new BigNumber(value * 100)) - : formatNumber(Number(value)) - : value} - -
- ); - } - return null; -}; - -export interface MarketInfoTableProps { - data: any; - decimalPlaces?: number; - asPercentage?: boolean; - unformatted?: boolean; - omits?: string[]; -} - -export const MarketInfoTable = ({ - data, - decimalPlaces, - asPercentage, - unformatted, - omits = ['__typename'], -}: MarketInfoTableProps) => { - return ( - - {Object.entries(omit(data, ...omits) || []).map(([key, value]) => ( - - ))} - - ); -}; diff --git a/libs/deal-ticket/src/components/market-info/tooltip-mapping.tsx b/libs/deal-ticket/src/components/market-info/tooltip-mapping.tsx new file mode 100644 index 000000000..ef78bac6e --- /dev/null +++ b/libs/deal-ticket/src/components/market-info/tooltip-mapping.tsx @@ -0,0 +1,103 @@ +import { t } from '@vegaprotocol/react-helpers'; +import { Link } from '@vegaprotocol/ui-toolkit'; +import type { ReactNode } from 'react'; + +export const tooltipMapping: Record = { + makerFee: t( + 'Maker portion of the fee is transferred to the non-aggressive, or passive party in the trade (the maker, as opposed to the taker).' + ), + liquidityFee: t( + 'Liquidity portion of the fee is paid to market makers for providing liquidity, and is transferred to the liquidity fee pool for the market.' + ), + 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.' + ), + openInterest: t( + 'The volume of all open positions in a given market (the sum of the size of all positions greater than 0).' + ), + indicativeVolume: t( + 'The volume at which all trades would occur if the auction was uncrossed now (when in auction mode).' + ), + bestBidVolume: t( + 'The aggregated volume being bid at the best bid price on the market.' + ), + bestOfferVolume: t( + 'The aggregated volume being offered at the best offer price on the market.' + ), + bestStaticBidVolume: t( + 'The aggregated volume being bid at the best static bid price on the market.' + ), + bestStaticOfferVolume: t( + 'The aggregated volume being offered at the best static offer price on the market.' + ), + + decimalPlaces: t('The smallest price increment on the book.'), + positionDecimalPlaces: t( + 'How big the smallest order / position on the market can be.' + ), + tradingMode: t('The trading mode the market is currently running.'), + state: t('The current state of the market'), + + base: t( + 'The first currency in a pair for a currency-based derivatives market.' + ), + quote: t( + 'The second currency in a pair for a currency-based derivatives market.' + ), + class: t( + 'The classification of the product. Examples: shares, commodities, crypto, FX.' + ), + sector: t( + 'Data about the sector. Example: "automotive" for a market based on value of Tesla shares.' + ), + + short: t( + 'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.' + ), + long: t( + 'A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.' + ), + + tau: ( + + {t('Projection horizon measured as a year fraction used in ')} + + {t('Expected Shortfall')} + + {t(' calculation when obtaining Risk Factor Long and Risk Factor Short')} + + ), + riskAversionParameter: ( + + {t('Probability level used in ')} + + {t('Expected Shortfall')} + + {t(' calculation when obtaining Risk Factor Long and Risk Factor Short')} + + ), + + horizonSecs: t('Time horizon of the price projection in seconds.'), + probability: t( + 'Probability level for price projection, e.g. value of 0.95 will result in a price range such that over the specified projection horizon, the prices observed in the market should be in that range 95% of the time.' + ), + auctionExtensionSecs: t( + 'Auction extension duration in seconds, should the price breach its theoretical level over the specified horizon at the specified probability level.' + ), + + triggeringRatio: t('The triggering ratio for entering liquidity auction.'), + timeWindow: t('The length of time over which open interest is measured.'), + scalingFactor: t( + 'The scaling between the liquidity demand estimate, based on open interest and target stake.' + ), +}; diff --git a/libs/liquidity/.babelrc b/libs/liquidity/.babelrc new file mode 100644 index 000000000..c7d82affe --- /dev/null +++ b/libs/liquidity/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@nrwl/next/babel"], + "plugins": [] +} diff --git a/libs/liquidity/.eslintrc.json b/libs/liquidity/.eslintrc.json new file mode 100644 index 000000000..db820c5d0 --- /dev/null +++ b/libs/liquidity/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "__generated__"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/liquidity/README.md b/libs/liquidity/README.md new file mode 100644 index 000000000..112274811 --- /dev/null +++ b/libs/liquidity/README.md @@ -0,0 +1,7 @@ +# Liquidity + +This library contains liquidity provision data providers and view containers. + +## Running unit tests + +Run `nx test liquidity` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/liquidity/jest.config.js b/libs/liquidity/jest.config.js new file mode 100644 index 000000000..fd4b91f52 --- /dev/null +++ b/libs/liquidity/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + displayName: 'positions', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/positions', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/libs/liquidity/package.json b/libs/liquidity/package.json new file mode 100644 index 000000000..b301271be --- /dev/null +++ b/libs/liquidity/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/liquidity", + "version": "0.0.1" +} diff --git a/libs/liquidity/project.json b/libs/liquidity/project.json new file mode 100644 index 000000000..f1b81c3c4 --- /dev/null +++ b/libs/liquidity/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/liquidity", + "sourceRoot": "libs/liquidity/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/liquidity", + "tsConfig": "libs/liquidity/tsconfig.lib.json", + "project": "libs/liquidity/package.json", + "entryFile": "libs/liquidity/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/liquidity/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/liquidity/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/liquidity"], + "options": { + "jestConfig": "libs/liquidity/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/liquidity/src/index.ts b/libs/liquidity/src/index.ts new file mode 100644 index 000000000..f41a696fd --- /dev/null +++ b/libs/liquidity/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/libs/liquidity/src/lib/__generated__/MarketLiquidity.ts b/libs/liquidity/src/lib/__generated__/MarketLiquidity.ts new file mode 100644 index 000000000..194f3f2fe --- /dev/null +++ b/libs/liquidity/src/lib/__generated__/MarketLiquidity.ts @@ -0,0 +1,252 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { AccountType, LiquidityProvisionStatus } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketLiquidity +// ==================================================== + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges_node { + __typename: "Account"; + /** + * Account type (General, Margin, etc) + */ + type: AccountType; + /** + * Balance as string - current account balance (approx. as balances can be updated several times per second) + */ + balance: string; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges { + __typename: "AccountEdge"; + /** + * The account + */ + node: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges_node; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection { + __typename: "AccountsConnection"; + /** + * List of accounts available for the connection + */ + edges: (MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection_edges | null)[] | null; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party { + __typename: "Party"; + /** + * Party identifier + */ + id: string; + /** + * Collateral accounts relating to a party + */ + accountsConnection: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party_accountsConnection; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges_node { + __typename: "LiquidityProvision"; + /** + * Unique identifier for the order (set by the system after consensus) + */ + id: string | null; + /** + * The Id of the party making this commitment + */ + party: MarketLiquidity_market_liquidityProvisionsConnection_edges_node_party; + /** + * When the liquidity provision was initially created (formatted RFC3339) + */ + createdAt: string; + /** + * RFC3339Nano time of when the liquidity provision was updated + */ + updatedAt: string | null; + /** + * Specified as a unit-less number that represents the amount of settlement asset of the market. + */ + commitmentAmount: string; + /** + * Nominated liquidity fee factor, which is an input to the calculation of maker fees on the market, as per setting fees and rewarding liquidity providers. + */ + fee: string; + /** + * The current status of this liquidity provision + */ + status: LiquidityProvisionStatus; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection_edges { + __typename: "LiquidityProvisionsEdge"; + node: MarketLiquidity_market_liquidityProvisionsConnection_edges_node; +} + +export interface MarketLiquidity_market_liquidityProvisionsConnection { + __typename: "LiquidityProvisionsConnection"; + edges: (MarketLiquidity_market_liquidityProvisionsConnection_edges | null)[] | null; +} + +export interface MarketLiquidity_market_tradableInstrument_instrument_product_settlementAsset { + __typename: "Asset"; + /** + * The ID of the asset + */ + id: string; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; + /** + * The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18 + */ + decimals: number; +} + +export interface MarketLiquidity_market_tradableInstrument_instrument_product { + __typename: "Future"; + /** + * The name of the asset (string) + */ + settlementAsset: MarketLiquidity_market_tradableInstrument_instrument_product_settlementAsset; +} + +export interface MarketLiquidity_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; + /** + * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) + */ + product: MarketLiquidity_market_tradableInstrument_instrument_product; +} + +export interface MarketLiquidity_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of, or reference to, a fully specified instrument. + */ + instrument: MarketLiquidity_market_tradableInstrument_instrument; +} + +export interface MarketLiquidity_market_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface MarketLiquidity_market_data_liquidityProviderFeeShare_party { + __typename: "Party"; + /** + * Party identifier + */ + id: string; +} + +export interface MarketLiquidity_market_data_liquidityProviderFeeShare { + __typename: "LiquidityProviderFeeShare"; + /** + * The liquidity provider party ID + */ + party: MarketLiquidity_market_data_liquidityProviderFeeShare_party; + /** + * The share owned by this liquidity provider (float) + */ + equityLikeShare: string; + /** + * The average entry valuation of the liquidity provider for the market + */ + averageEntryValuation: string; +} + +export interface MarketLiquidity_market_data { + __typename: "MarketData"; + /** + * market ID of the associated mark price + */ + market: MarketLiquidity_market_data_market; + /** + * the supplied stake for the market + */ + suppliedStake: string | null; + /** + * the sum of the size of all positions greater than 0. + */ + openInterest: string; + /** + * the amount of stake targeted for this market + */ + targetStake: string | null; + /** + * the market value proxy + */ + marketValueProxy: string; + /** + * the equity like share of liquidity fee for each liquidity provider + */ + liquidityProviderFeeShare: MarketLiquidity_market_data_liquidityProviderFeeShare[] | null; +} + +export interface MarketLiquidity_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * positionDecimalPlaces indicates the number of decimal places that an integer must be shifted in order to get a correct size (uint64). + * i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes. + * 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market. + * This sets how big the smallest order / position on the market can be. + */ + positionDecimalPlaces: number; + /** + * The list of the liquidity provision commitments for this market + */ + liquidityProvisionsConnection: MarketLiquidity_market_liquidityProvisionsConnection; + /** + * An instance of, or reference to, a tradable instrument. + */ + tradableInstrument: MarketLiquidity_market_tradableInstrument; + /** + * marketData for the given market + */ + data: MarketLiquidity_market_data | null; +} + +export interface MarketLiquidity { + /** + * An instrument that is trading on the Vega network + */ + market: MarketLiquidity_market | null; +} + +export interface MarketLiquidityVariables { + marketId: string; + partyId?: string | null; +} diff --git a/libs/liquidity/src/lib/__generated__/index.ts b/libs/liquidity/src/lib/__generated__/index.ts new file mode 100644 index 000000000..ac85c1929 --- /dev/null +++ b/libs/liquidity/src/lib/__generated__/index.ts @@ -0,0 +1 @@ +export * from './MarketLiquidity'; diff --git a/libs/liquidity/src/lib/index.ts b/libs/liquidity/src/lib/index.ts new file mode 100644 index 000000000..adca1cf63 --- /dev/null +++ b/libs/liquidity/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from './__generated__'; +export * from './liquidity-data-provider'; +export * from './liquidity-table'; diff --git a/libs/liquidity/src/lib/liquidity-data-provider.ts b/libs/liquidity/src/lib/liquidity-data-provider.ts new file mode 100644 index 000000000..c96eb285b --- /dev/null +++ b/libs/liquidity/src/lib/liquidity-data-provider.ts @@ -0,0 +1,172 @@ +import { gql, useQuery } from '@apollo/client'; +import type { LiquidityProvisionStatus } from '@vegaprotocol/types'; +import { AccountType } from '@vegaprotocol/types'; +import { useNetworkParam } from '@vegaprotocol/react-helpers'; +import BigNumber from 'bignumber.js'; +import type { + MarketLiquidity, + MarketLiquidity_market_data_liquidityProviderFeeShare, +} from './__generated__'; + +const SISKA_NETWORK_PARAMETER = 'market.liquidity.stakeToCcySiskas'; + +const MARKET_LIQUIDITY_QUERY = gql` + query MarketLiquidity($marketId: ID!, $partyId: String) { + market(id: $marketId) { + id + decimalPlaces + positionDecimalPlaces + liquidityProvisionsConnection(party: $partyId) { + edges { + node { + id + party { + id + accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) { + edges { + node { + type + balance + } + } + } + } + createdAt + updatedAt + commitmentAmount + fee + status + } + } + } + tradableInstrument { + instrument { + code + product { + ... on Future { + settlementAsset { + id + symbol + decimals + } + } + } + } + } + data { + market { + id + } + suppliedStake + openInterest + targetStake + marketValueProxy + liquidityProviderFeeShare { + party { + id + } + equityLikeShare + averageEntryValuation + } + } + } + } +`; + +export interface LiquidityProvision { + party: string; + commitmentAmount: string | undefined; + fee: string | undefined; + equityLikeShare: string; + averageEntryValuation: string; + obligation: string | null; + supplied: string | null; + status?: LiquidityProvisionStatus; + createdAt: string | undefined; + updatedAt: string | null | undefined; +} + +export interface LiquidityData { + liquidityProviders: LiquidityProvision[]; + suppliedStake?: string | null; + targetStake?: string | null; + code?: string; + symbol?: string; + decimalPlaces?: number; + positionDecimalPlaces?: number; + assetDecimalPlaces?: number; +} + +export const useLiquidityProvision = ({ + marketId, + partyId, +}: { + partyId?: string; + marketId?: string; +}) => { + const { data: stakeToCcySiskas } = useNetworkParam(SISKA_NETWORK_PARAMETER); + const stakeToCcySiska = stakeToCcySiskas && stakeToCcySiskas[0]; + const { data, loading, error } = useQuery( + MARKET_LIQUIDITY_QUERY, + { + variables: { marketId }, + } + ); + const liquidityProviders = ( + data?.market?.data?.liquidityProviderFeeShare || [] + ) + ?.filter( + (p: MarketLiquidity_market_data_liquidityProviderFeeShare) => + !partyId || p.party.id === partyId + ) // if partyId is provided, filter out other parties + .map((provider: MarketLiquidity_market_data_liquidityProviderFeeShare) => { + const liquidityProvisionConnection = + data?.market?.liquidityProvisionsConnection.edges?.find( + (e) => e?.node.party.id === provider.party.id + ); + const balance = + liquidityProvisionConnection?.node?.party.accountsConnection.edges?.reduce( + (acc, e) => { + return e?.node.type === AccountType.ACCOUNT_TYPE_BOND // just an extra check to make sure we only use bond accounts + ? acc.plus(new BigNumber(e?.node.balance ?? 0)) + : acc; + }, + new BigNumber(0) + ); + const obligation = + stakeToCcySiska && + new BigNumber(stakeToCcySiska) + .times(liquidityProvisionConnection?.node?.commitmentAmount ?? 1) + .toString(); + const supplied = + stakeToCcySiska && + new BigNumber(stakeToCcySiska).times(balance ?? 1).toString(); + return { + party: provider.party.id, + createdAt: liquidityProvisionConnection?.node?.createdAt, + updatedAt: liquidityProvisionConnection?.node?.updatedAt, + commitmentAmount: liquidityProvisionConnection?.node?.commitmentAmount, + fee: liquidityProvisionConnection?.node?.fee, + status: liquidityProvisionConnection?.node?.status, + equityLikeShare: provider.equityLikeShare, + averageEntryValuation: provider.averageEntryValuation, + obligation, + supplied, + }; + }); + const liquidityData: LiquidityData = { + liquidityProviders, + suppliedStake: data?.market?.data?.suppliedStake, + targetStake: data?.market?.data?.targetStake, + decimalPlaces: data?.market?.decimalPlaces, + positionDecimalPlaces: data?.market?.positionDecimalPlaces, + code: data?.market?.tradableInstrument.instrument.code, + assetDecimalPlaces: + data?.market?.tradableInstrument.instrument.product.settlementAsset + .decimals, + symbol: + data?.market?.tradableInstrument.instrument.product.settlementAsset + .symbol, + }; + return { data: liquidityData, loading, error }; +}; diff --git a/libs/liquidity/src/lib/liquidity-table.spec.tsx b/libs/liquidity/src/lib/liquidity-table.spec.tsx new file mode 100644 index 000000000..f96ef2e3e --- /dev/null +++ b/libs/liquidity/src/lib/liquidity-table.spec.tsx @@ -0,0 +1,53 @@ +import LiquidityTable from './liquidity-table'; +import { act, render, screen, waitFor } from '@testing-library/react'; +import { LiquidityProvisionStatus } from '@vegaprotocol/types'; +import type { LiquidityProvision } from './liquidity-data-provider'; + +const singleRow: LiquidityProvision = { + party: 'a3f762f0a6e998e1d0c6e73017a13ec8a22386c30f7f64a1bdca47330bc592dd', + createdAt: '2022-08-19T17:18:36.257028Z', + updatedAt: '2022-08-19T17:18:36.257028Z', + commitmentAmount: '56298653179', + fee: '0.001', + status: LiquidityProvisionStatus.STATUS_ACTIVE, + equityLikeShare: '0.5', + averageEntryValuation: '0.5', + supplied: '67895', + obligation: '56785', +}; + +const singleRowData = [singleRow]; + +describe('LiquidityTable', () => { + it('should render successfully', async () => { + await act(async () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); + }); + + it('should render correct columns', async () => { + act(async () => { + render(); + await waitFor(async () => { + const headers = await screen.getAllByRole('columnheader'); + expect(headers).toHaveLength(9); + expect( + headers.map((h) => + h.querySelector('[ref="eText"]')?.textContent?.trim() + ) + ).toEqual([ + 'Party', + 'Average entry valuation', + 'Updated', + 'Created', + 'Supplied (siskas)', + 'Obligation (siskas)', + 'Share', + 'Fee', + 'Status', + ]); + }); + }); + }); +}); diff --git a/libs/liquidity/src/lib/liquidity-table.tsx b/libs/liquidity/src/lib/liquidity-table.tsx new file mode 100644 index 000000000..a40e6c49f --- /dev/null +++ b/libs/liquidity/src/lib/liquidity-table.tsx @@ -0,0 +1,117 @@ +import { forwardRef } from 'react'; +import { + addDecimalsFormatNumber, + formatNumberPercentage, + getDateTimeFormat, + t, +} from '@vegaprotocol/react-helpers'; +import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; +import type { AgGridReact } from 'ag-grid-react'; +import { AgGridColumn } from 'ag-grid-react'; +import type { LiquidityProvision } from './liquidity-data-provider'; +import type { ValueFormatterParams } from 'ag-grid-community'; +import BigNumber from 'bignumber.js'; +import type { LiquidityProvisionStatus } from '@vegaprotocol/types'; +import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types'; + +const assetDecimalsFormatter = ({ value, data }: ValueFormatterParams) => { + if (!value) return '-'; + return addDecimalsFormatNumber(value, data.assetDecimalPlaces); +}; + +const percentageFormatter = ({ value }: ValueFormatterParams) => { + if (!value) return '-'; + return formatNumberPercentage(new BigNumber(value).times(100), 4) || '-'; +}; + +const dateValueFormatter = ({ value }: { value?: string | null }) => { + if (!value) { + return '-'; + } + return getDateTimeFormat().format(new Date(value)); +}; + +export interface LiquidityTableProps { + data: LiquidityProvision[]; +} + +export const LiquidityTable = forwardRef( + (props, ref) => { + return ( + data.party} + rowHeight={34} + ref={ref} + defaultColDef={{ + flex: 1, + resizable: true, + minWidth: 100, + }} + rowData={props.data} + {...props} + > + + + + + + + + { + if (!value) return value; + return LiquidityProvisionStatusMapping[value]; + }} + /> + + + + ); + } +); + +export default LiquidityTable; diff --git a/libs/liquidity/src/setup-tests.ts b/libs/liquidity/src/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/libs/liquidity/src/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/libs/liquidity/tsconfig.json b/libs/liquidity/tsconfig.json new file mode 100644 index 000000000..1eabf319c --- /dev/null +++ b/libs/liquidity/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/liquidity/tsconfig.lib.json b/libs/liquidity/tsconfig.lib.json new file mode 100644 index 000000000..de828a9a8 --- /dev/null +++ b/libs/liquidity/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/next/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/liquidity/tsconfig.spec.json b/libs/liquidity/tsconfig.spec.json new file mode 100644 index 000000000..0659641ca --- /dev/null +++ b/libs/liquidity/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node", "@testing-library/jest-dom"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts", + "../react-helpers/src/lib/grid-cells/summary-row.spec.ts" + ] +} diff --git a/libs/market-list/src/lib/components/select-market.tsx b/libs/market-list/src/lib/components/select-market.tsx index 130742d2b..bd56d3445 100644 --- a/libs/market-list/src/lib/components/select-market.tsx +++ b/libs/market-list/src/lib/components/select-market.tsx @@ -100,8 +100,7 @@ export const SelectMarketPopover = ({ marketName: string; onSelect: (id: string) => void; }) => { - const triggerClasses = - 'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-4 whitespace-nowrap'; + const triggerClasses = 'flex items-center gap-4 whitespace-nowrap'; const { keypair } = useVegaWallet(); const [open, setOpen] = useState(false); const { data, loading: marketsLoading } = useMarketList(); diff --git a/libs/orders/src/lib/components/order-feedback/order-feedback.spec.tsx b/libs/orders/src/lib/components/order-feedback/order-feedback.spec.tsx index 4b2ab1201..0cfaeb302 100644 --- a/libs/orders/src/lib/components/order-feedback/order-feedback.spec.tsx +++ b/libs/orders/src/lib/components/order-feedback/order-feedback.spec.tsx @@ -83,7 +83,7 @@ describe('OrderFeedback', () => { '1.00' ); expect(screen.getByText('Size').nextElementSibling).toHaveTextContent( - `+ 200` + `+200` ); }); }); diff --git a/libs/orders/src/lib/components/order-feedback/order-feedback.tsx b/libs/orders/src/lib/components/order-feedback/order-feedback.tsx index 7f8981033..484f1e7aa 100644 --- a/libs/orders/src/lib/components/order-feedback/order-feedback.tsx +++ b/libs/orders/src/lib/components/order-feedback/order-feedback.tsx @@ -1,20 +1,15 @@ import { useEnvironment } from '@vegaprotocol/environment'; import type { OrderEvent_busEvents_event_Order } from '../../order-hooks/__generated__/OrderEvent'; -import { - addDecimalsFormatNumber, - t, - positiveClassNames, - negativeClassNames, -} from '@vegaprotocol/react-helpers'; +import { addDecimalsFormatNumber, Size, t } from '@vegaprotocol/react-helpers'; import { OrderRejectionReasonMapping, OrderStatus, OrderStatusMapping, OrderTimeInForceMapping, OrderType, - Side, } from '@vegaprotocol/types'; import type { VegaTxState } from '@vegaprotocol/wallet'; +import { Link } from '@vegaprotocol/ui-toolkit'; export interface OrderFeedbackProps { transaction: VegaTxState; @@ -51,20 +46,12 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => { )}

{t(`Size`)}

-

- {`${ - order.side === Side.SIDE_BUY ? '+' : '-' - } ${addDecimalsFormatNumber( - order.size, - order.market?.positionDecimalPlaces ?? 0 - )} - `} +

+

@@ -72,8 +59,7 @@ export const OrderFeedback = ({ transaction, order }: OrderFeedbackProps) => { {transaction.txHash && ( )} diff --git a/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx b/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx index fc1a155dc..4d397d6c1 100644 --- a/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx +++ b/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx @@ -2,8 +2,9 @@ import { t, addDecimalsFormatNumber, toDecimal, + Size, } from '@vegaprotocol/react-helpers'; -import { OrderType, Side } from '@vegaprotocol/types'; +import { OrderType } from '@vegaprotocol/types'; import { FormGroup, Input, @@ -64,16 +65,15 @@ export const OrderEditDialog = ({ )}
-

{t(`Remaining size`)}

-

{t(`Size`)}

+

+ { + } - > - {order.side === Side.SIDE_BUY ? '+' : '-'} - {order.size}

diff --git a/libs/react-helpers/.eslintrc.json b/libs/react-helpers/.eslintrc.json index 734ddacee..db820c5d0 100644 --- a/libs/react-helpers/.eslintrc.json +++ b/libs/react-helpers/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "__generated__"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], diff --git a/libs/react-helpers/src/hooks/__generated__/NetworkParams.ts b/libs/react-helpers/src/hooks/__generated__/NetworkParams.ts new file mode 100644 index 000000000..493e5eaf1 --- /dev/null +++ b/libs/react-helpers/src/hooks/__generated__/NetworkParams.ts @@ -0,0 +1,27 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: NetworkParams +// ==================================================== + +export interface NetworkParams_networkParameters { + __typename: "NetworkParameter"; + /** + * The name of the network parameter + */ + key: string; + /** + * The value of the network parameter + */ + value: string; +} + +export interface NetworkParams { + /** + * return the full list of network parameters + */ + networkParameters: NetworkParams_networkParameters[] | null; +} diff --git a/libs/react-helpers/src/hooks/__generated__/index.ts b/libs/react-helpers/src/hooks/__generated__/index.ts new file mode 100644 index 000000000..3c5207c0e --- /dev/null +++ b/libs/react-helpers/src/hooks/__generated__/index.ts @@ -0,0 +1 @@ +export * from './NetworkParams'; diff --git a/libs/react-helpers/src/hooks/index.ts b/libs/react-helpers/src/hooks/index.ts index 77aeb7866..387bfe3ed 100644 --- a/libs/react-helpers/src/hooks/index.ts +++ b/libs/react-helpers/src/hooks/index.ts @@ -1,7 +1,8 @@ export * from './use-apply-grid-transaction'; export * from './use-data-provider'; -export * from './use-theme-switcher'; export * from './use-fetch'; -export * from './use-resize'; +export * from './use-network-params'; export * from './use-outside-click'; +export * from './use-resize'; export * from './use-screen-dimensions'; +export * from './use-theme-switcher'; diff --git a/libs/react-helpers/src/hooks/use-network-params.ts b/libs/react-helpers/src/hooks/use-network-params.ts new file mode 100644 index 000000000..c8c16cb3d --- /dev/null +++ b/libs/react-helpers/src/hooks/use-network-params.ts @@ -0,0 +1,37 @@ +import { gql, useQuery } from '@apollo/client'; +import type { NetworkParams } from './__generated__/NetworkParams'; + +export const NETWORK_PARAMETERS_QUERY = gql` + query NetworkParams { + networkParameters { + key + value + } + } +`; + +export const useNetworkParam = (param: string) => { + const { data, loading, error } = useQuery( + NETWORK_PARAMETERS_QUERY + ); + const foundParams = data?.networkParameters?.filter((p) => param === p.key); + return { + data: foundParams ? foundParams.map((f) => f.value) : null, + loading, + error, + }; +}; + +export const useNetworkParams = (params: string[]) => { + const { data, loading, error } = useQuery( + NETWORK_PARAMETERS_QUERY + ); + const foundParams = data?.networkParameters + ?.filter((p) => params.includes(p.key)) + .sort((a, b) => params.indexOf(a.key) - params.indexOf(b.key)); + return { + data: foundParams ? foundParams.map((f) => f.value) : null, + loading, + error, + }; +}; diff --git a/libs/react-helpers/src/lib/format/index.ts b/libs/react-helpers/src/lib/format/index.ts index 608da22cb..8aff8a189 100644 --- a/libs/react-helpers/src/lib/format/index.ts +++ b/libs/react-helpers/src/lib/format/index.ts @@ -1,5 +1,5 @@ export * from './date'; export * from './number'; -export * from './truncate'; export * from './size'; +export * from './truncate'; export * from './utils'; diff --git a/libs/react-helpers/src/lib/grid/index.tsx b/libs/react-helpers/src/lib/grid/index.ts similarity index 90% rename from libs/react-helpers/src/lib/grid/index.tsx rename to libs/react-helpers/src/lib/grid/index.ts index 1f96724a5..c71250dbb 100644 --- a/libs/react-helpers/src/lib/grid/index.tsx +++ b/libs/react-helpers/src/lib/grid/index.ts @@ -3,5 +3,6 @@ export * from './cumulative-vol-cell'; export * from './flash-cell'; export * from './price-cell'; export * from './price-flash-cell'; +export * from './size'; export * from './summary-rows'; export * from './vol-cell'; diff --git a/libs/react-helpers/src/lib/grid/size.tsx b/libs/react-helpers/src/lib/grid/size.tsx new file mode 100644 index 000000000..acd38c67b --- /dev/null +++ b/libs/react-helpers/src/lib/grid/size.tsx @@ -0,0 +1,47 @@ +import { Side } from '@vegaprotocol/types'; +import type { ICellRendererParams } from 'ag-grid-community'; +import classNames from 'classnames'; +import { addDecimalsFormatNumber } from '../format'; +import { negativeClassNames, positiveClassNames } from './cell-class-rules'; + +export const Size = ({ + value, + side, + positionDecimalPlaces = 0, +}: { + value: string; + side: Side; + positionDecimalPlaces?: number; +}) => { + return ( + + {side === Side.SIDE_BUY ? '+' : side === Side.SIDE_SELL ? '-' : ''} + {addDecimalsFormatNumber(value, positionDecimalPlaces)} + + ); +}; + +export interface ISizeCellProps extends ICellRendererParams { + value: number | string; +} + +export const SizeCell = ({ value, data }: ISizeCellProps) => { + if ((!value && value !== 0) || isNaN(Number(value))) { + return -; + } + return ( + + ); +}; + +SizeCell.displayName = 'SizeCell'; diff --git a/libs/react-helpers/src/lib/index.ts b/libs/react-helpers/src/lib/index.ts new file mode 100644 index 000000000..a380ae3dc --- /dev/null +++ b/libs/react-helpers/src/lib/index.ts @@ -0,0 +1,12 @@ +export * from './context'; +export * from './format'; +export * from './grid'; +export * from './storage'; +export * from './validate'; +export * from './assets'; +export * from './determine-id'; +export * from './generic-data-provider'; +export * from './i18n'; +export * from './pagination'; +export * from './remove-0x'; +export * from './time'; diff --git a/libs/types/src/__generated__/globalTypes.ts b/libs/types/src/__generated__/globalTypes.ts index cfc805369..44908cee9 100644 --- a/libs/types/src/__generated__/globalTypes.ts +++ b/libs/types/src/__generated__/globalTypes.ts @@ -98,6 +98,18 @@ export enum Interval { INTERVAL_I6H = "INTERVAL_I6H", } +/** + * Status of a liquidity provision order + */ +export enum LiquidityProvisionStatus { + STATUS_ACTIVE = "STATUS_ACTIVE", + STATUS_CANCELLED = "STATUS_CANCELLED", + STATUS_PENDING = "STATUS_PENDING", + STATUS_REJECTED = "STATUS_REJECTED", + STATUS_STOPPED = "STATUS_STOPPED", + STATUS_UNDEPLOYED = "STATUS_UNDEPLOYED", +} + /** * The current state of a market */ diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index ba453c2fc..281649e01 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -17,6 +17,18 @@ export enum AccountTypeMapping { ACCOUNT_TYPE_SETTLEMENT = 'Settlement', } +/** + * Status of a liquidity provision order + */ +export enum LiquidityProvisionStatusMapping { + STATUS_ACTIVE = 'Active', + STATUS_CANCELLED = 'Cancelled', + STATUS_PENDING = 'Pending', + STATUS_REJECTED = 'Rejected', + STATUS_STOPPED = 'Stopped', + STATUS_UNDEPLOYED = 'Undeployed', +} + export enum AuctionTriggerMapping { AUCTION_TRIGGER_BATCH = 'batch', AUCTION_TRIGGER_LIQUIDITY = 'liquidity', @@ -25,17 +37,6 @@ export enum AuctionTriggerMapping { AUCTION_TRIGGER_UNSPECIFIED = 'unspecified', } -/** - * Comparator describes the type of comparison. - */ -export enum ConditionOperatorMapping { - OPERATOR_EQUALS = 'EQUALS', - OPERATOR_GREATER_THAN = 'GREATER_THAN', - OPERATOR_GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL', - OPERATOR_LESS_THAN = 'LESS_THAN', - OPERATOR_LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL', -} - /** * The status of a deposit */ @@ -201,7 +202,7 @@ export enum ProposalRejectionReasonMapping { PROPOSAL_ERROR_INVALID_SHAPE = 'Invalid shape', PROPOSAL_ERROR_MAJORITY_THRESHOLD_NOT_REACHED = 'Majority threshold not reached', PROPOSAL_ERROR_MARKET_MISSING_LIQUIDITY_COMMITMENT = 'Market missing liquidity commitment', - PROPOSAL_ERROR_MISSING_BUILTIN_ASSET_FIELD = 'Missing builtin asset field', + PROPOSAL_ERROR_MISSING_BUILTIN_ASSET_FIELD = 'Missing built-in asset field', PROPOSAL_ERROR_MISSING_COMMITMENT_AMOUNT = 'Missing commitment amount', PROPOSAL_ERROR_MISSING_ERC20_CONTRACT_ADDRESS = 'Missing ERC20 contract address', PROPOSAL_ERROR_NETWORK_PARAMETER_INVALID_KEY = 'Network parameter invalid key', diff --git a/libs/ui-toolkit/src/components/tabs/tabs.tsx b/libs/ui-toolkit/src/components/tabs/tabs.tsx index 01f6cb41e..9efd08e1a 100644 --- a/libs/ui-toolkit/src/components/tabs/tabs.tsx +++ b/libs/ui-toolkit/src/components/tabs/tabs.tsx @@ -5,11 +5,12 @@ import { Children, isValidElement, useState } from 'react'; interface TabsProps { children: ReactElement[]; + active?: string; } -export const Tabs = ({ children }: TabsProps) => { +export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => { const [activeTab, setActiveTab] = useState(() => { - return children[0].props.id; + return activeDefaultId ?? children[0].props.id; }); return ( @@ -24,7 +25,7 @@ export const Tabs = ({ children }: TabsProps) => { role="tablist" > {Children.map(children, (child) => { - if (!isValidElement(child)) return null; + if (!isValidElement(child) || child.props.hidden) return null; const isActive = child.props.id === activeTab; const triggerClass = classNames( 'relative px-4 py-2 border-r border-neutral-300 dark:border-neutral-700', @@ -51,7 +52,7 @@ export const Tabs = ({ children }: TabsProps) => {
{Children.map(children, (child) => { - if (!isValidElement(child)) return null; + if (!isValidElement(child) || child.props.hidden) return null; return ( { diff --git a/libs/withdraws/src/index.ts b/libs/withdraws/src/index.ts index 329c09dd4..75697296d 100644 --- a/libs/withdraws/src/index.ts +++ b/libs/withdraws/src/index.ts @@ -10,3 +10,4 @@ export * from './lib/use-verify-withdrawal'; export * from './lib/use-withdrawals'; export * from './lib/__generated__/Withdrawals'; export * from './lib/__generated__/WithdrawalFields'; +export * from './lib/__generated__/WithdrawFormQuery'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 0d4de9e97..860b2e449 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -25,6 +25,7 @@ "@vegaprotocol/environment": ["libs/environment/src/index.ts"], "@vegaprotocol/fills": ["libs/fills/src/index.ts"], "@vegaprotocol/governance": ["libs/governance/src/index.ts"], + "@vegaprotocol/liquidity": ["libs/liquidity/src/index.ts"], "@vegaprotocol/market-depth": ["libs/market-depth/src/index.ts"], "@vegaprotocol/market-list": ["libs/market-list/src/index.ts"], "@vegaprotocol/network-info": ["libs/network-info/src/index.ts"], diff --git a/workspace.json b/workspace.json index 9b16e4d92..0034a59e4 100644 --- a/workspace.json +++ b/workspace.json @@ -14,6 +14,7 @@ "explorer-e2e": "apps/explorer-e2e", "fills": "libs/fills", "governance": "libs/governance", + "liquidity": "libs/liquidity", "market-depth": "libs/market-depth", "market-list": "libs/market-list", "network-info": "libs/network-info",