From 1027827576f629411f7be06974a904357cb18089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Fri, 16 Jun 2023 12:28:59 +0200 Subject: [PATCH] feat(trading): show margin level visualisation in asset breakdown modal (#4048) --- .../src/integration/trading-accounts.cy.ts | 2 +- .../trading-deal-ticket-basics.cy.ts | 2 +- .../trading-deal-ticket-order.cy.ts | 40 +- .../trading-deal-ticket-submit-account.cy.ts | 6 +- ...trading-deal-ticket-submit-suspended.cy.ts | 6 +- .../client-pages/market/trade-grid.tsx | 4 + .../client-pages/portfolio/portfolio.tsx | 5 +- .../accounts-container/accounts-container.tsx | 3 + libs/accounts/src/lib/Margins.graphql | 38 ++ .../accounts/src/lib/__generated__/Margins.ts | 114 +++++ libs/accounts/src/lib/accounts-manager.tsx | 86 +++- .../accounts/src/lib/breakdown-table.spec.tsx | 32 +- libs/accounts/src/lib/breakdown-table.tsx | 26 +- libs/accounts/src/lib/index.ts | 3 + .../src/lib/margin-data-provider.ts | 4 +- libs/accounts/src/lib/margin-health-chart.tsx | 242 ++++++++++ .../src/lib/margin-heath-chart.spec.tsx | 154 +++++++ libs/accounts/src/lib/use-account-balance.tsx | 1 - .../src/lib/use-market-account-balance.tsx | 1 - .../src/lib/cells/market-name-cell.tsx | 10 +- .../deal-ticket-validation/margin-warning.tsx | 2 +- .../zero-balance-error.tsx | 2 +- .../deal-ticket/deal-ticket-container.tsx | 3 + .../deal-ticket-fee-details.spec.tsx} | 4 +- .../deal-ticket/deal-ticket-fee-details.tsx | 415 ++++++++++++++++-- .../deal-ticket/deal-ticket-limit-amount.tsx | 4 +- .../deal-ticket/deal-ticket-market-amount.tsx | 2 +- .../components/deal-ticket/deal-ticket.tsx | 24 +- .../deal-ticket/expiry-selector.tsx | 2 +- .../deal-ticket/time-in-force-selector.tsx | 2 +- .../components/deal-ticket/type-selector.tsx | 2 +- libs/deal-ticket/src/hooks/index.ts | 2 +- .../src/hooks/use-estimate-fees.tsx | 25 ++ .../src/hooks/use-fee-deal-ticket-details.tsx | 327 -------------- libs/positions/src/index.ts | 1 - libs/positions/src/lib/Positions.graphql | 39 -- .../src/lib/__generated__/Positions.ts | 109 ----- libs/positions/src/lib/positions.mock.ts | 5 +- libs/positions/src/lib/use-market-margin.tsx | 4 +- 39 files changed, 1155 insertions(+), 598 deletions(-) create mode 100644 libs/accounts/src/lib/Margins.graphql create mode 100644 libs/accounts/src/lib/__generated__/Margins.ts rename libs/{positions => accounts}/src/lib/margin-data-provider.ts (96%) create mode 100644 libs/accounts/src/lib/margin-health-chart.tsx create mode 100644 libs/accounts/src/lib/margin-heath-chart.spec.tsx rename libs/deal-ticket/src/{hooks/use-fee-deal-ticket-details.spec.tsx => components/deal-ticket/deal-ticket-fee-details.spec.tsx} (93%) create mode 100644 libs/deal-ticket/src/hooks/use-estimate-fees.tsx delete mode 100644 libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx diff --git a/apps/trading-e2e/src/integration/trading-accounts.cy.ts b/apps/trading-e2e/src/integration/trading-accounts.cy.ts index 5ca544260..c6578bf9a 100644 --- a/apps/trading-e2e/src/integration/trading-accounts.cy.ts +++ b/apps/trading-e2e/src/integration/trading-accounts.cy.ts @@ -109,7 +109,7 @@ describe('accounts', { tags: '@smoke' }, () => { it('should open usage breakdown dialog when clicked on used', () => { // 7001-COLL-009 cy.get('[col-id="used"]').contains('1.01').click(); - const headers = ['Market', 'Account type', 'Balance']; + const headers = ['Market', 'Account type', 'Balance', 'Margin health']; cy.getByTestId('usage-breakdown').within(($headers) => { cy.wrap($headers) .get('.ag-header-cell-text') diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts index aa5fa7a0c..9512f317a 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts @@ -81,7 +81,7 @@ describe( cy.visit('/#/markets/market-0'); }); it('must display that market is not accepting orders', function () { - cy.getByTestId('dealticket-error-message-summary').should( + cy.getByTestId('deal-ticket-error-message-summary').should( 'have.text', `This market is ${marketState .split('_') diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts index d748a86c1..613d8311c 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts @@ -45,7 +45,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { cy.getByTestId(placeOrderBtn).click(); - cy.getByTestId('dealticket-error-message-expiry').should( + cy.getByTestId('deal-ticket-error-message-expiry').should( 'have.text', 'The expiry date that you have entered appears to be in the past' ); @@ -57,7 +57,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC'); cy.getByTestId(orderSizeField).clear().type('1'); cy.getByTestId(orderPriceField).clear().type('1.123456'); - cy.getByTestId('dealticket-error-message-price-limit').should( + cy.getByTestId('deal-ticket-error-message-price-limit').should( 'have.text', 'Price accepts up to 5 decimal places' ); @@ -79,7 +79,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { cy.getByTestId(orderSizeField).clear().type('1.234'); // 7002-SORD-060 cy.getByTestId(placeOrderBtn).should('be.enabled'); - cy.getByTestId('dealticket-error-message-size-market').should( + cy.getByTestId('deal-ticket-error-message-size-market').should( 'have.text', 'Size must be whole numbers for this market' ); @@ -88,7 +88,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { it('must warn if order size is set to 0', function () { cy.getByTestId(orderSizeField).clear().type('0'); cy.getByTestId(placeOrderBtn).should('be.enabled'); - cy.getByTestId('dealticket-error-message-size-market').should( + cy.getByTestId('deal-ticket-error-message-size-market').should( 'have.text', 'Size cannot be lower than 1' ); @@ -96,15 +96,29 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => { it('must have total margin available', () => { // 7001-COLL-011 - cy.getByTestId('tab-ticket') - .find('.text-xs') - .eq(5) - .within(() => { - cy.get('[data-state="closed"]').should( - 'have.text', - 'Total margin available' + '100.01 tDAI' - ); - }); + cy.getByTestId('deal-ticket-fee-total-margin-available').within(() => { + cy.get('[data-state="closed"]').should( + 'have.text', + 'Total margin available100.01 tDAI' + ); + }); + }); + + it('must have current margin allocation', () => { + cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => { + cy.get('[data-state="closed"]:first').should( + 'have.text', + 'Current margin allocation' + ); + }); + }); + + it('should open usage breakdown dialog when clicked on current margin allocation', () => { + cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => { + cy.get('button').click(); + }); + cy.getByTestId('usage-breakdown').should('exist'); + cy.getByTestId('dialog-close').click(); }); }); }); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts index fb703229b..e01dd607d 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-account.cy.ts @@ -26,7 +26,7 @@ describe( // 7002-SORD-060 cy.getByTestId('place-order').should('be.enabled'); // 7002-SORD-003 - cy.getByTestId('dealticket-error-message-zero-balance').should( + cy.getByTestId('deal-ticket-error-message-zero-balance').should( 'have.text', 'You need ' + 'tDAI' + @@ -54,11 +54,11 @@ describe( // 7002-SORD-003 // warning should show immediately - cy.getByTestId('dealticket-warning-margin').should( + cy.getByTestId('deal-ticket-warning-margin').should( 'contain.text', 'You may not have enough margin available to open this position' ); - cy.getByTestId('dealticket-warning-margin').should( + cy.getByTestId('deal-ticket-warning-margin').should( 'contain.text', 'You may not have enough margin available to open this position. 5.00 tDAI is currently required. You have only 0.01001 tDAI available.' ); diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-suspended.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-suspended.cy.ts index 18405b4e9..5dd19a79d 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-submit-suspended.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-submit-suspended.cy.ts @@ -37,7 +37,7 @@ describe('suspended market validation', { tags: '@regression' }, () => { // 7002-SORD-060 cy.getByTestId(placeOrderBtn).should('be.enabled'); cy.getByTestId(placeOrderBtn).click(); - cy.getByTestId('dealticket-error-message-type').should( + cy.getByTestId('deal-ticket-error-message-type').should( 'have.text', 'This market is in auction until it reaches sufficient liquidity. Only limit orders are permitted when market is in auction' ); @@ -48,7 +48,7 @@ describe('suspended market validation', { tags: '@regression' }, () => { cy.getByTestId(orderPriceField).clear().type('0.1'); cy.getByTestId(orderSizeField).clear().type('1'); cy.getByTestId(placeOrderBtn).should('be.enabled'); - cy.getByTestId('dealticket-warning-auction').should( + cy.getByTestId('deal-ticket-warning-auction').should( 'have.text', 'Any orders placed now will not trade until the auction ends' ); @@ -60,7 +60,7 @@ describe('suspended market validation', { tags: '@regression' }, () => { TIFlist.filter((item) => item.code === 'FOK')[0].value ); cy.getByTestId(placeOrderBtn).should('be.enabled'); - cy.getByTestId('dealticket-error-message-tif').should( + cy.getByTestId('deal-ticket-error-message-tif').should( 'have.text', 'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders' ); diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index ae68d8a0b..d104fab95 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -143,6 +143,7 @@ const MarketBottomPanel = memo( @@ -223,6 +224,7 @@ const MarketBottomPanel = memo( @@ -248,6 +250,7 @@ const MainGrid = memo( const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({ id: 'middle-1', }); + const onMarketClick = useMarketClickHandler(true); return ( @@ -266,6 +269,7 @@ const MainGrid = memo( navigate('/portfolio')} /> diff --git a/apps/trading/client-pages/portfolio/portfolio.tsx b/apps/trading/client-pages/portfolio/portfolio.tsx index 4968b459b..8e6a8fe91 100644 --- a/apps/trading/client-pages/portfolio/portfolio.tsx +++ b/apps/trading/client-pages/portfolio/portfolio.tsx @@ -92,7 +92,10 @@ export const Portfolio = () => { - + diff --git a/apps/trading/components/accounts-container/accounts-container.tsx b/apps/trading/components/accounts-container/accounts-container.tsx index d3bd2b4e7..95e921101 100644 --- a/apps/trading/components/accounts-container/accounts-container.tsx +++ b/apps/trading/components/accounts-container/accounts-container.tsx @@ -13,10 +13,12 @@ export const AccountsContainer = ({ pinnedAsset, hideButtons, storeKey, + onMarketClick, }: { pinnedAsset?: PinnedAsset; hideButtons?: boolean; storeKey?: string; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; }) => { const { pubKey, isReadOnly } = useVegaWallet(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); @@ -46,6 +48,7 @@ export const AccountsContainer = ({ onClickAsset={onClickAsset} onClickWithdraw={openWithdrawalDialog} onClickDeposit={openDepositDialog} + onMarketClick={onMarketClick} isReadOnly={isReadOnly} pinnedAsset={pinnedAsset} storeKey={storeKey} diff --git a/libs/accounts/src/lib/Margins.graphql b/libs/accounts/src/lib/Margins.graphql new file mode 100644 index 000000000..9580c96af --- /dev/null +++ b/libs/accounts/src/lib/Margins.graphql @@ -0,0 +1,38 @@ +fragment MarginFields on MarginLevels { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + asset { + id + } + market { + id + } +} + +query Margins($partyId: ID!) { + party(id: $partyId) { + id + marginsConnection { + edges { + node { + ...MarginFields + } + } + } + } +} + +subscription MarginsSubscription($partyId: ID!) { + margins(partyId: $partyId) { + marketId + asset + partyId + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + timestamp + } +} diff --git a/libs/accounts/src/lib/__generated__/Margins.ts b/libs/accounts/src/lib/__generated__/Margins.ts new file mode 100644 index 000000000..daf477bc6 --- /dev/null +++ b/libs/accounts/src/lib/__generated__/Margins.ts @@ -0,0 +1,114 @@ +import * as Types from '@vegaprotocol/types'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } }; + +export type MarginsQueryVariables = Types.Exact<{ + partyId: Types.Scalars['ID']; +}>; + + +export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null }; + +export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{ + partyId: Types.Scalars['ID']; +}>; + + +export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } }; + +export const MarginFieldsFragmentDoc = gql` + fragment MarginFields on MarginLevels { + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + asset { + id + } + market { + id + } +} + `; +export const MarginsDocument = gql` + query Margins($partyId: ID!) { + party(id: $partyId) { + id + marginsConnection { + edges { + node { + ...MarginFields + } + } + } + } +} + ${MarginFieldsFragmentDoc}`; + +/** + * __useMarginsQuery__ + * + * To run a query within a React component, call `useMarginsQuery` and pass it any options that fit your needs. + * When your component renders, `useMarginsQuery` 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 } = useMarginsQuery({ + * variables: { + * partyId: // value for 'partyId' + * }, + * }); + */ +export function useMarginsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(MarginsDocument, options); + } +export function useMarginsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(MarginsDocument, options); + } +export type MarginsQueryHookResult = ReturnType; +export type MarginsLazyQueryHookResult = ReturnType; +export type MarginsQueryResult = Apollo.QueryResult; +export const MarginsSubscriptionDocument = gql` + subscription MarginsSubscription($partyId: ID!) { + margins(partyId: $partyId) { + marketId + asset + partyId + maintenanceLevel + searchLevel + initialLevel + collateralReleaseLevel + timestamp + } +} + `; + +/** + * __useMarginsSubscriptionSubscription__ + * + * To run a query within a React component, call `useMarginsSubscriptionSubscription` and pass it any options that fit your needs. + * When your component renders, `useMarginsSubscriptionSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useMarginsSubscriptionSubscription({ + * variables: { + * partyId: // value for 'partyId' + * }, + * }); + */ +export function useMarginsSubscriptionSubscription(baseOptions: Apollo.SubscriptionHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSubscription(MarginsSubscriptionDocument, options); + } +export type MarginsSubscriptionSubscriptionHookResult = ReturnType; +export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult; \ No newline at end of file diff --git a/libs/accounts/src/lib/accounts-manager.tsx b/libs/accounts/src/lib/accounts-manager.tsx index 61d287ea4..437a08a3d 100644 --- a/libs/accounts/src/lib/accounts-manager.tsx +++ b/libs/accounts/src/lib/accounts-manager.tsx @@ -1,4 +1,4 @@ -import { useRef, memo, useState } from 'react'; +import { useRef, memo, useState, useCallback } from 'react'; import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; import { t } from '@vegaprotocol/i18n'; import { useDataProvider } from '@vegaprotocol/data-provider'; @@ -15,14 +15,25 @@ import BreakdownTable from './breakdown-table'; const AccountBreakdown = ({ assetId, partyId, + onMarketClick, }: { assetId: string; partyId: string; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; }) => { + const gridRef = useRef(null); const { data } = useDataProvider({ dataProvider: aggregatedAccountDataProvider, variables: { partyId, assetId }, + update: ({ data }) => { + if (gridRef.current?.api && data?.breakdown) { + gridRef.current?.api.setRowData(data?.breakdown); + return true; + } + return false; + }, }); + return (
)} - +
); }; +export const AccountBreakdownDialog = memo( + ({ + assetId, + partyId, + onClose, + onMarketClick, + }: { + assetId?: string; + partyId: string; + onClose: () => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; + }) => { + console.log('render'); + return ( + { + if (!isOpen) { + onClose(); + } + }} + > + {assetId && ( + + )} + + ); + } +); + interface AccountManagerProps { partyId: string; onClickAsset: (assetId: string) => void; onClickWithdraw?: (assetId?: string) => void; onClickDeposit?: (assetId?: string) => void; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; isReadOnly: boolean; pinnedAsset?: PinnedAsset; storeKey?: string; @@ -62,6 +114,7 @@ export const AccountManager = ({ isReadOnly, pinnedAsset, storeKey, + onMarketClick, }: AccountManagerProps) => { const gridRef = useRef(null); const [breakdownAssetId, setBreakdownAssetId] = useState(); @@ -71,6 +124,16 @@ export const AccountManager = ({ variables: { partyId }, }); + const onMarketClickInternal = useCallback( + (...args: Parameters>) => { + setBreakdownAssetId(undefined); + if (onMarketClick) { + onMarketClick(...args); + } + }, + [onMarketClick] + ); + return (
- { - if (!isOpen) { - setBreakdownAssetId(undefined); - } - }} - > - {breakdownAssetId && ( - - )} - + setBreakdownAssetId(undefined), [])} + onMarketClick={onMarketClick ? onMarketClickInternal : undefined} + />
); }; diff --git a/libs/accounts/src/lib/breakdown-table.spec.tsx b/libs/accounts/src/lib/breakdown-table.spec.tsx index 16bb21b80..6945e65db 100644 --- a/libs/accounts/src/lib/breakdown-table.spec.tsx +++ b/libs/accounts/src/lib/breakdown-table.spec.tsx @@ -4,6 +4,14 @@ import * as Types from '@vegaprotocol/types'; import type { AccountFields } from './accounts-data-provider'; import { getAccountData } from './accounts-data-provider'; +const marginHealthChartTestId = 'margin-health-chart'; + +jest.mock('./margin-health-chart', () => ({ + MarginHealthChart: () => { + return
; + }, +})); + const singleRow = { __typename: 'AccountBalance', type: Types.AccountType.ACCOUNT_TYPE_MARGIN, @@ -37,10 +45,10 @@ describe('BreakdownTable', () => { render(); }); const headers = await screen.findAllByRole('columnheader'); - expect(headers).toHaveLength(3); + expect(headers).toHaveLength(4); expect( headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim()) - ).toEqual(['Market', 'Account type', 'Balance']); + ).toEqual(['Market', 'Account type', 'Balance', 'Margin health']); }); it('should apply correct formatting', async () => { @@ -55,9 +63,27 @@ describe('BreakdownTable', () => { '1,256.00', '1,256.00', ]; - cells.forEach((cell, i) => { + cells.slice(0, -1).forEach((cell, i) => { expect(cell).toHaveTextContent(expectedValues[i]); }); + expect(screen.getByTestId(marginHealthChartTestId)).toBeInTheDocument(); + }); + + it('displays margin health chart only for margin account', async () => { + await act(async () => { + render( + + ); + }); + expect(screen.queryByTestId(marginHealthChartTestId)).toBeNull(); }); it('should get correct account data', () => { diff --git a/libs/accounts/src/lib/breakdown-table.tsx b/libs/accounts/src/lib/breakdown-table.tsx index d33206ebb..c36a82adc 100644 --- a/libs/accounts/src/lib/breakdown-table.tsx +++ b/libs/accounts/src/lib/breakdown-table.tsx @@ -9,16 +9,20 @@ import type { AgGridReact, AgGridReactProps } from 'ag-grid-react'; import type { AccountFields } from './accounts-data-provider'; import { AccountTypeMapping } from '@vegaprotocol/types'; import type { - VegaICellRendererParams, VegaValueFormatterParams, + VegaICellRendererParams, } from '@vegaprotocol/datagrid'; import { ProgressBarCell } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid, PriceCell } from '@vegaprotocol/datagrid'; import type { ColDef } from 'ag-grid-community'; import { accountValuesComparator } from './accounts-table'; +import { MarginHealthChart } from './margin-health-chart'; +import { MarketNameCell } from '@vegaprotocol/datagrid'; +import { AccountType } from '@vegaprotocol/types'; interface BreakdownTableProps extends AgGridReactProps { data: AccountFields[] | null; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; } const BreakdownTable = forwardRef( @@ -90,6 +94,24 @@ const BreakdownTable = forwardRef( }, comparator: accountValuesComparator, }, + { + headerName: t('Margin health'), + field: 'market.id', + flex: 2, + maxWidth: 500, + sortable: false, + cellRenderer: ({ + data, + }: VegaICellRendererParams) => + data?.market?.id && + data.type === AccountType['ACCOUNT_TYPE_MARGIN'] && + data?.asset.id ? ( + + ) : null, + }, ]; return defs; }, []); @@ -104,7 +126,7 @@ const BreakdownTable = forwardRef( } ref={ref} rowHeight={34} - components={{ PriceCell }} + components={{ PriceCell, MarketNameCell, ProgressBarCell }} tooltipShowDelay={500} defaultColDef={{ flex: 1, diff --git a/libs/accounts/src/lib/index.ts b/libs/accounts/src/lib/index.ts index ce1402607..54c49fbdd 100644 --- a/libs/accounts/src/lib/index.ts +++ b/libs/accounts/src/lib/index.ts @@ -8,3 +8,6 @@ export * from './use-account-balance'; export * from './get-settlement-account'; export * from './use-market-account-balance'; export * from './transfer-dialog'; +export * from './__generated__/Margins'; +export { MarginHealthChart } from './margin-health-chart'; +export * from './margin-data-provider'; diff --git a/libs/positions/src/lib/margin-data-provider.ts b/libs/accounts/src/lib/margin-data-provider.ts similarity index 96% rename from libs/positions/src/lib/margin-data-provider.ts rename to libs/accounts/src/lib/margin-data-provider.ts index e18ba0180..7e59da2f2 100644 --- a/libs/positions/src/lib/margin-data-provider.ts +++ b/libs/accounts/src/lib/margin-data-provider.ts @@ -7,13 +7,13 @@ import { import { MarginsSubscriptionDocument, MarginsDocument, -} from './__generated__/Positions'; +} from './__generated__/Margins'; import type { MarginsQuery, MarginFieldsFragment, MarginsSubscriptionSubscription, MarginsQueryVariables, -} from './__generated__/Positions'; +} from './__generated__/Margins'; const update = ( data: MarginFieldsFragment[] | null, diff --git a/libs/accounts/src/lib/margin-health-chart.tsx b/libs/accounts/src/lib/margin-health-chart.tsx new file mode 100644 index 000000000..8a2cd3184 --- /dev/null +++ b/libs/accounts/src/lib/margin-health-chart.tsx @@ -0,0 +1,242 @@ +import { addDecimalsFormatNumber } from '@vegaprotocol/utils'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { Tooltip, ExternalLink } from '@vegaprotocol/ui-toolkit'; +import { useDataProvider } from '@vegaprotocol/data-provider'; +import { marketMarginDataProvider } from './margin-data-provider'; +import { useAssetsMapProvider } from '@vegaprotocol/assets'; +import { t } from '@vegaprotocol/i18n'; +import { useAccountBalance } from './use-account-balance'; +import { useMarketAccountBalance } from './use-market-account-balance'; + +const MarginHealthChartTooltipRow = ({ + label, + value, + decimals, + href, +}: { + label: string; + value: string; + decimals: number; + href?: string; +}) => ( + <> +
+ {href ? ( + + {label} + + ) : ( + label + )} +
+
+ {addDecimalsFormatNumber(value, decimals)} +
+ +); + +export const MarginHealthChartTooltip = ({ + maintenanceLevel, + searchLevel, + initialLevel, + collateralReleaseLevel, + decimals, + marginAccountBalance, +}: { + maintenanceLevel: string; + searchLevel: string; + initialLevel: string; + collateralReleaseLevel: string; + decimals: number; + marginAccountBalance?: string; +}) => { + const tooltipContent = [ + , + , + , + , + ]; + + if (marginAccountBalance) { + const balance = ( + + ); + if (BigInt(marginAccountBalance) < BigInt(searchLevel)) { + tooltipContent.splice(1, 0, balance); + } else if (BigInt(marginAccountBalance) < BigInt(initialLevel)) { + tooltipContent.splice(2, 0, balance); + } else if (BigInt(marginAccountBalance) < BigInt(collateralReleaseLevel)) { + tooltipContent.splice(3, 0, balance); + } else { + tooltipContent.push(balance); + } + } + return ( +
+ {tooltipContent} +
+ ); +}; + +export const MarginHealthChart = ({ + marketId, + assetId, +}: { + marketId: string; + assetId: string; +}) => { + const { data: assetsMap } = useAssetsMapProvider(); + const { pubKey: partyId } = useVegaWallet(); + const { data } = useDataProvider({ + dataProvider: marketMarginDataProvider, + variables: { marketId, partyId: partyId ?? '' }, + skip: !partyId, + }); + const { accountBalance: rawGeneralAccountBalance } = + useAccountBalance(assetId); + const { accountBalance: rawMarginAccountBalance } = + useMarketAccountBalance(marketId); + const asset = assetsMap && assetsMap[assetId]; + if (!data || !asset) { + return null; + } + const { decimals } = asset; + + const collateralReleaseLevel = Number(data.collateralReleaseLevel); + const initialLevel = Number(data.initialLevel); + const maintenanceLevel = Number(data.maintenanceLevel); + const searchLevel = Number(data.searchLevel); + const marginAccountBalance = Number(rawMarginAccountBalance); + const generalAccountBalance = Number(rawGeneralAccountBalance); + const max = Math.max( + marginAccountBalance + generalAccountBalance, + collateralReleaseLevel + ); + + const red = maintenanceLevel / max; + const orange = (searchLevel - maintenanceLevel) / max; + const yellow = ((searchLevel + initialLevel) / 2 - searchLevel) / max; + const green = (collateralReleaseLevel - initialLevel) / max + yellow; + const balanceMarker = marginAccountBalance / max; + + const tooltip = ( + + ); + + return ( +
+ {addDecimalsFormatNumber( + (BigInt(marginAccountBalance) - BigInt(maintenanceLevel)).toString(), + decimals + )}{' '} + {t('above')}{' '} + + {t('maintenance level')} + + +
+
+
+
+
+ {balanceMarker > 0 && balanceMarker < 100 && ( +
+ )} +
+
+
+ ); +}; diff --git a/libs/accounts/src/lib/margin-heath-chart.spec.tsx b/libs/accounts/src/lib/margin-heath-chart.spec.tsx new file mode 100644 index 000000000..83149f04a --- /dev/null +++ b/libs/accounts/src/lib/margin-heath-chart.spec.tsx @@ -0,0 +1,154 @@ +import { + MarginHealthChart, + MarginHealthChartTooltip, +} from './margin-health-chart'; +import { act, render, screen } from '@testing-library/react'; +import type { MarginFieldsFragment } from './__generated__/Margins'; +import type { AssetFieldsFragment } from '@vegaprotocol/assets'; + +const asset: AssetFieldsFragment = { + id: 'assetId', + decimals: 2, +} as AssetFieldsFragment; +const margins: MarginFieldsFragment = { + asset: { + id: 'assetId', + }, + collateralReleaseLevel: '1000', + initialLevel: '800', + searchLevel: '600', + maintenanceLevel: '400', + market: { + id: 'marketId', + }, +}; + +const getMargins = jest.fn(() => margins); +const getBalance = jest.fn(() => '0'); + +jest.mock('./margin-data-provider', () => ({})); + +jest.mock('@vegaprotocol/assets', () => ({ + useAssetsMapProvider: () => { + return { + data: { + assetId: asset, + }, + }; + }, +})); + +jest.mock('@vegaprotocol/wallet', () => ({ + useVegaWallet: () => { + return { + pubKey: 'partyId', + }; + }, +})); + +jest.mock('@vegaprotocol/data-provider', () => ({ + useDataProvider: () => { + return { + data: getMargins(), + }; + }, +})); + +jest.mock('./use-account-balance', () => ({ + useAccountBalance: () => { + return { + accountBalance: getBalance(), + }; + }, +})); + +jest.mock('./use-market-account-balance', () => ({ + useMarketAccountBalance: () => { + return { + accountBalance: '700', + }; + }, +})); + +describe('MarginHealthChart', () => { + it('should render correct values', async () => { + render(); + const chart = screen.getByTestId('margin-health-chart'); + expect(chart).toHaveTextContent('3.00 above maintenance level'); + const red = screen.getByTestId('margin-health-chart-red'); + const orange = screen.getByTestId('margin-health-chart-orange'); + const yellow = screen.getByTestId('margin-health-chart-yellow'); + const green = screen.getByTestId('margin-health-chart-green'); + const balance = screen.getByTestId('margin-health-chart-balance'); + expect(parseInt(red.style.width)).toBe(40); + expect(parseInt(orange.style.width)).toBe(20); + expect(parseInt(yellow.style.width)).toBe(10); + expect(parseInt(green.style.width)).toBe(30); + expect(parseInt(balance.style.left)).toBe(70); + }); + + it('should use correct scale', async () => { + getBalance.mockReturnValueOnce('1300'); + await act(async () => { + render(); + }); + await screen.findByTestId('margin-health-chart'); + const red = screen.getByTestId('margin-health-chart-red'); + expect(parseInt(red.style.width)).toBe(20); + }); +}); + +describe('MarginHealthChartTooltip', () => { + it('renders correct values and labels', async () => { + await act(async () => { + render( + + ); + }); + const labels = await screen.findAllByTestId('margin-health-tooltip-label'); + const expectedLabels = [ + 'maintenance level', + 'balance', + 'search level', + 'initial level', + 'release level', + ]; + labels.forEach((value, i) => { + expect(value).toHaveTextContent(expectedLabels[i]); + }); + const values = await screen.findAllByTestId('margin-health-tooltip-value'); + const expectedValues = ['4.00', '5.00', '6.00', '8.00', '10.00']; + values.forEach((value, i) => { + expect(value).toHaveTextContent(expectedValues[i]); + }); + }); + + it('renders balance in correct place', async () => { + const { rerender } = render( + + ); + + let values = await screen.findAllByTestId('margin-health-tooltip-value'); + expect(values[2]).toHaveTextContent('7.00'); + + rerender( + + ); + + values = await screen.findAllByTestId('margin-health-tooltip-value'); + expect(values.length).toBe(5); + expect(values[3]).toHaveTextContent('9.00'); + }); +}); diff --git a/libs/accounts/src/lib/use-account-balance.tsx b/libs/accounts/src/lib/use-account-balance.tsx index 2b50182bf..c6be87f8a 100644 --- a/libs/accounts/src/lib/use-account-balance.tsx +++ b/libs/accounts/src/lib/use-account-balance.tsx @@ -23,7 +23,6 @@ export const useAccountBalance = (assetId?: string) => { }, [assetId] ); - useDataProvider({ dataProvider: accountsDataProvider, variables, diff --git a/libs/accounts/src/lib/use-market-account-balance.tsx b/libs/accounts/src/lib/use-market-account-balance.tsx index 8491598a2..bb7ff3c26 100644 --- a/libs/accounts/src/lib/use-market-account-balance.tsx +++ b/libs/accounts/src/lib/use-market-account-balance.tsx @@ -22,7 +22,6 @@ export const useMarketAccountBalance = (marketId: string) => { }, [marketId] ); - useDataProvider({ dataProvider: accountsDataProvider, variables: { partyId: pubKey || '' }, diff --git a/libs/datagrid/src/lib/cells/market-name-cell.tsx b/libs/datagrid/src/lib/cells/market-name-cell.tsx index 9ce81c21d..2ef42db1a 100644 --- a/libs/datagrid/src/lib/cells/market-name-cell.tsx +++ b/libs/datagrid/src/lib/cells/market-name-cell.tsx @@ -1,4 +1,4 @@ -import type { MouseEvent } from 'react'; +import type { MouseEvent, ReactNode } from 'react'; import { useCallback } from 'react'; import get from 'lodash/get'; @@ -7,6 +7,7 @@ interface MarketNameCellProps { data?: { id?: string; marketId?: string; market?: { id: string } }; idPath?: string; onMarketClick?: (marketId: string, metaKey?: boolean) => void; + defaultValue?: ReactNode; } export const MarketNameCell = ({ @@ -26,10 +27,13 @@ export const MarketNameCell = ({ }, [id, onMarketClick] ); - if (!data) return null; - return ( + if (!value || !data) return null; + return onMarketClick ? ( + ) : ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <>{value} ); }; diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx index 86efe1982..329e47afe 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx @@ -18,7 +18,7 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => { return ( {t( diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx index c106469b5..6786fd2c6 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx @@ -7,11 +7,13 @@ import { DealTicket } from './deal-ticket'; export interface DealTicketContainerProps { marketId: string; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; onClickCollateral?: () => void; } export const DealTicketContainer = ({ marketId, + onMarketClick, onClickCollateral, }: DealTicketContainerProps) => { const { @@ -47,6 +49,7 @@ export const DealTicketContainer = ({ marketData={marketData} submit={(orderSubmission) => create({ orderSubmission })} onClickCollateral={onClickCollateral} + onMarketClick={onMarketClick} /> ) : ( diff --git a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.spec.tsx similarity index 93% rename from libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.spec.tsx rename to libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.spec.tsx index f5a899fbf..871b12c3e 100644 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.spec.tsx @@ -1,6 +1,6 @@ -import { formatRange, formatValue } from './use-fee-deal-ticket-details'; +import { formatRange, formatValue } from './deal-ticket-fee-details'; -describe('useFeeDealTicketDetails', () => { +describe('formatRange, formatValue', () => { it.each([ { v: 123000, d: 5, o: '1.23' }, { v: 123000, d: 3, o: '123.00' }, diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index 4309d0069..df0f9f40e 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -1,14 +1,70 @@ +import { useCallback, useState } from 'react'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; import classnames from 'classnames'; import type { ReactNode } from 'react'; -import { getFeeDetailsValues } from '../../hooks/use-fee-deal-ticket-details'; -import type { FeeDetails } from '../../hooks/use-fee-deal-ticket-details'; +import { t } from '@vegaprotocol/i18n'; +import { FeesBreakdown } from '@vegaprotocol/markets'; +import { useVegaWallet } from '@vegaprotocol/wallet'; -export interface DealTicketFeeDetailProps { +import type { Market } from '@vegaprotocol/markets'; +import type { EstimatePositionQuery } from '@vegaprotocol/positions'; +import type { EstimateFeesQuery } from '../../hooks/__generated__/EstimateOrder'; +import { AccountBreakdownDialog } from '@vegaprotocol/accounts'; + +import { + addDecimalsFormatNumber, + isNumeric, + addDecimalsFormatNumberQuantum, +} from '@vegaprotocol/utils'; +import { marketMarginDataProvider } from '@vegaprotocol/accounts'; +import { useDataProvider } from '@vegaprotocol/data-provider'; + +import { + NOTIONAL_SIZE_TOOLTIP_TEXT, + MARGIN_DIFF_TOOLTIP_TEXT, + DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, + TOTAL_MARGIN_AVAILABLE, + LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT, + EST_TOTAL_MARGIN_TOOLTIP_TEXT, + MARGIN_ACCOUNT_TOOLTIP_TEXT, +} from '../../constants'; + +const emptyValue = '-'; + +export const formatValue = ( + value: string | number | null | undefined, + formatDecimals: number, + quantum?: string +): string => { + if (!isNumeric(value)) return emptyValue; + if (!quantum) return addDecimalsFormatNumber(value, formatDecimals); + return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum); +}; + +export const formatRange = ( + min: string | number | null | undefined, + max: string | number | null | undefined, + formatDecimals: number, + quantum?: string +) => { + const minFormatted = formatValue(min, formatDecimals, quantum); + const maxFormatted = formatValue(max, formatDecimals, quantum); + if (minFormatted !== maxFormatted) { + return `${minFormatted} - ${maxFormatted}`; + } + if (minFormatted !== emptyValue) { + return minFormatted; + } + return maxFormatted; +}; +export interface DealTicketFeeDetailPros { label: string; - value?: string | number | null; - labelDescription?: string | ReactNode; - symbol?: string; + value?: string | null | undefined; + symbol: string; + indent?: boolean | undefined; + labelDescription?: ReactNode; + formattedValue?: string; + onClick?: () => void; } export const DealTicketFeeDetail = ({ @@ -16,51 +72,322 @@ export const DealTicketFeeDetail = ({ value, labelDescription, symbol, -}: DealTicketFeeDetailProps) => ( -
-
+ indent, + onClick, + formattedValue, +}: DealTicketFeeDetailPros) => { + const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`; + const valueElement = onClick ? ( + + ) : ( +
{displayValue}
+ ); + return ( +
{label}
+ + {valueElement} +
-
{`${value ?? '-'} ${ - symbol || '' - }`}
-
-); + ); +}; + +export interface DealTicketFeeDetailsProps { + generalAccountBalance?: string; + marginAccountBalance?: string; + market: Market; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; + assetSymbol: string; + notionalSize: string | null; + feeEstimate: EstimateFeesQuery['estimateFees'] | undefined; + positionEstimate: EstimatePositionQuery['estimatePosition']; +} + +export const DealTicketFeeDetails = ({ + marginAccountBalance, + generalAccountBalance, + assetSymbol, + feeEstimate, + market, + onMarketClick, + notionalSize, + positionEstimate, +}: DealTicketFeeDetailsProps) => { + const [breakdownDialog, setBreakdownDialog] = useState(false); + const { pubKey: partyId } = useVegaWallet(); + const { data: currentMargins } = useDataProvider({ + dataProvider: marketMarginDataProvider, + variables: { marketId: market.id, partyId: partyId || '' }, + skip: !partyId, + }); + const liquidationEstimate = positionEstimate?.liquidation; + const marginEstimate = positionEstimate?.margin; + const totalBalance = + BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0'); + const { settlementAsset: asset } = + market.tradableInstrument.instrument.product; + const { decimals: assetDecimals, quantum } = asset; + let marginRequiredBestCase: string | undefined = undefined; + let marginRequiredWorstCase: string | undefined = undefined; + if (marginEstimate) { + if (currentMargins) { + marginRequiredBestCase = ( + BigInt(marginEstimate.bestCase.initialLevel) - + BigInt(currentMargins.initialLevel) + ).toString(); + if (marginRequiredBestCase.startsWith('-')) { + marginRequiredBestCase = '0'; + } + marginRequiredWorstCase = ( + BigInt(marginEstimate.worstCase.initialLevel) - + BigInt(currentMargins.initialLevel) + ).toString(); + if (marginRequiredWorstCase.startsWith('-')) { + marginRequiredWorstCase = '0'; + } + } else { + marginRequiredBestCase = marginEstimate.bestCase.initialLevel; + marginRequiredWorstCase = marginEstimate.worstCase.initialLevel; + } + } + + const totalMarginAvailable = ( + currentMargins + ? totalBalance - BigInt(currentMargins.maintenanceLevel) + : totalBalance + ).toString(); + + let deductionFromCollateral = null; + let projectedMargin = null; + if (marginAccountBalance) { + const deductionFromCollateralBestCase = + BigInt(marginEstimate?.bestCase.initialLevel ?? 0) - + BigInt(marginAccountBalance); + + const deductionFromCollateralWorstCase = + BigInt(marginEstimate?.worstCase.initialLevel ?? 0) - + BigInt(marginAccountBalance); + + deductionFromCollateral = ( + 0 + ? deductionFromCollateralBestCase.toString() + : '0', + deductionFromCollateralWorstCase > 0 + ? deductionFromCollateralWorstCase.toString() + : '0', + assetDecimals + )} + formattedValue={formatRange( + deductionFromCollateralBestCase > 0 + ? deductionFromCollateralBestCase.toString() + : '0', + deductionFromCollateralWorstCase > 0 + ? deductionFromCollateralWorstCase.toString() + : '0', + assetDecimals, + quantum + )} + symbol={assetSymbol} + labelDescription={DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol)} + /> + ); + projectedMargin = ( + + ); + } + + let liquidationPriceEstimate = emptyValue; + let liquidationPriceEstimateFormatted; + + if (liquidationEstimate) { + const liquidationEstimateBestCaseIncludingBuyOrders = BigInt( + liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '') + ); + const liquidationEstimateBestCaseIncludingSellOrders = BigInt( + liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '') + ); + const liquidationEstimateBestCase = + liquidationEstimateBestCaseIncludingBuyOrders > + liquidationEstimateBestCaseIncludingSellOrders + ? liquidationEstimateBestCaseIncludingBuyOrders + : liquidationEstimateBestCaseIncludingSellOrders; + + const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt( + liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCaseIncludingSellOrders = BigInt( + liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') + ); + const liquidationEstimateWorstCase = + liquidationEstimateWorstCaseIncludingBuyOrders > + liquidationEstimateWorstCaseIncludingSellOrders + ? liquidationEstimateWorstCaseIncludingBuyOrders + : liquidationEstimateWorstCaseIncludingSellOrders; + liquidationPriceEstimate = formatRange( + (liquidationEstimateBestCase < liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + (liquidationEstimateBestCase > liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + assetDecimals + ); + liquidationPriceEstimateFormatted = formatRange( + (liquidationEstimateBestCase < liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + (liquidationEstimateBestCase > liquidationEstimateWorstCase + ? liquidationEstimateBestCase + : liquidationEstimateWorstCase + ).toString(), + assetDecimals, + quantum + ); + } + + const onAccountBreakdownDialogClose = useCallback( + () => setBreakdownDialog(false), + [] + ); -export const DealTicketFeeDetails = (props: FeeDetails) => { - const details = getFeeDetailsValues(props); return (
- {details.map( - ({ - label, - value, - labelDescription, - symbol, - indent, - formattedValue, - }) => ( -
-
- -
{label}
-
-
- -
{`${ - formattedValue ?? '-' - } ${symbol || ''}`}
-
-
- ) + + + + {t( + `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` + )} + + + + } + symbol={assetSymbol} + /> + + + {deductionFromCollateral} + setBreakdownDialog(true) : undefined + } + value={formatValue(marginAccountBalance, assetDecimals)} + symbol={assetSymbol} + labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT} + formattedValue={formatValue( + marginAccountBalance, + assetDecimals, + quantum + )} + /> + {projectedMargin} + + {partyId && ( + )}
); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx index dcbd52274..0db3f545a 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx @@ -25,7 +25,7 @@ export const DealTicketLimitAmount = ({ const renderError = () => { if (sizeError) { return ( - + {sizeError} ); @@ -33,7 +33,7 @@ export const DealTicketLimitAmount = ({ if (priceError) { return ( - + {priceError} ); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx index c55c7fbe8..6933ff15f 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx @@ -90,7 +90,7 @@ export const DealTicketMarketAmount = ({ {sizeError && ( {sizeError} diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index a57c2b911..0108dc9fe 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -31,7 +31,7 @@ import { } from '@vegaprotocol/positions'; import { toBigNum, removeDecimal } from '@vegaprotocol/utils'; import { activeOrdersProvider } from '@vegaprotocol/orders'; -import { useEstimateFees } from '../../hooks/use-fee-deal-ticket-details'; +import { useEstimateFees } from '../../hooks/use-estimate-fees'; import { getDerivedPrice } from '../../utils/get-price'; import type { OrderInfo } from '@vegaprotocol/types'; @@ -55,17 +55,17 @@ import { OrderTimeInForce, OrderType } from '@vegaprotocol/types'; import { useOrderForm } from '../../hooks/use-order-form'; import { useDataProvider } from '@vegaprotocol/data-provider'; -import { marketMarginDataProvider } from '@vegaprotocol/positions'; - export interface DealTicketProps { market: Market; marketData: MarketData; + onMarketClick?: (marketId: string, metaKey?: boolean) => void; submit: (order: OrderSubmission) => void; onClickCollateral?: () => void; } export const DealTicket = ({ market, + onMarketClick, marketData, submit, onClickCollateral, @@ -176,12 +176,6 @@ export const DealTicket = ({ const assetSymbol = market.tradableInstrument.instrument.product.settlementAsset.symbol; - const { data: currentMargins } = useDataProvider({ - dataProvider: marketMarginDataProvider, - variables: { marketId: market.id, partyId: pubKey || '' }, - skip: !pubKey, - }); - useEffect(() => { if (!pubKey) { setError('summary', { @@ -200,7 +194,8 @@ export const DealTicket = ({ return; } - const hasNoBalance = !BigInt(generalAccountBalance); + const hasNoBalance = + !generalAccountBalance || !BigInt(generalAccountBalance); if (hasNoBalance) { setError('summary', { message: SummaryValidationType.NoCollateral, @@ -487,6 +482,7 @@ export const DealTicket = ({ } /> @@ -536,7 +530,7 @@ const SummaryMessage = memo( if (isReadOnly) { return (
- + { 'You need to connect your own wallet to start trading on this market' } @@ -585,7 +579,7 @@ const SummaryMessage = memo( if (errorMessage) { return (
- + {errorMessage}
@@ -613,7 +607,7 @@ const SummaryMessage = memo(
{errorMessage && ( - + {errorMessage} )} diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx index 1c75a30aa..a0cfc94d9 100644 --- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx @@ -108,7 +108,7 @@ export const TimeInForceSelector = ({ ))} {errorMessage && ( - + {renderError(errorMessage)} )} diff --git a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx index 9b03f459c..037835e15 100644 --- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx @@ -83,7 +83,7 @@ export const TypeSelector = ({ onChange={(e) => onSelect(e.target.value as Schema.OrderType)} /> {errorMessage && ( - + {renderError(errorMessage as MarketModeValidationType)} )} diff --git a/libs/deal-ticket/src/hooks/index.ts b/libs/deal-ticket/src/hooks/index.ts index 577896228..776b08278 100644 --- a/libs/deal-ticket/src/hooks/index.ts +++ b/libs/deal-ticket/src/hooks/index.ts @@ -1,2 +1,2 @@ export * from './__generated__/EstimateOrder'; -export * from './use-fee-deal-ticket-details'; +export * from './use-estimate-fees'; diff --git a/libs/deal-ticket/src/hooks/use-estimate-fees.tsx b/libs/deal-ticket/src/hooks/use-estimate-fees.tsx new file mode 100644 index 000000000..9ad2b0b5a --- /dev/null +++ b/libs/deal-ticket/src/hooks/use-estimate-fees.tsx @@ -0,0 +1,25 @@ +import { useVegaWallet } from '@vegaprotocol/wallet'; +import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; + +import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; + +export const useEstimateFees = ( + order?: OrderSubmissionBody['orderSubmission'] +) => { + const { pubKey } = useVegaWallet(); + + const { data } = useEstimateFeesQuery({ + variables: order && { + marketId: order.marketId, + partyId: pubKey || '', + price: order.price, + size: order.size, + side: order.side, + timeInForce: order.timeInForce, + type: order.type, + }, + fetchPolicy: 'no-cache', + skip: !pubKey || !order?.size || !order?.price, + }); + return data?.estimateFees; +}; diff --git a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx deleted file mode 100644 index 735c2b54f..000000000 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx +++ /dev/null @@ -1,327 +0,0 @@ -import { FeesBreakdown } from '@vegaprotocol/markets'; -import { - addDecimalsFormatNumber, - addDecimalsFormatNumberQuantum, - isNumeric, -} from '@vegaprotocol/utils'; -import { t } from '@vegaprotocol/i18n'; -import { useVegaWallet } from '@vegaprotocol/wallet'; -import type { Market } from '@vegaprotocol/markets'; -import type { EstimatePositionQuery } from '@vegaprotocol/positions'; -import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import { - EST_TOTAL_MARGIN_TOOLTIP_TEXT, - NOTIONAL_SIZE_TOOLTIP_TEXT, - MARGIN_ACCOUNT_TOOLTIP_TEXT, - MARGIN_DIFF_TOOLTIP_TEXT, - DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT, - TOTAL_MARGIN_AVAILABLE, - LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT, -} from '../constants'; - -import { useEstimateFeesQuery } from './__generated__/EstimateOrder'; -import type { EstimateFeesQuery } from './__generated__/EstimateOrder'; - -export const useEstimateFees = ( - order?: OrderSubmissionBody['orderSubmission'] -) => { - const { pubKey } = useVegaWallet(); - - const { data } = useEstimateFeesQuery({ - variables: order && { - marketId: order.marketId, - partyId: pubKey || '', - price: order.price, - size: order.size, - side: order.side, - timeInForce: order.timeInForce, - type: order.type, - }, - skip: !pubKey || !order?.size || !order?.price, - fetchPolicy: 'no-cache', - }); - return data?.estimateFees; -}; - -export interface FeeDetails { - generalAccountBalance?: string; - marginAccountBalance?: string; - market: Market; - assetSymbol: string; - notionalSize: string | null; - feeEstimate: EstimateFeesQuery['estimateFees'] | undefined; - currentInitialMargin?: string; - currentMaintenanceMargin?: string; - positionEstimate: EstimatePositionQuery['estimatePosition']; -} - -const emptyValue = '-'; - -export const formatValue = ( - value: string | number | null | undefined, - formatDecimals: number, - quantum?: string -): string => { - if (!isNumeric(value)) return emptyValue; - if (!quantum) return addDecimalsFormatNumber(value, formatDecimals); - return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum); -}; - -export const formatRange = ( - min: string | number | null | undefined, - max: string | number | null | undefined, - formatDecimals: number, - quantum?: string -) => { - const minFormatted = formatValue(min, formatDecimals, quantum); - const maxFormatted = formatValue(max, formatDecimals, quantum); - if (minFormatted !== maxFormatted) { - return `${minFormatted} - ${maxFormatted}`; - } - if (minFormatted !== emptyValue) { - return minFormatted; - } - return maxFormatted; -}; - -export const getFeeDetailsValues = ({ - marginAccountBalance, - generalAccountBalance, - assetSymbol, - feeEstimate, - market, - notionalSize, - currentInitialMargin, - currentMaintenanceMargin, - positionEstimate, -}: FeeDetails) => { - const liquidationEstimate = positionEstimate?.liquidation; - const marginEstimate = positionEstimate?.margin; - const totalBalance = - BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0'); - const assetDecimals = - market.tradableInstrument.instrument.product.settlementAsset.decimals; - const quantum = - market.tradableInstrument.instrument.product.settlementAsset.quantum; - const details: { - label: string; - value?: string | null; - formattedValue?: string | null; - symbol: string; - indent?: boolean; - labelDescription?: React.ReactNode; - }[] = [ - { - label: t('Notional'), - value: formatValue(notionalSize, assetDecimals), - formattedValue: formatValue(notionalSize, assetDecimals, quantum), - symbol: assetSymbol, - labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol), - }, - { - label: t('Fees'), - value: - feeEstimate?.totalFeeAmount && - `~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`, - formattedValue: - feeEstimate?.totalFeeAmount && - `~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`, - labelDescription: ( - <> - - {t( - `An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.` - )} - - - - ), - symbol: assetSymbol, - }, - ]; - let marginRequiredBestCase: string | undefined = undefined; - let marginRequiredWorstCase: string | undefined = undefined; - if (marginEstimate) { - if (currentInitialMargin) { - marginRequiredBestCase = ( - BigInt(marginEstimate.bestCase.initialLevel) - - BigInt(currentInitialMargin) - ).toString(); - if (marginRequiredBestCase.startsWith('-')) { - marginRequiredBestCase = '0'; - } - marginRequiredWorstCase = ( - BigInt(marginEstimate.worstCase.initialLevel) - - BigInt(currentInitialMargin) - ).toString(); - if (marginRequiredWorstCase.startsWith('-')) { - marginRequiredWorstCase = '0'; - } - } else { - marginRequiredBestCase = marginEstimate.bestCase.initialLevel; - marginRequiredWorstCase = marginEstimate.worstCase.initialLevel; - } - } - details.push({ - label: t('Margin required'), - formattedValue: formatRange( - marginRequiredBestCase, - marginRequiredWorstCase, - assetDecimals, - quantum - ), - value: formatRange( - marginRequiredBestCase, - marginRequiredWorstCase, - assetDecimals - ), - symbol: assetSymbol, - labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol), - }); - - const totalMarginAvailable = ( - currentMaintenanceMargin - ? totalBalance - BigInt(currentMaintenanceMargin) - : totalBalance - ).toString(); - - details.push({ - indent: true, - label: t('Total margin available'), - formattedValue: formatValue(totalMarginAvailable, assetDecimals, quantum), - value: formatValue(totalMarginAvailable, assetDecimals), - symbol: assetSymbol, - labelDescription: TOTAL_MARGIN_AVAILABLE( - formatValue(generalAccountBalance, assetDecimals, quantum), - formatValue(marginAccountBalance, assetDecimals, quantum), - formatValue(currentMaintenanceMargin, assetDecimals, quantum), - assetSymbol - ), - }); - - if (marginAccountBalance) { - const deductionFromCollateralBestCase = - BigInt(marginEstimate?.bestCase.initialLevel ?? 0) - - BigInt(marginAccountBalance); - - const deductionFromCollateralWorstCase = - BigInt(marginEstimate?.worstCase.initialLevel ?? 0) - - BigInt(marginAccountBalance); - - details.push({ - indent: true, - label: t('Deduction from collateral'), - value: formatRange( - deductionFromCollateralBestCase > 0 - ? deductionFromCollateralBestCase.toString() - : '0', - deductionFromCollateralWorstCase > 0 - ? deductionFromCollateralWorstCase.toString() - : '0', - assetDecimals - ), - formattedValue: formatRange( - deductionFromCollateralBestCase > 0 - ? deductionFromCollateralBestCase.toString() - : '0', - deductionFromCollateralWorstCase > 0 - ? deductionFromCollateralWorstCase.toString() - : '0', - assetDecimals, - quantum - ), - symbol: assetSymbol, - labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol), - }); - - details.push({ - label: t('Projected margin'), - value: formatRange( - marginEstimate?.bestCase.initialLevel, - marginEstimate?.worstCase.initialLevel, - assetDecimals - ), - formattedValue: formatRange( - marginEstimate?.bestCase.initialLevel, - marginEstimate?.worstCase.initialLevel, - assetDecimals, - quantum - ), - symbol: assetSymbol, - labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT, - }); - } - details.push({ - label: t('Current margin allocation'), - value: formatValue(marginAccountBalance, assetDecimals), - symbol: assetSymbol, - labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT, - formattedValue: formatValue(marginAccountBalance, assetDecimals, quantum), - }); - - let liquidationPriceEstimate = emptyValue; - let liquidationPriceEstimateFormatted; - - if (liquidationEstimate) { - const liquidationEstimateBestCaseIncludingBuyOrders = BigInt( - liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '') - ); - const liquidationEstimateBestCaseIncludingSellOrders = BigInt( - liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '') - ); - const liquidationEstimateBestCase = - liquidationEstimateBestCaseIncludingBuyOrders > - liquidationEstimateBestCaseIncludingSellOrders - ? liquidationEstimateBestCaseIncludingBuyOrders - : liquidationEstimateBestCaseIncludingSellOrders; - - const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt( - liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '') - ); - const liquidationEstimateWorstCaseIncludingSellOrders = BigInt( - liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '') - ); - const liquidationEstimateWorstCase = - liquidationEstimateWorstCaseIncludingBuyOrders > - liquidationEstimateWorstCaseIncludingSellOrders - ? liquidationEstimateWorstCaseIncludingBuyOrders - : liquidationEstimateWorstCaseIncludingSellOrders; - liquidationPriceEstimate = formatRange( - (liquidationEstimateBestCase < liquidationEstimateWorstCase - ? liquidationEstimateBestCase - : liquidationEstimateWorstCase - ).toString(), - (liquidationEstimateBestCase > liquidationEstimateWorstCase - ? liquidationEstimateBestCase - : liquidationEstimateWorstCase - ).toString(), - assetDecimals - ); - liquidationPriceEstimateFormatted = formatRange( - (liquidationEstimateBestCase < liquidationEstimateWorstCase - ? liquidationEstimateBestCase - : liquidationEstimateWorstCase - ).toString(), - (liquidationEstimateBestCase > liquidationEstimateWorstCase - ? liquidationEstimateBestCase - : liquidationEstimateWorstCase - ).toString(), - assetDecimals, - quantum - ); - } - - details.push({ - label: t('Liquidation price estimate'), - value: liquidationPriceEstimate, - formattedValue: liquidationPriceEstimateFormatted, - symbol: assetSymbol, - labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT, - }); - return details; -}; diff --git a/libs/positions/src/index.ts b/libs/positions/src/index.ts index e6b3b3a92..6139213d0 100644 --- a/libs/positions/src/index.ts +++ b/libs/positions/src/index.ts @@ -1,7 +1,6 @@ export * from './lib/__generated__/Positions'; export * from './lib/positions-container'; export * from './lib/positions-data-providers'; -export * from './lib/margin-data-provider'; export * from './lib/positions-table'; export * from './lib/use-market-margin'; export * from './lib/use-open-volume'; diff --git a/libs/positions/src/lib/Positions.graphql b/libs/positions/src/lib/Positions.graphql index cbf7be70a..657ca9493 100644 --- a/libs/positions/src/lib/Positions.graphql +++ b/libs/positions/src/lib/Positions.graphql @@ -38,45 +38,6 @@ subscription PositionsSubscription($partyId: ID!) { } } -fragment MarginFields on MarginLevels { - maintenanceLevel - searchLevel - initialLevel - collateralReleaseLevel - asset { - id - } - market { - id - } -} - -query Margins($partyId: ID!) { - party(id: $partyId) { - id - marginsConnection { - edges { - node { - ...MarginFields - } - } - } - } -} - -subscription MarginsSubscription($partyId: ID!) { - margins(partyId: $partyId) { - marketId - asset - partyId - maintenanceLevel - searchLevel - initialLevel - collateralReleaseLevel - timestamp - } -} - query EstimatePosition( $marketId: ID! $openVolume: String! diff --git a/libs/positions/src/lib/__generated__/Positions.ts b/libs/positions/src/lib/__generated__/Positions.ts index 804ab64fe..3cd120a3b 100644 --- a/libs/positions/src/lib/__generated__/Positions.ts +++ b/libs/positions/src/lib/__generated__/Positions.ts @@ -19,22 +19,6 @@ export type PositionsSubscriptionSubscriptionVariables = Types.Exact<{ export type PositionsSubscriptionSubscription = { __typename?: 'Subscription', positions: Array<{ __typename?: 'PositionUpdate', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, marketId: string, lossSocializationAmount: string, positionStatus: Types.PositionStatus, partyId: string }> }; -export type MarginFieldsFragment = { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } }; - -export type MarginsQueryVariables = Types.Exact<{ - partyId: Types.Scalars['ID']; -}>; - - -export type MarginsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, asset: { __typename?: 'Asset', id: string }, market: { __typename?: 'Market', id: string } } }> | null } | null } | null }; - -export type MarginsSubscriptionSubscriptionVariables = Types.Exact<{ - partyId: Types.Scalars['ID']; -}>; - - -export type MarginsSubscriptionSubscription = { __typename?: 'Subscription', margins: { __typename?: 'MarginLevelsUpdate', marketId: string, asset: string, partyId: string, maintenanceLevel: string, searchLevel: string, initialLevel: string, collateralReleaseLevel: string, timestamp: any } }; - export type EstimatePositionQueryVariables = Types.Exact<{ marketId: Types.Scalars['ID']; openVolume: Types.Scalars['String']; @@ -62,20 +46,6 @@ export const PositionFieldsFragmentDoc = gql` } } `; -export const MarginFieldsFragmentDoc = gql` - fragment MarginFields on MarginLevels { - maintenanceLevel - searchLevel - initialLevel - collateralReleaseLevel - asset { - id - } - market { - id - } -} - `; export const PositionsDocument = gql` query Positions($partyIds: [ID!]!) { positions(filter: {partyIds: $partyIds}) { @@ -153,85 +123,6 @@ export function usePositionsSubscriptionSubscription(baseOptions: Apollo.Subscri } export type PositionsSubscriptionSubscriptionHookResult = ReturnType; export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult; -export const MarginsDocument = gql` - query Margins($partyId: ID!) { - party(id: $partyId) { - id - marginsConnection { - edges { - node { - ...MarginFields - } - } - } - } -} - ${MarginFieldsFragmentDoc}`; - -/** - * __useMarginsQuery__ - * - * To run a query within a React component, call `useMarginsQuery` and pass it any options that fit your needs. - * When your component renders, `useMarginsQuery` 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 } = useMarginsQuery({ - * variables: { - * partyId: // value for 'partyId' - * }, - * }); - */ -export function useMarginsQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(MarginsDocument, options); - } -export function useMarginsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(MarginsDocument, options); - } -export type MarginsQueryHookResult = ReturnType; -export type MarginsLazyQueryHookResult = ReturnType; -export type MarginsQueryResult = Apollo.QueryResult; -export const MarginsSubscriptionDocument = gql` - subscription MarginsSubscription($partyId: ID!) { - margins(partyId: $partyId) { - marketId - asset - partyId - maintenanceLevel - searchLevel - initialLevel - collateralReleaseLevel - timestamp - } -} - `; - -/** - * __useMarginsSubscriptionSubscription__ - * - * To run a query within a React component, call `useMarginsSubscriptionSubscription` and pass it any options that fit your needs. - * When your component renders, `useMarginsSubscriptionSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useMarginsSubscriptionSubscription({ - * variables: { - * partyId: // value for 'partyId' - * }, - * }); - */ -export function useMarginsSubscriptionSubscription(baseOptions: Apollo.SubscriptionHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useSubscription(MarginsSubscriptionDocument, options); - } -export type MarginsSubscriptionSubscriptionHookResult = ReturnType; -export type MarginsSubscriptionSubscriptionResult = Apollo.SubscriptionResult; export const EstimatePositionDocument = gql` query EstimatePosition($marketId: ID!, $openVolume: String!, $orders: [OrderInfo!], $collateralAvailable: String) { estimatePosition( diff --git a/libs/positions/src/lib/positions.mock.ts b/libs/positions/src/lib/positions.mock.ts index 340dfa479..635eda23d 100644 --- a/libs/positions/src/lib/positions.mock.ts +++ b/libs/positions/src/lib/positions.mock.ts @@ -4,9 +4,12 @@ import type { PartialDeep } from 'type-fest'; import type { PositionsQuery, PositionFieldsFragment, +} from './__generated__/Positions'; + +import type { MarginsQuery, MarginFieldsFragment, -} from './__generated__/Positions'; +} from '@vegaprotocol/accounts'; export const positionsQuery = ( override?: PartialDeep diff --git a/libs/positions/src/lib/use-market-margin.tsx b/libs/positions/src/lib/use-market-margin.tsx index 89ff18634..1eb4b6bdf 100644 --- a/libs/positions/src/lib/use-market-margin.tsx +++ b/libs/positions/src/lib/use-market-margin.tsx @@ -1,8 +1,8 @@ import { useCallback, useState } from 'react'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { useDataProvider } from '@vegaprotocol/data-provider'; -import { marginsDataProvider } from './margin-data-provider'; -import type { MarginFieldsFragment } from './__generated__/Positions'; +import { marginsDataProvider } from '@vegaprotocol/accounts'; +import type { MarginFieldsFragment } from '@vegaprotocol/accounts'; export const useMarketMargin = (marketId: string) => { const { pubKey } = useVegaWallet();