diff --git a/.gitignore b/.gitignore index ee5c9d833..a3032528b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ testem.log # System Files .DS_Store Thumbs.db + +.local.env +.env.local diff --git a/.prettierignore b/.prettierignore index d0b804da2..eb82b9e9f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ /dist /coverage +__generated__ diff --git a/apps/explorer-e2e/cypress.json b/apps/explorer-e2e/cypress.json index bad6108fe..6f4e8dcc8 100644 --- a/apps/explorer-e2e/cypress.json +++ b/apps/explorer-e2e/cypress.json @@ -1,5 +1,5 @@ { - "baseUrl": "http://localhost:4200", + "baseUrl": "http://localhost:3000", "projectId": "et4snf", "fileServerFolder": ".", "fixturesFolder": false, diff --git a/apps/explorer/project.json b/apps/explorer/project.json index e9a9d4566..c63274f86 100644 --- a/apps/explorer/project.json +++ b/apps/explorer/project.json @@ -40,6 +40,7 @@ "serve": { "executor": "@nrwl/web:dev-server", "options": { + "port": 3000, "buildTarget": "explorer:build", "hmr": true }, @@ -65,16 +66,6 @@ "passWithNoTests": true } }, - "generate": { - "builder": "@nrwl/workspace:run-commands", - "options": { - "commands": [ - { - "command": "npx apollo codegen:generate --config=apps/explorer/apollo.config.js --target=typescript --globalTypesFile=apps/explorer/src/__generated__/globalTypes.ts" - } - ] - } - }, "build-netlify": { "builder": "@nrwl/workspace:run-commands", "options": { diff --git a/apps/explorer/src/__generated__/globalTypes.ts b/apps/explorer/src/__generated__/globalTypes.ts index 95fd90e8d..74aa66291 100644 --- a/apps/explorer/src/__generated__/globalTypes.ts +++ b/apps/explorer/src/__generated__/globalTypes.ts @@ -11,92 +11,92 @@ * The various account types we have (used by collateral) */ export enum AccountType { - Bond = 'Bond', - FeeInfrastructure = 'FeeInfrastructure', - FeeLiquidity = 'FeeLiquidity', - General = 'General', - GlobalInsurance = 'GlobalInsurance', - Insurance = 'Insurance', - LockWithdraw = 'LockWithdraw', - Margin = 'Margin', - Settlement = 'Settlement', + Bond = "Bond", + FeeInfrastructure = "FeeInfrastructure", + FeeLiquidity = "FeeLiquidity", + General = "General", + GlobalInsurance = "GlobalInsurance", + Insurance = "Insurance", + LockWithdraw = "LockWithdraw", + Margin = "Margin", + Settlement = "Settlement", } export enum AuctionTrigger { - Batch = 'Batch', - Liquidity = 'Liquidity', - Opening = 'Opening', - Price = 'Price', - Unspecified = 'Unspecified', + Batch = "Batch", + Liquidity = "Liquidity", + Opening = "Opening", + Price = "Price", + Unspecified = "Unspecified", } /** * The current state of a market */ export enum MarketState { - Active = 'Active', - Cancelled = 'Cancelled', - Closed = 'Closed', - Pending = 'Pending', - Proposed = 'Proposed', - Rejected = 'Rejected', - Settled = 'Settled', - Suspended = 'Suspended', - TradingTerminated = 'TradingTerminated', + Active = "Active", + Cancelled = "Cancelled", + Closed = "Closed", + Pending = "Pending", + Proposed = "Proposed", + Rejected = "Rejected", + Settled = "Settled", + Suspended = "Suspended", + TradingTerminated = "TradingTerminated", } /** * What market trading mode are we in */ export enum MarketTradingMode { - BatchAuction = 'BatchAuction', - Continuous = 'Continuous', - MonitoringAuction = 'MonitoringAuction', - OpeningAuction = 'OpeningAuction', + BatchAuction = "BatchAuction", + Continuous = "Continuous", + MonitoringAuction = "MonitoringAuction", + OpeningAuction = "OpeningAuction", } export enum NodeStatus { - NonValidator = 'NonValidator', - Validator = 'Validator', + NonValidator = "NonValidator", + Validator = "Validator", } /** * Reason for the proposal being rejected by the core node */ export enum ProposalRejectionReason { - CloseTimeTooLate = 'CloseTimeTooLate', - CloseTimeTooSoon = 'CloseTimeTooSoon', - CouldNotInstantiateMarket = 'CouldNotInstantiateMarket', - EnactTimeTooLate = 'EnactTimeTooLate', - EnactTimeTooSoon = 'EnactTimeTooSoon', - IncompatibleTimestamps = 'IncompatibleTimestamps', - InsufficientTokens = 'InsufficientTokens', - InvalidAsset = 'InvalidAsset', - InvalidAssetDetails = 'InvalidAssetDetails', - InvalidFeeAmount = 'InvalidFeeAmount', - InvalidFutureMaturityTimestamp = 'InvalidFutureMaturityTimestamp', - InvalidFutureProduct = 'InvalidFutureProduct', - InvalidInstrumentSecurity = 'InvalidInstrumentSecurity', - InvalidRiskParameter = 'InvalidRiskParameter', - InvalidShape = 'InvalidShape', - MajorityThresholdNotReached = 'MajorityThresholdNotReached', - MarketMissingLiquidityCommitment = 'MarketMissingLiquidityCommitment', - MissingBuiltinAssetField = 'MissingBuiltinAssetField', - MissingCommitmentAmount = 'MissingCommitmentAmount', - MissingERC20ContractAddress = 'MissingERC20ContractAddress', - NetworkParameterInvalidKey = 'NetworkParameterInvalidKey', - NetworkParameterInvalidValue = 'NetworkParameterInvalidValue', - NetworkParameterValidationFailed = 'NetworkParameterValidationFailed', - NoProduct = 'NoProduct', - NoRiskParameters = 'NoRiskParameters', - NoTradingMode = 'NoTradingMode', - NodeValidationFailed = 'NodeValidationFailed', - OpeningAuctionDurationTooLarge = 'OpeningAuctionDurationTooLarge', - OpeningAuctionDurationTooSmall = 'OpeningAuctionDurationTooSmall', - ParticipationThresholdNotReached = 'ParticipationThresholdNotReached', - ProductMaturityIsPassed = 'ProductMaturityIsPassed', - UnsupportedProduct = 'UnsupportedProduct', - UnsupportedTradingMode = 'UnsupportedTradingMode', + CloseTimeTooLate = "CloseTimeTooLate", + CloseTimeTooSoon = "CloseTimeTooSoon", + CouldNotInstantiateMarket = "CouldNotInstantiateMarket", + EnactTimeTooLate = "EnactTimeTooLate", + EnactTimeTooSoon = "EnactTimeTooSoon", + IncompatibleTimestamps = "IncompatibleTimestamps", + InsufficientTokens = "InsufficientTokens", + InvalidAsset = "InvalidAsset", + InvalidAssetDetails = "InvalidAssetDetails", + InvalidFeeAmount = "InvalidFeeAmount", + InvalidFutureMaturityTimestamp = "InvalidFutureMaturityTimestamp", + InvalidFutureProduct = "InvalidFutureProduct", + InvalidInstrumentSecurity = "InvalidInstrumentSecurity", + InvalidRiskParameter = "InvalidRiskParameter", + InvalidShape = "InvalidShape", + MajorityThresholdNotReached = "MajorityThresholdNotReached", + MarketMissingLiquidityCommitment = "MarketMissingLiquidityCommitment", + MissingBuiltinAssetField = "MissingBuiltinAssetField", + MissingCommitmentAmount = "MissingCommitmentAmount", + MissingERC20ContractAddress = "MissingERC20ContractAddress", + NetworkParameterInvalidKey = "NetworkParameterInvalidKey", + NetworkParameterInvalidValue = "NetworkParameterInvalidValue", + NetworkParameterValidationFailed = "NetworkParameterValidationFailed", + NoProduct = "NoProduct", + NoRiskParameters = "NoRiskParameters", + NoTradingMode = "NoTradingMode", + NodeValidationFailed = "NodeValidationFailed", + OpeningAuctionDurationTooLarge = "OpeningAuctionDurationTooLarge", + OpeningAuctionDurationTooSmall = "OpeningAuctionDurationTooSmall", + ParticipationThresholdNotReached = "ParticipationThresholdNotReached", + ProductMaturityIsPassed = "ProductMaturityIsPassed", + UnsupportedProduct = "UnsupportedProduct", + UnsupportedTradingMode = "UnsupportedTradingMode", } /** @@ -107,18 +107,18 @@ export enum ProposalRejectionReason { * Proposal can enter Failed state from any other state. */ export enum ProposalState { - Declined = 'Declined', - Enacted = 'Enacted', - Failed = 'Failed', - Open = 'Open', - Passed = 'Passed', - Rejected = 'Rejected', - WaitingForNodeVote = 'WaitingForNodeVote', + Declined = "Declined", + Enacted = "Enacted", + Failed = "Failed", + Open = "Open", + Passed = "Passed", + Rejected = "Rejected", + WaitingForNodeVote = "WaitingForNodeVote", } export enum VoteValue { - No = 'No', - Yes = 'Yes', + No = "No", + Yes = "Yes", } //============================================================== diff --git a/apps/explorer/src/app/__generated__/globalTypes.ts b/apps/explorer/src/app/__generated__/globalTypes.ts deleted file mode 100644 index 74aa66291..000000000 --- a/apps/explorer/src/app/__generated__/globalTypes.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -//============================================================== -// START Enums and Input Objects -//============================================================== - -/** - * The various account types we have (used by collateral) - */ -export enum AccountType { - Bond = "Bond", - FeeInfrastructure = "FeeInfrastructure", - FeeLiquidity = "FeeLiquidity", - General = "General", - GlobalInsurance = "GlobalInsurance", - Insurance = "Insurance", - LockWithdraw = "LockWithdraw", - Margin = "Margin", - Settlement = "Settlement", -} - -export enum AuctionTrigger { - Batch = "Batch", - Liquidity = "Liquidity", - Opening = "Opening", - Price = "Price", - Unspecified = "Unspecified", -} - -/** - * The current state of a market - */ -export enum MarketState { - Active = "Active", - Cancelled = "Cancelled", - Closed = "Closed", - Pending = "Pending", - Proposed = "Proposed", - Rejected = "Rejected", - Settled = "Settled", - Suspended = "Suspended", - TradingTerminated = "TradingTerminated", -} - -/** - * What market trading mode are we in - */ -export enum MarketTradingMode { - BatchAuction = "BatchAuction", - Continuous = "Continuous", - MonitoringAuction = "MonitoringAuction", - OpeningAuction = "OpeningAuction", -} - -export enum NodeStatus { - NonValidator = "NonValidator", - Validator = "Validator", -} - -/** - * Reason for the proposal being rejected by the core node - */ -export enum ProposalRejectionReason { - CloseTimeTooLate = "CloseTimeTooLate", - CloseTimeTooSoon = "CloseTimeTooSoon", - CouldNotInstantiateMarket = "CouldNotInstantiateMarket", - EnactTimeTooLate = "EnactTimeTooLate", - EnactTimeTooSoon = "EnactTimeTooSoon", - IncompatibleTimestamps = "IncompatibleTimestamps", - InsufficientTokens = "InsufficientTokens", - InvalidAsset = "InvalidAsset", - InvalidAssetDetails = "InvalidAssetDetails", - InvalidFeeAmount = "InvalidFeeAmount", - InvalidFutureMaturityTimestamp = "InvalidFutureMaturityTimestamp", - InvalidFutureProduct = "InvalidFutureProduct", - InvalidInstrumentSecurity = "InvalidInstrumentSecurity", - InvalidRiskParameter = "InvalidRiskParameter", - InvalidShape = "InvalidShape", - MajorityThresholdNotReached = "MajorityThresholdNotReached", - MarketMissingLiquidityCommitment = "MarketMissingLiquidityCommitment", - MissingBuiltinAssetField = "MissingBuiltinAssetField", - MissingCommitmentAmount = "MissingCommitmentAmount", - MissingERC20ContractAddress = "MissingERC20ContractAddress", - NetworkParameterInvalidKey = "NetworkParameterInvalidKey", - NetworkParameterInvalidValue = "NetworkParameterInvalidValue", - NetworkParameterValidationFailed = "NetworkParameterValidationFailed", - NoProduct = "NoProduct", - NoRiskParameters = "NoRiskParameters", - NoTradingMode = "NoTradingMode", - NodeValidationFailed = "NodeValidationFailed", - OpeningAuctionDurationTooLarge = "OpeningAuctionDurationTooLarge", - OpeningAuctionDurationTooSmall = "OpeningAuctionDurationTooSmall", - ParticipationThresholdNotReached = "ParticipationThresholdNotReached", - ProductMaturityIsPassed = "ProductMaturityIsPassed", - UnsupportedProduct = "UnsupportedProduct", - UnsupportedTradingMode = "UnsupportedTradingMode", -} - -/** - * Various states a proposal can transition through: - * Open -> - * - Passed -> Enacted. - * - Rejected. - * Proposal can enter Failed state from any other state. - */ -export enum ProposalState { - Declined = "Declined", - Enacted = "Enacted", - Failed = "Failed", - Open = "Open", - Passed = "Passed", - Rejected = "Rejected", - WaitingForNodeVote = "WaitingForNodeVote", -} - -export enum VoteValue { - No = "No", - Yes = "Yes", -} - -//============================================================== -// END Enums and Input Objects -//============================================================== diff --git a/apps/explorer/src/app/components/search/index.tsx b/apps/explorer/src/app/components/search/index.tsx index d1d9d8066..ff6134ea1 100644 --- a/apps/explorer/src/app/components/search/index.tsx +++ b/apps/explorer/src/app/components/search/index.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import debounce from 'lodash.debounce'; -import { Guess, GuessVariables } from './__generated__/Guess'; +import { Guess, GuessVariables } from '@vegaprotocol/graphql'; const TX_LENGTH = 64; diff --git a/apps/explorer/src/app/routes/assets/index.tsx b/apps/explorer/src/app/routes/assets/index.tsx index 56001abd7..e7be5892e 100644 --- a/apps/explorer/src/app/routes/assets/index.tsx +++ b/apps/explorer/src/app/routes/assets/index.tsx @@ -1,7 +1,7 @@ import { gql, useQuery } from '@apollo/client'; import React from 'react'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; -import { AssetsQuery } from './__generated__/AssetsQuery'; +import { AssetsQuery } from '@vegaprotocol/graphql'; export const ASSETS_QUERY = gql` query AssetsQuery { @@ -11,7 +11,6 @@ export const ASSETS_QUERY = gql` symbol totalSupply decimals - minLpStake source { ... on ERC20 { contractAddress diff --git a/apps/explorer/src/app/routes/blocks/id/index.tsx b/apps/explorer/src/app/routes/blocks/id/index.tsx index 700f3af01..5e1b73ffc 100644 --- a/apps/explorer/src/app/routes/blocks/id/index.tsx +++ b/apps/explorer/src/app/routes/blocks/id/index.tsx @@ -34,10 +34,7 @@ const Block = () => { Mined by - + {header.proposer_address} diff --git a/apps/explorer/src/app/routes/governance/index.tsx b/apps/explorer/src/app/routes/governance/index.tsx index d9dc20ddc..9c35a17f5 100644 --- a/apps/explorer/src/app/routes/governance/index.tsx +++ b/apps/explorer/src/app/routes/governance/index.tsx @@ -4,7 +4,7 @@ import { SyntaxHighlighter } from '../../components/syntax-highlighter'; import { ProposalsQuery, ProposalsQuery_proposals_terms_change, -} from './__generated__/ProposalsQuery'; +} from '@vegaprotocol/graphql'; export function getProposalName(change: ProposalsQuery_proposals_terms_change) { if (change.__typename === 'NewAsset') { diff --git a/apps/explorer/src/app/routes/markets/index.tsx b/apps/explorer/src/app/routes/markets/index.tsx index bd79f0177..1fc494e18 100644 --- a/apps/explorer/src/app/routes/markets/index.tsx +++ b/apps/explorer/src/app/routes/markets/index.tsx @@ -1,5 +1,5 @@ import { gql, useQuery } from '@apollo/client'; -import { MarketsQuery } from './__generated__/MarketsQuery'; +import { MarketsQuery } from '@vegaprotocol/graphql'; import React from 'react'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; @@ -26,7 +26,6 @@ const MARKETS_QUERY = gql` code product { ... on Future { - maturity settlementAsset { id name diff --git a/apps/explorer/src/app/routes/network-parameters/index.tsx b/apps/explorer/src/app/routes/network-parameters/index.tsx index 22689c61b..46df31664 100644 --- a/apps/explorer/src/app/routes/network-parameters/index.tsx +++ b/apps/explorer/src/app/routes/network-parameters/index.tsx @@ -1,5 +1,5 @@ import { gql, useQuery } from '@apollo/client'; -import { NetworkParametersQuery } from './__generated__/NetworkParametersQuery'; +import { NetworkParametersQuery } from '@vegaprotocol/graphql'; export const NETWORK_PARAMETERS_QUERY = gql` query NetworkParametersQuery { diff --git a/apps/explorer/src/app/routes/parties/id/index.tsx b/apps/explorer/src/app/routes/parties/id/index.tsx index 6f17b82b0..dd883bdd4 100644 --- a/apps/explorer/src/app/routes/parties/id/index.tsx +++ b/apps/explorer/src/app/routes/parties/id/index.tsx @@ -8,7 +8,7 @@ import { TendermintSearchTransactionResponse } from '../tendermint-transaction-r import { PartyAssetsQuery, PartyAssetsQueryVariables, -} from './__generated__/PartyAssetsQuery'; +} from '@vegaprotocol/graphql'; const PARTY_ASSETS_QUERY = gql` query PartyAssetsQuery($partyId: ID!) { @@ -58,7 +58,7 @@ const Party = () => { { // Don't cache data for this query, party information can move quite quickly fetchPolicy: 'network-only', - variables: { partyId: party?.replace('0x', '') }, + variables: { partyId: party?.replace('0x', '') || '' }, skip: !party, } ); diff --git a/apps/explorer/src/app/routes/validators/index.tsx b/apps/explorer/src/app/routes/validators/index.tsx index ff76b1ed4..d27b85c47 100644 --- a/apps/explorer/src/app/routes/validators/index.tsx +++ b/apps/explorer/src/app/routes/validators/index.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { DATA_SOURCES } from '../../config'; import useFetch from '../../hooks/use-fetch'; import { TendermintValidatorsResponse } from './tendermint-validator-response'; -import { NodesQuery } from './__generated__/NodesQuery'; +import { NodesQuery } from '@vegaprotocol/graphql'; const NODES_QUERY = gql` query NodesQuery { @@ -26,8 +26,6 @@ const NODES_QUERY = gql` online } status - score - normalisedScore name } } diff --git a/apps/trading/.eslintrc.json b/apps/trading/.eslintrc.json index bc7243829..2548ff963 100644 --- a/apps/trading/.eslintrc.json +++ b/apps/trading/.eslintrc.json @@ -5,7 +5,7 @@ "next", "next/core-web-vitals" ], - "ignorePatterns": ["!**/*", "__generated__"], + "ignorePatterns": ["!**/*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], diff --git a/apps/trading/__generated__/globalTypes.ts b/apps/trading/__generated__/globalTypes.ts deleted file mode 100644 index 8d9b7dd7a..000000000 --- a/apps/trading/__generated__/globalTypes.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -//============================================================== -// START Enums and Input Objects -//============================================================== - -//============================================================== -// END Enums and Input Objects -//============================================================== diff --git a/apps/trading/apollo.config.js b/apps/trading/apollo.config.js deleted file mode 100644 index b71577567..000000000 --- a/apps/trading/apollo.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - client: { - service: { - name: 'vega', - url: process.env.NX_VEGA_URL, - }, - includes: ['{components,lib,pages}/**/*.{ts,tsx,js,jsx,graphql}'], - }, -}; diff --git a/apps/trading/components/deal-ticket-container/deal-ticket-container.tsx b/apps/trading/components/deal-ticket-container/deal-ticket-container.tsx new file mode 100644 index 000000000..2f83b19a7 --- /dev/null +++ b/apps/trading/components/deal-ticket-container/deal-ticket-container.tsx @@ -0,0 +1,74 @@ +import { Dialog, Intent } from '@vegaprotocol/ui-toolkit'; +import { DealTicket } from '@vegaprotocol/deal-ticket'; +import { OrderStatus } from '@vegaprotocol/graphql'; +import { useOrderSubmit } from '../../hooks/use-order-submit'; +import { useEffect, useState } from 'react'; +import { VegaTxStatus } from '../../hooks/use-vega-transaction'; +import { OrderDialog } from './order-dialog'; + +export const DealTicketContainer = ({ market }) => { + const [orderDialogOpen, setOrderDialogOpen] = useState(false); + const { submit, transaction, finalizedOrder, reset } = useOrderSubmit(market); + + const getDialogIntent = (status: VegaTxStatus) => { + if (finalizedOrder) { + if ( + finalizedOrder.status === OrderStatus.Active || + finalizedOrder.status === OrderStatus.Filled || + finalizedOrder.status === OrderStatus.PartiallyFilled + ) { + return Intent.Success; + } + + if (finalizedOrder.status === OrderStatus.Parked) { + return Intent.Warning; + } + + return Intent.Danger; + } + + if (status === VegaTxStatus.Rejected) { + return Intent.Danger; + } + + return Intent.Progress; + }; + + useEffect(() => { + if (transaction.status !== VegaTxStatus.Default) { + setOrderDialogOpen(true); + } + }, [transaction.status]); + + return ( + <> + + { + setOrderDialogOpen(isOpen); + + // If closing reset + if (!isOpen) { + reset(); + } + }} + intent={getDialogIntent(transaction.status)} + > + + + + ); +}; diff --git a/apps/trading/components/deal-ticket-container/index.ts b/apps/trading/components/deal-ticket-container/index.ts new file mode 100644 index 000000000..6d3e9ada5 --- /dev/null +++ b/apps/trading/components/deal-ticket-container/index.ts @@ -0,0 +1 @@ +export * from './deal-ticket-container'; diff --git a/apps/trading/components/deal-ticket-container/order-dialog.tsx b/apps/trading/components/deal-ticket-container/order-dialog.tsx new file mode 100644 index 000000000..09938a2b4 --- /dev/null +++ b/apps/trading/components/deal-ticket-container/order-dialog.tsx @@ -0,0 +1,95 @@ +import { Icon, Loader } from '@vegaprotocol/ui-toolkit'; +import { ReactNode } from 'react'; +import { + TransactionState, + VegaTxStatus, +} from '../../hooks/use-vega-transaction'; +import { OrderEvent_busEvents_event_Order } from '@vegaprotocol/graphql'; + +interface OrderDialogProps { + transaction: TransactionState; + finalizedOrder: OrderEvent_busEvents_event_Order | null; +} + +export const OrderDialog = ({ + transaction, + finalizedOrder, +}: OrderDialogProps) => { + // TODO: When wallets support confirming transactions return UI for 'awaiting confirmation' step + + // Rejected by wallet + if (transaction.status === VegaTxStatus.Rejected) { + return ( + } + > + {transaction.error && ( +
+            {JSON.stringify(transaction.error, null, 2)}
+          
+ )} +
+ ); + } + + // Pending consensus + if (!finalizedOrder) { + return ( + } + > + {transaction.hash && ( +

