From b749a057362d96d073720d00b7a321a70114c8e8 Mon Sep 17 00:00:00 2001 From: macqbat Date: Tue, 22 Nov 2022 09:31:26 +0100 Subject: [PATCH] chore(2071): deal ticket data update fixing (#2169) * chore: update of deal ticket data * chore: update of deal ticket data * chore: update of deal ticket data - fix re-render loop * chore: update of deal ticket data - add marketDealTicketProvider * chore: update of deal ticket data - add marketDealTicketProvider * chore: update of deal ticket data - adjust console-lite to the changes * chore: update of deal ticket data - fix failing unit tests * chore: update of deal ticket data - fix failing unit tests * chore: update of deal ticket data - fix linter failings * chore: update of deal ticket data - adjust console-lite-e2e * chore: update of deal ticket data - fix build-spec failings * chore: update of deal ticket data - fix failing e2e tests * chore: update of deal ticket data - fix failing e2e tests * chore: update of deal ticket data - fix failing e2e tests * chore: update of deal ticket data - remove unnecessary gqls * chore: update of deal ticket data - remove unnecessary gqls * chore: update of deal ticket data - remove unnecessary gqls * chore: update of deal ticket data - fix failings build * chore: update of deal ticket data - remove redundant data provider * chore: update of deal ticket data - remove redundant data provider * chore: update of deal ticket data - fix some types --- .../src/integration/market-selector.test.ts | 6 +- .../src/integration/market-trade.test.ts | 4 +- .../src/support/mocks/commons.ts | 4 + .../src/support/mocks/generate-deal-ticket.ts | 52 -------- .../src/support/mocks/generate-markets.ts | 117 +++++++++++------- .../support/mocks/generate-party-balance.ts | 6 +- .../deal-ticket/deal-ticket-balance.spec.tsx | 21 ++-- .../deal-ticket/deal-ticket-balance.tsx | 8 +- .../deal-ticket/deal-ticket-container.tsx | 97 +++++++++------ .../deal-ticket/deal-ticket-steps.tsx | 8 +- .../components/deal-ticket/review-trade.tsx | 8 +- .../deal-ticket/use-order-validation.spec.tsx | 116 +++++++++++------ .../deal-ticket/use-order-validation.tsx | 44 +++---- .../src/integration/trading-deal-ticket.cy.ts | 10 +- .../src/support/mocks/generate-accounts.ts | 13 ++ .../src/support/mocks/generate-assets.ts | 24 ++++ .../mocks/generate-deal-ticket-query.ts | 54 -------- .../src/support/mocks/generate-market.ts | 22 ++-- apps/trading-e2e/src/support/trading.ts | 15 +-- .../market-trading-mode.tsx | 8 +- .../src/lib/get-settlement-account.ts | 15 +++ libs/accounts/src/lib/index.ts | 2 + libs/accounts/src/lib/use-account-balance.tsx | 43 +++++++ .../components/deal-ticket/DealTicket.graphql | 54 -------- .../deal-ticket/__generated__/DealTicket.ts | 99 --------------- .../deal-ticket/deal-ticket-amount.tsx | 4 +- .../deal-ticket/deal-ticket-container.tsx | 45 ++++--- .../deal-ticket/deal-ticket-fee-details.tsx | 15 ++- .../deal-ticket/deal-ticket-manager.tsx | 4 +- .../deal-ticket/deal-ticket-market-amount.tsx | 2 +- .../deal-ticket/deal-ticket.spec.tsx | 8 +- .../components/deal-ticket/deal-ticket.tsx | 65 +++------- .../src/components/deal-ticket/index.ts | 1 - .../deal-ticket/market-selector.tsx | 4 +- .../deal-ticket/time-in-force-selector.tsx | 4 +- .../components/deal-ticket/type-selector.tsx | 4 +- .../compile-grid-data.tsx | 4 +- .../src/hooks/use-fee-deal-ticket-details.tsx | 31 +++-- .../src/hooks/use-has-no-balance.tsx | 11 ++ .../src/hooks/use-order-closeout.spec.tsx | 8 +- .../src/hooks/use-order-closeout.ts | 4 +- .../src/hooks/use-order-margin-validation.ts | 64 ++++------ .../src/hooks/use-order-margin.spec.ts | 10 +- .../deal-ticket/src/hooks/use-order-margin.ts | 4 +- .../src/hooks/use-persisted-order.ts | 10 +- .../src/utils/is-market-in-auction.ts | 4 +- .../src/utils/validate-time-in-force.ts | 4 +- libs/deal-ticket/src/utils/validate-type.ts | 4 +- .../src/lib/__generated___/market.ts | 9 +- libs/market-list/src/lib/market-provider.ts | 23 +++- libs/market-list/src/lib/market.graphql | 5 + 51 files changed, 576 insertions(+), 625 deletions(-) delete mode 100644 apps/console-lite-e2e/src/support/mocks/generate-deal-ticket.ts delete mode 100644 apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts create mode 100644 libs/accounts/src/lib/get-settlement-account.ts create mode 100644 libs/accounts/src/lib/use-account-balance.tsx delete mode 100644 libs/deal-ticket/src/components/deal-ticket/DealTicket.graphql delete mode 100644 libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicket.ts create mode 100644 libs/deal-ticket/src/hooks/use-has-no-balance.tsx diff --git a/apps/console-lite-e2e/src/integration/market-selector.test.ts b/apps/console-lite-e2e/src/integration/market-selector.test.ts index fd3f6197c..b4791f9e9 100644 --- a/apps/console-lite-e2e/src/integration/market-selector.test.ts +++ b/apps/console-lite-e2e/src/integration/market-selector.test.ts @@ -1,11 +1,12 @@ import { connectVegaWallet } from '../support/vega-wallet'; import { aliasQuery } from '@vegaprotocol/cypress'; import { + generateMarket, + generateMarketData, generateMarketsCandles, generateMarketsData, generateSimpleMarkets, } from '../support/mocks/generate-markets'; -import { generateDealTicket } from '../support/mocks/generate-deal-ticket'; import { generateMarketTags } from '../support/mocks/generate-market-tags'; import { generateMarketPositions } from '../support/mocks/generate-market-positions'; import { generateEstimateOrder } from '../support/mocks/generate-estimate-order'; @@ -27,7 +28,8 @@ describe('market selector', { tags: '@smoke' }, () => { aliasQuery(req, 'Markets', generateSimpleMarkets()); aliasQuery(req, 'MarketsCandles', generateMarketsCandles()); aliasQuery(req, 'MarketsData', generateMarketsData()); - aliasQuery(req, 'DealTicket', generateDealTicket()); + aliasQuery(req, 'MarketData', generateMarketData()); + aliasQuery(req, 'Market', generateMarket()); aliasQuery(req, 'MarketTags', generateMarketTags()); aliasQuery(req, 'MarketPositions', generateMarketPositions()); aliasQuery(req, 'EstimateOrder', generateEstimateOrder()); diff --git a/apps/console-lite-e2e/src/integration/market-trade.test.ts b/apps/console-lite-e2e/src/integration/market-trade.test.ts index 3fb92cc21..84fd1502c 100644 --- a/apps/console-lite-e2e/src/integration/market-trade.test.ts +++ b/apps/console-lite-e2e/src/integration/market-trade.test.ts @@ -5,8 +5,8 @@ import { generateMarketsCandles, generateMarketsData, generateMarket, + generateMarketData, } from '../support/mocks/generate-markets'; -import { generateDealTicket } from '../support/mocks/generate-deal-ticket'; import { generateMarketTags } from '../support/mocks/generate-market-tags'; import { generateMarketPositions } from '../support/mocks/generate-market-positions'; import { generateEstimateOrder } from '../support/mocks/generate-estimate-order'; @@ -28,7 +28,6 @@ describe('Market trade', { tags: '@smoke' }, () => { aliasQuery(req, 'MarketsCandles', generateMarketsCandles()); aliasQuery(req, 'MarketsData', generateMarketsData()); aliasQuery(req, 'SimpleMarkets', generateSimpleMarkets()); - aliasQuery(req, 'DealTicket', generateDealTicket()); aliasQuery(req, 'MarketTags', generateMarketTags()); aliasQuery(req, 'MarketPositions', generateMarketPositions()); aliasQuery(req, 'EstimateOrder', generateEstimateOrder()); @@ -37,6 +36,7 @@ describe('Market trade', { tags: '@smoke' }, () => { aliasQuery(req, 'MarketMarkPrice', generateMarketMarkPrice()); aliasQuery(req, 'MarketDepth', generateMarketDepth()); aliasQuery(req, 'Market', generateMarket()); + aliasQuery(req, 'MarketData', generateMarketData()); }); cy.visit('/markets'); cy.wait('@Markets').then((response) => { diff --git a/apps/console-lite-e2e/src/support/mocks/commons.ts b/apps/console-lite-e2e/src/support/mocks/commons.ts index bbb7ad5f7..a5b3744c9 100644 --- a/apps/console-lite-e2e/src/support/mocks/commons.ts +++ b/apps/console-lite-e2e/src/support/mocks/commons.ts @@ -144,4 +144,8 @@ export const singleMarket: SingleMarketFieldsFragment = { }, }, }, + depth: { + __typename: 'MarketDepth', + lastTrade: { price: '9893006', __typename: 'Trade' }, + }, }; diff --git a/apps/console-lite-e2e/src/support/mocks/generate-deal-ticket.ts b/apps/console-lite-e2e/src/support/mocks/generate-deal-ticket.ts deleted file mode 100644 index 10f0e3780..000000000 --- a/apps/console-lite-e2e/src/support/mocks/generate-deal-ticket.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { DealTicketQuery } from '@vegaprotocol/deal-ticket'; -import { Schema } from '@vegaprotocol/types'; -import merge from 'lodash/merge'; -import type { PartialDeep } from 'type-fest'; - -export const generateDealTicket = ( - override?: PartialDeep -): DealTicketQuery => { - const defaultResult: DealTicketQuery = { - market: { - id: 'ca7768f6de84bf86a21bbb6b0109d9659c81917b0e0339b2c262566c9b581a15', - decimalPlaces: 5, - positionDecimalPlaces: 0, - state: Schema.MarketState.STATE_ACTIVE, - tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, - tradableInstrument: { - instrument: { - id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', - name: 'AAVEDAI Monthly (30 Jun 2022)', - product: { - quoteName: 'DAI', - settlementAsset: { - id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', - symbol: 'tDAI', - name: 'tDAI TEST', - decimals: 5, - __typename: 'Asset', - }, - __typename: 'Future', - }, - __typename: 'Instrument', - }, - __typename: 'TradableInstrument', - }, - depth: { - lastTrade: { price: '9893006', __typename: 'Trade' }, - __typename: 'MarketDepth', - }, - fees: { - factors: { - makerFee: '0.0002', - infrastructureFee: '0.0005', - liquidityFee: '0.001', - __typename: 'FeeFactors', - }, - __typename: 'Fees', - }, - __typename: 'Market', - }, - }; - return merge(defaultResult, override); -}; diff --git a/apps/console-lite-e2e/src/support/mocks/generate-markets.ts b/apps/console-lite-e2e/src/support/mocks/generate-markets.ts index 317b8608e..fd9eefb7a 100644 --- a/apps/console-lite-e2e/src/support/mocks/generate-markets.ts +++ b/apps/console-lite-e2e/src/support/mocks/generate-markets.ts @@ -11,6 +11,7 @@ import type { import { protoMarket, protoCandles, singleMarket } from './commons'; import type { PartialDeep } from 'type-fest'; import type { MarketQuery } from '@vegaprotocol/market-list'; +import type { MarketDataQuery } from '@vegaprotocol/market-list'; export const generateSimpleMarkets = (): MarketsQuery => { const markets: Market[] = [ @@ -1139,52 +1140,51 @@ export const generateFillsMarkets = () => { }; }; +const markets = [ + { + data: { + market: { + id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', + __typename: 'Market', + }, + marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '17588787', + trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + __typename: 'MarketData', + }, + __typename: 'Market', + }, + { + data: { + market: { + id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', + __typename: 'Market', + }, + marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '84377569', + trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + __typename: 'MarketData', + }, + __typename: 'Market', + }, +]; export const generateMarketsData = ( override?: PartialDeep ): MarketsDataQuery => { - const markets = [ - { - data: { - market: { - id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d', - __typename: 'Market', - }, - marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, - staticMidPrice: '0', - indicativePrice: '0', - bestStaticBidPrice: '0', - bestStaticOfferPrice: '0', - indicativeVolume: '0', - bestBidPrice: '0', - bestOfferPrice: '0', - markPrice: '17588787', - trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, - __typename: 'MarketData', - }, - __typename: 'Market', - }, - { - data: { - market: { - id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376', - __typename: 'Market', - }, - marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, - staticMidPrice: '0', - indicativePrice: '0', - bestStaticBidPrice: '0', - bestStaticOfferPrice: '0', - indicativeVolume: '0', - bestBidPrice: '0', - bestOfferPrice: '0', - markPrice: '84377569', - trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, - __typename: 'MarketData', - }, - __typename: 'Market', - }, - ]; - const defaultResult: MarketsDataQuery = { marketsConnection: { __typename: 'MarketConnection', @@ -1382,3 +1382,34 @@ export const generateMarket = (): MarketQuery => { }, }; }; + +export const generateMarketData = (): MarketDataQuery => { + return { + marketsConnection: { + edges: [ + { + node: { + data: { + market: { + id: protoMarket.id, + __typename: 'Market', + }, + marketTradingMode: + Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, + staticMidPrice: '0', + indicativePrice: '0', + bestStaticBidPrice: '0', + bestStaticOfferPrice: '0', + indicativeVolume: '0', + bestBidPrice: '0', + bestOfferPrice: '0', + markPrice: '17588787', + trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + __typename: 'MarketData', + }, + }, + }, + ], + }, + }; +}; diff --git a/apps/console-lite-e2e/src/support/mocks/generate-party-balance.ts b/apps/console-lite-e2e/src/support/mocks/generate-party-balance.ts index ac9dac55a..f181004de 100644 --- a/apps/console-lite-e2e/src/support/mocks/generate-party-balance.ts +++ b/apps/console-lite-e2e/src/support/mocks/generate-party-balance.ts @@ -17,7 +17,7 @@ export const generatePartyBalance = ( balance: '88474051', type: Types.AccountType.ACCOUNT_TYPE_GENERAL, asset: { - id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + id: 'dai-id', symbol: 'tDAI', name: 'tDAI TEST', decimals: 5, @@ -47,7 +47,7 @@ export const generatePartyBalance = ( balance: '3412867', type: Types.AccountType.ACCOUNT_TYPE_GENERAL, asset: { - id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + id: 'dai-id', symbol: 'tDAI', name: 'tDAI TEST', decimals: 5, @@ -62,7 +62,7 @@ export const generatePartyBalance = ( balance: '70007', type: Types.AccountType.ACCOUNT_TYPE_GENERAL, asset: { - id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61', + id: 'dai-id', symbol: 'tDAI', name: 'tDAI TEST', decimals: 5, diff --git a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.spec.tsx b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.spec.tsx index 075b2d25f..25c0d0a68 100644 --- a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.spec.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.spec.tsx @@ -1,20 +1,17 @@ import React from 'react'; import { render } from '@testing-library/react'; -import type { - AccountFragment, - DealTicketMarketFragment, -} from '@vegaprotocol/deal-ticket'; +import type { AccountFragment } from '@vegaprotocol/deal-ticket'; import { DealTicketBalance } from './deal-ticket-balance'; import { Schema } from '@vegaprotocol/types'; +import type { MarketDealTicketAsset } from '@vegaprotocol/market-list'; -const tDAI: DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset'] = - { - __typename: 'Asset', - id: '1', - symbol: 'tDAI', - name: 'TDAI', - decimals: 2, - }; +const tDAI: MarketDealTicketAsset = { + __typename: 'Asset', + id: '1', + symbol: 'tDAI', + name: 'TDAI', + decimals: 2, +}; const accounts: AccountFragment[] = [ { diff --git a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.tsx b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.tsx index 33b1fbaec..f4db8213b 100644 --- a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-balance.tsx @@ -1,14 +1,12 @@ import classNames from 'classnames'; import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers'; import { Schema } from '@vegaprotocol/types'; -import type { - AccountFragment, - DealTicketMarketFragment, -} from '@vegaprotocol/deal-ticket'; +import type { AccountFragment } from '@vegaprotocol/deal-ticket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { useSettlementAccount } from '@vegaprotocol/deal-ticket'; interface DealTicketBalanceProps { - settlementAsset: DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset']; + settlementAsset: MarketDealTicket['tradableInstrument']['instrument']['product']['settlementAsset']; accounts: AccountFragment[]; isWalletConnected: boolean; className?: string; diff --git a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx index cb8f014d2..89636a4e2 100644 --- a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-container.tsx @@ -2,16 +2,21 @@ import { useParams } from 'react-router-dom'; import compact from 'lodash/compact'; import { DealTicketManager, - DealTicketContainer as Container, usePartyBalanceQuery, } from '@vegaprotocol/deal-ticket'; -import { Loader } from '@vegaprotocol/ui-toolkit'; -import { t } from '@vegaprotocol/react-helpers'; +import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; +import { t, useDataProvider } from '@vegaprotocol/react-helpers'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { DealTicketSteps } from './deal-ticket-steps'; import { DealTicketBalance } from './deal-ticket-balance'; import Baubles from './baubles-decor'; import ConnectWallet from '../wallet-connector'; +import { useMemo } from 'react'; +import type { + MarketDataUpdateFieldsFragment, + MarketDealTicket, +} from '@vegaprotocol/market-list'; +import { marketDealTicketProvider } from '@vegaprotocol/market-list'; const tempEmptyText = (

{t('Please select a market from the markets page')}

@@ -21,52 +26,64 @@ export const DealTicketContainer = () => { const { marketId } = useParams<{ marketId: string }>(); const { pubKey } = useVegaWallet(); - const { data: partyData, loading } = usePartyBalanceQuery({ + const { data: partyData } = usePartyBalanceQuery({ variables: { partyId: pubKey || '' }, skip: !pubKey, }); + const variables = useMemo( + () => ({ + marketId: marketId || '', + }), + [marketId] + ); + const { data, loading } = useDataProvider< + MarketDealTicket, + MarketDataUpdateFieldsFragment + >({ + dataProvider: marketDealTicketProvider, + variables, + skip: !marketId, + }); + + const accounts = compact(partyData?.party?.accountsConnection?.edges).map( + (e) => e.node + ); + const loader = ; - - const container = marketId ? ( - - {(data) => { - if (!data.market) { - return null as unknown as JSX.Element; + if (marketId && data) { + const balance = ( + + ); - const accounts = compact( - partyData?.party?.accountsConnection?.edges - ).map((e) => e.node); - const balance = ( - - ); + const container = ( + + {loading ? loader : balance} + + + ); - return ( - - {loading ? loader : balance} - - - ); - }} - + return ( +
+
+ {pubKey ? container : } +
+ +
+ ); + } + return marketId ? ( + +

{t('Could not load market')}

+
) : ( tempEmptyText ); - - return ( -
-
- {pubKey ? container : } -
- -
- ); }; diff --git a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-steps.tsx b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-steps.tsx index 2811630ca..fca280017 100644 --- a/apps/console-lite/src/app/components/deal-ticket/deal-ticket-steps.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/deal-ticket-steps.tsx @@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom'; import { useForm, Controller } from 'react-hook-form'; import compact from 'lodash/compact'; import { Stepper } from '../stepper'; -import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket'; import { getDefaultOrder, useOrderCloseOut, @@ -42,9 +41,10 @@ import ReviewTrade from './review-trade'; import { Schema } from '@vegaprotocol/types'; import { DealTicketSlippage } from './deal-ticket-slippage'; import { useOrderValidation } from './use-order-validation'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; interface DealTicketMarketProps { - market: DealTicketMarketFragment; + market: MarketDealTicket; } export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { @@ -78,10 +78,8 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { }); const { message: invalidText, isDisabled } = useOrderValidation({ market, - orderType: order.type, - orderTimeInForce: order.timeInForce, + order, fieldErrors: errors, - estMargin, }); const { submit, transaction, finalizedOrder, Dialog } = useOrderSubmit(); diff --git a/apps/console-lite/src/app/components/deal-ticket/review-trade.tsx b/apps/console-lite/src/app/components/deal-ticket/review-trade.tsx index 6796e49f4..0374cb5f5 100644 --- a/apps/console-lite/src/app/components/deal-ticket/review-trade.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/review-trade.tsx @@ -5,17 +5,17 @@ import { KeyValueTableRow, } from '@vegaprotocol/ui-toolkit'; import classNames from 'classnames'; -import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket'; import { DealTicketEstimates } from '@vegaprotocol/deal-ticket'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { SIDE_NAMES } from './side-selector'; import { gql, useQuery } from '@apollo/client'; +import { Schema } from '@vegaprotocol/types'; +import { MarketExpires } from '@vegaprotocol/market-info'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import type { MarketTags, MarketTagsVariables, } from './__generated__/MarketTags'; -import { Schema } from '@vegaprotocol/types'; -import { MarketExpires } from '@vegaprotocol/market-info'; export const MARKET_TAGS_QUERY = gql` query MarketTags($marketId: ID!) { @@ -32,7 +32,7 @@ export const MARKET_TAGS_QUERY = gql` `; interface Props { - market: DealTicketMarketFragment; + market: MarketDealTicket; isDisabled: boolean; transactionStatus?: string; order: OrderSubmissionBody['orderSubmission']; diff --git a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.spec.tsx b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.spec.tsx index 7c4bd3ec7..9d62eed91 100644 --- a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.spec.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.spec.tsx @@ -3,12 +3,15 @@ import { renderHook } from '@testing-library/react'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { MockedProvider } from '@apollo/client/testing'; import type { VegaWalletContextShape } from '@vegaprotocol/wallet'; -import { MarketStateMapping, Schema } from '@vegaprotocol/types'; +import { + MarketStateMapping, + Schema as Types, + Schema, +} from '@vegaprotocol/types'; import type { ValidationProps } from './use-order-validation'; import { marketTranslations, useOrderValidation } from './use-order-validation'; -import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import * as DealTicket from '@vegaprotocol/deal-ticket'; -import BigNumber from 'bignumber.js'; jest.mock('@vegaprotocol/wallet'); jest.mock('@vegaprotocol/deal-ticket', () => { @@ -19,7 +22,7 @@ jest.mock('@vegaprotocol/deal-ticket', () => { }); type SettlementAsset = - DealTicketMarketFragment['tradableInstrument']['instrument']['product']['settlementAsset']; + MarketDealTicket['tradableInstrument']['instrument']['product']['settlementAsset']; const asset: SettlementAsset = { __typename: 'Asset', id: 'asset-id', @@ -28,7 +31,7 @@ const asset: SettlementAsset = { decimals: 2, }; -const market: DealTicketMarketFragment = { +const market: MarketDealTicket = { id: 'market-id', decimalPlaces: 2, positionDecimalPlaces: 1, @@ -40,10 +43,17 @@ const market: DealTicketMarketFragment = { __typename: 'Instrument', id: 'instrument-id', name: 'instrument-name', + code: 'instriment-code', + metadata: { + tags: [], + }, product: { __typename: 'Future', quoteName: 'quote-name', settlementAsset: asset, + dataSourceSpecForTradingTermination: { + id: 'dataSource-id', + }, }, }, }, @@ -63,6 +73,25 @@ const market: DealTicketMarketFragment = { liquidityFee: '3', }, }, + data: { + __typename: 'MarketData', + bestBidPrice: '1605489971', + bestOfferPrice: '1606823730', + markPrice: '1606823730', + trigger: Types.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED, + staticMidPrice: '1606156850', + marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, + indicativeVolume: '0', + indicativePrice: '0', + bestStaticBidPrice: '1605489971', + bestStaticOfferPrice: '1606823730', + targetStake: '8561302732', + suppliedStake: '727654170336', + auctionStart: null, + auctionEnd: null, + market: { __typename: 'Market', id: 'market-id' }, + }, + marketTimestamps: {}, }; const defaultWalletContext = { @@ -75,19 +104,19 @@ const defaultWalletContext = { connector: null, }; +const order = { + type: Schema.OrderType.TYPE_MARKET, + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, + marketId: 'market-id', + side: Schema.Side.SIDE_BUY, + size: '0.1', +}; + const defaultOrder = { - market, + market: { ...market }, step: 0.1, - orderType: Schema.OrderType.TYPE_MARKET, - orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, - estMargin: { - margin: '0,000001', - totalFees: '0,000006', - fees: { - makerFee: '0,000003', - liquidityFee: '0,000002', - infrastructureFee: '0,000001', - }, + order: { + ...order, }, }; @@ -127,9 +156,9 @@ describe('useOrderValidation', () => { it('Returns empty string when given valid data', () => { jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({ - balance: new BigNumber(0), - margin: new BigNumber(100), - asset, + balance: '0', + margin: '100', + balanceError: false, }); const { result } = setup(); @@ -142,9 +171,9 @@ describe('useOrderValidation', () => { it('Returns an error message when no keypair found', () => { jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({ - balance: new BigNumber(0), - margin: new BigNumber(100), - asset, + balance: '0', + margin: '100', + balanceError: false, }); const { result } = setup(defaultOrder, { pubKey: null }); expect(result.current).toStrictEqual({ @@ -183,9 +212,10 @@ describe('useOrderValidation', () => { 'Returns an error message for market state suspended or pending', ({ state }) => { jest.spyOn(DealTicket, 'useOrderMarginValidation').mockReturnValue({ - balance: new BigNumber(0), - margin: new BigNumber(100), - asset, + balance: '0', + margin: '100', + balanceError: false, + // asset, }); const { result } = setup({ market: { @@ -193,8 +223,11 @@ describe('useOrderValidation', () => { state, tradingMode: Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION, }, - orderType: Schema.OrderType.TYPE_LIMIT, - orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT, + order: { + ...order, + type: Schema.OrderType.TYPE_LIMIT, + timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT, + }, }); expect(result.current).toStrictEqual({ isDisabled: false, @@ -216,7 +249,10 @@ describe('useOrderValidation', () => { ({ tradingMode, errorMessage }) => { const { result } = setup({ market: { ...defaultOrder.market, tradingMode }, - orderType: Schema.OrderType.TYPE_MARKET, + order: { + ...order, + type: Schema.OrderType.TYPE_MARKET, + }, }); expect(result.current.isDisabled).toBeTruthy(); expect(result.current.message).toBe(errorMessage); @@ -239,8 +275,11 @@ describe('useOrderValidation', () => { ({ tradingMode, orderTimeInForce, errorMessage }) => { const { result } = setup({ market: { ...defaultOrder.market, tradingMode }, - orderType: Schema.OrderType.TYPE_LIMIT, - orderTimeInForce, + order: { + ...order, + type: Schema.OrderType.TYPE_LIMIT, + timeInForce: orderTimeInForce, + }, }); expect(result.current).toStrictEqual({ isDisabled: true, @@ -261,7 +300,10 @@ describe('useOrderValidation', () => { ({ fieldName, errorType, section, errorMessage }) => { const { result } = setup({ fieldErrors: { [fieldName]: { type: errorType } }, - orderType: Schema.OrderType.TYPE_LIMIT, + order: { + ...order, + type: Schema.OrderType.TYPE_LIMIT, + }, }); expect(result.current).toStrictEqual({ isDisabled: true, @@ -300,9 +342,9 @@ describe('useOrderValidation', () => { it('Returns an error message when the estimated margin is higher than collateral', async () => { const invalidatedMockValue = { - balance: new BigNumber(100), - margin: new BigNumber(200), - asset, + balance: '100', + margin: '200', + balanceError: true, }; jest @@ -315,9 +357,9 @@ describe('useOrderValidation', () => { const testElement = ( ); expect((result.current.message as React.ReactElement)?.props).toEqual( diff --git a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx index 7dcfd4ef9..31f559161 100644 --- a/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx +++ b/apps/console-lite/src/app/components/deal-ticket/use-order-validation.tsx @@ -6,10 +6,6 @@ import { useVegaWallet } from '@vegaprotocol/wallet'; import { MarketStateMapping, Schema } from '@vegaprotocol/types'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; -import type { - DealTicketMarketFragment, - OrderMargin, -} from '@vegaprotocol/deal-ticket'; import { MarketDataGrid, compileGridData, @@ -18,6 +14,7 @@ import { ERROR_SIZE_DECIMAL, useOrderMarginValidation, } from '@vegaprotocol/deal-ticket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; export const DEAL_TICKET_SECTION = { TYPE: 'sec-type', @@ -32,11 +29,9 @@ export const ERROR_EXPIRATION_IN_THE_PAST = 'ERROR_EXPIRATION_IN_THE_PAST'; export type ValidationProps = { step?: number; - market: DealTicketMarketFragment; - orderType: Schema.OrderType; - orderTimeInForce: Schema.OrderTimeInForce; + market: MarketDealTicket; + order: OrderSubmissionBody['orderSubmission']; fieldErrors?: FieldErrors; - estMargin: OrderMargin | null; }; export const marketTranslations = (marketState: Schema.MarketState) => { @@ -55,9 +50,7 @@ export type DealTicketSection = export const useOrderValidation = ({ market, fieldErrors, - orderType, - orderTimeInForce, - estMargin, + order, }: ValidationProps): { message: ReactNode | string; isDisabled: boolean; @@ -65,7 +58,7 @@ export const useOrderValidation = ({ } => { const { pubKey } = useVegaWallet(); const minSize = toDecimal(market.positionDecimalPlaces); - const isInvalidOrderMargin = useOrderMarginValidation({ market, estMargin }); + const isInvalidOrderMargin = useOrderMarginValidation({ market, order }); const fieldErrorChecking = useMemo<{ message: ReactNode | string; @@ -91,7 +84,7 @@ export const useOrderValidation = ({ if ( fieldErrors?.price?.type === 'required' && - orderType !== Schema.OrderType.TYPE_MARKET + order.type !== Schema.OrderType.TYPE_MARKET ) { return { isDisabled: true, @@ -102,7 +95,7 @@ export const useOrderValidation = ({ if ( fieldErrors?.price?.type === 'min' && - orderType !== Schema.OrderType.TYPE_MARKET + order.type !== Schema.OrderType.TYPE_MARKET ) { return { isDisabled: true, @@ -151,7 +144,7 @@ export const useOrderValidation = ({ fieldErrors?.price?.type, fieldErrors?.expiresAt?.type, fieldErrors?.expiresAt?.message, - orderType, + order.type, minSize, market.positionDecimalPlaces, ]); @@ -210,7 +203,7 @@ export const useOrderValidation = ({ } if (isMarketInAuction(market)) { - if (orderType === Schema.OrderType.TYPE_MARKET) { + if (order.type === Schema.OrderType.TYPE_MARKET) { if ( market.tradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION && @@ -269,12 +262,12 @@ export const useOrderValidation = ({ }; } if ( - orderType === Schema.OrderType.TYPE_LIMIT && + order.type === Schema.OrderType.TYPE_LIMIT && [ Schema.OrderTimeInForce.TIME_IN_FORCE_FOK, Schema.OrderTimeInForce.TIME_IN_FORCE_IOC, Schema.OrderTimeInForce.TIME_IN_FORCE_GFN, - ].includes(orderTimeInForce) + ].includes(order.timeInForce) ) { if ( market.tradingMode === @@ -343,17 +336,14 @@ export const useOrderValidation = ({ return fieldErrorChecking; } - if ( - isInvalidOrderMargin.balance.isGreaterThan(0) && - isInvalidOrderMargin.balance.isLessThan(isInvalidOrderMargin.margin) - ) { + if (isInvalidOrderMargin.balanceError) { return { isDisabled: false, message: ( ), section: DEAL_TICKET_SECTION.PRICE, @@ -386,8 +376,8 @@ export const useOrderValidation = ({ market, fieldErrorChecking, isInvalidOrderMargin, - orderType, - orderTimeInForce, + order.type, + order.timeInForce, ]); return { message, isDisabled, section }; diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts index 992237a03..dd7751df4 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts @@ -499,6 +499,7 @@ describe('deal ticket size validation', { tags: '@smoke' }, function () { describe('limit order validations', { tags: '@smoke' }, () => { before(() => { cy.mockTradingPage(); + cy.mockGQLSubscription(); cy.visit('/#/markets/market-0'); connectVegaWallet(); cy.wait('@Market'); @@ -634,6 +635,7 @@ describe('suspended market validation', { tags: '@regression' }, () => { Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY ); + cy.mockGQLSubscription(); cy.visit('/#/markets/market-0'); cy.wait('@Market'); connectVegaWallet(); @@ -682,11 +684,15 @@ describe('account validation', { tags: '@regression' }, () => { 'EstimateOrder', generateEstimateOrder({ estimateOrder: { - marginLevels: { __typename: 'MarginLevels', initialLevel: '1000' }, + marginLevels: { + __typename: 'MarginLevels', + initialLevel: '1000000000', + }, }, }) ); }); + cy.mockGQLSubscription(); cy.visit('/#/markets/market-0'); connectVegaWallet(); cy.wait('@Market'); @@ -712,7 +718,7 @@ describe('account validation', { tags: '@regression' }, () => { ); cy.getByTestId('dealticket-warning-margin').should( 'contain.text', - '0.01 tBTC currently required, 0.001 tBTC available' + '10,000 tBTC currently required, 1,000 tBTC available' ); cy.getByTestId('deal-ticket-deposit-dialog-button').click(); cy.getByTestId('dialog-content') diff --git a/apps/trading-e2e/src/support/mocks/generate-accounts.ts b/apps/trading-e2e/src/support/mocks/generate-accounts.ts index 80b799038..58141da8e 100644 --- a/apps/trading-e2e/src/support/mocks/generate-accounts.ts +++ b/apps/trading-e2e/src/support/mocks/generate-accounts.ts @@ -101,6 +101,19 @@ export const generateAccounts = ( }, }, }, + { + __typename: 'AccountEdge', + node: { + __typename: 'AccountBalance', + type: Schema.AccountType.ACCOUNT_TYPE_GENERAL, + balance: '100000000', + market: null, + asset: { + __typename: 'Asset', + id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', + }, + }, + }, ], }, }, diff --git a/apps/trading-e2e/src/support/mocks/generate-assets.ts b/apps/trading-e2e/src/support/mocks/generate-assets.ts index b4245028b..2289dca91 100644 --- a/apps/trading-e2e/src/support/mocks/generate-assets.ts +++ b/apps/trading-e2e/src/support/mocks/generate-assets.ts @@ -164,6 +164,30 @@ export const generateAssets = (override?: PartialDeep) => { __typename: 'Asset', }, }, + { + node: { + id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', + symbol: 'tBTC', + decimals: 5, + name: 'tBTC TEST', + source: { + maxFaucetAmountMint: '5000000000', + __typename: 'BuiltinAsset', + }, + quantum: '1', + status: Types.AssetStatus.STATUS_ENABLED, + infrastructureFeeAccount: { + balance: '0', + __typename: 'AccountBalance', + }, + globalRewardPoolAccount: null, + takerFeeRewardAccount: null, + makerFeeRewardAccount: null, + lpFeeRewardAccount: null, + marketProposerRewardAccount: null, + __typename: 'Asset', + }, + }, // NOTE: These assets ids and contract addresses are real assets on Sepolia, this is needed // because we don't currently mock our seplia infura provider. If we change network these will // need to be updated diff --git a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts b/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts deleted file mode 100644 index b7c98c8c5..000000000 --- a/apps/trading-e2e/src/support/mocks/generate-deal-ticket-query.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { DealTicketQuery } from '@vegaprotocol/deal-ticket'; -import { Schema } from '@vegaprotocol/types'; -import merge from 'lodash/merge'; -import type { PartialDeep } from 'type-fest'; - -export const generateDealTicketQuery = ( - override?: PartialDeep -): DealTicketQuery => { - const defaultResult: DealTicketQuery = { - market: { - __typename: 'Market', - id: 'market-0', - decimalPlaces: 5, - positionDecimalPlaces: 0, - state: Schema.MarketState.STATE_ACTIVE, - tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, - fees: { - factors: { - makerFee: '0.0002', - infrastructureFee: '0.0005', - liquidityFee: '0.0005', - }, - }, - tradableInstrument: { - __typename: 'TradableInstrument', - instrument: { - __typename: 'Instrument', - id: 'tBTC TEST', - name: 'ETHBTC Quarterly (30 Jun 2022)', - product: { - __typename: 'Future', - quoteName: 'BTC', - settlementAsset: { - __typename: 'Asset', - id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', - symbol: 'tBTC', - name: 'tBTC TEST', - decimals: 5, - }, - }, - }, - }, - depth: { - __typename: 'MarketDepth', - lastTrade: { - __typename: 'Trade', - price: '100', - }, - }, - }, - }; - - return merge(defaultResult, override); -}; diff --git a/apps/trading-e2e/src/support/mocks/generate-market.ts b/apps/trading-e2e/src/support/mocks/generate-market.ts index de2e02641..433a5a0ab 100644 --- a/apps/trading-e2e/src/support/mocks/generate-market.ts +++ b/apps/trading-e2e/src/support/mocks/generate-market.ts @@ -13,7 +13,7 @@ export const generateMarket = ( const defaultResult: MarketQuery = { market: { id: 'market-0', - tradingMode: Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, + tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, state: Schema.MarketState.STATE_ACTIVE, decimalPlaces: 5, positionDecimalPlaces: 0, @@ -38,12 +38,12 @@ export const generateMarket = ( id: 'd253c16c6a17ab88e098479635c611ab503582a1079752d1a49ac15f656f7e7b', __typename: 'DataSourceSpec', }, - quoteName: 'BTCUSD Monthly', + quoteName: 'BTC', settlementAsset: { - decimals: 0, - id: '000', - symbol: 'USD', - name: 'United States Dollar', + decimals: 5, + id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c', + symbol: 'tBTC', + name: 'tBTC TEST', __typename: 'Asset', }, __typename: 'Future', @@ -61,12 +61,16 @@ export const generateMarket = ( __typename: 'Fees', factors: { __typename: 'FeeFactors', - makerFee: '', - infrastructureFee: '', - liquidityFee: '', + makerFee: '0.0002', + infrastructureFee: '0.0005', + liquidityFee: '0.0005', }, }, __typename: 'Market', + depth: { + __typename: 'MarketDepth', + lastTrade: { price: '100', __typename: 'Trade' }, + }, }, }; diff --git a/apps/trading-e2e/src/support/trading.ts b/apps/trading-e2e/src/support/trading.ts index 7d84ad0fd..f7d135475 100644 --- a/apps/trading-e2e/src/support/trading.ts +++ b/apps/trading-e2e/src/support/trading.ts @@ -6,7 +6,6 @@ import { generateAsset, generateAssets } from './mocks/generate-assets'; import { generateCandles } from './mocks/generate-candles'; import { generateChainId } from './mocks/generate-chain-id'; import { generateChart } from './mocks/generate-chart'; -import { generateDealTicketQuery } from './mocks/generate-deal-ticket-query'; import { generateMarket, generateMarketData } from './mocks/generate-market'; import { generateMarketDepth } from './mocks/generate-market-depth'; import { generateMarketInfoQuery } from './mocks/generate-market-info-query'; @@ -48,6 +47,7 @@ const mockTradingPage = ( }, }, state: state, + tradingMode: tradingMode, }, }) ); @@ -69,19 +69,6 @@ const mockTradingPage = ( aliasQuery(req, 'Accounts', generateAccounts()); aliasQuery(req, 'Positions', generatePositions()); aliasQuery(req, 'Margins', generateMargins()); - aliasQuery( - req, - 'DealTicket', - generateDealTicketQuery({ - market: { - state, - tradingMode: tradingMode, - data: { - trigger: trigger, - }, - }, - }) - ); aliasQuery(req, 'Assets', generateAssets()); aliasQuery(req, 'Asset', generateAsset()); diff --git a/apps/trading/components/market-trading-mode/market-trading-mode.tsx b/apps/trading/components/market-trading-mode/market-trading-mode.tsx index 52f28dfeb..30b3e01ab 100644 --- a/apps/trading/components/market-trading-mode/market-trading-mode.tsx +++ b/apps/trading/components/market-trading-mode/market-trading-mode.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from 'react'; import { t, useDataProvider } from '@vegaprotocol/react-helpers'; -import type { DealTicketMarketFragment } from '@vegaprotocol/deal-ticket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { compileGridData, TradingModeTooltip } from '@vegaprotocol/deal-ticket'; import type { Schema as Types } from '@vegaprotocol/types'; import { @@ -21,13 +21,11 @@ interface Props { onSelect?: (marketId: string) => void; } -type TradingModeMarket = Omit; - export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => { const [tradingMode, setTradingMode] = useState(null); const [trigger, setTrigger] = useState(null); - const [market, setMarket] = useState(null); + const [market, setMarket] = useState(null); const variables = useMemo( () => ({ marketId: marketId, @@ -49,7 +47,7 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => { setMarket({ ...data, data: marketData, - } as TradingModeMarket); + } as MarketDealTicket); } return true; }, diff --git a/libs/accounts/src/lib/get-settlement-account.ts b/libs/accounts/src/lib/get-settlement-account.ts new file mode 100644 index 000000000..223a4e9a0 --- /dev/null +++ b/libs/accounts/src/lib/get-settlement-account.ts @@ -0,0 +1,15 @@ +import type { Account } from './accounts-data-provider'; +import { Schema } from '@vegaprotocol/types'; + +interface Props { + accounts: Account[] | null; + assetId: string; +} + +export const getSettlementAccount = ({ accounts, assetId }: Props) => + accounts?.find((account) => { + return ( + account.asset.id === assetId && + account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL + ); + }) || null; diff --git a/libs/accounts/src/lib/index.ts b/libs/accounts/src/lib/index.ts index 86e857552..0dff722bf 100644 --- a/libs/accounts/src/lib/index.ts +++ b/libs/accounts/src/lib/index.ts @@ -4,3 +4,5 @@ export * from './accounts-table'; export * from './asset-balance'; export * from './accounts-manager'; export * from './breakdown-table'; +export * from './use-account-balance'; +export * from './get-settlement-account'; diff --git a/libs/accounts/src/lib/use-account-balance.tsx b/libs/accounts/src/lib/use-account-balance.tsx new file mode 100644 index 000000000..02538c7d2 --- /dev/null +++ b/libs/accounts/src/lib/use-account-balance.tsx @@ -0,0 +1,43 @@ +import { useCallback, useMemo, useState } from 'react'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { useDataProvider } from '@vegaprotocol/react-helpers'; +import { accountsDataProvider } from './accounts-data-provider'; +import type { Account } from './accounts-data-provider'; +import { getSettlementAccount } from './get-settlement-account'; + +export const useAccountBalance = (assetId: string) => { + const { pubKey } = useVegaWallet(); + const [accountBalance, setAccountBalance] = useState(''); + const [accountDecimals, setAccountDecimals] = useState(null); + const variables = useMemo(() => { + return { partyId: pubKey || '' }; + }, [pubKey]); + const update = useCallback( + ({ data }: { data: Account[] | null }) => { + const account = getSettlementAccount({ accounts: data, assetId }); + if (accountBalance !== account?.balance) { + setAccountBalance(account?.balance || ''); + } + if (accountDecimals !== account?.asset.decimals) { + setAccountDecimals(account?.asset.decimals || null); + } + return true; + }, + [accountBalance, accountDecimals, assetId] + ); + + useDataProvider({ + dataProvider: accountsDataProvider, + variables, + skip: !pubKey || !assetId, + update, + }); + + return useMemo( + () => ({ + accountBalance, + accountDecimals, + }), + [accountBalance, accountDecimals] + ); +}; diff --git a/libs/deal-ticket/src/components/deal-ticket/DealTicket.graphql b/libs/deal-ticket/src/components/deal-ticket/DealTicket.graphql deleted file mode 100644 index be8332b9a..000000000 --- a/libs/deal-ticket/src/components/deal-ticket/DealTicket.graphql +++ /dev/null @@ -1,54 +0,0 @@ -fragment DealTicketMarket on Market { - id - decimalPlaces - positionDecimalPlaces - state - tradingMode - data { - market { - id - } - indicativePrice - indicativeVolume - targetStake - suppliedStake - auctionStart - auctionEnd - trigger - } - fees { - factors { - makerFee - infrastructureFee - liquidityFee - } - } - tradableInstrument { - instrument { - id - name - product { - ... on Future { - quoteName - settlementAsset { - id - symbol - decimals - name - } - } - } - } - } - depth { - lastTrade { - price - } - } -} - -query DealTicket($marketId: ID!) { - market(id: $marketId) { - ...DealTicketMarket - } -} diff --git a/libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicket.ts b/libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicket.ts deleted file mode 100644 index 31837ccc2..000000000 --- a/libs/deal-ticket/src/components/deal-ticket/__generated__/DealTicket.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Schema as Types } from '@vegaprotocol/types'; - -import { gql } from '@apollo/client'; -import * as Apollo from '@apollo/client'; -const defaultOptions = {} as const; -export type DealTicketMarketFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } }; - -export type DealTicketQueryVariables = Types.Exact<{ - marketId: Types.Scalars['ID']; -}>; - - -export type DealTicketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, data?: { __typename?: 'MarketData', indicativePrice: string, indicativeVolume: string, targetStake?: string | null, suppliedStake?: string | null, auctionStart?: string | null, auctionEnd?: string | null, trigger: Types.AuctionTrigger, market: { __typename?: 'Market', id: string } } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string } } } }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null }; - -export const DealTicketMarketFragmentDoc = gql` - fragment DealTicketMarket on Market { - id - decimalPlaces - positionDecimalPlaces - state - tradingMode - data { - market { - id - } - indicativePrice - indicativeVolume - targetStake - suppliedStake - auctionStart - auctionEnd - trigger - } - fees { - factors { - makerFee - infrastructureFee - liquidityFee - } - } - tradableInstrument { - instrument { - id - name - product { - ... on Future { - quoteName - settlementAsset { - id - symbol - decimals - name - } - } - } - } - } - depth { - lastTrade { - price - } - } -} - `; -export const DealTicketDocument = gql` - query DealTicket($marketId: ID!) { - market(id: $marketId) { - ...DealTicketMarket - } -} - ${DealTicketMarketFragmentDoc}`; - -/** - * __useDealTicketQuery__ - * - * To run a query within a React component, call `useDealTicketQuery` and pass it any options that fit your needs. - * When your component renders, `useDealTicketQuery` 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 } = useDealTicketQuery({ - * variables: { - * marketId: // value for 'marketId' - * }, - * }); - */ -export function useDealTicketQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(DealTicketDocument, options); - } -export function useDealTicketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(DealTicketDocument, options); - } -export type DealTicketQueryHookResult = ReturnType; -export type DealTicketLazyQueryHookResult = ReturnType; -export type DealTicketQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx index 95bed6573..80903b6e9 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-amount.tsx @@ -1,13 +1,13 @@ import type { UseFormRegister } from 'react-hook-form'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { DealTicketMarketAmount } from './deal-ticket-market-amount'; import { DealTicketLimitAmount } from './deal-ticket-limit-amount'; -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; import { Schema } from '@vegaprotocol/types'; import type { DealTicketFormFields } from './deal-ticket'; export interface DealTicketAmountProps { orderType: Schema.OrderType; - market: DealTicketMarketFragment; + market: MarketDealTicket; register: UseFormRegister; sizeError?: string; priceError?: string; 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 c2e20c86d..11c0a2c63 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 @@ -1,30 +1,41 @@ +import { useMemo } from 'react'; import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit'; +import { t, useDataProvider } from '@vegaprotocol/react-helpers'; +import type { + MarketDataUpdateFieldsFragment, + MarketDealTicket, +} from '@vegaprotocol/market-list'; +import { marketDealTicketProvider } from '@vegaprotocol/market-list'; import { DealTicketManager } from './deal-ticket-manager'; -import { t } from '@vegaprotocol/react-helpers'; -import { useDealTicketQuery } from './__generated__/DealTicket'; -import type { DealTicketQuery } from './__generated__/DealTicket'; export interface DealTicketContainerProps { marketId: string; - children?(props: DealTicketQuery): JSX.Element; } -export const DealTicketContainer = ({ - marketId, - children, -}: DealTicketContainerProps) => { - const { data, loading, error } = useDealTicketQuery({ - variables: { marketId }, +export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { + const variables = useMemo( + () => ({ + marketId: marketId || '', + }), + [marketId] + ); + const { data, error, loading } = useDataProvider< + MarketDealTicket, + MarketDataUpdateFieldsFragment + >({ + dataProvider: marketDealTicketProvider, + variables, + skip: !marketId, }); return ( - data={data} loading={loading} error={error}> - {data && data.market ? ( - children ? ( - children(data) - ) : ( - - ) + + data={data || undefined} + loading={loading} + error={error} + > + {data ? ( + ) : (

{t('Could not load market')}

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 51a513fa5..70625c2a3 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,9 +1,15 @@ import { Tooltip } from '@vegaprotocol/ui-toolkit'; - import type { ReactNode } from 'react'; +import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; +import { + getFeeDetailsValues, + useFeeDealTicketDetails, +} from '../../hooks/use-fee-deal-ticket-details'; interface DealTicketFeeDetailsProps { - details: DealTicketFeeDetails[]; + order: OrderSubmissionBody['orderSubmission']; + market: MarketDealTicket; } export interface DealTicketFeeDetails { @@ -14,8 +20,11 @@ export interface DealTicketFeeDetails { } export const DealTicketFeeDetails = ({ - details, + order, + market, }: DealTicketFeeDetailsProps) => { + const feeDetails = useFeeDealTicketDetails(order, market); + const details = getFeeDetailsValues(feeDetails); return (
{details.map(({ label, value, labelDescription, quoteName }) => ( diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx index 6c7702bbf..5aeaa65ff 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-manager.tsx @@ -1,14 +1,14 @@ import type { ReactNode } from 'react'; import { VegaTxStatus } from '@vegaprotocol/wallet'; import { DealTicket } from './deal-ticket'; -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { useOrderSubmit, OrderFeedback } from '@vegaprotocol/orders'; import { Schema } from '@vegaprotocol/types'; import { Icon, Intent } from '@vegaprotocol/ui-toolkit'; import { t } from '@vegaprotocol/react-helpers'; export interface DealTicketManagerProps { - market: DealTicketMarketFragment; + market: MarketDealTicket; children?: ReactNode | ReactNode[]; } 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 1d233960a..52d88077f 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 @@ -34,7 +34,7 @@ export const DealTicketMarketAmount = ({ price = market.data.indicativePrice; } } else { - price = market.depth.lastTrade?.price; + price = market.depth?.lastTrade?.price; } const priceFormatted = price diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx index aa0f0f8c7..434804979 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { VegaWalletContext } from '@vegaprotocol/wallet'; import { fireEvent, render, screen, act } from '@testing-library/react'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { DealTicket } from './deal-ticket'; -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; import { Schema } from '@vegaprotocol/types'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import type { MockedResponse } from '@apollo/client/testing'; @@ -10,7 +10,7 @@ import { MockedProvider } from '@apollo/client/testing'; import type { ChainIdQuery } from '@vegaprotocol/react-helpers'; import { ChainIdDocument, addDecimal } from '@vegaprotocol/react-helpers'; -const market: DealTicketMarketFragment = { +const market = { __typename: 'Market', id: 'market-id', decimalPlaces: 2, @@ -50,7 +50,7 @@ const market: DealTicketMarketFragment = { price: '100', }, }, -}; +} as MarketDealTicket; const submit = jest.fn(); const transactionStatus = 'default'; @@ -109,7 +109,7 @@ describe('DealTicket', () => { // Assert last price is shown expect(screen.getByTestId('last-price')).toHaveTextContent( // eslint-disable-next-line - `~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${ + `~${addDecimal(market!.depth!.lastTrade!.price, market.decimalPlaces)} ${ market.tradableInstrument.instrument.product.settlementAsset.symbol }` ); 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 efecaa04b..91905e2d0 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -2,11 +2,6 @@ import { removeDecimal, t } from '@vegaprotocol/react-helpers'; import { Schema } from '@vegaprotocol/types'; import { memo, useCallback, useEffect } from 'react'; import { Controller, useForm } from 'react-hook-form'; - -import { - getFeeDetailsValues, - useFeeDealTicketDetails, -} from '../../hooks/use-fee-deal-ticket-details'; import { DealTicketAmount } from './deal-ticket-amount'; import { DealTicketButton } from './deal-ticket-button'; import { DealTicketFeeDetails } from './deal-ticket-fee-details'; @@ -14,8 +9,6 @@ import { ExpirySelector } from './expiry-selector'; import { SideSelector } from './side-selector'; import { TimeInForceSelector } from './time-in-force-selector'; import { TypeSelector } from './type-selector'; - -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { InputError } from '@vegaprotocol/ui-toolkit'; @@ -31,12 +24,13 @@ import { } from '../../utils'; import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error'; import { AccountValidationType } from '../../constants'; -import type BigNumber from 'bignumber.js'; +import { useHasNoBalance } from '../../hooks/use-has-no-balance'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; export type TransactionStatus = 'default' | 'pending'; export interface DealTicketProps { - market: DealTicketMarketFragment; + market: MarketDealTicket; submit: (order: OrderSubmissionBody['orderSubmission']) => void; transactionStatus: TransactionStatus; defaultOrder?: OrderSubmissionBody['orderSubmission']; @@ -67,17 +61,11 @@ export const DealTicket = ({ }); const order = watch(); - const feeDetails = useFeeDealTicketDetails(order, market); - const details = getFeeDetailsValues(feeDetails); - // When order state changes persist it in local storage useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]); - - const accountData = useOrderMarginValidation({ - market, - estMargin: feeDetails.estMargin, - }); - + const hasNoBalance = useHasNoBalance( + market.tradableInstrument.instrument.product.settlementAsset.id + ); const onSubmit = useCallback( (order: OrderSubmissionBody['orderSubmission']) => { if (!pubKey) { @@ -91,7 +79,7 @@ export const DealTicket = ({ return; } - if (accountData.balance.isZero()) { + if (hasNoBalance) { setError('summary', { message: AccountValidationType.NoCollateral }); return; } @@ -117,7 +105,7 @@ export const DealTicket = ({ [ submit, pubKey, - accountData, + hasNoBalance, market.positionDecimalPlaces, market.decimalPlaces, market.state, @@ -195,9 +183,9 @@ export const DealTicket = ({ - + ); }; @@ -208,22 +196,18 @@ export const DealTicket = ({ */ interface SummaryMessageProps { errorMessage?: string; - market: DealTicketMarketFragment; - accountData: { - balance: BigNumber; - margin: BigNumber; - asset: { - id: string; - symbol: string; - decimals: number; - name: string; - }; - }; + market: MarketDealTicket; + order: OrderSubmissionBody['orderSubmission']; } const SummaryMessage = memo( - ({ errorMessage, market, accountData }: SummaryMessageProps) => { + ({ errorMessage, market, order }: SummaryMessageProps) => { // Specific error UI for if balance is so we can // render a deposit dialog + const asset = market.tradableInstrument.instrument.product.settlementAsset; + const { balanceError, balance, margin } = useOrderMarginValidation({ + market, + order, + }); if (errorMessage === AccountValidationType.NoCollateral) { return ( - ); + if (balanceError) { + return ; } // Show auction mode warning diff --git a/libs/deal-ticket/src/components/deal-ticket/index.ts b/libs/deal-ticket/src/components/deal-ticket/index.ts index d9f11eae6..86e34e81e 100644 --- a/libs/deal-ticket/src/components/deal-ticket/index.ts +++ b/libs/deal-ticket/src/components/deal-ticket/index.ts @@ -1,4 +1,3 @@ -export * from './__generated__/DealTicket'; export * from './deal-ticket-amount'; export * from './deal-ticket-container'; export * from './deal-ticket-limit-amount'; diff --git a/libs/deal-ticket/src/components/deal-ticket/market-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/market-selector.tsx index 3cbc99c9f..c331fa501 100644 --- a/libs/deal-ticket/src/components/deal-ticket/market-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/market-selector.tsx @@ -8,7 +8,6 @@ import React, { } from 'react'; import * as DialogPrimitives from '@radix-ui/react-dialog'; import classNames from 'classnames'; -import type { DealTicketMarketFragment } from './'; import { ButtonLink, Icon, @@ -25,10 +24,11 @@ import { import { IconNames } from '@blueprintjs/icons'; import { Schema } from '@vegaprotocol/types'; import type { Market } from '@vegaprotocol/market-list'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { marketsProvider } from '@vegaprotocol/market-list'; interface Props { - market: DealTicketMarketFragment; + market: MarketDealTicket; setMarket: (marketId: string) => void; ItemRenderer?: React.FC<{ market: Market; 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 14940a9f0..79a26b0e0 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 @@ -8,7 +8,7 @@ import { import { Schema } from '@vegaprotocol/types'; import { t } from '@vegaprotocol/react-helpers'; import { timeInForceLabel } from '@vegaprotocol/orders'; -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { compileGridData, MarketDataGrid } from '../trading-mode-tooltip'; import { MarketModeValidationType } from '../../constants'; @@ -16,7 +16,7 @@ interface TimeInForceSelectorProps { value: Schema.OrderTimeInForce; orderType: Schema.OrderType; onSelect: (tif: Schema.OrderTimeInForce) => void; - market: DealTicketMarketFragment; + market: MarketDealTicket; errorMessage?: string; } 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 5ec1b49bb..7c9fb2ccd 100644 --- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx @@ -2,14 +2,14 @@ import { FormGroup, InputError, Tooltip } from '@vegaprotocol/ui-toolkit'; import { t } from '@vegaprotocol/react-helpers'; import { Schema } from '@vegaprotocol/types'; import { Toggle } from '@vegaprotocol/ui-toolkit'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { compileGridData, MarketDataGrid } from '../trading-mode-tooltip'; -import type { DealTicketMarketFragment } from './__generated__/DealTicket'; import { MarketModeValidationType } from '../../constants'; interface TypeSelectorProps { value: Schema.OrderType; onSelect: (type: Schema.OrderType) => void; - market: DealTicketMarketFragment; + market: MarketDealTicket; errorMessage?: string; } diff --git a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx index e6d1378e9..7c3f4c861 100644 --- a/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx +++ b/libs/deal-ticket/src/components/trading-mode-tooltip/compile-grid-data.tsx @@ -7,11 +7,11 @@ import { Schema } from '@vegaprotocol/types'; import { Link as UILink } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; import type { MarketDataGridProps } from './market-data-grid'; -import type { DealTicketMarketFragment } from '../deal-ticket/__generated__/DealTicket'; import { Link } from 'react-router-dom'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; export const compileGridData = ( - market: Omit, + market: MarketDealTicket, onSelect?: (id: string) => void ): { label: ReactNode; value?: ReactNode }[] => { const grid: MarketDataGridProps['grid'] = []; 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 index cc2e49702..8a1f81293 100644 --- a/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx +++ b/libs/deal-ticket/src/hooks/use-fee-deal-ticket-details.tsx @@ -8,7 +8,8 @@ import { Schema } from '@vegaprotocol/types'; import { useVegaWallet } from '@vegaprotocol/wallet'; import BigNumber from 'bignumber.js'; import { useMemo } from 'react'; - +import type { MarketDealTicket } from '@vegaprotocol/market-list'; +import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { EST_CLOSEOUT_TOOLTIP_TEXT, EST_MARGIN_TOOLTIP_TEXT, @@ -18,21 +19,18 @@ import { usePartyBalanceQuery } from './__generated__/PartyBalance'; import { useCalculateSlippage } from './use-calculate-slippage'; import { useOrderCloseOut } from './use-order-closeout'; import { useOrderMargin } from './use-order-margin'; - -import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import type { DealTicketMarketFragment } from '../components'; import type { OrderMargin } from './use-order-margin'; export const useFeeDealTicketDetails = ( order: OrderSubmissionBody['orderSubmission'], - market: DealTicketMarketFragment + market: MarketDealTicket ) => { const { pubKey } = useVegaWallet(); const slippage = useCalculateSlippage({ marketId: market.id, order }); const price = useMemo(() => { - const estPrice = order.price || market.depth.lastTrade?.price; + const estPrice = order.price || market.depth?.lastTrade?.price; if (estPrice) { if (slippage && parseFloat(slippage) !== 0) { const isLong = order.side === Schema.Side.SIDE_BUY; @@ -44,7 +42,7 @@ export const useFeeDealTicketDetails = ( return order.price; } return null; - }, [market.depth.lastTrade?.price, order.price, order.side, slippage]); + }, [market.depth?.lastTrade?.price, order.price, order.side, slippage]); const estMargin: OrderMargin | null = useOrderMargin({ order, @@ -72,7 +70,18 @@ export const useFeeDealTicketDetails = ( const quoteName = market.tradableInstrument.instrument.product.quoteName; - return { + return useMemo(() => { + return { + market, + quoteName, + notionalSize, + estMargin, + estCloseOut, + slippage, + price, + partyData: partyBalance, + }; + }, [ market, quoteName, notionalSize, @@ -80,12 +89,12 @@ export const useFeeDealTicketDetails = ( estCloseOut, slippage, price, - partyData: partyBalance, - }; + partyBalance, + ]); }; export interface FeeDetails { - market: DealTicketMarketFragment; + market: MarketDealTicket; quoteName: string; notionalSize: string | null; estMargin: OrderMargin | null; diff --git a/libs/deal-ticket/src/hooks/use-has-no-balance.tsx b/libs/deal-ticket/src/hooks/use-has-no-balance.tsx new file mode 100644 index 000000000..d95bc024d --- /dev/null +++ b/libs/deal-ticket/src/hooks/use-has-no-balance.tsx @@ -0,0 +1,11 @@ +import { useAccountBalance } from '@vegaprotocol/accounts'; +import { toBigNum } from '@vegaprotocol/react-helpers'; + +export const useHasNoBalance = (assetId: string) => { + const { accountBalance, accountDecimals } = useAccountBalance(assetId); + const balance = + accountBalance && accountDecimals !== null + ? toBigNum(accountBalance, accountDecimals) + : toBigNum('0', 0); + return balance.isZero(); +}; diff --git a/libs/deal-ticket/src/hooks/use-order-closeout.spec.tsx b/libs/deal-ticket/src/hooks/use-order-closeout.spec.tsx index 76fa883f5..0334a79e5 100644 --- a/libs/deal-ticket/src/hooks/use-order-closeout.spec.tsx +++ b/libs/deal-ticket/src/hooks/use-order-closeout.spec.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { renderHook } from '@testing-library/react'; import { MockedProvider } from '@apollo/client/testing'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import type { PartyBalanceQuery } from './__generated__/PartyBalance'; import { useOrderCloseOut } from './use-order-closeout'; -import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated__/DealTicket'; jest.mock('@vegaprotocol/wallet', () => ({ ...jest.requireActual('@vegaprotocol/wallet'), @@ -53,7 +53,7 @@ describe('useOrderCloseOut', () => { () => useOrderCloseOut({ order: order as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyData: partyData as PartyBalanceQuery, }), { @@ -73,7 +73,7 @@ describe('useOrderCloseOut', () => { ...order, side: 'SIDE_SELL', } as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyData: partyData as PartyBalanceQuery, }), { @@ -93,7 +93,7 @@ describe('useOrderCloseOut', () => { ...order, side: 'SIDE_SELL', } as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, }), { wrapper: ({ children }: { children: React.ReactNode }) => ( diff --git a/libs/deal-ticket/src/hooks/use-order-closeout.ts b/libs/deal-ticket/src/hooks/use-order-closeout.ts index 8d189e7fa..95cff141b 100644 --- a/libs/deal-ticket/src/hooks/use-order-closeout.ts +++ b/libs/deal-ticket/src/hooks/use-order-closeout.ts @@ -7,13 +7,13 @@ import { useMarketPositions } from './use-market-positions'; import { useMarketDataMarkPrice } from './use-market-data-mark-price'; import { usePartyMarketDataQuery } from './__generated__/PartyMarketData'; import { Schema } from '@vegaprotocol/types'; -import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated__/DealTicket'; import type { PartyBalanceQuery } from './__generated__/PartyBalance'; import { useSettlementAccount } from './use-settlement-account'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; interface Props { order: OrderSubmissionBody['orderSubmission']; - market: DealTicketMarketFragment; + market: MarketDealTicket; partyData?: PartyBalanceQuery; } diff --git a/libs/deal-ticket/src/hooks/use-order-margin-validation.ts b/libs/deal-ticket/src/hooks/use-order-margin-validation.ts index f6e91c8f2..b46040e4b 100644 --- a/libs/deal-ticket/src/hooks/use-order-margin-validation.ts +++ b/libs/deal-ticket/src/hooks/use-order-margin-validation.ts @@ -1,53 +1,43 @@ import { useMemo } from 'react'; -import compact from 'lodash/compact'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { Schema } from '@vegaprotocol/types'; import { toBigNum } from '@vegaprotocol/react-helpers'; -import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated__/DealTicket'; import type { OrderMargin } from './use-order-margin'; -import { usePartyBalanceQuery } from './__generated__/PartyBalance'; -import { useSettlementAccount } from './use-settlement-account'; +import { useAccountBalance } from '@vegaprotocol/accounts'; +import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import { useOrderMargin } from './use-order-margin'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; interface Props { - market: DealTicketMarketFragment; - estMargin: OrderMargin | null; + market: MarketDealTicket; + order: OrderSubmissionBody['orderSubmission']; } -export const useOrderMarginValidation = ({ market, estMargin }: Props) => { +export const useOrderMarginValidation = ({ market, order }: Props) => { const { pubKey } = useVegaWallet(); - - const { data: partyBalance } = usePartyBalanceQuery({ - variables: { partyId: pubKey || '' }, - skip: !pubKey, - fetchPolicy: 'no-cache', + const estMargin: OrderMargin | null = useOrderMargin({ + order, + market, + partyId: pubKey || '', }); + const { id: assetId, decimals: assetDecimals } = + market.tradableInstrument.instrument.product.settlementAsset; - const accounts = compact(partyBalance?.party?.accountsConnection?.edges).map( - (e) => e.node - ); - const settlementAccount = useSettlementAccount( - market.tradableInstrument.instrument.product.settlementAsset.id, - accounts, - Schema.AccountType.ACCOUNT_TYPE_GENERAL - ); - const assetDecimals = - market.tradableInstrument.instrument.product.settlementAsset.decimals; - const balance = settlementAccount - ? toBigNum( - settlementAccount.balance || 0, - settlementAccount.asset.decimals || 0 - ) - : toBigNum('0', assetDecimals); + const { accountBalance, accountDecimals } = useAccountBalance(assetId); + const balance = + accountBalance && accountDecimals !== null + ? toBigNum(accountBalance, accountDecimals) + : toBigNum('0', assetDecimals); const margin = toBigNum(estMargin?.margin || 0, assetDecimals); - const asset = market.tradableInstrument.instrument.product.settlementAsset; - const memoizedValue = useMemo(() => { + // return only simple types (bool, string) for make memo sensible + const balanceError = balance.isGreaterThan(0) && balance.isLessThan(margin); + const balanceAsString = balance.toString(); + const marginAsString = margin.toString(); + return useMemo(() => { return { - balance, - margin, - asset, + balance: balanceAsString, + margin: marginAsString, + balanceError, }; - }, [balance, margin, asset]); - - return memoizedValue; + }, [balanceAsString, marginAsString, balanceError]); }; diff --git a/libs/deal-ticket/src/hooks/use-order-margin.spec.ts b/libs/deal-ticket/src/hooks/use-order-margin.spec.ts index 3ec6410e0..e2918a725 100644 --- a/libs/deal-ticket/src/hooks/use-order-margin.spec.ts +++ b/libs/deal-ticket/src/hooks/use-order-margin.spec.ts @@ -2,9 +2,9 @@ import { renderHook } from '@testing-library/react'; import { useQuery } from '@apollo/client'; import { BigNumber } from 'bignumber.js'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import type { PositionMargin } from './use-market-positions'; import { useOrderMargin } from './use-order-margin'; -import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated__/DealTicket'; let mockEstimateData = { estimateOrder: { @@ -72,7 +72,7 @@ describe('useOrderMargin', () => { const { result } = renderHook(() => useOrderMargin({ order: order as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyId, }) ); @@ -86,7 +86,7 @@ describe('useOrderMargin', () => { const { result } = renderHook(() => useOrderMargin({ order: order as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyId, }) ); @@ -98,7 +98,7 @@ describe('useOrderMargin', () => { const { result } = renderHook(() => useOrderMargin({ order: order as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyId, }) ); @@ -125,7 +125,7 @@ describe('useOrderMargin', () => { const { result } = renderHook(() => useOrderMargin({ order: order as OrderSubmissionBody['orderSubmission'], - market: market as DealTicketMarketFragment, + market: market as MarketDealTicket, partyId, }) ); diff --git a/libs/deal-ticket/src/hooks/use-order-margin.ts b/libs/deal-ticket/src/hooks/use-order-margin.ts index 53551f324..96e50c475 100644 --- a/libs/deal-ticket/src/hooks/use-order-margin.ts +++ b/libs/deal-ticket/src/hooks/use-order-margin.ts @@ -2,15 +2,15 @@ import { BigNumber } from 'bignumber.js'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { Schema } from '@vegaprotocol/types'; import { removeDecimal } from '@vegaprotocol/react-helpers'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; import { useMarketPositions } from './use-market-positions'; import { useMarketDataMarkPrice } from './use-market-data-mark-price'; import type { EstimateOrderQuery } from './__generated__/EstimateOrder'; import { useEstimateOrderQuery } from './__generated__/EstimateOrder'; -import type { DealTicketMarketFragment } from '../components/deal-ticket/__generated__/DealTicket'; interface Props { order: OrderSubmissionBody['orderSubmission']; - market: DealTicketMarketFragment; + market: MarketDealTicket; partyId: string; } diff --git a/libs/deal-ticket/src/hooks/use-persisted-order.ts b/libs/deal-ticket/src/hooks/use-persisted-order.ts index ca713968e..a4591bb60 100644 --- a/libs/deal-ticket/src/hooks/use-persisted-order.ts +++ b/libs/deal-ticket/src/hooks/use-persisted-order.ts @@ -1,6 +1,6 @@ import { useLocalStorage } from '@vegaprotocol/react-helpers'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; type OrderData = OrderSubmissionBody['orderSubmission'] | null; @@ -9,8 +9,12 @@ export const usePersistedOrder = (market: { }): [OrderData, (value: OrderData) => void] => { const [value, setValue] = useLocalStorage(`deal-ticket-order-${market.id}`); const order = value != null ? (JSON.parse(value) as OrderData) : null; + const setOrder = useCallback( + (order: OrderData) => setValue(JSON.stringify(order)), + [setValue] + ); return useMemo<[OrderData, (value: OrderData) => void]>( - () => [order, (order: OrderData) => setValue(JSON.stringify(order))], - [order, setValue] + () => [order, setOrder], + [order, setOrder] ); }; diff --git a/libs/deal-ticket/src/utils/is-market-in-auction.ts b/libs/deal-ticket/src/utils/is-market-in-auction.ts index 37253af4b..0f3a1555c 100644 --- a/libs/deal-ticket/src/utils/is-market-in-auction.ts +++ b/libs/deal-ticket/src/utils/is-market-in-auction.ts @@ -1,7 +1,7 @@ import { Schema } from '@vegaprotocol/types'; -import type { DealTicketMarketFragment } from '../components'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; -export const isMarketInAuction = (market: DealTicketMarketFragment) => { +export const isMarketInAuction = (market: MarketDealTicket) => { return [ Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION, Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, diff --git a/libs/deal-ticket/src/utils/validate-time-in-force.ts b/libs/deal-ticket/src/utils/validate-time-in-force.ts index cc5ff62b0..4352930c2 100644 --- a/libs/deal-ticket/src/utils/validate-time-in-force.ts +++ b/libs/deal-ticket/src/utils/validate-time-in-force.ts @@ -1,9 +1,9 @@ import { Schema } from '@vegaprotocol/types'; -import type { DealTicketMarketFragment } from '../components'; import { MarketModeValidationType } from '../constants'; import { isMarketInAuction } from './is-market-in-auction'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; -export const validateTimeInForce = (market: DealTicketMarketFragment) => { +export const validateTimeInForce = (market: MarketDealTicket) => { return (value: Schema.OrderTimeInForce) => { const isMonitoringAuction = market.tradingMode === diff --git a/libs/deal-ticket/src/utils/validate-type.ts b/libs/deal-ticket/src/utils/validate-type.ts index 5118660bd..34f85d77c 100644 --- a/libs/deal-ticket/src/utils/validate-type.ts +++ b/libs/deal-ticket/src/utils/validate-type.ts @@ -1,9 +1,9 @@ import { Schema } from '@vegaprotocol/types'; -import type { DealTicketMarketFragment } from '../components'; import { MarketModeValidationType } from '../constants'; import { isMarketInAuction } from './is-market-in-auction'; +import type { MarketDealTicket } from '@vegaprotocol/market-list'; -export const validateType = (market: DealTicketMarketFragment) => { +export const validateType = (market: MarketDealTicket) => { return (value: Schema.OrderType) => { if (isMarketInAuction(market) && value === Schema.OrderType.TYPE_MARKET) { const isMonitoringAuction = diff --git a/libs/market-list/src/lib/__generated___/market.ts b/libs/market-list/src/lib/__generated___/market.ts index 03546331f..f8846136b 100644 --- a/libs/market-list/src/lib/__generated___/market.ts +++ b/libs/market-list/src/lib/__generated___/market.ts @@ -3,14 +3,14 @@ import { Schema as Types } from '@vegaprotocol/types'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type SingleMarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null } }; +export type SingleMarketFieldsFragment = { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } }; export type MarketQueryVariables = Types.Exact<{ marketId: Types.Scalars['ID']; }>; -export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null } } | null }; +export type MarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array | null }, product: { __typename?: 'Future', quoteName: string, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number } } } }, marketTimestamps: { __typename?: 'MarketTimestamps', open?: string | null, close?: string | null }, depth: { __typename?: 'MarketDepth', lastTrade?: { __typename?: 'Trade', price: string } | null } } | null }; export const SingleMarketFieldsFragmentDoc = gql` fragment SingleMarketFields on Market { @@ -54,6 +54,11 @@ export const SingleMarketFieldsFragmentDoc = gql` open close } + depth { + lastTrade { + price + } + } } `; export const MarketDocument = gql` diff --git a/libs/market-list/src/lib/market-provider.ts b/libs/market-list/src/lib/market-provider.ts index 0da206256..2ca8ebac6 100644 --- a/libs/market-list/src/lib/market-provider.ts +++ b/libs/market-list/src/lib/market-provider.ts @@ -1,9 +1,14 @@ -import { makeDataProvider } from '@vegaprotocol/react-helpers'; +import { + makeDataProvider, + makeDerivedDataProvider, +} from '@vegaprotocol/react-helpers'; import { MarketDocument } from './__generated___/market'; import type { MarketQuery, SingleMarketFieldsFragment, } from './__generated___/market'; +import type { MarketData } from './market-data-provider'; +import { marketDataProvider } from './market-data-provider'; const getData = ( responseData: MarketQuery @@ -18,3 +23,19 @@ export const marketProvider = makeDataProvider< query: MarketDocument, getData, }); + +export type MarketDealTicket = SingleMarketFieldsFragment & { + data: MarketData; +}; +export type MarketDealTicketAsset = + MarketDealTicket['tradableInstrument']['instrument']['product']['settlementAsset']; + +export const marketDealTicketProvider = makeDerivedDataProvider< + MarketDealTicket, + never +>([marketProvider, marketDataProvider], ([market, marketData]) => { + return { + ...market, + data: marketData, + }; +}); diff --git a/libs/market-list/src/lib/market.graphql b/libs/market-list/src/lib/market.graphql index 316056fe2..c83fe3db2 100644 --- a/libs/market-list/src/lib/market.graphql +++ b/libs/market-list/src/lib/market.graphql @@ -39,6 +39,11 @@ fragment SingleMarketFields on Market { open close } + depth { + lastTrade { + price + } + } } query Market($marketId: ID!) {