Tx hash: {transaction.hash}

+ )} +
+ ); + } + + // Order on network but was rejected + if (finalizedOrder.status === 'Rejected') { + return ( + } + > +

Reason: {finalizedOrder.rejectionReason}

+
+ ); + } + + return ( + } + > +

Status: {finalizedOrder.status}

+

Market: {finalizedOrder.market.name}

+

Amount: {finalizedOrder.size}

+ {finalizedOrder.type === 'Limit' &&

Price: {finalizedOrder.price}

} +
+ ); +}; + +interface OrderDialogWrapperProps { + children: ReactNode; + icon: ReactNode; + title: string; +} + +const OrderDialogWrapper = ({ + children, + icon, + title, +}: OrderDialogWrapperProps) => { + return ( +
+
{icon}
+
+

{title}

+ {children} +
+
+ ); +}; diff --git a/apps/trading/components/page-query-container/index.tsx b/apps/trading/components/page-query-container/index.tsx index f1e7bf9ae..455302743 100644 --- a/apps/trading/components/page-query-container/index.tsx +++ b/apps/trading/components/page-query-container/index.tsx @@ -1,7 +1,7 @@ import { OperationVariables, QueryHookOptions, useQuery } from '@apollo/client'; -import classNames from 'classnames'; import { DocumentNode } from 'graphql'; import { ReactNode } from 'react'; +import { Splash } from '@vegaprotocol/ui-toolkit'; interface PageQueryContainerProps { query: DocumentNode; @@ -15,19 +15,13 @@ export const PageQueryContainer = ({ children, }: PageQueryContainerProps) => { const { data, loading, error } = useQuery(query, options); - const splashClasses = classNames( - 'w-full h-full', - 'flex items-center justify-center' - ); if (loading || !data) { - return
Loading...
; + return Loading...; } if (error) { - return ( -
Something went wrong: {error.message}
- ); + return Something went wrong: {error.message}; } return <>{children(data)}; diff --git a/apps/trading/hooks/use-markets.ts b/apps/trading/hooks/use-markets.ts new file mode 100644 index 000000000..c2fd4e570 --- /dev/null +++ b/apps/trading/hooks/use-markets.ts @@ -0,0 +1,131 @@ +import { gql, useApolloClient } from '@apollo/client'; +import { singletonHook } from 'react-singleton-hook'; +import { + Markets, + Markets_markets, + MarketDataSub, + MarketDataSub_marketData, +} from '@vegaprotocol/graphql'; +import { useCallback, useEffect, useState } from 'react'; + +const MARKET_DATA_FRAGMENT = gql` + fragment MarketDataFields on MarketData { + market { + id + state + tradingMode + } + bestBidPrice + bestOfferPrice + markPrice + } +`; + +const MARKETS_QUERY = gql` + ${MARKET_DATA_FRAGMENT} + query Markets { + markets { + id + name + decimalPlaces + data { + ...MarketDataFields + } + tradableInstrument { + instrument { + code + product { + ... on Future { + settlementAsset { + symbol + } + } + } + } + } + } + } +`; + +const MARKET_DATA_SUB = gql` + ${MARKET_DATA_FRAGMENT} + subscription MarketDataSub { + marketData { + ...MarketDataFields + } + } +`; + +export const useMarketsImpl = () => { + const client = useApolloClient(); + const [markets, setMarkets] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const mergeMarketData = useCallback((update: MarketDataSub_marketData) => { + setMarkets((curr) => { + return curr.map((m) => { + if (update.market.id === m.id) { + return { + ...m, + data: update, + }; + } + + return m; + }); + }); + }, []); + + // Make initial fetch + useEffect(() => { + const fetchOrders = async () => { + setLoading(true); + + try { + const res = await client.query({ + query: MARKETS_QUERY, + }); + + if (!res.data.markets?.length) return; + + setMarkets(res.data.markets); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchOrders(); + }, [mergeMarketData, client]); + + // Start subscription + useEffect(() => { + const sub = client + // This data callback will unfortunately be called separately with an update for every market, + // perhaps we should batch this somehow... + .subscribe({ + query: MARKET_DATA_SUB, + }) + .subscribe(({ data }) => { + mergeMarketData(data.marketData); + }); + + return () => { + if (sub) { + sub.unsubscribe(); + } + }; + }, [client, mergeMarketData]); + + return { markets, error, loading }; +}; + +const initial = { + markets: [], + error: null, + loading: false, +}; + +export const useMarkets = singletonHook(initial, useMarketsImpl); diff --git a/apps/trading/hooks/use-order-submit.spec.tsx b/apps/trading/hooks/use-order-submit.spec.tsx new file mode 100644 index 000000000..2bc3e5979 --- /dev/null +++ b/apps/trading/hooks/use-order-submit.spec.tsx @@ -0,0 +1,153 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { Order } from '@vegaprotocol/deal-ticket'; +import { + VegaKeyExtended, + VegaWalletContext, + VegaWalletContextShape, +} from '@vegaprotocol/wallet'; +import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; +import { ReactNode } from 'react'; +import { useOrderSubmit } from './use-order-submit'; +import { VegaTxStatus } from './use-vega-transaction'; + +const defaultWalletContext = { + keypair: null, + keypairs: [], + sendTx: jest.fn().mockReturnValue(Promise.resolve(null)), + connect: jest.fn(), + disconnect: jest.fn(), + selectPublicKey: jest.fn(), + connector: null, +}; + +function setup( + context?: Partial, + market = { id: 'market-id', decimalPlaces: 2 } +) { + const wrapper = ({ children }: { children: ReactNode }) => ( + + + {children} + + + ); + return renderHook(() => useOrderSubmit(market), { wrapper }); +} + +test('Has the correct default state', () => { + const { result } = setup(); + expect(typeof result.current.submit).toEqual('function'); + expect(typeof result.current.reset).toEqual('function'); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Default); + expect(result.current.transaction.hash).toEqual(null); + expect(result.current.transaction.error).toEqual(null); +}); + +test('Should not sendTx if no keypair', async () => { + const mockSendTx = jest.fn(); + const { result } = setup({ sendTx: mockSendTx, keypairs: [], keypair: null }); + await act(async () => { + result.current.submit({} as Order); + }); + expect(mockSendTx).not.toHaveBeenCalled(); +}); + +test('Should not sendTx side is not specified', async () => { + const mockSendTx = jest.fn(); + const keypair = { + pub: '0x123', + } as VegaKeyExtended; + const { result } = setup({ + sendTx: mockSendTx, + keypairs: [keypair], + keypair, + }); + await act(async () => { + result.current.submit({} as Order); + }); + expect(mockSendTx).not.toHaveBeenCalled(); +}); + +test('Create an Id if a signature is returned', async () => { + const signature = + '597a7706491e6523c091bab1e4d655b62c45a224e80f6cd92ac366aa5dd9a070cc7dd3c6919cb07b81334b876c662dd43bdbe5e827c8baa17a089feb654fab0b'; + const expectedId = + '2FE09B0E2E6ED35F8883802629C7D609D3CC2FC9CE3CEC0B7824A0D581BD3747'; + const successObj = { + tx: { + inputData: 'input-data', + signature: { + algo: 'algo', + version: 1, + value: signature, + }, + }, + txHash: '0x123', + }; + const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj)); + const keypair = { + pub: '0x123', + } as VegaKeyExtended; + const { result } = setup({ + sendTx: mockSendTx, + keypairs: [keypair], + keypair, + }); + await act(async () => { + result.current.submit({ + type: OrderType.Market, + side: OrderSide.Buy, + size: '1', + timeInForce: OrderTimeInForce.FOK, + }); + }); + expect(result.current.id).toEqual(expectedId); +}); + +test('Should submit a correctly formatted order', async () => { + const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({})); + const keypair = { + pub: '0x123', + } as VegaKeyExtended; + const market = { + id: 'market-id', + decimalPlaces: 2, + }; + const { result } = setup( + { + sendTx: mockSendTx, + keypairs: [keypair], + keypair, + }, + market + ); + + const order = { + type: OrderType.Limit, + size: '10', + timeInForce: OrderTimeInForce.GTT, + side: OrderSide.Buy, + price: '1234567.89', + expiration: new Date('2022-01-01'), + }; + await act(async () => { + result.current.submit(order); + }); + + expect(mockSendTx).toHaveBeenCalledWith({ + pubKey: keypair.pub, + propagate: true, + orderSubmission: { + type: OrderType.Limit, + marketId: market.id, // Market provided from hook arugment + size: '10', + side: OrderSide.Buy, + timeInForce: OrderTimeInForce.GTT, + price: '123456789', // Decimal removed + expiresAt: order.expiration.getTime() + '000000', // Nanoseconds appened + }, + }); +}); diff --git a/apps/trading/hooks/use-order-submit.ts b/apps/trading/hooks/use-order-submit.ts new file mode 100644 index 000000000..c81a61062 --- /dev/null +++ b/apps/trading/hooks/use-order-submit.ts @@ -0,0 +1,157 @@ +import { useCallback, useEffect, useState } from 'react'; +import { gql, useSubscription } from '@apollo/client'; +import { ethers } from 'ethers'; +import { SHA3 } from 'sha3'; +import { Order } from '@vegaprotocol/deal-ticket'; +import { OrderType, useVegaWallet } from '@vegaprotocol/wallet'; +import { useVegaTransaction } from './use-vega-transaction'; +import { + OrderEvent, + OrderEventVariables, + OrderEvent_busEvents_event_Order, +} from '@vegaprotocol/graphql'; +import { removeDecimal } from '@vegaprotocol/react-helpers'; + +const ORDER_EVENT_SUB = gql` + subscription OrderEvent($partyId: ID!) { + busEvents(partyId: $partyId, batchSize: 0, types: [Order]) { + eventId + block + type + event { + ... on Order { + type + id + status + rejectionReason + createdAt + size + price + market { + name + } + } + } + } + } +`; + +interface UseOrderSubmitMarket { + id: string; + decimalPlaces: number; +} + +export const useOrderSubmit = (market: UseOrderSubmitMarket) => { + const { keypair } = useVegaWallet(); + const { send, transaction, reset: resetTransaction } = useVegaTransaction(); + const [id, setId] = useState(''); + const [finalizedOrder, setFinalizedOrder] = + useState(null); + + // Start a subscription looking for the newly created order + useSubscription(ORDER_EVENT_SUB, { + variables: { partyId: keypair?.pub || '' }, + skip: !id, + onSubscriptionData: ({ subscriptionData }) => { + if (!subscriptionData.data.busEvents.length) { + return; + } + + // No types available for the subscription result + const matchingOrderEvent = subscriptionData.data.busEvents.find((e) => { + if (e.event.__typename !== 'Order') { + return false; + } + + if (e.event.id === id) { + return true; + } + + return false; + }); + + if ( + matchingOrderEvent && + matchingOrderEvent.event.__typename === 'Order' + ) { + setFinalizedOrder(matchingOrderEvent.event); + } + }, + }); + + useEffect(() => { + if (finalizedOrder) { + resetTransaction(); + } + }, [finalizedOrder, resetTransaction]); + + const submit = useCallback( + async (order: Order) => { + if (!keypair || !order.side) { + return; + } + + setFinalizedOrder(null); + + const res = await send({ + pubKey: keypair.pub, + propagate: true, + orderSubmission: { + marketId: market.id, + price: + order.type === OrderType.Market + ? undefined + : removeDecimal(order.price, market.decimalPlaces), + size: order.size, + type: order.type, + side: order.side, + timeInForce: order.timeInForce, + expiresAt: order.expiration + ? // Wallet expects timestamp in nanoseconds, we don't have that level of accuracy so + // just append 6 zeroes + order.expiration.getTime().toString() + '000000' + : undefined, + }, + }); + + if (res?.signature) { + setId(determineId(res.signature).toUpperCase()); + } + }, + [market, keypair, send] + ); + + const reset = useCallback(() => { + resetTransaction(); + setFinalizedOrder(null); + setId(''); + }, [resetTransaction]); + + return { + transaction, + finalizedOrder, + id, + submit, + reset, + }; +}; + +/** + * This function creates an ID in the same way that core does on the backend. This way we + * Can match up the newly created order with incoming orders via a subscription + */ +export const determineId = (sig: string) => { + // Prepend 0x + if (sig.slice(0, 2) !== '0x') { + sig = '0x' + sig; + } + + // Create the ID + const hash = new SHA3(256); + const bytes = ethers.utils.arrayify(sig); + hash.update(Buffer.from(bytes)); + const id = ethers.utils.hexlify(hash.digest()); + + // Remove 0x as core doesn't keep them in the API + return id.substring(2); +}; diff --git a/apps/trading/hooks/use-vega-transaction.spec.tsx b/apps/trading/hooks/use-vega-transaction.spec.tsx new file mode 100644 index 000000000..5b7be1f4d --- /dev/null +++ b/apps/trading/hooks/use-vega-transaction.spec.tsx @@ -0,0 +1,98 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import { + OrderSubmission, + VegaWalletContext, + VegaWalletContextShape, +} from '@vegaprotocol/wallet'; +import { ReactNode } from 'react'; +import { useVegaTransaction, VegaTxStatus } from './use-vega-transaction'; + +const defaultWalletContext = { + keypair: null, + keypairs: [], + sendTx: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), + selectPublicKey: jest.fn(), + connector: null, +}; + +function setup(context?: Partial) { + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + return renderHook(() => useVegaTransaction(), { wrapper }); +} + +test('Has the correct default state', () => { + const { result } = setup(); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Default); + expect(result.current.transaction.hash).toEqual(null); + expect(result.current.transaction.signature).toEqual(null); + expect(result.current.transaction.error).toEqual(null); + expect(typeof result.current.reset).toEqual('function'); + expect(typeof result.current.send).toEqual('function'); +}); + +test('If provider returns null status should be default', async () => { + const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(null)); + const { result } = setup({ sendTx: mockSendTx }); + await act(async () => { + result.current.send({} as OrderSubmission); + }); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Default); +}); + +test('Handles a single error', async () => { + const errorMessage = 'Oops error!'; + const mockSendTx = jest + .fn() + .mockReturnValue(Promise.resolve({ error: errorMessage })); + const { result } = setup({ sendTx: mockSendTx }); + await act(async () => { + result.current.send({} as OrderSubmission); + }); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Rejected); + expect(result.current.transaction.error).toEqual({ error: errorMessage }); +}); + +test('Handles multiple errors', async () => { + const errorObj = { + errors: { + something: 'Went wrong!', + }, + }; + const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(errorObj)); + const { result } = setup({ sendTx: mockSendTx }); + await act(async () => { + result.current.send({} as OrderSubmission); + }); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Rejected); + expect(result.current.transaction.error).toEqual(errorObj); +}); + +test('Returns the signature if successful', async () => { + const successObj = { + tx: { + inputData: 'input-data', + signature: { + algo: 'algo', + version: 1, + value: 'signature', + }, + }, + txHash: '0x123', + }; + const mockSendTx = jest.fn().mockReturnValue(Promise.resolve(successObj)); + const { result } = setup({ sendTx: mockSendTx }); + await act(async () => { + result.current.send({} as OrderSubmission); + }); + expect(result.current.transaction.status).toEqual(VegaTxStatus.Pending); + expect(result.current.transaction.hash).toEqual(successObj.txHash); + expect(result.current.transaction.signature).toEqual( + successObj.tx.signature.value + ); +}); diff --git a/apps/trading/hooks/use-vega-transaction.ts b/apps/trading/hooks/use-vega-transaction.ts new file mode 100644 index 000000000..6765b8ad5 --- /dev/null +++ b/apps/trading/hooks/use-vega-transaction.ts @@ -0,0 +1,89 @@ +import { useCallback, useState } from 'react'; +import { useVegaWallet, SendTxError, Transaction } from '@vegaprotocol/wallet'; + +export enum VegaTxStatus { + Default = 'Default', + AwaitingConfirmation = 'AwaitingConfirmation', + Rejected = 'Rejected', + Pending = 'Pending', +} + +export interface TransactionState { + status: VegaTxStatus; + error: object | null; + hash: string | null; + signature: string | null; +} + +export const useVegaTransaction = () => { + const { sendTx } = useVegaWallet(); + const [transaction, _setTransaction] = useState({ + status: VegaTxStatus.Default, + error: null, + hash: null, + signature: null, + }); + + const setTransaction = useCallback((update: Partial) => { + _setTransaction((curr) => ({ + ...curr, + ...update, + })); + }, []); + + const handleError = useCallback( + (error: SendTxError) => { + setTransaction({ error, status: VegaTxStatus.Rejected }); + }, + [setTransaction] + ); + + const send = useCallback( + async (tx: Transaction) => { + setTransaction({ + error: null, + hash: null, + signature: null, + status: VegaTxStatus.AwaitingConfirmation, + }); + + const res = await sendTx(tx); + + if (res === null) { + setTransaction({ status: VegaTxStatus.Default }); + return null; + } + + if ('error' in res) { + handleError(res); + return null; + } else if ('errors' in res) { + handleError(res); + return null; + } else if (res.tx && res.txHash) { + setTransaction({ + status: VegaTxStatus.Pending, + hash: res.txHash, + signature: res.tx.signature.value, + }); + return { + signature: res.tx.signature?.value, + }; + } + + return null; + }, + [sendTx, handleError, setTransaction] + ); + + const reset = useCallback(() => { + setTransaction({ + error: null, + hash: null, + signature: null, + status: VegaTxStatus.Default, + }); + }, [setTransaction]); + + return { send, transaction, reset }; +}; diff --git a/apps/trading/lib/apollo-client.ts b/apps/trading/lib/apollo-client.ts index 2b2ed69bd..134799317 100644 --- a/apps/trading/lib/apollo-client.ts +++ b/apps/trading/lib/apollo-client.ts @@ -1,6 +1,15 @@ -import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client'; +import { + ApolloClient, + split, + from, + HttpLink, + InMemoryCache, +} from '@apollo/client'; +import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; +import { getMainDefinition } from '@apollo/client/utilities'; import { onError } from '@apollo/client/link/error'; import { RetryLink } from '@apollo/client/link/retry'; +import { createClient as createWSClient } from 'graphql-ws'; export function createClient(base?: string) { if (!base) { @@ -14,13 +23,18 @@ export function createClient(base?: string) { const cache = new InMemoryCache({ typePolicies: { - Query: {}, Account: { keyFields: false, fields: { balanceFormatted: {}, }, }, + Instrument: { + keyFields: false, + }, + MarketData: { + keyFields: ['market', ['id']], + }, Node: { keyFields: false, }, @@ -40,6 +54,28 @@ export function createClient(base?: string) { credentials: 'same-origin', }); + const wsLink = process.browser + ? new GraphQLWsLink( + createWSClient({ + url: urlWS.href, + }) + ) + : null; + + const splitLink = process.browser + ? split( + ({ query }) => { + const definition = getMainDefinition(query); + return ( + definition.kind === 'OperationDefinition' && + definition.operation === 'subscription' + ); + }, + wsLink, + httpLink + ) + : httpLink; + const errorLink = onError(({ graphQLErrors, networkError }) => { console.log(graphQLErrors); console.log(networkError); @@ -47,7 +83,7 @@ export function createClient(base?: string) { return new ApolloClient({ connectToDevTools: process.env['NODE_ENV'] === 'development', - link: from([errorLink, retryLink, httpLink]), + link: from([errorLink, retryLink, splitLink]), cache, }); } diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index d4086e7aa..b93d7c834 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -5,6 +5,7 @@ import { ThemeContext } from '@vegaprotocol/react-helpers'; import { VegaConnectDialog, VegaWalletProvider } from '@vegaprotocol/wallet'; import { Connectors } from '../lib/connectors'; import { useCallback, useMemo, useState } from 'react'; +import { SingletonHooksContainer } from 'react-singleton-hook'; import { createClient } from '../lib/apollo-client'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ApolloProvider } from '@apollo/client'; @@ -17,9 +18,7 @@ import './styles.css'; function VegaTradingApp({ Component, pageProps }: AppProps) { const client = useMemo( () => - createClient( - process.env['NX_VEGA_URL'] || 'https://n03.stagnet2.vega.xyz' - ), + createClient(process.env['NX_VEGA_URL'] || 'https://lb.testnet.vega.xyz'), [] ); const [dialogOpen, setDialogOpen] = useState(false); @@ -36,6 +35,7 @@ function VegaTradingApp({ Component, pageProps }: AppProps) { + Welcome to trading! diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx index 6b5bd7a2b..3377e03af 100644 --- a/apps/trading/pages/index.page.tsx +++ b/apps/trading/pages/index.page.tsx @@ -2,6 +2,7 @@ import { AgGridDynamic as AgGrid, Button, Callout, + Intent, } from '@vegaprotocol/ui-toolkit'; import { AgGridColumn } from 'ag-grid-react'; @@ -15,7 +16,7 @@ export function Index() {
{ marketId: Array.isArray(marketId) ? marketId[0] : marketId, }, skip: !marketId, + fetchPolicy: 'network-only', }} > - {({ market }) => - w > 1050 ? ( + {({ market }) => { + if (!market) { + return Market not found; + } + + return w > 960 ? ( ) : ( - ) - } + ); + }} ); }; diff --git a/apps/trading/pages/markets/__generated__/Market.ts b/apps/trading/pages/markets/__generated__/Market.ts deleted file mode 100644 index 58c8f008b..000000000 --- a/apps/trading/pages/markets/__generated__/Market.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: Market -// ==================================================== - -export interface Market_market_trades { - __typename: 'Trade'; - /** - * The hash of the trade data - */ - id: string; - /** - * The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64) - */ - price: string; - /** - * The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64) - */ - size: string; - /** - * RFC3339Nano time for when the trade occurred - */ - createdAt: string; -} - -export interface Market_market { - __typename: 'Market'; - /** - * Market ID - */ - id: string; - /** - * Market full name - */ - name: string; - /** - * Trades on a market - */ - trades: Market_market_trades[] | null; -} - -export interface Market { - /** - * An instrument that is trading on the VEGA network - */ - market: Market_market | null; -} - -export interface MarketVariables { - marketId: string; -} diff --git a/apps/trading/pages/markets/__generated__/Markets.ts b/apps/trading/pages/markets/__generated__/Markets.ts deleted file mode 100644 index 1b9efcdf5..000000000 --- a/apps/trading/pages/markets/__generated__/Markets.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @generated -// This file was automatically generated and should not be edited. - -// ==================================================== -// GraphQL query operation: Markets -// ==================================================== - -export interface Markets_markets { - __typename: "Market"; - /** - * Market ID - */ - id: string; -} - -export interface Markets { - /** - * One or more instruments that are trading on the VEGA network - */ - markets: Markets_markets[] | null; -} diff --git a/apps/trading/pages/markets/index.page.tsx b/apps/trading/pages/markets/index.page.tsx index 70e2c3ba8..a930be52c 100644 --- a/apps/trading/pages/markets/index.page.tsx +++ b/apps/trading/pages/markets/index.page.tsx @@ -1,40 +1,28 @@ -import { gql } from '@apollo/client'; -import { PageQueryContainer } from '../../components/page-query-container'; -import Link from 'next/link'; +import { Markets } from '@vegaprotocol/graphql'; import { useRouter } from 'next/router'; -import { Markets } from './__generated__/Markets'; - -const MARKETS_QUERY = gql` - query Markets { - markets { - id - } - } -`; +import { MarketListTable } from '@vegaprotocol/market-list'; +import { useMarkets } from '../../hooks/use-markets'; +import { Splash } from '@vegaprotocol/ui-toolkit'; const Markets = () => { - const { pathname } = useRouter(); + const { pathname, push } = useRouter(); + const { markets, error, loading } = useMarkets(); + + if (error) { + return Something went wrong: {error.message}; + } + + if (loading) { + return Loading...; + } return ( - query={MARKETS_QUERY}> - {(data) => ( - <> -

Markets

- - - )} - + + push(`${pathname}/${id}?portfolio=orders&trade=orderbook`) + } + /> ); }; diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index 2ae0e2538..b3b9b4c42 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -1,9 +1,26 @@ +import { Market_market } from '@vegaprotocol/graphql'; import classNames from 'classnames'; import AutoSizer from 'react-virtualized-auto-sizer'; import { useState, ReactNode } from 'react'; -import { TradingView, TradingViews } from '@vegaprotocol/react-helpers'; -import { Market_market } from './__generated__/Market'; import { GridTab, GridTabs } from './grid-tabs'; +import { DealTicketContainer } from '../../components/deal-ticket-container'; + +const Chart = () =>
TODO: Chart
; +const Orderbook = () =>
TODO: Orderbook
; +const Orders = () =>
TODO: Orders
; +const Positions = () =>
TODO: Positions
; +const Collateral = () =>
TODO: Collateral
; + +type TradingView = keyof typeof TradingViews; + +const TradingViews = { + chart: Chart, + ticket: DealTicketContainer, + orderbook: Orderbook, + orders: Orders, + positions: Positions, + collateral: Collateral, +}; interface TradeGridProps { market: Market_market; @@ -25,7 +42,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => { - + @@ -88,7 +105,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => { throw new Error(`No component for view: ${view}`); } - return ; + return ; }; return ( diff --git a/apps/trading/project.json b/apps/trading/project.json index fc87faf48..2e9f6071b 100644 --- a/apps/trading/project.json +++ b/apps/trading/project.json @@ -48,16 +48,6 @@ "options": { "lintFilePatterns": ["apps/trading/**/*.{ts,tsx,js,jsx}"] } - }, - "generate": { - "builder": "@nrwl/workspace:run-commands", - "options": { - "commands": [ - { - "command": "npx apollo client:codegen --config=apps/trading/apollo.config.js --target=typescript --globalTypesFile=apps/trading/__generated__/globalTypes.ts" - } - ] - } } }, "tags": [] diff --git a/apps/trading/tsconfig.json b/apps/trading/tsconfig.json index f7e818fd1..7f521fe09 100644 --- a/apps/trading/tsconfig.json +++ b/apps/trading/tsconfig.json @@ -14,5 +14,5 @@ "incremental": true }, "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], - "exclude": ["node_modules", "__generated__"] + "exclude": ["node_modules"] } diff --git a/libs/deal-ticket/.babelrc b/libs/deal-ticket/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/deal-ticket/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/deal-ticket/.eslintrc.json b/libs/deal-ticket/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/deal-ticket/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/deal-ticket/README.md b/libs/deal-ticket/README.md new file mode 100644 index 000000000..8e0882636 --- /dev/null +++ b/libs/deal-ticket/README.md @@ -0,0 +1,7 @@ +# deal-ticket + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test deal-ticket` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/deal-ticket/jest.config.js b/libs/deal-ticket/jest.config.js new file mode 100644 index 000000000..e194e5637 --- /dev/null +++ b/libs/deal-ticket/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'deal-ticket', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/deal-ticket', +}; diff --git a/libs/deal-ticket/package.json b/libs/deal-ticket/package.json new file mode 100644 index 000000000..b5b50d47e --- /dev/null +++ b/libs/deal-ticket/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/deal-ticket", + "version": "0.0.1" +} diff --git a/libs/deal-ticket/project.json b/libs/deal-ticket/project.json new file mode 100644 index 000000000..941520f1a --- /dev/null +++ b/libs/deal-ticket/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/deal-ticket", + "sourceRoot": "libs/deal-ticket/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/deal-ticket", + "tsConfig": "libs/deal-ticket/tsconfig.lib.json", + "project": "libs/deal-ticket/package.json", + "entryFile": "libs/deal-ticket/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/deal-ticket/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/deal-ticket/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/deal-ticket"], + "options": { + "jestConfig": "libs/deal-ticket/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/deal-ticket/src/button-radio.tsx b/libs/deal-ticket/src/button-radio.tsx new file mode 100644 index 000000000..a735e8265 --- /dev/null +++ b/libs/deal-ticket/src/button-radio.tsx @@ -0,0 +1,38 @@ +import { Button } from '@vegaprotocol/ui-toolkit'; + +interface ButtonRadioProps { + name: string; + options: Array<{ value: string; text: string }>; + currentOption: string | null; + onSelect: (option: string) => void; +} + +export const ButtonRadio = ({ + name, + options, + currentOption, + onSelect, +}: ButtonRadioProps) => { + return ( +
+ {options.map((option) => { + const isSelected = option.value === currentOption; + return ( + + ); + })} +
+ ); +}; diff --git a/libs/deal-ticket/src/deal-ticket-limit.tsx b/libs/deal-ticket/src/deal-ticket-limit.tsx new file mode 100644 index 000000000..b327ccacd --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket-limit.tsx @@ -0,0 +1,78 @@ +import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; +import { OrderTimeInForce } from '@vegaprotocol/wallet'; +import { TransactionStatus } from './deal-ticket'; +import { Market_market } from '@vegaprotocol/graphql'; +import { ExpirySelector } from './expiry-selector'; +import { SideSelector } from './side-selector'; +import { SubmitButton } from './submit-button'; +import { TimeInForceSelector } from './time-in-force-selector'; +import { TypeSelector } from './type-selector'; +import { Order } from './use-order-state'; + +interface DealTicketLimitProps { + order: Order; + updateOrder: (order: Partial) => void; + transactionStatus: TransactionStatus; + market: Market_market; +} + +export const DealTicketLimit = ({ + order, + updateOrder, + transactionStatus, + market, +}: DealTicketLimitProps) => { + return ( + <> + updateOrder({ type })} /> + updateOrder({ side })} /> +
+
+ + updateOrder({ size: e.target.value })} + className="w-full" + type="number" + data-testid="order-size" + /> + +
+
@
+
+ + updateOrder({ price: e.target.value })} + className="w-full" + type="number" + data-testid="order-price" + /> + +
+
+ updateOrder({ timeInForce })} + /> + {order.timeInForce === OrderTimeInForce.GTT && ( + { + if (date) { + updateOrder({ expiration: date }); + } + }} + /> + )} + + + ); +}; diff --git a/libs/deal-ticket/src/deal-ticket-market.tsx b/libs/deal-ticket/src/deal-ticket-market.tsx new file mode 100644 index 000000000..3d46e4770 --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket-market.tsx @@ -0,0 +1,63 @@ +import { addDecimal } from '@vegaprotocol/react-helpers'; +import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; +import { Market_market } from '@vegaprotocol/graphql'; +import { TransactionStatus } from './deal-ticket'; +import { SideSelector } from './side-selector'; +import { SubmitButton } from './submit-button'; +import { TimeInForceSelector } from './time-in-force-selector'; +import { TypeSelector } from './type-selector'; +import { Order } from './use-order-state'; + +interface DealTicketMarketProps { + order: Order; + updateOrder: (order: Partial) => void; + transactionStatus: TransactionStatus; + market: Market_market; +} + +export const DealTicketMarket = ({ + order, + updateOrder, + transactionStatus, + market, +}: DealTicketMarketProps) => { + return ( + <> + updateOrder({ type })} /> + updateOrder({ side })} /> +
+
+ + updateOrder({ size: e.target.value })} + className="w-full" + type="number" + data-testid="order-size" + /> + +
+
@
+
+ {market.depth.lastTrade ? ( + <> + ~{addDecimal(market.depth.lastTrade.price, market.decimalPlaces)}{' '} + {market.tradableInstrument.instrument.product.quoteName} + + ) : ( + '-' + )} +
+
+ updateOrder({ timeInForce })} + /> + + + ); +}; diff --git a/libs/deal-ticket/src/deal-ticket.spec.tsx b/libs/deal-ticket/src/deal-ticket.spec.tsx new file mode 100644 index 000000000..d259afe5b --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket.spec.tsx @@ -0,0 +1,141 @@ +import '@testing-library/jest-dom'; +import { + VegaWalletContext, + OrderTimeInForce, + OrderType, +} from '@vegaprotocol/wallet'; +import { addDecimal } from '@vegaprotocol/react-helpers'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { DealTicket, Market } from './deal-ticket'; +import { Order } from './use-order-state'; + +const order: Order = { + type: OrderType.Market, + size: '100', + timeInForce: OrderTimeInForce.FOK, + side: null, +}; +const market: Market = { + id: 'market-id', + decimalPlaces: 2, + tradingMode: 'Continuous', + state: 'Active', + tradableInstrument: { + instrument: { + product: { + quoteName: 'quote-name', + settlementAsset: { + id: 'asset-id', + symbol: 'asset-symbol', + name: 'asset-name', + }, + }, + }, + }, + depth: { + lastTrade: { + price: '100', + }, + }, +}; + +function generateJsx() { + return ( + + + + ); +} + +test('Deal ticket defaults', () => { + render(generateJsx()); + + // Assert defaults are used + expect( + screen.getByTestId(`order-type-${order.type}-selected`) + ).toBeInTheDocument(); + expect( + screen.queryByTestId('order-side-SIDE_BUY-selected') + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId('order-side-SIDE_SELL-selected') + ).not.toBeInTheDocument(); + expect(screen.getByTestId('order-size')).toHaveDisplayValue(order.size); + expect(screen.getByTestId('order-tif')).toHaveValue(order.timeInForce); + + // Assert last price is shown + expect(screen.getByTestId('last-price')).toHaveTextContent( + // eslint-disable-next-line + `~${addDecimal(market.depth.lastTrade!.price, market.decimalPlaces)} ${ + market.tradableInstrument.instrument.product.quoteName + }` + ); +}); + +test('Can edit deal ticket', () => { + render(generateJsx()); + + // Asssert changing values + fireEvent.click(screen.getByTestId('order-side-SIDE_BUY')); + expect( + screen.getByTestId('order-side-SIDE_BUY-selected') + ).toBeInTheDocument(); + + fireEvent.change(screen.getByTestId('order-size'), { + target: { value: '200' }, + }); + expect(screen.getByTestId('order-size')).toHaveDisplayValue('200'); + + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: OrderTimeInForce.IOC }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC); + + // Switch to limit order + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + + // Assert price input shown with default value + expect(screen.getByTestId('order-price')).toHaveDisplayValue('0'); + + // Check all TIF options shown + expect(screen.getByTestId('order-tif').children).toHaveLength( + Object.keys(OrderTimeInForce).length + ); +}); + +test('Handles TIF select box dependent on order type', () => { + render(generateJsx()); + + // Check only IOC and + expect( + Array.from(screen.getByTestId('order-tif').children).map( + (o) => o.textContent + ) + ).toEqual(['IOC', 'FOK']); + + // Switch to limit order and check all TIF options shown + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + expect(screen.getByTestId('order-tif').children).toHaveLength( + Object.keys(OrderTimeInForce).length + ); + + // Change to GTC + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: OrderTimeInForce.GTC }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.GTC); + + // Switch back to market order and TIF should now be IOC + fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET')); + expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC); + + // Switch tif to FOK + fireEvent.change(screen.getByTestId('order-tif'), { + target: { value: OrderTimeInForce.FOK }, + }); + expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.FOK); + + // Change back to limit and check we are still on FOK + fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT')); + expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.FOK); +}); diff --git a/libs/deal-ticket/src/deal-ticket.tsx b/libs/deal-ticket/src/deal-ticket.tsx new file mode 100644 index 000000000..a49dbf958 --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket.tsx @@ -0,0 +1,69 @@ +import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; +import { Market_market } from '@vegaprotocol/graphql'; +import { FormEvent } from 'react'; +import { Order, useOrderState } from './use-order-state'; +import { DealTicketMarket } from './deal-ticket-market'; +import { DealTicketLimit } from './deal-ticket-limit'; + +const DEFAULT_ORDER: Order = { + type: OrderType.Market, + side: OrderSide.Buy, + size: '1', + timeInForce: OrderTimeInForce.IOC, +}; + +// TODO: Consider using a generated type when we have a better solution for +// sharing the types from GQL + +export type TransactionStatus = 'default' | 'pending'; + +export interface DealTicketProps { + market: Market_market; + submit: (order: Order) => void; + transactionStatus: TransactionStatus; + defaultOrder?: Order; +} + +export const DealTicket = ({ + market, + submit, + transactionStatus, + defaultOrder = DEFAULT_ORDER, +}: DealTicketProps) => { + const [order, updateOrder] = useOrderState(defaultOrder); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + submit(order); + }; + + let ticket = null; + + if (order.type === OrderType.Market) { + ticket = ( + + ); + } else if (order.type === OrderType.Limit) { + ticket = ( + + ); + } else { + throw new Error('Invalid ticket type'); + } + + return ( +
+ {ticket} +
+ ); +}; diff --git a/libs/deal-ticket/src/expiry-selector.tsx b/libs/deal-ticket/src/expiry-selector.tsx new file mode 100644 index 000000000..01f01243d --- /dev/null +++ b/libs/deal-ticket/src/expiry-selector.tsx @@ -0,0 +1,26 @@ +import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; +import { Order } from './use-order-state'; +import { formatForInput } from '@vegaprotocol/react-helpers'; + +interface ExpirySelectorProps { + order: Order; + onSelect: (expiration: Date | null) => void; +} + +export const ExpirySelector = ({ order, onSelect }: ExpirySelectorProps) => { + const date = order.expiration ? new Date(order.expiration) : new Date(); + const dateFormatted = formatForInput(date); + const minDate = formatForInput(date); + return ( + + onSelect(new Date(e.target.value))} + min={minDate} + /> + + ); +}; diff --git a/libs/deal-ticket/src/index.ts b/libs/deal-ticket/src/index.ts new file mode 100644 index 000000000..22bedf9bd --- /dev/null +++ b/libs/deal-ticket/src/index.ts @@ -0,0 +1,2 @@ +export * from './deal-ticket'; +export * from './use-order-state'; diff --git a/libs/deal-ticket/src/side-selector.tsx b/libs/deal-ticket/src/side-selector.tsx new file mode 100644 index 000000000..029ea318a --- /dev/null +++ b/libs/deal-ticket/src/side-selector.tsx @@ -0,0 +1,25 @@ +import { FormGroup } from '@vegaprotocol/ui-toolkit'; +import { OrderSide } from '@vegaprotocol/wallet'; +import { ButtonRadio } from './button-radio'; +import { Order } from './use-order-state'; + +interface SideSelectorProps { + order: Order; + onSelect: (side: OrderSide) => void; +} + +export const SideSelector = ({ order, onSelect }: SideSelectorProps) => { + return ( + + ({ + text, + value, + }))} + currentOption={order.side} + onSelect={(value) => onSelect(value as OrderSide)} + /> + + ); +}; diff --git a/libs/deal-ticket/src/submit-button.tsx b/libs/deal-ticket/src/submit-button.tsx new file mode 100644 index 000000000..610c78633 --- /dev/null +++ b/libs/deal-ticket/src/submit-button.tsx @@ -0,0 +1,78 @@ +import { Button, InputError } from '@vegaprotocol/ui-toolkit'; +import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; +import { Market_market } from '@vegaprotocol/graphql'; +import { useMemo } from 'react'; +import { Order } from './use-order-state'; +import { useVegaWallet } from '@vegaprotocol/wallet'; +import { TransactionStatus } from './deal-ticket'; + +interface SubmitButtonProps { + transactionStatus: TransactionStatus; + market: Market_market; + order: Order; +} + +export const SubmitButton = ({ + market, + transactionStatus, + order, +}: SubmitButtonProps) => { + const { keypair } = useVegaWallet(); + + const invalidText = useMemo(() => { + if (!keypair) { + return 'No public key selected'; + } + + if (keypair.tainted) { + return 'Selected public key has been tainted'; + } + + // TODO: Change these to use enums from @vegaprotocol/graphql + if (market.state !== 'Active') { + if (market.state === 'Suspended') { + return 'Market is currently suspended'; + } + + if (market.state === 'Proposed' || market.state === 'Pending') { + return 'Market is not active yet'; + } + + return 'Market is no longer active'; + } + + if (market.tradingMode !== 'Continuous') { + if (order.type === OrderType.Market) { + return 'Only limit orders are permitted when market is in auction'; + } + + if ( + [ + OrderTimeInForce.FOK, + OrderTimeInForce.IOC, + OrderTimeInForce.GFN, + ].includes(order.timeInForce) + ) { + return 'Only GTT, GTC and GFA are permitted when market is in auction'; + } + } + + return ''; + }, [keypair, market, order]); + + const disabled = transactionStatus === 'pending' || Boolean(invalidText); + + return ( + <> + + {invalidText && {invalidText}} + + ); +}; diff --git a/libs/deal-ticket/src/time-in-force-selector.tsx b/libs/deal-ticket/src/time-in-force-selector.tsx new file mode 100644 index 000000000..6c793ea46 --- /dev/null +++ b/libs/deal-ticket/src/time-in-force-selector.tsx @@ -0,0 +1,40 @@ +import { FormGroup, Select } from '@vegaprotocol/ui-toolkit'; +import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; +import { Order } from './use-order-state'; + +interface TimeInForceSelectorProps { + order: Order; + onSelect: (tif: OrderTimeInForce) => void; +} + +export const TimeInForceSelector = ({ + order, + onSelect, +}: TimeInForceSelectorProps) => { + const options = + order.type === OrderType.Limit + ? Object.entries(OrderTimeInForce) + : Object.entries(OrderTimeInForce).filter( + ([key, value]) => + value === OrderTimeInForce.FOK || value === OrderTimeInForce.IOC + ); + + return ( + + + + ); +}; diff --git a/libs/deal-ticket/src/type-selector.tsx b/libs/deal-ticket/src/type-selector.tsx new file mode 100644 index 000000000..b3621843a --- /dev/null +++ b/libs/deal-ticket/src/type-selector.tsx @@ -0,0 +1,25 @@ +import { FormGroup } from '@vegaprotocol/ui-toolkit'; +import { OrderType } from '@vegaprotocol/wallet'; +import { ButtonRadio } from './button-radio'; +import { Order } from './use-order-state'; + +interface TypeSelectorProps { + order: Order; + onSelect: (type: OrderType) => void; +} + +export const TypeSelector = ({ order, onSelect }: TypeSelectorProps) => { + return ( + + ({ + text, + value, + }))} + currentOption={order.type} + onSelect={(value) => onSelect(value as OrderType)} + /> + + ); +}; diff --git a/libs/deal-ticket/src/use-order-state.ts b/libs/deal-ticket/src/use-order-state.ts new file mode 100644 index 000000000..42545ce01 --- /dev/null +++ b/libs/deal-ticket/src/use-order-state.ts @@ -0,0 +1,78 @@ +import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet'; +import { useState, useCallback } from 'react'; + +export interface Order { + size: string; + type: OrderType; + timeInForce: OrderTimeInForce; + side: OrderSide | null; + price?: string; + expiration?: Date; +} + +export type UpdateOrder = (order: Partial) => void; + +export const useOrderState = (defaultOrder: Order): [Order, UpdateOrder] => { + const [order, setOrder] = useState(defaultOrder); + + const updateOrder = useCallback((orderUpdate: Partial) => { + setOrder((curr) => { + // Type is switching to market so return new market order object with correct defaults + if ( + orderUpdate.type === OrderType.Market && + curr.type !== OrderType.Market + ) { + // Check if provided TIF or current TIF is valid for a market order and default + // to IOC if its not + + const isTifValid = (tif: OrderTimeInForce) => { + return tif === OrderTimeInForce.FOK || tif === OrderTimeInForce.IOC; + }; + + // Default + let timeInForce = OrderTimeInForce.IOC; + + if (orderUpdate.timeInForce) { + if (isTifValid(orderUpdate.timeInForce)) { + timeInForce = orderUpdate.timeInForce; + } + } else { + if (isTifValid(curr.timeInForce)) { + timeInForce = curr.timeInForce; + } + } + + return { + type: orderUpdate.type, + size: orderUpdate.size || curr.size, + side: orderUpdate.side || curr.side, + timeInForce, + price: undefined, + expiration: undefined, + }; + } + + // Type is switching to limit so return new order object with correct defaults + if ( + orderUpdate.type === OrderType.Limit && + curr.type !== OrderType.Limit + ) { + return { + type: orderUpdate.type, + size: orderUpdate.size || curr.size, + side: orderUpdate.side || curr.side, + timeInForce: orderUpdate.timeInForce || curr.timeInForce, + price: orderUpdate.price || '0', + expiration: orderUpdate.expiration || undefined, + }; + } + + return { + ...curr, + ...orderUpdate, + }; + }); + }, []); + + return [order, updateOrder]; +}; diff --git a/libs/deal-ticket/tsconfig.json b/libs/deal-ticket/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/deal-ticket/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/deal-ticket/tsconfig.lib.json b/libs/deal-ticket/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/deal-ticket/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/deal-ticket/tsconfig.spec.json b/libs/deal-ticket/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/deal-ticket/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/graphql/.babelrc b/libs/graphql/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/graphql/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/graphql/.eslintrc.json b/libs/graphql/.eslintrc.json new file mode 100644 index 000000000..db820c5d0 --- /dev/null +++ b/libs/graphql/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "__generated__"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/graphql/README.md b/libs/graphql/README.md new file mode 100644 index 000000000..6342967d9 --- /dev/null +++ b/libs/graphql/README.md @@ -0,0 +1,7 @@ +# graphql + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test graphql` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/apps/explorer/apollo.config.js b/libs/graphql/apollo.config.js similarity index 64% rename from apps/explorer/apollo.config.js rename to libs/graphql/apollo.config.js index 97e1c2c59..0a0c2499e 100644 --- a/apps/explorer/apollo.config.js +++ b/libs/graphql/apollo.config.js @@ -4,5 +4,6 @@ module.exports = { name: 'vega', url: process.env.NX_VEGA_URL, }, + includes: ['../../{apps,lib}/**/*.{ts,tsx,js,jsx,graphql}'], }, }; diff --git a/libs/graphql/jest.config.js b/libs/graphql/jest.config.js new file mode 100644 index 000000000..51bb43cdc --- /dev/null +++ b/libs/graphql/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'graphql', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/graphql', +}; diff --git a/libs/graphql/package.json b/libs/graphql/package.json new file mode 100644 index 000000000..d2fdb2ce1 --- /dev/null +++ b/libs/graphql/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/graphql", + "version": "0.0.1" +} diff --git a/libs/graphql/project.json b/libs/graphql/project.json new file mode 100644 index 000000000..071816ba2 --- /dev/null +++ b/libs/graphql/project.json @@ -0,0 +1,53 @@ +{ + "root": "libs/graphql", + "sourceRoot": "libs/graphql/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/graphql", + "tsConfig": "libs/graphql/tsconfig.lib.json", + "project": "libs/graphql/package.json", + "entryFile": "libs/graphql/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/graphql/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/graphql/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/graphql"], + "options": { + "jestConfig": "libs/graphql/jest.config.js", + "passWithNoTests": true + } + }, + "generate": { + "builder": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + { + "command": "npx apollo client:codegen libs/graphql/src/__generated__ --config=libs/graphql/apollo.config.js --target=typescript --globalTypesFile=libs/graphql/src/__generated__/globalTypes.ts --outputFlat" + } + ] + } + } + } +} diff --git a/apps/explorer/src/app/routes/assets/__generated__/AssetsQuery.ts b/libs/graphql/src/__generated__/AssetsQuery.ts similarity index 81% rename from apps/explorer/src/app/routes/assets/__generated__/AssetsQuery.ts rename to libs/graphql/src/__generated__/AssetsQuery.ts index ae0f80b04..ea9b6f249 100644 --- a/apps/explorer/src/app/routes/assets/__generated__/AssetsQuery.ts +++ b/libs/graphql/src/__generated__/AssetsQuery.ts @@ -3,14 +3,14 @@ // @generated // This file was automatically generated and should not be edited. -import { AccountType } from './../../../../__generated__/globalTypes'; +import { AccountType } from "./globalTypes"; // ==================================================== // GraphQL query operation: AssetsQuery // ==================================================== export interface AssetsQuery_assets_source_ERC20 { - __typename: 'ERC20'; + __typename: "ERC20"; /** * The address of the erc20 contract */ @@ -18,19 +18,17 @@ export interface AssetsQuery_assets_source_ERC20 { } export interface AssetsQuery_assets_source_BuiltinAsset { - __typename: 'BuiltinAsset'; + __typename: "BuiltinAsset"; /** * Maximum amount that can be requested by a party through the built-in asset faucet at a time */ maxFaucetAmountMint: string; } -export type AssetsQuery_assets_source = - | AssetsQuery_assets_source_ERC20 - | AssetsQuery_assets_source_BuiltinAsset; +export type AssetsQuery_assets_source = AssetsQuery_assets_source_ERC20 | AssetsQuery_assets_source_BuiltinAsset; export interface AssetsQuery_assets_infrastructureFeeAccount_market { - __typename: 'Market'; + __typename: "Market"; /** * Market ID */ @@ -38,7 +36,7 @@ export interface AssetsQuery_assets_infrastructureFeeAccount_market { } export interface AssetsQuery_assets_infrastructureFeeAccount { - __typename: 'Account'; + __typename: "Account"; /** * Account type (General, Margin, etc) */ @@ -54,7 +52,7 @@ export interface AssetsQuery_assets_infrastructureFeeAccount { } export interface AssetsQuery_assets { - __typename: 'Asset'; + __typename: "Asset"; /** * The id of the asset */ @@ -75,10 +73,6 @@ export interface AssetsQuery_assets { * The precision of the asset */ decimals: number; - /** - * The min stake to become an lp for any market using this asset for settlement - */ - minLpStake: string; /** * The origin source of the asset (e.g: an erc20 asset) */ diff --git a/apps/explorer/src/app/components/search/__generated__/Guess.ts b/libs/graphql/src/__generated__/Guess.ts similarity index 93% rename from apps/explorer/src/app/components/search/__generated__/Guess.ts rename to libs/graphql/src/__generated__/Guess.ts index 3f51b12d8..7e096adf3 100644 --- a/apps/explorer/src/app/components/search/__generated__/Guess.ts +++ b/libs/graphql/src/__generated__/Guess.ts @@ -8,7 +8,7 @@ // ==================================================== export interface Guess_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ @@ -16,7 +16,7 @@ export interface Guess_party { } export interface Guess_market { - __typename: 'Market'; + __typename: "Market"; /** * Market ID */ diff --git a/libs/graphql/src/__generated__/Market.ts b/libs/graphql/src/__generated__/Market.ts new file mode 100644 index 000000000..61cf341ce --- /dev/null +++ b/libs/graphql/src/__generated__/Market.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "./globalTypes"; + +// ==================================================== +// GraphQL query operation: Market +// ==================================================== + +export interface Market_market_tradableInstrument_instrument_product_settlementAsset { + __typename: "Asset"; + /** + * The id of the asset + */ + id: string; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; + /** + * The full name of the asset (e.g: Great British Pound) + */ + name: string; +} + +export interface Market_market_tradableInstrument_instrument_product { + __typename: "Future"; + /** + * String representing the quote (e.g. BTCUSD -> USD is quote) + */ + quoteName: string; + /** + * The name of the asset (string) + */ + settlementAsset: Market_market_tradableInstrument_instrument_product_settlementAsset; +} + +export interface Market_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) + */ + product: Market_market_tradableInstrument_instrument_product; +} + +export interface Market_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: Market_market_tradableInstrument_instrument; +} + +export interface Market_market_trades { + __typename: "Trade"; + /** + * The hash of the trade data + */ + id: string; + /** + * The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64) + */ + price: string; + /** + * The number of contracts trades, will always be <= the remaining size of both orders immediately before the trade (uint64) + */ + size: string; + /** + * RFC3339Nano time for when the trade occurred + */ + createdAt: string; +} + +export interface Market_market_depth_lastTrade { + __typename: "Trade"; + /** + * The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64) + */ + price: string; +} + +export interface Market_market_depth { + __typename: "MarketDepth"; + /** + * Last trade for the given market (if available) + */ + lastTrade: Market_market_depth_lastTrade | null; +} + +export interface Market_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: Market_market_tradableInstrument; + /** + * Trades on a market + */ + trades: Market_market_trades[] | null; + /** + * Current depth on the order book for this market + */ + depth: Market_market_depth; +} + +export interface Market { + /** + * An instrument that is trading on the VEGA network + */ + market: Market_market | null; +} + +export interface MarketVariables { + marketId: string; +} diff --git a/libs/graphql/src/__generated__/MarketDataFields.ts b/libs/graphql/src/__generated__/MarketDataFields.ts new file mode 100644 index 000000000..eb9675cbe --- /dev/null +++ b/libs/graphql/src/__generated__/MarketDataFields.ts @@ -0,0 +1,46 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "./globalTypes"; + +// ==================================================== +// GraphQL fragment: MarketDataFields +// ==================================================== + +export interface MarketDataFields_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; +} + +export interface MarketDataFields { + __typename: "MarketData"; + /** + * market id of the associated mark price + */ + market: MarketDataFields_market; + /** + * the highest price level on an order book for buy orders. + */ + bestBidPrice: string; + /** + * the lowest price level on an order book for offer orders. + */ + bestOfferPrice: string; + /** + * the mark price (actually an unsigned int) + */ + markPrice: string; +} diff --git a/libs/graphql/src/__generated__/MarketDataSub.ts b/libs/graphql/src/__generated__/MarketDataSub.ts new file mode 100644 index 000000000..9e88d087f --- /dev/null +++ b/libs/graphql/src/__generated__/MarketDataSub.ts @@ -0,0 +1,53 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "./globalTypes"; + +// ==================================================== +// GraphQL subscription operation: MarketDataSub +// ==================================================== + +export interface MarketDataSub_marketData_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; +} + +export interface MarketDataSub_marketData { + __typename: "MarketData"; + /** + * market id of the associated mark price + */ + market: MarketDataSub_marketData_market; + /** + * the highest price level on an order book for buy orders. + */ + bestBidPrice: string; + /** + * the lowest price level on an order book for offer orders. + */ + bestOfferPrice: string; + /** + * the mark price (actually an unsigned int) + */ + markPrice: string; +} + +export interface MarketDataSub { + /** + * Subscribe to the mark price changes + */ + marketData: MarketDataSub_marketData; +} diff --git a/libs/graphql/src/__generated__/Markets.ts b/libs/graphql/src/__generated__/Markets.ts new file mode 100644 index 000000000..e7b0cd958 --- /dev/null +++ b/libs/graphql/src/__generated__/Markets.ts @@ -0,0 +1,126 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketState, MarketTradingMode } from "./globalTypes"; + +// ==================================================== +// GraphQL query operation: Markets +// ==================================================== + +export interface Markets_markets_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Current state of the market + */ + state: MarketState; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; +} + +export interface Markets_markets_data { + __typename: "MarketData"; + /** + * market id of the associated mark price + */ + market: Markets_markets_data_market; + /** + * the highest price level on an order book for buy orders. + */ + bestBidPrice: string; + /** + * the lowest price level on an order book for offer orders. + */ + bestOfferPrice: string; + /** + * the mark price (actually an unsigned int) + */ + markPrice: string; +} + +export interface Markets_markets_tradableInstrument_instrument_product_settlementAsset { + __typename: "Asset"; + /** + * The symbol of the asset (e.g: GBP) + */ + symbol: string; +} + +export interface Markets_markets_tradableInstrument_instrument_product { + __typename: "Future"; + /** + * The name of the asset (string) + */ + settlementAsset: Markets_markets_tradableInstrument_instrument_product_settlementAsset; +} + +export interface Markets_markets_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; + /** + * A reference to or instance of a fully specified product, including all required product parameters for that product (Product union) + */ + product: Markets_markets_tradableInstrument_instrument_product; +} + +export interface Markets_markets_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: Markets_markets_tradableInstrument_instrument; +} + +export interface Markets_markets { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * marketData for the given market + */ + data: Markets_markets_data | null; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: Markets_markets_tradableInstrument; +} + +export interface Markets { + /** + * One or more instruments that are trading on the VEGA network + */ + markets: Markets_markets[] | null; +} diff --git a/apps/explorer/src/app/routes/markets/__generated__/MarketsQuery.ts b/libs/graphql/src/__generated__/MarketsQuery.ts similarity index 87% rename from apps/explorer/src/app/routes/markets/__generated__/MarketsQuery.ts rename to libs/graphql/src/__generated__/MarketsQuery.ts index 49f849c78..18707af7a 100644 --- a/apps/explorer/src/app/routes/markets/__generated__/MarketsQuery.ts +++ b/libs/graphql/src/__generated__/MarketsQuery.ts @@ -3,19 +3,14 @@ // @generated // This file was automatically generated and should not be edited. -import { - MarketTradingMode, - MarketState, - AccountType, - AuctionTrigger, -} from './../../../../__generated__/globalTypes'; +import { MarketTradingMode, MarketState, AccountType, AuctionTrigger } from "./globalTypes"; // ==================================================== // GraphQL query operation: MarketsQuery // ==================================================== export interface MarketsQuery_markets_fees_factors { - __typename: 'FeeFactors'; + __typename: "FeeFactors"; /** * The factor applied to calculate MakerFees, a non-negative float */ @@ -31,7 +26,7 @@ export interface MarketsQuery_markets_fees_factors { } export interface MarketsQuery_markets_fees { - __typename: 'Fees'; + __typename: "Fees"; /** * The factors used to calculate the different fees */ @@ -39,7 +34,7 @@ export interface MarketsQuery_markets_fees { } export interface MarketsQuery_markets_tradableInstrument_instrument_metadata { - __typename: 'InstrumentMetadata'; + __typename: "InstrumentMetadata"; /** * An arbitrary list of tags to associated to associate to the Instrument (string list) */ @@ -47,7 +42,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_metadata { } export interface MarketsQuery_markets_tradableInstrument_instrument_product_settlementAsset_globalRewardPoolAccount { - __typename: 'Account'; + __typename: "Account"; /** * Balance as string - current account balance (approx. as balances can be updated several times per second) */ @@ -55,7 +50,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_product_sett } export interface MarketsQuery_markets_tradableInstrument_instrument_product_settlementAsset { - __typename: 'Asset'; + __typename: "Asset"; /** * The id of the asset */ @@ -79,11 +74,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_product_sett } export interface MarketsQuery_markets_tradableInstrument_instrument_product { - __typename: 'Future'; - /** - * RFC3339Nano maturity date of the product - */ - maturity: string; + __typename: "Future"; /** * The name of the asset (string) */ @@ -91,7 +82,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_product { } export interface MarketsQuery_markets_tradableInstrument_instrument { - __typename: 'Instrument'; + __typename: "Instrument"; /** * Full and fairly descriptive name for the instrument */ @@ -101,7 +92,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument { */ metadata: MarketsQuery_markets_tradableInstrument_instrument_metadata; /** - * Uniquely identify an instrument accrods all instruments available on Vega (string) + * Uniquely identify an instrument across all instruments available on Vega (string) */ id: string; /** @@ -115,7 +106,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument { } export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel_params { - __typename: 'LogNormalModelParams'; + __typename: "LogNormalModelParams"; /** * r parameter */ @@ -131,7 +122,7 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRisk } export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel { - __typename: 'LogNormalRiskModel'; + __typename: "LogNormalRiskModel"; /** * Tau parameter of the risk model */ @@ -147,7 +138,7 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRisk } export interface MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskModel_params { - __typename: 'SimpleRiskModelParams'; + __typename: "SimpleRiskModelParams"; /** * Risk factor for long */ @@ -159,19 +150,17 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskMod } export interface MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskModel { - __typename: 'SimpleRiskModel'; + __typename: "SimpleRiskModel"; /** * Params for the simple risk model */ params: MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskModel_params; } -export type MarketsQuery_markets_tradableInstrument_riskModel = - | MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel - | MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskModel; +export type MarketsQuery_markets_tradableInstrument_riskModel = MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel | MarketsQuery_markets_tradableInstrument_riskModel_SimpleRiskModel; export interface MarketsQuery_markets_tradableInstrument_marginCalculator_scalingFactors { - __typename: 'ScalingFactors'; + __typename: "ScalingFactors"; /** * the scaling factor that determines the margin level at which Vega has to search for more money */ @@ -187,7 +176,7 @@ export interface MarketsQuery_markets_tradableInstrument_marginCalculator_scalin } export interface MarketsQuery_markets_tradableInstrument_marginCalculator { - __typename: 'MarginCalculator'; + __typename: "MarginCalculator"; /** * The scaling factors that will be used for margin calculation */ @@ -195,7 +184,7 @@ export interface MarketsQuery_markets_tradableInstrument_marginCalculator { } export interface MarketsQuery_markets_tradableInstrument { - __typename: 'TradableInstrument'; + __typename: "TradableInstrument"; /** * An instance of or reference to a fully specified instrument. */ @@ -211,7 +200,7 @@ export interface MarketsQuery_markets_tradableInstrument { } export interface MarketsQuery_markets_openingAuction { - __typename: 'AuctionDuration'; + __typename: "AuctionDuration"; /** * Duration of the auction in seconds */ @@ -223,7 +212,7 @@ export interface MarketsQuery_markets_openingAuction { } export interface MarketsQuery_markets_priceMonitoringSettings_parameters_triggers { - __typename: 'PriceMonitoringTrigger'; + __typename: "PriceMonitoringTrigger"; /** * Price monitoring projection horizon Ï„ in seconds (> 0). */ @@ -241,17 +230,15 @@ export interface MarketsQuery_markets_priceMonitoringSettings_parameters_trigger } export interface MarketsQuery_markets_priceMonitoringSettings_parameters { - __typename: 'PriceMonitoringParameters'; + __typename: "PriceMonitoringParameters"; /** * The list of triggers for this price monitoring */ - triggers: - | MarketsQuery_markets_priceMonitoringSettings_parameters_triggers[] - | null; + triggers: MarketsQuery_markets_priceMonitoringSettings_parameters_triggers[] | null; } export interface MarketsQuery_markets_priceMonitoringSettings { - __typename: 'PriceMonitoringSettings'; + __typename: "PriceMonitoringSettings"; /** * Specified a set of PriceMonitoringParameters to be use for price monitoring purposes */ @@ -263,7 +250,7 @@ export interface MarketsQuery_markets_priceMonitoringSettings { } export interface MarketsQuery_markets_liquidityMonitoringParameters_targetStakeParameters { - __typename: 'TargetStakeParameters'; + __typename: "TargetStakeParameters"; /** * Specifies length of time window expressed in seconds for target stake calculation */ @@ -275,7 +262,7 @@ export interface MarketsQuery_markets_liquidityMonitoringParameters_targetStakeP } export interface MarketsQuery_markets_liquidityMonitoringParameters { - __typename: 'LiquidityMonitoringParameters'; + __typename: "LiquidityMonitoringParameters"; /** * Specifies the triggering ratio for entering liquidity auction */ @@ -287,7 +274,7 @@ export interface MarketsQuery_markets_liquidityMonitoringParameters { } export interface MarketsQuery_markets_proposal { - __typename: 'Proposal'; + __typename: "Proposal"; /** * Proposal ID that is filled by VEGA once proposal reaches the network */ @@ -295,7 +282,7 @@ export interface MarketsQuery_markets_proposal { } export interface MarketsQuery_markets_accounts_asset { - __typename: 'Asset'; + __typename: "Asset"; /** * The id of the asset */ @@ -307,7 +294,7 @@ export interface MarketsQuery_markets_accounts_asset { } export interface MarketsQuery_markets_accounts { - __typename: 'Account'; + __typename: "Account"; /** * Asset, the 'currency' */ @@ -323,7 +310,7 @@ export interface MarketsQuery_markets_accounts { } export interface MarketsQuery_markets_data_priceMonitoringBounds_trigger { - __typename: 'PriceMonitoringTrigger'; + __typename: "PriceMonitoringTrigger"; /** * Price monitoring auction extension duration in seconds should the price * breach it's theoretical level over the specified horizon at the specified @@ -337,7 +324,7 @@ export interface MarketsQuery_markets_data_priceMonitoringBounds_trigger { } export interface MarketsQuery_markets_data_priceMonitoringBounds { - __typename: 'PriceMonitoringBounds'; + __typename: "PriceMonitoringBounds"; /** * Minimum price that isn't currently breaching the specified price monitoring trigger */ @@ -357,7 +344,7 @@ export interface MarketsQuery_markets_data_priceMonitoringBounds { } export interface MarketsQuery_markets_data_liquidityProviderFeeShare_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ @@ -365,7 +352,7 @@ export interface MarketsQuery_markets_data_liquidityProviderFeeShare_party { } export interface MarketsQuery_markets_data_liquidityProviderFeeShare { - __typename: 'LiquidityProviderFeeShare'; + __typename: "LiquidityProviderFeeShare"; /** * The liquidity provider party id */ @@ -375,15 +362,15 @@ export interface MarketsQuery_markets_data_liquidityProviderFeeShare { */ equityLikeShare: string; /** - * the average entry valuation of the liqidity provider for the market + * the average entry valuation of the liquidity provider for the market */ averageEntryValuation: string; } export interface MarketsQuery_markets_data { - __typename: 'MarketData'; + __typename: "MarketData"; /** - * the mark price (actually an unsgined int) + * the mark price (actually an unsigned int) */ markPrice: string; /** @@ -427,7 +414,7 @@ export interface MarketsQuery_markets_data { */ staticMidPrice: string; /** - * RFC3339Nano time at which this market price was releavant + * RFC3339Nano time at which this market price was relevant */ timestamp: string; /** @@ -469,9 +456,7 @@ export interface MarketsQuery_markets_data { /** * A list of valid price ranges per associated trigger */ - priceMonitoringBounds: - | MarketsQuery_markets_data_priceMonitoringBounds[] - | null; + priceMonitoringBounds: MarketsQuery_markets_data_priceMonitoringBounds[] | null; /** * the market value proxy */ @@ -479,13 +464,11 @@ export interface MarketsQuery_markets_data { /** * the equity like share of liquidity fee for each liquidity provider */ - liquidityProviderFeeShare: - | MarketsQuery_markets_data_liquidityProviderFeeShare[] - | null; + liquidityProviderFeeShare: MarketsQuery_markets_data_liquidityProviderFeeShare[] | null; } export interface MarketsQuery_markets { - __typename: 'Market'; + __typename: "Market"; /** * Market ID */ @@ -505,14 +488,14 @@ export interface MarketsQuery_markets { /** * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct * number denominated in the currency of the Market. (uint64) - * + * * Examples: * Currency Balance decimalPlaces Real Balance * GBP 100 0 GBP 100 * GBP 100 2 GBP 1.00 * GBP 100 4 GBP 0.01 * GBP 1 4 GBP 0.0001 ( 0.01p ) - * + * * GBX (pence) 100 0 GBP 1.00 (100p ) * GBX (pence) 100 2 GBP 0.01 ( 1p ) * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) diff --git a/apps/explorer/src/app/routes/network-parameters/__generated__/NetworkParametersQuery.ts b/libs/graphql/src/__generated__/NetworkParametersQuery.ts similarity index 100% rename from apps/explorer/src/app/routes/network-parameters/__generated__/NetworkParametersQuery.ts rename to libs/graphql/src/__generated__/NetworkParametersQuery.ts diff --git a/apps/explorer/src/app/routes/validators/__generated__/NodesQuery.ts b/libs/graphql/src/__generated__/NodesQuery.ts similarity index 90% rename from apps/explorer/src/app/routes/validators/__generated__/NodesQuery.ts rename to libs/graphql/src/__generated__/NodesQuery.ts index e926f6466..0b8079143 100644 --- a/apps/explorer/src/app/routes/validators/__generated__/NodesQuery.ts +++ b/libs/graphql/src/__generated__/NodesQuery.ts @@ -3,14 +3,14 @@ // @generated // This file was automatically generated and should not be edited. -import { NodeStatus } from './../../../../__generated__/globalTypes'; +import { NodeStatus } from "./globalTypes"; // ==================================================== // GraphQL query operation: NodesQuery // ==================================================== export interface NodesQuery_nodes_epochData { - __typename: 'EpochData'; + __typename: "EpochData"; /** * Total number of epochs since node was created */ @@ -26,7 +26,7 @@ export interface NodesQuery_nodes_epochData { } export interface NodesQuery_nodes { - __typename: 'Node'; + __typename: "Node"; /** * The node url eg n01.vega.xyz */ @@ -71,8 +71,6 @@ export interface NodesQuery_nodes { pendingStake: string; epochData: NodesQuery_nodes_epochData | null; status: NodeStatus; - score: string; - normalisedScore: string; } export interface NodesQuery { diff --git a/libs/graphql/src/__generated__/OrderEvent.ts b/libs/graphql/src/__generated__/OrderEvent.ts new file mode 100644 index 000000000..9d93b9fbf --- /dev/null +++ b/libs/graphql/src/__generated__/OrderEvent.ts @@ -0,0 +1,91 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { BusEventType, OrderType, OrderStatus, OrderRejectionReason } from "./globalTypes"; + +// ==================================================== +// GraphQL subscription operation: OrderEvent +// ==================================================== + +export interface OrderEvent_busEvents_event_TimeUpdate { + __typename: "TimeUpdate" | "MarketEvent" | "TransferResponses" | "PositionResolution" | "Trade" | "Account" | "Party" | "MarginLevels" | "Proposal" | "Vote" | "MarketData" | "NodeSignature" | "LossSocialization" | "SettlePosition" | "Market" | "Asset" | "MarketTick" | "SettleDistressed" | "AuctionEvent" | "RiskFactor" | "Deposit" | "Withdrawal" | "OracleSpec" | "LiquidityProvision"; +} + +export interface OrderEvent_busEvents_event_Order_market { + __typename: "Market"; + /** + * Market full name + */ + name: string; +} + +export interface OrderEvent_busEvents_event_Order { + __typename: "Order"; + /** + * Type the order type (defaults to PARTY) + */ + type: OrderType | null; + /** + * Hash of the order data + */ + id: string; + /** + * The status of an order, for example 'Active' + */ + status: OrderStatus; + /** + * Reason for the order to be rejected + */ + rejectionReason: OrderRejectionReason | null; + /** + * RFC3339Nano formatted date and time for when the order was created (timestamp) + */ + createdAt: string; + /** + * Total number of contracts that may be bought or sold (immutable) (uint64) + */ + size: string; + /** + * The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) + */ + price: string; + /** + * The market the order is trading on (probably stored internally as a hash of the market details) + */ + market: OrderEvent_busEvents_event_Order_market | null; +} + +export type OrderEvent_busEvents_event = OrderEvent_busEvents_event_TimeUpdate | OrderEvent_busEvents_event_Order; + +export interface OrderEvent_busEvents { + __typename: "BusEvent"; + /** + * the id for this event + */ + eventId: string; + /** + * the block hash + */ + block: string; + /** + * the type of event we're dealing with + */ + type: BusEventType; + /** + * the payload - the wrapped event + */ + event: OrderEvent_busEvents_event; +} + +export interface OrderEvent { + /** + * Subscribe to event data from the event bus + */ + busEvents: OrderEvent_busEvents[] | null; +} + +export interface OrderEventVariables { + partyId: string; +} diff --git a/apps/explorer/src/app/routes/parties/id/__generated__/PartyAssetsQuery.ts b/libs/graphql/src/__generated__/PartyAssetsQuery.ts similarity index 81% rename from apps/explorer/src/app/routes/parties/id/__generated__/PartyAssetsQuery.ts rename to libs/graphql/src/__generated__/PartyAssetsQuery.ts index 0f9807f74..1fe3f42fa 100644 --- a/apps/explorer/src/app/routes/parties/id/__generated__/PartyAssetsQuery.ts +++ b/libs/graphql/src/__generated__/PartyAssetsQuery.ts @@ -3,14 +3,14 @@ // @generated // This file was automatically generated and should not be edited. -import { AccountType } from '../../../../../__generated__/globalTypes'; +import { AccountType } from "./globalTypes"; // ==================================================== // GraphQL query operation: PartyAssetsQuery // ==================================================== export interface PartyAssetsQuery_party_delegations_node { - __typename: 'Node'; + __typename: "Node"; /** * The node url eg n01.vega.xyz */ @@ -19,7 +19,7 @@ export interface PartyAssetsQuery_party_delegations_node { } export interface PartyAssetsQuery_party_delegations { - __typename: 'Delegation'; + __typename: "Delegation"; /** * Amount delegated */ @@ -35,7 +35,7 @@ export interface PartyAssetsQuery_party_delegations { } export interface PartyAssetsQuery_party_stake { - __typename: 'PartyStake'; + __typename: "PartyStake"; /** * The stake currently available for the party */ @@ -43,23 +43,21 @@ export interface PartyAssetsQuery_party_stake { } export interface PartyAssetsQuery_party_accounts_asset_source_BuiltinAsset { - __typename: 'BuiltinAsset'; + __typename: "BuiltinAsset"; } export interface PartyAssetsQuery_party_accounts_asset_source_ERC20 { - __typename: 'ERC20'; + __typename: "ERC20"; /** * The address of the erc20 contract */ contractAddress: string; } -export type PartyAssetsQuery_party_accounts_asset_source = - | PartyAssetsQuery_party_accounts_asset_source_BuiltinAsset - | PartyAssetsQuery_party_accounts_asset_source_ERC20; +export type PartyAssetsQuery_party_accounts_asset_source = PartyAssetsQuery_party_accounts_asset_source_BuiltinAsset | PartyAssetsQuery_party_accounts_asset_source_ERC20; export interface PartyAssetsQuery_party_accounts_asset { - __typename: 'Asset'; + __typename: "Asset"; /** * The full name of the asset (e.g: Great British Pound) */ @@ -83,7 +81,7 @@ export interface PartyAssetsQuery_party_accounts_asset { } export interface PartyAssetsQuery_party_accounts { - __typename: 'Account'; + __typename: "Account"; /** * Asset, the 'currency' */ @@ -99,14 +97,14 @@ export interface PartyAssetsQuery_party_accounts { } export interface PartyAssetsQuery_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ id: string; delegations: PartyAssetsQuery_party_delegations[] | null; /** - * The staking informations for this Party + * The staking information for this Party */ stake: PartyAssetsQuery_party_stake; /** @@ -123,5 +121,5 @@ export interface PartyAssetsQuery { } export interface PartyAssetsQueryVariables { - partyId: string | undefined; + partyId: string; } diff --git a/apps/explorer/src/app/routes/governance/__generated__/ProposalsQuery.ts b/libs/graphql/src/__generated__/ProposalsQuery.ts similarity index 81% rename from apps/explorer/src/app/routes/governance/__generated__/ProposalsQuery.ts rename to libs/graphql/src/__generated__/ProposalsQuery.ts index c19e3ff2d..f47bef14f 100644 --- a/apps/explorer/src/app/routes/governance/__generated__/ProposalsQuery.ts +++ b/libs/graphql/src/__generated__/ProposalsQuery.ts @@ -3,26 +3,26 @@ // @generated // This file was automatically generated and should not be edited. -import { - ProposalState, - ProposalRejectionReason, - VoteValue, -} from './../../../../__generated__/globalTypes'; +import { ProposalState, ProposalRejectionReason, VoteValue } from "./globalTypes"; // ==================================================== // GraphQL query operation: ProposalsQuery // ==================================================== export interface ProposalsQuery_proposals_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ id: string; } +export interface ProposalsQuery_proposals_terms_change_NewFreeform { + __typename: "NewFreeform"; +} + export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument { - __typename: 'InstrumentConfiguration'; + __typename: "InstrumentConfiguration"; /** * Full and fairly descriptive name for the instrument */ @@ -30,7 +30,7 @@ export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument { } export interface ProposalsQuery_proposals_terms_change_NewMarket { - __typename: 'NewMarket'; + __typename: "NewMarket"; /** * New market instrument configuration */ @@ -38,12 +38,12 @@ export interface ProposalsQuery_proposals_terms_change_NewMarket { } export interface ProposalsQuery_proposals_terms_change_UpdateMarket { - __typename: 'UpdateMarket'; + __typename: "UpdateMarket"; marketId: string; } export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset { - __typename: 'BuiltinAsset'; + __typename: "BuiltinAsset"; /** * Maximum amount that can be requested by a party through the built-in asset faucet at a time */ @@ -51,19 +51,17 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAs } export interface ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20 { - __typename: 'ERC20'; + __typename: "ERC20"; /** * The address of the erc20 contract */ contractAddress: string; } -export type ProposalsQuery_proposals_terms_change_NewAsset_source = - | ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset - | ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20; +export type ProposalsQuery_proposals_terms_change_NewAsset_source = ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20; export interface ProposalsQuery_proposals_terms_change_NewAsset { - __typename: 'NewAsset'; + __typename: "NewAsset"; /** * The symbol of the asset (e.g: GBP) */ @@ -75,7 +73,7 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset { } export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter { - __typename: 'NetworkParameter'; + __typename: "NetworkParameter"; /** * The name of the network parameter */ @@ -87,18 +85,14 @@ export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_ne } export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter { - __typename: 'UpdateNetworkParameter'; + __typename: "UpdateNetworkParameter"; networkParameter: ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter; } -export type ProposalsQuery_proposals_terms_change = - | ProposalsQuery_proposals_terms_change_NewMarket - | ProposalsQuery_proposals_terms_change_UpdateMarket - | ProposalsQuery_proposals_terms_change_NewAsset - | ProposalsQuery_proposals_terms_change_UpdateNetworkParameter; +export type ProposalsQuery_proposals_terms_change = ProposalsQuery_proposals_terms_change_NewFreeform | ProposalsQuery_proposals_terms_change_NewMarket | ProposalsQuery_proposals_terms_change_UpdateMarket | ProposalsQuery_proposals_terms_change_NewAsset | ProposalsQuery_proposals_terms_change_UpdateNetworkParameter; export interface ProposalsQuery_proposals_terms { - __typename: 'ProposalTerms'; + __typename: "ProposalTerms"; /** * RFC3339Nano time and date when voting closes for this proposal. * Constrained by "minClose" and "maxClose" network parameters. @@ -116,7 +110,7 @@ export interface ProposalsQuery_proposals_terms { } export interface ProposalsQuery_proposals_votes_yes_votes_party_stake { - __typename: 'PartyStake'; + __typename: "PartyStake"; /** * The stake currently available for the party */ @@ -124,19 +118,19 @@ export interface ProposalsQuery_proposals_votes_yes_votes_party_stake { } export interface ProposalsQuery_proposals_votes_yes_votes_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ id: string; /** - * The staking informations for this Party + * The staking information for this Party */ stake: ProposalsQuery_proposals_votes_yes_votes_party_stake; } export interface ProposalsQuery_proposals_votes_yes_votes { - __typename: 'Vote'; + __typename: "Vote"; /** * The vote value cast */ @@ -152,7 +146,7 @@ export interface ProposalsQuery_proposals_votes_yes_votes { } export interface ProposalsQuery_proposals_votes_yes { - __typename: 'ProposalVoteSide'; + __typename: "ProposalVoteSide"; /** * Total tokens of governance token from the votes casted for this side */ @@ -168,7 +162,7 @@ export interface ProposalsQuery_proposals_votes_yes { } export interface ProposalsQuery_proposals_votes_no_votes_party_stake { - __typename: 'PartyStake'; + __typename: "PartyStake"; /** * The stake currently available for the party */ @@ -176,19 +170,19 @@ export interface ProposalsQuery_proposals_votes_no_votes_party_stake { } export interface ProposalsQuery_proposals_votes_no_votes_party { - __typename: 'Party'; + __typename: "Party"; /** * Party identifier */ id: string; /** - * The staking informations for this Party + * The staking information for this Party */ stake: ProposalsQuery_proposals_votes_no_votes_party_stake; } export interface ProposalsQuery_proposals_votes_no_votes { - __typename: 'Vote'; + __typename: "Vote"; /** * The vote value cast */ @@ -204,7 +198,7 @@ export interface ProposalsQuery_proposals_votes_no_votes { } export interface ProposalsQuery_proposals_votes_no { - __typename: 'ProposalVoteSide'; + __typename: "ProposalVoteSide"; /** * Total tokens of governance token from the votes casted for this side */ @@ -220,7 +214,7 @@ export interface ProposalsQuery_proposals_votes_no { } export interface ProposalsQuery_proposals_votes { - __typename: 'ProposalVotes'; + __typename: "ProposalVotes"; /** * Yes votes cast for this proposal */ @@ -232,7 +226,7 @@ export interface ProposalsQuery_proposals_votes { } export interface ProposalsQuery_proposals { - __typename: 'Proposal'; + __typename: "Proposal"; /** * Proposal ID that is filled by VEGA once proposal reaches the network */ diff --git a/libs/graphql/src/__generated__/globalTypes.ts b/libs/graphql/src/__generated__/globalTypes.ts new file mode 100644 index 000000000..e4c7a3e64 --- /dev/null +++ b/libs/graphql/src/__generated__/globalTypes.ts @@ -0,0 +1,234 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +//============================================================== +// START Enums and Input Objects +//============================================================== + +/** + * The various account types we have (used by collateral) + */ +export enum AccountType { + Bond = "Bond", + External = "External", + FeeInfrastructure = "FeeInfrastructure", + FeeLiquidity = "FeeLiquidity", + FeeMaker = "FeeMaker", + General = "General", + GlobalInsurance = "GlobalInsurance", + GlobalReward = "GlobalReward", + Insurance = "Insurance", + LockWithdraw = "LockWithdraw", + Margin = "Margin", + PendingTransfers = "PendingTransfers", + RewardLpReceivedFees = "RewardLpReceivedFees", + RewardMakerReceivedFees = "RewardMakerReceivedFees", + RewardMarketProposers = "RewardMarketProposers", + RewardTakerPaidFees = "RewardTakerPaidFees", + Settlement = "Settlement", +} + +export enum AuctionTrigger { + Batch = "Batch", + Liquidity = "Liquidity", + Opening = "Opening", + Price = "Price", + Unspecified = "Unspecified", +} + +export enum BusEventType { + Account = "Account", + Asset = "Asset", + Auction = "Auction", + Deposit = "Deposit", + LiquidityProvision = "LiquidityProvision", + LossSocialization = "LossSocialization", + MarginLevels = "MarginLevels", + Market = "Market", + MarketCreated = "MarketCreated", + MarketData = "MarketData", + MarketTick = "MarketTick", + MarketUpdated = "MarketUpdated", + NodeSignature = "NodeSignature", + OracleSpec = "OracleSpec", + Order = "Order", + Party = "Party", + PositionResolution = "PositionResolution", + Proposal = "Proposal", + RiskFactor = "RiskFactor", + SettleDistressed = "SettleDistressed", + SettlePosition = "SettlePosition", + TimeUpdate = "TimeUpdate", + Trade = "Trade", + TransferResponses = "TransferResponses", + Vote = "Vote", + Withdrawal = "Withdrawal", +} + +/** + * The current state of a market + */ +export enum MarketState { + Active = "Active", + Cancelled = "Cancelled", + Closed = "Closed", + Pending = "Pending", + Proposed = "Proposed", + Rejected = "Rejected", + Settled = "Settled", + Suspended = "Suspended", + TradingTerminated = "TradingTerminated", +} + +/** + * What market trading mode are we in + */ +export enum MarketTradingMode { + BatchAuction = "BatchAuction", + Continuous = "Continuous", + MonitoringAuction = "MonitoringAuction", + OpeningAuction = "OpeningAuction", +} + +export enum NodeStatus { + NonValidator = "NonValidator", + Validator = "Validator", +} + +/** + * Reason for the order being rejected by the core node + */ +export enum OrderRejectionReason { + AmendToGTTWithoutExpiryAt = "AmendToGTTWithoutExpiryAt", + CannotAmendFromGFAOrGFN = "CannotAmendFromGFAOrGFN", + CannotAmendPeggedOrderDetailsOnNonPeggedOrder = "CannotAmendPeggedOrderDetailsOnNonPeggedOrder", + CannotAmendToFOKOrIOC = "CannotAmendToFOKOrIOC", + CannotAmendToGFAOrGFN = "CannotAmendToGFAOrGFN", + EditNotAllowed = "EditNotAllowed", + ExpiryAtBeforeCreatedAt = "ExpiryAtBeforeCreatedAt", + FOKOrderDuringAuction = "FOKOrderDuringAuction", + GFAOrderDuringContinuousTrading = "GFAOrderDuringContinuousTrading", + GFNOrderDuringAuction = "GFNOrderDuringAuction", + GTCWithExpiryAtNotValid = "GTCWithExpiryAtNotValid", + IOCOrderDuringAuction = "IOCOrderDuringAuction", + InsufficientAssetBalance = "InsufficientAssetBalance", + InsufficientFundsToPayFees = "InsufficientFundsToPayFees", + InternalError = "InternalError", + InvalidExpirationTime = "InvalidExpirationTime", + InvalidMarketId = "InvalidMarketId", + InvalidMarketType = "InvalidMarketType", + InvalidOrderId = "InvalidOrderId", + InvalidOrderReference = "InvalidOrderReference", + InvalidPartyId = "InvalidPartyId", + InvalidPersistence = "InvalidPersistence", + InvalidRemainingSize = "InvalidRemainingSize", + InvalidSize = "InvalidSize", + InvalidTimeInForce = "InvalidTimeInForce", + InvalidType = "InvalidType", + MarginCheckFailed = "MarginCheckFailed", + MarketClosed = "MarketClosed", + MissingGeneralAccount = "MissingGeneralAccount", + NonPersistentOrderExceedsPriceBounds = "NonPersistentOrderExceedsPriceBounds", + OrderAmendFailure = "OrderAmendFailure", + OrderNotFound = "OrderNotFound", + OrderOutOfSequence = "OrderOutOfSequence", + OrderRemovalFailure = "OrderRemovalFailure", + PeggedOrderBuyCannotReferenceBestAskPrice = "PeggedOrderBuyCannotReferenceBestAskPrice", + PeggedOrderMustBeGTTOrGTC = "PeggedOrderMustBeGTTOrGTC", + PeggedOrderMustBeLimitOrder = "PeggedOrderMustBeLimitOrder", + PeggedOrderOffsetMustBeGreaterOrEqualToZero = "PeggedOrderOffsetMustBeGreaterOrEqualToZero", + PeggedOrderOffsetMustBeGreaterThanZero = "PeggedOrderOffsetMustBeGreaterThanZero", + PeggedOrderSellCannotReferenceBestBidPrice = "PeggedOrderSellCannotReferenceBestBidPrice", + PeggedOrderWithoutReferencePrice = "PeggedOrderWithoutReferencePrice", + SelfTrading = "SelfTrading", + TimeFailure = "TimeFailure", + UnableToAmendPeggedOrderPrice = "UnableToAmendPeggedOrderPrice", + UnableToRepricePeggedOrder = "UnableToRepricePeggedOrder", +} + +/** + * Valid order statuses, these determine several states for an order that cannot be expressed with other fields in Order. + */ +export enum OrderStatus { + Active = "Active", + Cancelled = "Cancelled", + Expired = "Expired", + Filled = "Filled", + Parked = "Parked", + PartiallyFilled = "PartiallyFilled", + Rejected = "Rejected", + Stopped = "Stopped", +} + +export enum OrderType { + Limit = "Limit", + Market = "Market", + Network = "Network", +} + +/** + * Reason for the proposal being rejected by the core node + */ +export enum ProposalRejectionReason { + CloseTimeTooLate = "CloseTimeTooLate", + CloseTimeTooSoon = "CloseTimeTooSoon", + CouldNotInstantiateMarket = "CouldNotInstantiateMarket", + EnactTimeTooLate = "EnactTimeTooLate", + EnactTimeTooSoon = "EnactTimeTooSoon", + IncompatibleTimestamps = "IncompatibleTimestamps", + InsufficientTokens = "InsufficientTokens", + InvalidAsset = "InvalidAsset", + InvalidAssetDetails = "InvalidAssetDetails", + InvalidFeeAmount = "InvalidFeeAmount", + InvalidFutureMaturityTimestamp = "InvalidFutureMaturityTimestamp", + InvalidFutureProduct = "InvalidFutureProduct", + InvalidInstrumentSecurity = "InvalidInstrumentSecurity", + InvalidRiskParameter = "InvalidRiskParameter", + InvalidShape = "InvalidShape", + MajorityThresholdNotReached = "MajorityThresholdNotReached", + MarketMissingLiquidityCommitment = "MarketMissingLiquidityCommitment", + MissingBuiltinAssetField = "MissingBuiltinAssetField", + MissingCommitmentAmount = "MissingCommitmentAmount", + MissingERC20ContractAddress = "MissingERC20ContractAddress", + NetworkParameterInvalidKey = "NetworkParameterInvalidKey", + NetworkParameterInvalidValue = "NetworkParameterInvalidValue", + NetworkParameterValidationFailed = "NetworkParameterValidationFailed", + NoProduct = "NoProduct", + NoRiskParameters = "NoRiskParameters", + NoTradingMode = "NoTradingMode", + NodeValidationFailed = "NodeValidationFailed", + OpeningAuctionDurationTooLarge = "OpeningAuctionDurationTooLarge", + OpeningAuctionDurationTooSmall = "OpeningAuctionDurationTooSmall", + ParticipationThresholdNotReached = "ParticipationThresholdNotReached", + ProductMaturityIsPassed = "ProductMaturityIsPassed", + UnsupportedProduct = "UnsupportedProduct", + UnsupportedTradingMode = "UnsupportedTradingMode", +} + +/** + * Various states a proposal can transition through: + * Open -> + * - Passed -> Enacted. + * - Rejected. + * Proposal can enter Failed state from any other state. + */ +export enum ProposalState { + Declined = "Declined", + Enacted = "Enacted", + Failed = "Failed", + Open = "Open", + Passed = "Passed", + Rejected = "Rejected", + WaitingForNodeVote = "WaitingForNodeVote", +} + +export enum VoteValue { + No = "No", + Yes = "Yes", +} + +//============================================================== +// END Enums and Input Objects +//============================================================== diff --git a/libs/graphql/src/index.ts b/libs/graphql/src/index.ts new file mode 100644 index 000000000..3e7077947 --- /dev/null +++ b/libs/graphql/src/index.ts @@ -0,0 +1,13 @@ +export * from './__generated__/AssetsQuery'; +export * from './__generated__/globalTypes'; +export * from './__generated__/Guess'; +export * from './__generated__/Market'; +export * from './__generated__/Markets'; +export * from './__generated__/MarketsQuery'; +export * from './__generated__/MarketDataSub'; +export * from './__generated__/MarketDataFields'; +export * from './__generated__/NetworkParametersQuery'; +export * from './__generated__/NodesQuery'; +export * from './__generated__/OrderEvent'; +export * from './__generated__/PartyAssetsQuery'; +export * from './__generated__/ProposalsQuery'; diff --git a/libs/graphql/tsconfig.json b/libs/graphql/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/graphql/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/graphql/tsconfig.lib.json b/libs/graphql/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/graphql/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/graphql/tsconfig.spec.json b/libs/graphql/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/graphql/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/market-list/.babelrc b/libs/market-list/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/market-list/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/market-list/.eslintrc.json b/libs/market-list/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/market-list/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/market-list/README.md b/libs/market-list/README.md new file mode 100644 index 000000000..129c47d2b --- /dev/null +++ b/libs/market-list/README.md @@ -0,0 +1,7 @@ +# market-list + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test market-list` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/market-list/jest.config.js b/libs/market-list/jest.config.js new file mode 100644 index 000000000..a5c40cc3b --- /dev/null +++ b/libs/market-list/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'market-list', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/market-list', +}; diff --git a/libs/market-list/package.json b/libs/market-list/package.json new file mode 100644 index 000000000..3dd740a11 --- /dev/null +++ b/libs/market-list/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/market-list", + "version": "0.0.1" +} diff --git a/libs/market-list/project.json b/libs/market-list/project.json new file mode 100644 index 000000000..cbd8cd093 --- /dev/null +++ b/libs/market-list/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/market-list", + "sourceRoot": "libs/market-list/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/market-list", + "tsConfig": "libs/market-list/tsconfig.lib.json", + "project": "libs/market-list/package.json", + "entryFile": "libs/market-list/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/market-list/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/market-list/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/market-list"], + "options": { + "jestConfig": "libs/market-list/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/market-list/src/index.ts b/libs/market-list/src/index.ts new file mode 100644 index 000000000..a681b7d24 --- /dev/null +++ b/libs/market-list/src/index.ts @@ -0,0 +1 @@ +export * from './lib/market-list-table'; diff --git a/libs/market-list/src/lib/market-list-table.spec.tsx b/libs/market-list/src/lib/market-list-table.spec.tsx new file mode 100644 index 000000000..1989c52d2 --- /dev/null +++ b/libs/market-list/src/lib/market-list-table.spec.tsx @@ -0,0 +1,14 @@ +import { render } from '@testing-library/react'; +import { MockedProvider } from '@apollo/react-testing'; +import MarketListTable from './market-list-table'; + +describe('MarketListTable', () => { + it('should render successfully', () => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/libs/market-list/src/lib/market-list-table.tsx b/libs/market-list/src/lib/market-list-table.tsx new file mode 100644 index 000000000..70afd331e --- /dev/null +++ b/libs/market-list/src/lib/market-list-table.tsx @@ -0,0 +1,88 @@ +import type { GridApi, ValueFormatterParams } from 'ag-grid-community'; +import { + PriceCell, + formatNumber, + useApplyGridTransaction, +} from '@vegaprotocol/react-helpers'; +import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; +import { Markets_markets } from '@vegaprotocol/graphql'; +import { AgGridColumn } from 'ag-grid-react'; +import { useRef, useState } from 'react'; + +interface MarketListTableProps { + markets: Markets_markets[]; + onRowClicked: (marketId: string) => void; +} + +export const MarketListTable = ({ + markets, + onRowClicked, +}: MarketListTableProps) => { + const [initialMarkets] = useState(markets); + const gridApi = useRef(null); + useApplyGridTransaction(markets, gridApi.current); + + return ( + data.id} + defaultColDef={{ + flex: 1, + resizable: true, + }} + onGridReady={(params) => { + gridApi.current = params.api; + }} + onRowClicked={({ data }) => onRowClicked(data.id)} + components={{ PriceCell }} + > + + + + `${value.market.state} (${value.market.tradingMode})` + } + /> + + formatNumber(value, data.decimalPlaces) + } + /> + + formatNumber(value, data.decimalPlaces) + } + cellRenderer="PriceCell" + /> + + formatNumber(value, data.decimalPlaces) + } + /> + + + ); +}; + +export default MarketListTable; diff --git a/libs/market-list/src/lib/summary-cell.tsx b/libs/market-list/src/lib/summary-cell.tsx new file mode 100644 index 000000000..1d558ee9d --- /dev/null +++ b/libs/market-list/src/lib/summary-cell.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; + +/* +import { useMarketOverview } from '../../../../hooks/use-market-overview' +import { colorByMarketMovement } from '../../../../lib/vega-colours' +import { Sparkline } from '../../components/sparkline' +import { VEGA_TABLE_CLASSES } from '../../components/vega-table' +*/ + +export interface SummaryCellProps { + value: string; // marketId +} + +export const SummaryCellView = ({ value }: SummaryCellProps) => { + // const { sparkline, change, bullish } = useMarketOverview(value) + // const color = colorByMarketMovement(bullish) + + return ( + <> + {/* */} + {'change'} + + ); +}; + +SummaryCellView.displayName = 'SummaryCellView'; + +export const SummaryCell = React.memo(SummaryCellView); +SummaryCell.displayName = 'SummaryCell'; diff --git a/libs/market-list/tsconfig.json b/libs/market-list/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/market-list/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/market-list/tsconfig.lib.json b/libs/market-list/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/market-list/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/market-list/tsconfig.spec.json b/libs/market-list/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/market-list/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts b/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts new file mode 100644 index 000000000..4eda7be25 --- /dev/null +++ b/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts @@ -0,0 +1,33 @@ +import { GridApi } from 'ag-grid-community'; +import { useEffect } from 'react'; + +export const useApplyGridTransaction = ( + data: T[], + gridApi: GridApi | null +) => { + useEffect(() => { + if (!gridApi) return; + + const update: T[] = []; + const add: T[] = []; + + // split into updates and adds + data.forEach((d) => { + if (!gridApi) return; + + const rowNode = gridApi.getRowNode(d.id); + + if (rowNode) { + update.push(d); + } else { + add.push(d); + } + }); + + gridApi.applyTransaction({ + update, + add, + addIndex: 0, + }); + }, [data, gridApi]); +}; diff --git a/libs/react-helpers/src/index.ts b/libs/react-helpers/src/index.ts index 61504ca1d..17009c30a 100644 --- a/libs/react-helpers/src/index.ts +++ b/libs/react-helpers/src/index.ts @@ -1,3 +1,8 @@ export * from './lib/context'; +export * from './lib/datetime'; +export * from './lib/decimals'; +export * from './lib/format'; +export * from './lib/grid-cells'; export * from './lib/storage'; -export * from './lib/trading'; + +export * from './hooks/use-apply-grid-transaction'; diff --git a/libs/react-helpers/src/lib/datetime/datetime.ts b/libs/react-helpers/src/lib/datetime/datetime.ts new file mode 100644 index 000000000..e792f8968 --- /dev/null +++ b/libs/react-helpers/src/lib/datetime/datetime.ts @@ -0,0 +1,13 @@ +/** Returns date in a format suitable for input[type=date] elements */ +export const formatForInput = (date: Date) => { + const padZero = (num: number) => num.toString().padStart(2, '0'); + + const year = date.getFullYear(); + const month = padZero(date.getMonth() + 1); + const day = padZero(date.getDate()); + const hours = padZero(date.getHours()); + const minutes = padZero(date.getMinutes()); + const secs = padZero(date.getSeconds()); + + return `${year}-${month}-${day}T${hours}:${minutes}:${secs}`; +}; diff --git a/libs/react-helpers/src/lib/datetime/index.ts b/libs/react-helpers/src/lib/datetime/index.ts new file mode 100644 index 000000000..bb1c9cca2 --- /dev/null +++ b/libs/react-helpers/src/lib/datetime/index.ts @@ -0,0 +1 @@ +export * from './datetime'; diff --git a/libs/react-helpers/src/lib/decimals/index.ts b/libs/react-helpers/src/lib/decimals/index.ts new file mode 100644 index 000000000..bedea9595 --- /dev/null +++ b/libs/react-helpers/src/lib/decimals/index.ts @@ -0,0 +1,12 @@ +import { BigNumber } from 'bignumber.js'; + +export function addDecimal(value: string, decimals: number): string { + if (!decimals) return value; + return new BigNumber(value || 0) + .dividedBy(Math.pow(10, decimals)) + .toFixed(decimals); +} +export function removeDecimal(value: string, decimals: number): string { + if (!decimals) return value; + return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0); +} diff --git a/libs/react-helpers/src/lib/format/format.ts b/libs/react-helpers/src/lib/format/format.ts new file mode 100644 index 000000000..cd0478c81 --- /dev/null +++ b/libs/react-helpers/src/lib/format/format.ts @@ -0,0 +1,54 @@ +import once from 'lodash.once'; +import memoize from 'lodash.memoize'; +import { addDecimal } from '../decimals'; + +const getUserLocale = () => 'default'; + +export const splitAt = (index: number) => (x: string) => + [x.slice(0, index), x.slice(index)]; + +export const getTimeFormat = once( + () => + new Intl.DateTimeFormat(getUserLocale(), { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }) +); + +export const getDateFormat = once( + () => + new Intl.DateTimeFormat(getUserLocale(), { + year: 'numeric', + month: 'numeric', + day: 'numeric', + }) +); + +export const getDateTimeFormat = once( + () => + new Intl.DateTimeFormat(getUserLocale(), { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }) +); + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat +export const getNumberFormat = memoize( + (minimumFractionDigits: number) => + new Intl.NumberFormat(getUserLocale(), { minimumFractionDigits }) +); + +export const getRelativeTimeFormat = once( + () => new Intl.RelativeTimeFormat(getUserLocale()) +); + +export const formatNumber = (rawValue: string, decimalPlaces: number) => { + const x = addDecimal(rawValue, decimalPlaces); + + return getNumberFormat(decimalPlaces).format(Number(x)); +}; diff --git a/libs/react-helpers/src/lib/format/index.ts b/libs/react-helpers/src/lib/format/index.ts new file mode 100644 index 000000000..16c5b2b50 --- /dev/null +++ b/libs/react-helpers/src/lib/format/index.ts @@ -0,0 +1 @@ +export * from './format'; diff --git a/libs/react-helpers/src/lib/grid-cells/flash-cell.tsx b/libs/react-helpers/src/lib/grid-cells/flash-cell.tsx new file mode 100644 index 000000000..ce7db8c6b --- /dev/null +++ b/libs/react-helpers/src/lib/grid-cells/flash-cell.tsx @@ -0,0 +1,98 @@ +/** + * A component that will display a number, and when it is updated will use animation to + * highlight the direction of change. This defaults to red for downwards, and green for + * upwards. + * + * @author Matt + * @author Edd + * @author John + */ + +import { memo, useRef, useEffect } from 'react'; +import { theme } from '@vegaprotocol/tailwindcss-config'; +import { splitAt } from '../format'; + +const FLASH_DURATION = 800; // Duration of flash animation in milliseconds + +export interface FlashCellProps { + /** + * The string representation of value. It can be formatted in bespoke ways, + * so we can't simply convert value + */ + children: string; + /** The numeric representation of 'children' */ + value: number; +} + +/** + * Given two strings, finds the index first character that has changed in string 2 + * + * Used by to highlight substrings to highlight the portion of a number that has changed + * From: https://stackoverflow.com/questions/32858626/detect-position-of-first-difference-in-2-strings + * + * @param a String to diff + * @param b Second string to diff + * @return number Index of first different character, or -1 + */ +export function findFirstDiffPos(a: string, b: string): number { + let i = 0; + if (a === b) return -1; + if (!a || !b) return -1; + + while (a[i] === b[i]) i++; + return i; +} + +/** + * Get value from previous render + */ +function usePrevious(value: T): T | undefined { + const ref = useRef(); + + useEffect(() => { + ref.current = value; + }, [value]); + + return ref.current; +} + +export const FlashCell = memo(({ children, value }: FlashCellProps) => { + const ref = useRef(null); + const previousLabel = usePrevious(children); + const previousValue = usePrevious(value); + + const indexOfChange = previousLabel + ? findFirstDiffPos(previousLabel, children) + : 0; + + const splitText = splitAt(indexOfChange)(children); + + if (indexOfChange !== -1 && previousValue !== undefined) { + if (value < previousValue) { + ref.current?.animate( + [ + { color: theme.colors.vega.pink }, + { color: theme.colors.vega.pink, offset: 0.8 }, + { color: 'inherit' }, + ], + FLASH_DURATION + ); + } else if (value > previousValue) { + ref.current?.animate( + [ + { color: theme.colors.vega.green }, + { color: theme.colors.vega.green, offset: 0.8 }, + { color: 'inherit' }, + ], + FLASH_DURATION + ); + } + } + + return ( + + {splitText[0]} + {splitText[1]} + + ); +}); diff --git a/libs/react-helpers/src/lib/grid-cells/index.tsx b/libs/react-helpers/src/lib/grid-cells/index.tsx new file mode 100644 index 000000000..aa17139a9 --- /dev/null +++ b/libs/react-helpers/src/lib/grid-cells/index.tsx @@ -0,0 +1,2 @@ +export * from './flash-cell'; +export * from './price-cell'; diff --git a/libs/react-helpers/src/lib/grid-cells/price-cell.tsx b/libs/react-helpers/src/lib/grid-cells/price-cell.tsx new file mode 100644 index 000000000..27daa50d1 --- /dev/null +++ b/libs/react-helpers/src/lib/grid-cells/price-cell.tsx @@ -0,0 +1,19 @@ +import { FlashCell } from './flash-cell'; + +export interface IPriceCellProps { + value: number | bigint | null | undefined; + valueFormatted: string; +} + +export const PriceCell = ({ value, valueFormatted }: IPriceCellProps) => { + if (!value || isNaN(Number(value))) return -; + return ( + + + {valueFormatted} + + + ); +}; + +PriceCell.displayName = 'PriceCell'; diff --git a/libs/react-helpers/src/lib/trading/index.tsx b/libs/react-helpers/src/lib/trading/index.tsx deleted file mode 100644 index ad460b543..000000000 --- a/libs/react-helpers/src/lib/trading/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export const Chart = () =>
TODO: Chart
; -export const Ticket = () =>
TODO: Ticket
; -export const Orderbook = () =>
TODO: Orderbook
; -export const Orders = () =>
TODO: Orders
; -export const Positions = () =>
TODO: Positions
; -export const Collateral = () =>
TODO: Collateral
; - -export type TradingView = keyof typeof TradingViews; - -export const TradingViews = { - chart: Chart, - ticket: Ticket, - orderbook: Orderbook, - orders: Orders, - positions: Positions, - collateral: Collateral, -}; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index b5f47045a..2602a06f8 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -150,11 +150,9 @@ module.exports = { 'ui-small': ['10px', '16px'], }, - extend: { - boxShadow: { - callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)', - focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600', - 'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600', - }, + boxShadow: { + callout: '5px 5px 0 1px rgba(255, 255, 255, 0.05)', + focus: '0px 0px 0px 1px #FFFFFF, 0px 0px 3px 2px #FFE600', + 'focus-dark': '0px 0px 0px 1px #000000, 0px 0px 3px 2px #FFE600', }, }; diff --git a/libs/ui-toolkit/.storybook/preview.js b/libs/ui-toolkit/.storybook/preview.js index 6258bc80b..6d8a1840c 100644 --- a/libs/ui-toolkit/.storybook/preview.js +++ b/libs/ui-toolkit/.storybook/preview.js @@ -18,12 +18,22 @@ export const decorators = [
) : (
-
+ -
-
+ + -
+
), ]; + +const StoryWrapper = ({ children, className }) => ( +
+
+
+ {children} +
+
+
+); diff --git a/libs/ui-toolkit/src/components/button/button.tsx b/libs/ui-toolkit/src/components/button/button.tsx index 6402fa382..e53d0fef0 100644 --- a/libs/ui-toolkit/src/components/button/button.tsx +++ b/libs/ui-toolkit/src/components/button/button.tsx @@ -128,6 +128,7 @@ export const Button = forwardRef( ( { variant = 'primary', + type = 'button', children, className, prependIconName, @@ -137,7 +138,12 @@ export const Button = forwardRef( ref ) => { return ( - ); diff --git a/libs/ui-toolkit/src/components/callout/callout.stories.tsx b/libs/ui-toolkit/src/components/callout/callout.stories.tsx index 1c5da802e..249c84ea1 100644 --- a/libs/ui-toolkit/src/components/callout/callout.stories.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.stories.tsx @@ -3,6 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Callout } from './callout'; import { Button } from '../button'; +import { Intent } from '../../utils/intent'; export default { title: 'Callout', @@ -20,43 +21,43 @@ Default.args = { export const Danger = Template.bind({}); Danger.args = { - intent: 'danger', + intent: Intent.Danger, children: 'Content', }; export const Warning = Template.bind({}); Warning.args = { - intent: 'warning', + intent: Intent.Warning, children: 'Content', }; export const Prompt = Template.bind({}); Prompt.args = { - intent: 'prompt', + intent: Intent.Prompt, children: 'Content', }; export const Progress = Template.bind({}); Progress.args = { - intent: 'progress', + intent: Intent.Progress, children: 'Content', }; export const Success = Template.bind({}); Success.args = { - intent: 'success', + intent: Intent.Success, children: 'Content', }; export const Help = Template.bind({}); Help.args = { - intent: 'help', + intent: Intent.Help, children: 'Content', }; export const IconAndContent = Template.bind({}); IconAndContent.args = { - intent: 'help', + intent: Intent.Help, title: 'This is what this thing does', iconName: 'endorsed', children: ( diff --git a/libs/ui-toolkit/src/components/callout/callout.test.tsx b/libs/ui-toolkit/src/components/callout/callout.test.tsx index 561052ee1..303f9f0bd 100644 --- a/libs/ui-toolkit/src/components/callout/callout.test.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { Callout } from '.'; +import { Intent } from '../../utils/intent'; test('It renders content within callout', () => { render(Content); @@ -15,25 +16,17 @@ test('It renders title and icon', () => { expect(screen.getByText('title')).toBeInTheDocument(); }); -const intents = ['danger', 'warning', 'prompt', 'success', 'help'] as [ - 'danger', - 'warning', - 'prompt', - 'success', - 'help' -]; +const intents = Object.values(Intent).filter((i) => i !== Intent.Progress); -intents.map((intent) => - test(`Applies class for ${intent}`, () => { - render(); - expect(screen.getByTestId('callout')).toHaveClass( - `shadow-intent-${intent}` - ); - }) -); +test.each(intents)('Applies class for %s', (intent) => { + render(); + expect(screen.getByTestId('callout')).toHaveClass( + `shadow-intent-${intent.toLowerCase()}` + ); +}); test(`Applies class for progress`, () => { - render(); + render(); expect(screen.getByTestId('callout')).toHaveClass( 'shadow-black', 'dark:shadow-white' diff --git a/libs/ui-toolkit/src/components/callout/callout.tsx b/libs/ui-toolkit/src/components/callout/callout.tsx index 815e44e45..ef25176a0 100644 --- a/libs/ui-toolkit/src/components/callout/callout.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.tsx @@ -1,10 +1,11 @@ import classNames from 'classnames'; +import { getIntentShadow, Intent } from '../../utils/intent'; import { Icon, IconName } from '../icon'; export interface CalloutProps { children?: React.ReactNode; title?: React.ReactElement | string; - intent?: 'danger' | 'warning' | 'prompt' | 'progress' | 'success' | 'help'; + intent?: Intent; iconName?: IconName; headingLevel?: 1 | 2 | 3 | 4 | 5 | 6; } @@ -12,25 +13,19 @@ export interface CalloutProps { export function Callout({ children, title, - intent = 'help', + intent = Intent.Help, iconName, headingLevel, }: CalloutProps) { const className = classNames( - 'shadow-callout', 'border', 'border-black', 'dark:border-white', 'text-body-large', 'dark:text-white', 'p-8', + getIntentShadow(intent), { - 'shadow-intent-danger': intent === 'danger', - 'shadow-intent-warning': intent === 'warning', - 'shadow-intent-prompt': intent === 'prompt', - 'shadow-black dark:shadow-white': intent === 'progress', - 'shadow-intent-success': intent === 'success', - 'shadow-intent-help': intent === 'help', flex: !!iconName, } ); diff --git a/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx new file mode 100644 index 000000000..97ec9cc31 --- /dev/null +++ b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import { Dialog } from './dialog'; +import { Button } from '../button'; +import { Intent } from '../../utils/intent'; + +export default { + title: 'Dialog', + component: Dialog, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + const [open, setOpen] = useState(args.open); + return ( +
+ + +
+ ); +}; + +export const Default = Template.bind({}); +Default.args = { + open: false, + title: 'Title', + setOpen: () => undefined, + children:

Some content

, +}; + +export const Danger = Template.bind({}); +Danger.args = { + open: false, + title: 'Danger', + setOpen: () => undefined, + children:

Some content

, + intent: Intent.Danger, +}; + +export const Success = Template.bind({}); +Success.args = { + open: false, + title: 'Success', + setOpen: () => undefined, + children:

Some content

, + intent: Intent.Success, +}; + +export const Warning = Template.bind({}); +Warning.args = { + open: false, + title: 'Warning', + setOpen: () => undefined, + children:

Some content

, + intent: Intent.Warning, +}; diff --git a/libs/ui-toolkit/src/components/dialog/dialog.tsx b/libs/ui-toolkit/src/components/dialog/dialog.tsx new file mode 100644 index 000000000..79727c523 --- /dev/null +++ b/libs/ui-toolkit/src/components/dialog/dialog.tsx @@ -0,0 +1,43 @@ +import * as DialogPrimitives from '@radix-ui/react-dialog'; +import classNames from 'classnames'; +import { ReactNode } from 'react'; +import { getIntentShadow, Intent } from '../../utils/intent'; +import { Icon } from '../icon'; + +interface DialogProps { + children: ReactNode; + open: boolean; + onChange: (isOpen: boolean) => void; + title?: string; + intent?: Intent; +} + +export function Dialog({ + children, + open, + onChange, + title, + intent, +}: DialogProps) { + const contentClasses = classNames( + // Positions the modal in the center of screen + 'fixed w-[520px] px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]', + // Need to apply background and text colors again as content is rendered in a portal + 'dark:bg-black dark:text-white-95 bg-white text-black-95', + getIntentShadow(intent) + ); + return ( + onChange(x)}> + + + + + + + {title &&

{title}

} + {children} +
+
+
+ ); +} diff --git a/libs/ui-toolkit/src/components/dialog/index.ts b/libs/ui-toolkit/src/components/dialog/index.ts new file mode 100644 index 000000000..20da8e550 --- /dev/null +++ b/libs/ui-toolkit/src/components/dialog/index.ts @@ -0,0 +1 @@ +export * from './dialog'; diff --git a/libs/ui-toolkit/src/components/dialog/index.tsx b/libs/ui-toolkit/src/components/dialog/index.tsx deleted file mode 100644 index a966995b8..000000000 --- a/libs/ui-toolkit/src/components/dialog/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as DialogPrimitives from '@radix-ui/react-dialog'; -import { ReactNode } from 'react'; - -interface DialogProps { - children: ReactNode; - open: boolean; - setOpen: (isOpen: boolean) => void; - title?: string; -} - -export function Dialog({ children, open, setOpen, title }: DialogProps) { - return ( - setOpen(x)}> - - - - {title &&

{title}

} - {children} -
-
-
- ); -} diff --git a/libs/ui-toolkit/src/components/form-group/index.tsx b/libs/ui-toolkit/src/components/form-group/index.tsx index 72972b6aa..2a7d13074 100644 --- a/libs/ui-toolkit/src/components/form-group/index.tsx +++ b/libs/ui-toolkit/src/components/form-group/index.tsx @@ -1,16 +1,26 @@ +import classNames from 'classnames'; import { ReactNode } from 'react'; interface FormGroupProps { children: ReactNode; - label: string; - labelFor: string; + label?: string; + labelFor?: string; + labelAlign?: 'left' | 'right'; } -export const FormGroup = ({ children, label, labelFor }: FormGroupProps) => { +export const FormGroup = ({ + children, + label, + labelFor, + labelAlign = 'left', +}: FormGroupProps) => { + const labelClasses = classNames('block text-ui mb-4', { + 'text-right': labelAlign === 'right', + }); return ( -
+
{label && ( -