Chore/657 refactor wallet and orders libs (#709)
* feat: 470 edit orders hook and @vegaprotocol/vegawallet-service-api-client@0.4.14 * fix: 470 add methods for dialog intent and title * fix: #657 rename order-list lib to orders * chore: #657 move hooks to orders lib * chore: #657 vega tx dialog used for order cancellation and order submission * chore: #657 use client subscribe and unsubscribe on reset, refactor vegatxdialog * fix: #657 revert script src=./env-config.js ending * fix: #657 format project.json * Update project.json * fix: #657 cancel all subs and async tasks in useffect cleanup function * feat: #657 styling updates on vega order dialog * fix: #657 rename set dialog open and awaiting confirmation dialog update * fix: #657 updates on cancel order id check * fix: #657 fix vega tx dialog test * fix: #657 fix cypress trading-deal-tciket test * fix: #657 fix data-testid market test * Update libs/orders/README.md Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> * Update libs/wallet/src/vega-order-transaction-dialog/vega-order-transaction-dialog.tsx Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> * Update libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> * Update libs/wallet/src/vega-order-transaction-dialog/vega-order-transaction-dialog.tsx Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> * Update libs/wallet/src/vega-order-transaction-dialog/vega-order-transaction-dialog.tsx Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> * fix: #657 remove the magic string and use the ordertype enum from types package * fix: #657 guarantee that order.id is present at this point or we need to determine the id of the order * fix: #657 fix translation in dialog * fix: #657 rename wallet types, delete ticket query, set finalized order null in submit * fix: #657 fix deal ticket steps test * fix: #657 remove settings.json * fix: #657 use order submit in orders lib * fix: #463 final modal links to block explorer * fix: #745 long/short instead of buy/sell * fix: #657 use only one vega tx dialog * fix: #657 keep ref of subscription and unsubscribe * fix: #657 hide cancelled orders * fix: #657 sub only when id set * fix: WIP: trying to unsub when order updated * fix: #745 long/short instead of buy/sell * fix: ensure observable defined * fix: #657 remove redundant test * Update libs/orders/src/lib/order-hooks/use-order-submit.ts * fix: failing test due to resizeobserver loop limit exceeded * fix: lint * fix: #657 fix test resize observer loop limit exceeded Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> Co-authored-by: Matthew Russell <mattrussell36@gmail.com> Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
parent
0291c37cfb
commit
07abc2b1eb
@ -2,25 +2,28 @@ import * as React from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import Box from '@mui/material/Box';
|
||||
import { Stepper } from '../stepper';
|
||||
import type { DealTicketQuery_market, Order } from '@vegaprotocol/deal-ticket';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ExpirySelector,
|
||||
SideSelector,
|
||||
TimeInForceSelector,
|
||||
TypeSelector,
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
useOrderSubmit,
|
||||
DealTicketAmount,
|
||||
MarketSelector,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import {
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
VegaWalletOrderTimeInForce as OrderTimeInForce,
|
||||
VegaWalletOrderType as OrderType,
|
||||
VegaTxStatus,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
useOrderSubmit,
|
||||
} from '@vegaprotocol/orders';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||
|
@ -18,21 +18,25 @@ describe('accounts', () => {
|
||||
|
||||
cy.getByTestId('tab-accounts').should('be.visible');
|
||||
cy.getByTestId('tab-accounts')
|
||||
.should('be.visible')
|
||||
.get(`[row-id='General-tEURO-null']`)
|
||||
.find('[col-id="asset.symbol"]')
|
||||
.should('have.text', 'tEURO');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.should('be.visible')
|
||||
.get(`[row-id='General-tEURO-null']`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'General');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.should('be.visible')
|
||||
.get(`[row-id='General-tEURO-null']`)
|
||||
.find('[col-id="market.name"]')
|
||||
.should('have.text', '—');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.should('be.visible')
|
||||
.get(`[row-id='General-tEURO-null']`)
|
||||
.find('[col-id="balance"]')
|
||||
.should('have.text', '1,000.00000');
|
||||
|
@ -33,7 +33,7 @@ describe('deal ticket orders', () => {
|
||||
const orderTIFDropDown = 'order-tif';
|
||||
const placeOrderBtn = 'place-order';
|
||||
const orderStatusHeader = 'order-status-header';
|
||||
const orderTransactionHash = 'tx-hash';
|
||||
const orderTransactionHash = 'tx-block-explorer';
|
||||
|
||||
before(() => {
|
||||
cy.mockGQL((req) => {
|
||||
|
@ -19,6 +19,7 @@ describe('positions', () => {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions')
|
||||
.get('[col-id="market.tradableInstrument.instrument.code"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Orders, Orders_party_orders } from '@vegaprotocol/order-list';
|
||||
import type { Orders, Orders_party_orders } from '@vegaprotocol/orders';
|
||||
import {
|
||||
OrderStatus,
|
||||
OrderTimeInForce,
|
||||
|
@ -3,7 +3,7 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MarketTradingMode } from "@vegaprotocol/types";
|
||||
import { MarketTradingMode, MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketsLanding
|
||||
@ -27,6 +27,10 @@ export interface MarketsLanding_markets {
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* timestamps for state changes in the market
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useGlobalStore } from '../stores';
|
||||
@ -12,6 +12,7 @@ const MARKETS_QUERY = gql`
|
||||
markets {
|
||||
id
|
||||
tradingMode
|
||||
state
|
||||
marketTimestamps {
|
||||
open
|
||||
}
|
||||
@ -20,13 +21,13 @@ const MARKETS_QUERY = gql`
|
||||
`;
|
||||
|
||||
const marketList = ({ markets }: MarketsLanding) =>
|
||||
sortBy(
|
||||
orderBy(
|
||||
markets?.filter(
|
||||
({ marketTimestamps, tradingMode }) =>
|
||||
marketTimestamps.open && tradingMode === MarketTradingMode.Continuous
|
||||
) || [],
|
||||
'marketTimestamps.open',
|
||||
'id'
|
||||
['state', 'marketTimestamps.open', 'id'],
|
||||
['asc', 'asc', 'asc']
|
||||
);
|
||||
|
||||
export function Index() {
|
||||
|
@ -81,6 +81,7 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
return (
|
||||
<PageQueryContainer<Market, MarketVariables>
|
||||
query={MARKET_QUERY}
|
||||
data-testid="market"
|
||||
options={{
|
||||
variables: {
|
||||
marketId,
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
DealTicketContainer,
|
||||
MarketInfoContainer,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { OrderListContainer } from '@vegaprotocol/order-list';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Web3Container } from '../../components/web3-container';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderListContainer } from '@vegaprotocol/order-list';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||
import { AnchorButton, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { WithdrawalsContainer } from './withdrawals/withdrawals-container';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type { UseFormRegister } from 'react-hook-form';
|
||||
import { OrderType } from '@vegaprotocol/wallet';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { VegaWalletOrderType } from '@vegaprotocol/wallet';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||
|
||||
export interface DealTicketAmountProps {
|
||||
orderType: OrderType;
|
||||
orderType: VegaWalletOrderType;
|
||||
step: number;
|
||||
register: UseFormRegister<Order>;
|
||||
quoteName: string;
|
||||
@ -17,9 +17,9 @@ export const DealTicketAmount = ({
|
||||
...props
|
||||
}: DealTicketAmountProps) => {
|
||||
switch (orderType) {
|
||||
case OrderType.Market:
|
||||
case VegaWalletOrderType.Market:
|
||||
return <DealTicketMarketAmount {...props} />;
|
||||
case OrderType.Limit:
|
||||
case VegaWalletOrderType.Limit:
|
||||
return <DealTicketLimitAmount {...props} />;
|
||||
default: {
|
||||
throw new Error('Invalid ticket type');
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { validateSize } from '../utils/validate-size';
|
||||
import { validateSize } from '@vegaprotocol/orders';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
|
||||
export type DealTicketLimitAmountProps = Omit<
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { VegaOrderTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { useState } from 'react';
|
||||
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import { useOrderSubmit } from '../hooks/use-order-submit';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import { useOrderSubmit } from '@vegaprotocol/orders';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export interface DealTicketManagerProps {
|
||||
market: DealTicketQuery_market;
|
||||
@ -18,43 +17,20 @@ export const DealTicketManager = ({
|
||||
}: DealTicketManagerProps) => {
|
||||
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;
|
||||
const getDialogTitle = (status?: string) => {
|
||||
switch (status) {
|
||||
case OrderStatus.Active:
|
||||
return 'Order submitted';
|
||||
case OrderStatus.Filled:
|
||||
return 'Order filled';
|
||||
case OrderStatus.PartiallyFilled:
|
||||
return 'Order partially filled';
|
||||
case OrderStatus.Parked:
|
||||
return 'Order parked';
|
||||
default:
|
||||
return 'Submission failed';
|
||||
}
|
||||
|
||||
if (finalizedOrder.status === OrderStatus.Parked) {
|
||||
return Intent.Warning;
|
||||
}
|
||||
|
||||
return Intent.Danger;
|
||||
}
|
||||
|
||||
if (status === VegaTxStatus.Requested) {
|
||||
return Intent.Warning;
|
||||
}
|
||||
|
||||
if (status === VegaTxStatus.Error) {
|
||||
return Intent.Danger;
|
||||
}
|
||||
|
||||
return Intent.None;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (transaction.status !== VegaTxStatus.Default || finalizedOrder) {
|
||||
setOrderDialogOpen(true);
|
||||
} else {
|
||||
setOrderDialogOpen(false);
|
||||
}
|
||||
}, [finalizedOrder, transaction.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children || (
|
||||
@ -69,23 +45,15 @@ export const DealTicketManager = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Dialog
|
||||
open={orderDialogOpen}
|
||||
onChange={(isOpen) => {
|
||||
setOrderDialogOpen(isOpen);
|
||||
|
||||
// If closing reset
|
||||
if (!isOpen) {
|
||||
reset();
|
||||
}
|
||||
}}
|
||||
intent={getDialogIntent(transaction.status)}
|
||||
>
|
||||
<VegaOrderTransactionDialog
|
||||
transaction={transaction}
|
||||
<VegaTransactionDialog
|
||||
key={`submit-order-dialog-${transaction.txHash}`}
|
||||
orderDialogOpen={orderDialogOpen}
|
||||
setOrderDialogOpen={setOrderDialogOpen}
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
reset={reset}
|
||||
title={getDialogTitle(finalizedOrder?.status)}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FormGroup, Input } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { validateSize } from '../utils/validate-size';
|
||||
import { validateSize } from '@vegaprotocol/orders';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
|
||||
export type DealTicketMarketAmountProps = Omit<
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
VegaWalletContext,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||
@ -64,7 +64,7 @@ it('Displays ticket defaults', () => {
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${OrderType.Market}-selected`)
|
||||
screen.getByTestId(`order-type-${VegaWalletOrderType.Market}-selected`)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY-selected')
|
||||
@ -75,7 +75,9 @@ it('Displays ticket defaults', () => {
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
String(1 / Math.pow(10, market.positionDecimalPlaces))
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
@ -101,9 +103,11 @@ it('Can edit deal ticket', async () => {
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
|
||||
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.IOC },
|
||||
target: { value: VegaWalletOrderTimeInForce.IOC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.IOC);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch to limit order
|
||||
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
|
||||
@ -113,7 +117,7 @@ it('Can edit deal ticket', async () => {
|
||||
|
||||
// Check all TIF options shown
|
||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||
Object.keys(OrderTimeInForce).length
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
@ -130,26 +134,34 @@ it('Handles TIF select box dependent on order type', () => {
|
||||
// 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
|
||||
Object.keys(VegaWalletOrderTimeInForce).length
|
||||
);
|
||||
|
||||
// Change to GTC
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.GTC },
|
||||
target: { value: VegaWalletOrderTimeInForce.GTC },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.GTC);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.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);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
// Switch tif to FOK
|
||||
fireEvent.change(screen.getByTestId('order-tif'), {
|
||||
target: { value: OrderTimeInForce.FOK },
|
||||
target: { value: VegaWalletOrderTimeInForce.FOK },
|
||||
});
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(OrderTimeInForce.FOK);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.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);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
VegaWalletOrderTimeInForce.FOK
|
||||
);
|
||||
});
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { OrderType, OrderTimeInForce } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
VegaWalletOrderType,
|
||||
VegaWalletOrderTimeInForce,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { Button, InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import { SideSelector } from './side-selector';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { useOrderValidation } from '../hooks/use-order-validation';
|
||||
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { getDefaultOrder } from '../utils/get-default-order';
|
||||
import { ExpirySelector } from './expiry-selector';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { getDefaultOrder, useOrderValidation } from '@vegaprotocol/orders';
|
||||
|
||||
export type TransactionStatus = 'default' | 'pending';
|
||||
|
||||
@ -97,8 +99,8 @@ export const DealTicket = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{orderType === OrderType.Limit &&
|
||||
orderTimeInForce === OrderTimeInForce.GTT && (
|
||||
{orderType === VegaWalletOrderType.Limit &&
|
||||
orderTimeInForce === VegaWalletOrderTimeInForce.GTT && (
|
||||
<Controller
|
||||
name="expiration"
|
||||
control={control}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderSide } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface SideSelectorProps {
|
||||
value: OrderSide;
|
||||
onSelect: (side: OrderSide) => void;
|
||||
value: VegaWalletOrderSide;
|
||||
onSelect: (side: VegaWalletOrderSide) => void;
|
||||
}
|
||||
|
||||
export const SideSelector = ({ value, onSelect }: SideSelectorProps) => {
|
||||
const toggles = Object.entries(OrderSide).map(([label, value]) => ({
|
||||
label,
|
||||
const toggles = Object.entries(VegaWalletOrderSide).map(([label, value]) => ({
|
||||
label: label === 'Buy' ? 'Long' : 'Short',
|
||||
value,
|
||||
}));
|
||||
|
||||
@ -21,7 +21,7 @@ export const SideSelector = ({ value, onSelect }: SideSelectorProps) => {
|
||||
name="order-side"
|
||||
toggles={toggles}
|
||||
checkedValue={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderSide)}
|
||||
onChange={(e) => onSelect(e.target.value as VegaWalletOrderSide)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { FormGroup, Select } from '@vegaprotocol/ui-toolkit';
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface TimeInForceSelectorProps {
|
||||
value: OrderTimeInForce;
|
||||
orderType: OrderType;
|
||||
onSelect: (tif: OrderTimeInForce) => void;
|
||||
value: VegaWalletOrderTimeInForce;
|
||||
orderType: VegaWalletOrderType;
|
||||
onSelect: (tif: VegaWalletOrderTimeInForce) => void;
|
||||
}
|
||||
|
||||
export const TimeInForceSelector = ({
|
||||
@ -14,12 +17,12 @@ export const TimeInForceSelector = ({
|
||||
onSelect,
|
||||
}: TimeInForceSelectorProps) => {
|
||||
const options =
|
||||
orderType === OrderType.Limit
|
||||
? Object.entries(OrderTimeInForce)
|
||||
: Object.entries(OrderTimeInForce).filter(
|
||||
orderType === VegaWalletOrderType.Limit
|
||||
? Object.entries(VegaWalletOrderTimeInForce)
|
||||
: Object.entries(VegaWalletOrderTimeInForce).filter(
|
||||
([_, timeInForce]) =>
|
||||
timeInForce === OrderTimeInForce.FOK ||
|
||||
timeInForce === OrderTimeInForce.IOC
|
||||
timeInForce === VegaWalletOrderTimeInForce.FOK ||
|
||||
timeInForce === VegaWalletOrderTimeInForce.IOC
|
||||
);
|
||||
|
||||
return (
|
||||
@ -27,7 +30,7 @@ export const TimeInForceSelector = ({
|
||||
<Select
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderTimeInForce)}
|
||||
onChange={(e) => onSelect(e.target.value as VegaWalletOrderTimeInForce)}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { FormGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { OrderType } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletOrderType } from '@vegaprotocol/wallet';
|
||||
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface TypeSelectorProps {
|
||||
value: OrderType;
|
||||
onSelect: (type: OrderType) => void;
|
||||
value: VegaWalletOrderType;
|
||||
onSelect: (type: VegaWalletOrderType) => void;
|
||||
}
|
||||
|
||||
const toggles = Object.entries(OrderType).map(([label, value]) => ({
|
||||
const toggles = Object.entries(VegaWalletOrderType).map(([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}));
|
||||
@ -21,7 +21,7 @@ export const TypeSelector = ({ value, onSelect }: TypeSelectorProps) => {
|
||||
name="order-type"
|
||||
toggles={toggles}
|
||||
checkedValue={value}
|
||||
onChange={(e) => onSelect(e.target.value as OrderType)}
|
||||
onChange={(e) => onSelect(e.target.value as VegaWalletOrderType)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
114
libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts
generated
114
libs/deal-ticket/src/hooks/__generated__/OrderEvent.ts
generated
@ -1,114 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { BusEventType, OrderType, OrderStatus, OrderRejectionReason } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// 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;
|
||||
/**
|
||||
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||
* number denominated in the currency of the Market. (uint64)
|
||||
*
|
||||
* Examples:
|
||||
* Currency Balance decimalPlaces Real Balance
|
||||
* GBP 100 0 GBP 100
|
||||
* GBP 100 2 GBP 1.00
|
||||
* GBP 100 4 GBP 0.01
|
||||
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||
*
|
||||
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||
*/
|
||||
decimalPlaces: number;
|
||||
/**
|
||||
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
|
||||
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
|
||||
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
|
||||
*/
|
||||
positionDecimalPlaces: number;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import type {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { OrderSide, OrderTimeInForce, OrderType } from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOrderSubmit } from './use-order-submit';
|
||||
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
|
||||
|
||||
const defaultMarket: DealTicketQuery_market = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
state: MarketState.Active,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'quote-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: {
|
||||
__typename: 'Trade',
|
||||
price: '100',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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<VegaWalletContextShape>,
|
||||
market = defaultMarket
|
||||
) {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ ...defaultWalletContext, ...context }}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
return renderHook(() => useOrderSubmit(market), { wrapper });
|
||||
}
|
||||
|
||||
it('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.txHash).toEqual(null);
|
||||
expect(result.current.transaction.error).toEqual(null);
|
||||
});
|
||||
|
||||
it('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();
|
||||
});
|
||||
|
||||
it('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();
|
||||
});
|
||||
|
||||
it('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);
|
||||
});
|
||||
|
||||
it('Should submit a correctly formatted order', async () => {
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const { result } = setup(
|
||||
{
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
},
|
||||
defaultMarket
|
||||
);
|
||||
|
||||
const order: 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: defaultMarket.id, // Market provided from hook arugment
|
||||
size: '100', // size adjusted based on positionDecimalPlaces
|
||||
side: OrderSide.Buy,
|
||||
timeInForce: OrderTimeInForce.GTT,
|
||||
price: '123456789', // Decimal removed
|
||||
expiresAt: order.expiration?.getTime() + '000000', // Nanoseconds appened
|
||||
},
|
||||
});
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useSubscription } from '@apollo/client';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
OrderType,
|
||||
useVegaWallet,
|
||||
ORDER_EVENT_SUB,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaTransaction } from '@vegaprotocol/wallet';
|
||||
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
|
||||
|
||||
export const useOrderSubmit = (market: DealTicketQuery_market) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [id, setId] = useState('');
|
||||
const [finalizedOrder, setFinalizedOrder] =
|
||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||
|
||||
// Start a subscription looking for the newly created order
|
||||
useSubscription<OrderEvent, OrderEventVariables>(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;
|
||||
}
|
||||
|
||||
return e.event.id === id;
|
||||
});
|
||||
|
||||
if (
|
||||
matchingOrderEvent &&
|
||||
matchingOrderEvent.event.__typename === 'Order'
|
||||
) {
|
||||
setFinalizedOrder(matchingOrderEvent.event);
|
||||
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.Limit && order.price
|
||||
? removeDecimal(order.price, market.decimalPlaces)
|
||||
: undefined,
|
||||
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
||||
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));
|
||||
}
|
||||
},
|
||||
[market, keypair, send]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setFinalizedOrder(null);
|
||||
setId('');
|
||||
}, [resetTransaction]);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
id,
|
||||
submit,
|
||||
reset,
|
||||
};
|
||||
};
|
@ -1,4 +1 @@
|
||||
export * from './components';
|
||||
export * from './utils/get-default-order';
|
||||
export * from './hooks/use-order-submit';
|
||||
export * from './hooks/use-order-validation';
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { OrderTimeInForce, OrderType, OrderSide } from '@vegaprotocol/wallet';
|
||||
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
|
||||
|
||||
export type Order =
|
||||
| {
|
||||
size: string;
|
||||
type: OrderType.Market;
|
||||
timeInForce: OrderTimeInForce;
|
||||
side: OrderSide;
|
||||
price?: never;
|
||||
expiration?: never;
|
||||
}
|
||||
| {
|
||||
size: string;
|
||||
type: OrderType.Limit;
|
||||
timeInForce: OrderTimeInForce;
|
||||
side: OrderSide;
|
||||
price?: string;
|
||||
expiration?: Date;
|
||||
};
|
||||
|
||||
export const getDefaultOrder = (market: DealTicketQuery_market): Order => ({
|
||||
type: OrderType.Market,
|
||||
side: OrderSide.Buy,
|
||||
timeInForce: OrderTimeInForce.IOC,
|
||||
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||
});
|
@ -3,7 +3,7 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Interval } from "@vegaprotocol/types";
|
||||
import { Interval, MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketList
|
||||
@ -108,6 +108,10 @@ export interface MarketList_markets {
|
||||
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||
*/
|
||||
decimalPlaces: number;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* marketData for the given market
|
||||
*/
|
||||
|
@ -55,6 +55,7 @@ export const MARKET_LIST_QUERY = gql`
|
||||
markets {
|
||||
id
|
||||
decimalPlaces
|
||||
state
|
||||
data {
|
||||
market {
|
||||
id
|
||||
|
@ -1,4 +1,4 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type {
|
||||
MarketList,
|
||||
MarketList_markets,
|
||||
@ -10,7 +10,7 @@ export const lastPrice = ({ candles }: MarketList_markets) =>
|
||||
: undefined;
|
||||
|
||||
export const mapDataToMarketList = ({ markets }: MarketList) =>
|
||||
sortBy(
|
||||
orderBy(
|
||||
markets?.map((m) => {
|
||||
return {
|
||||
id: m.id,
|
||||
@ -24,8 +24,9 @@ export const mapDataToMarketList = ({ markets }: MarketList) =>
|
||||
close: m.marketTimestamps.close
|
||||
? new Date(m.marketTimestamps.close).getTime()
|
||||
: null,
|
||||
state: m.state,
|
||||
};
|
||||
}) || [],
|
||||
'open',
|
||||
'id'
|
||||
['state', 'open', 'id'],
|
||||
['asc', 'asc', 'asc']
|
||||
);
|
||||
|
@ -1,7 +0,0 @@
|
||||
# order-list
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test order-list` to execute the unit tests via [Jest](https://jestjs.io).
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/order-list",
|
||||
"version": "0.0.1"
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { OrderStatus, OrderType } from '@vegaprotocol/types';
|
||||
import type { VegaTxState, Order } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import type { CancelDialogProps } from './cancel-dialog';
|
||||
import { CancelDialog } from './cancel-dialog';
|
||||
|
||||
describe('CancelDialog', () => {
|
||||
let defaultProps: CancelDialogProps;
|
||||
|
||||
beforeEach(() => {
|
||||
defaultProps = {
|
||||
orderDialogOpen: true,
|
||||
setOrderDialogOpen: () => false,
|
||||
transaction: {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
},
|
||||
finalizedOrder: {
|
||||
status: OrderStatus.Cancelled,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
},
|
||||
reset: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
it('should render when an order is successfully cancelled', () => {
|
||||
render(<CancelDialog {...defaultProps} />);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Order cancelled'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when an order is not successfully cancelled', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
const finalizedOrder: Order = {
|
||||
status: OrderStatus.Active,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
const propsForTest = {
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
};
|
||||
|
||||
render(<CancelDialog {...defaultProps} {...propsForTest} />);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Cancellation failed'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,65 +0,0 @@
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Order, VegaTxState } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus, VegaOrderTransactionDialog } from '@vegaprotocol/wallet';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export interface CancelDialogProps {
|
||||
orderDialogOpen: boolean;
|
||||
setOrderDialogOpen: (isOpen: boolean) => void;
|
||||
finalizedOrder: Order | null;
|
||||
transaction: VegaTxState;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const CancelDialog = ({
|
||||
orderDialogOpen,
|
||||
setOrderDialogOpen,
|
||||
finalizedOrder,
|
||||
transaction,
|
||||
reset,
|
||||
}: CancelDialogProps) => {
|
||||
const getDialogIntent = () => {
|
||||
if (finalizedOrder) {
|
||||
if (finalizedOrder.status === OrderStatus.Cancelled) {
|
||||
return Intent.Success;
|
||||
}
|
||||
|
||||
return Intent.Danger;
|
||||
}
|
||||
return Intent.None;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (transaction.status !== VegaTxStatus.Default || finalizedOrder) {
|
||||
setOrderDialogOpen(true);
|
||||
} else {
|
||||
setOrderDialogOpen(false);
|
||||
}
|
||||
}, [finalizedOrder, setOrderDialogOpen, transaction.status]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={orderDialogOpen}
|
||||
onChange={(isOpen) => {
|
||||
setOrderDialogOpen(isOpen);
|
||||
|
||||
// If closing reset
|
||||
if (!isOpen) {
|
||||
reset();
|
||||
}
|
||||
}}
|
||||
intent={getDialogIntent()}
|
||||
>
|
||||
<VegaOrderTransactionDialog
|
||||
transaction={transaction}
|
||||
finalizedOrder={finalizedOrder}
|
||||
title={
|
||||
finalizedOrder?.status === OrderStatus.Cancelled
|
||||
? 'Order cancelled'
|
||||
: 'Cancellation failed'
|
||||
}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './cancel-dialog';
|
@ -1 +0,0 @@
|
||||
export * from './components';
|
7
libs/orders/README.md
Normal file
7
libs/orders/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Orders
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test orders` to execute the unit tests via [Jest](https://jestjs.io).
|
@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
displayName: 'order-list',
|
||||
displayName: 'orders',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/order-list',
|
||||
coverageDirectory: '../../coverage/libs/orders',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
4
libs/orders/package.json
Normal file
4
libs/orders/package.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/orders",
|
||||
"version": "0.0.1"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"root": "libs/order-list",
|
||||
"sourceRoot": "libs/order-list/src",
|
||||
"root": "libs/orders",
|
||||
"sourceRoot": "libs/orders/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
@ -8,16 +8,16 @@
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/order-list",
|
||||
"tsConfig": "libs/order-list/tsconfig.lib.json",
|
||||
"project": "libs/order-list/package.json",
|
||||
"entryFile": "libs/order-list/src/index.ts",
|
||||
"outputPath": "dist/libs/orders",
|
||||
"tsConfig": "libs/orders/tsconfig.lib.json",
|
||||
"project": "libs/orders/package.json",
|
||||
"entryFile": "libs/orders/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/order-list/README.md",
|
||||
"glob": "libs/orders/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
@ -28,14 +28,14 @@
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/order-list/**/*.{ts,tsx,js,jsx}"]
|
||||
"lintFilePatterns": ["libs/orders/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/order-list"],
|
||||
"outputs": ["coverage/libs/orders"],
|
||||
"options": {
|
||||
"jestConfig": "libs/order-list/jest.config.js",
|
||||
"jestConfig": "libs/orders/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
@ -45,7 +45,7 @@
|
||||
"uiFramework": "@storybook/react",
|
||||
"port": 4400,
|
||||
"config": {
|
||||
"configFolder": "libs/order-list/.storybook"
|
||||
"configFolder": "libs/orders/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
@ -59,9 +59,9 @@
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"outputPath": "dist/storybook/order-list",
|
||||
"outputPath": "dist/storybook/orders",
|
||||
"config": {
|
||||
"configFolder": "libs/order-list/.storybook"
|
||||
"configFolder": "libs/orders/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
@ -1,5 +1,4 @@
|
||||
export * from './__generated__';
|
||||
export * from './cancel-order-dialog';
|
||||
export * from './mocks';
|
||||
export * from './order-data-provider';
|
||||
export * from './order-list';
|
@ -22,7 +22,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
|
||||
return merge(defaultResult, override);
|
||||
};
|
||||
|
||||
export const generateOrder = (partialOrder: Partial<Orders_party_orders>) =>
|
||||
export const generateOrder = (partialOrder?: Partial<Orders_party_orders>) =>
|
||||
merge(
|
||||
{
|
||||
__typename: 'Order',
|
@ -70,6 +70,7 @@ export const OrderListManager = ({ partyId }: OrderListManagerProps) => {
|
||||
return sortOrders(data);
|
||||
}, [data]);
|
||||
|
||||
// We can set <OrderList showCancelled={false} to hide cancelled orders
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={orders}>
|
||||
<OrderList ref={gridRef} data={orders} />
|
@ -2,7 +2,7 @@ import { act, render, screen } from '@testing-library/react';
|
||||
import { addDecimal, getDateTimeFormat } from '@vegaprotocol/react-helpers';
|
||||
import type { Orders_party_orders } from '../__generated__/Orders';
|
||||
import { OrderStatus, OrderRejectionReason } from '@vegaprotocol/types';
|
||||
import { OrderList } from './order-list';
|
||||
import { OrderListTable } from './order-list';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
@ -16,13 +16,13 @@ const generateJsx = (
|
||||
return (
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<OrderList data={orders} />
|
||||
<OrderListTable data={orders} cancel={jest.fn()} />
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('OrderList', () => {
|
||||
describe('OrderListTable', () => {
|
||||
it('should show no orders message', async () => {
|
||||
await act(async () => {
|
||||
render(generateJsx([]));
|
@ -1,10 +1,9 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { OrderType, OrderStatus } from '@vegaprotocol/types';
|
||||
import { OrderList, OrderListTable } from './order-list';
|
||||
import { CancelDialog } from '../cancel-order-dialog';
|
||||
import { useState } from 'react';
|
||||
import type { VegaTxState, Order } from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import type { Order, VegaTxState } from '@vegaprotocol/wallet';
|
||||
import { VegaTransactionDialog, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { generateOrdersArray } from '../mocks';
|
||||
|
||||
export default {
|
||||
@ -38,7 +37,7 @@ const Template2: Story = (args) => {
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
market: { name: 'ETH/DAI (30 Jun 2022)', decimalPlaces: 5 },
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
const reset = () => null;
|
||||
@ -47,12 +46,13 @@ const Template2: Story = (args) => {
|
||||
<div style={{ height: 1000 }}>
|
||||
<OrderListTable data={args.data} cancel={cancel} />
|
||||
</div>
|
||||
<CancelDialog
|
||||
<VegaTransactionDialog
|
||||
orderDialogOpen={open}
|
||||
setOrderDialogOpen={setOpen}
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
reset={reset}
|
||||
title={'Order cancelled'}
|
||||
/>
|
||||
</>
|
||||
);
|
@ -9,27 +9,45 @@ import type {
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { useOrderCancel } from '@vegaprotocol/wallet';
|
||||
import { CancelDialog } from '../cancel-order-dialog/cancel-dialog';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
||||
import { VegaTransactionDialog } from '@vegaprotocol/wallet';
|
||||
|
||||
interface OrderListProps {
|
||||
data: Orders_party_orders[] | null;
|
||||
showCancelled?: boolean;
|
||||
}
|
||||
|
||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
({ data }, ref) => {
|
||||
const [orderDialogOpen, setOrderDialogOpen] = useState(false);
|
||||
const { transaction, finalizedOrder, reset, cancel } = useOrderCancel();
|
||||
({ data, showCancelled = true }, ref) => {
|
||||
const [cancelOrderDialogOpen, setCancelOrderDialogOpen] = useState(false);
|
||||
const { transaction, updatedOrder, reset, cancel } = useOrderCancel();
|
||||
const ordersData = showCancelled
|
||||
? data
|
||||
: data?.filter((o) => o.status !== OrderStatus.Cancelled) || null;
|
||||
const getDialogTitle = (status?: string) => {
|
||||
switch (status) {
|
||||
case OrderStatus.Cancelled:
|
||||
return 'Order cancelled';
|
||||
case OrderStatus.Rejected:
|
||||
return 'Order rejected';
|
||||
case OrderStatus.Expired:
|
||||
return 'Order expired';
|
||||
default:
|
||||
return 'Cancellation failed';
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<OrderListTable data={data} cancel={cancel} ref={ref} />
|
||||
<CancelDialog
|
||||
orderDialogOpen={orderDialogOpen}
|
||||
setOrderDialogOpen={setOrderDialogOpen}
|
||||
finalizedOrder={finalizedOrder}
|
||||
<OrderListTable data={ordersData} cancel={cancel} ref={ref} />
|
||||
<VegaTransactionDialog
|
||||
key={`cancel-order-dialog-${transaction.txHash}`}
|
||||
orderDialogOpen={cancelOrderDialogOpen}
|
||||
setOrderDialogOpen={setCancelOrderDialogOpen}
|
||||
finalizedOrder={updatedOrder}
|
||||
transaction={transaction}
|
||||
reset={reset}
|
||||
title={getDialogTitle(updatedOrder?.status)}
|
||||
/>
|
||||
</>
|
||||
);
|
4
libs/orders/src/lib/index.ts
Normal file
4
libs/orders/src/lib/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './components';
|
||||
export * from './order-hooks';
|
||||
export * from './utils';
|
||||
export * from './market';
|
13
libs/orders/src/lib/market.ts
Normal file
13
libs/orders/src/lib/market.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
|
||||
export interface Market {
|
||||
__typename?: string;
|
||||
id: string;
|
||||
positionDecimalPlaces: number;
|
||||
state: MarketState;
|
||||
decimalPlaces: number;
|
||||
tradingMode: MarketTradingMode;
|
||||
tradableInstrument?: any;
|
||||
depth?: any;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { BusEventType, OrderType, OrderStatus, OrderRejectionReason } from "@vegaprotocol/types";
|
||||
import { BusEventType, OrderType, OrderStatus, OrderRejectionReason, OrderTimeInForce, Side } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL subscription operation: OrderEvent
|
||||
@ -68,6 +68,14 @@ export interface OrderEvent_busEvents_event_Order {
|
||||
* The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64)
|
||||
*/
|
||||
price: string;
|
||||
/**
|
||||
* The timeInForce of order (determines how and if it executes, and whether it persists on the book)
|
||||
*/
|
||||
timeInForce: OrderTimeInForce;
|
||||
/**
|
||||
* Whether the order is to buy or sell
|
||||
*/
|
||||
side: Side;
|
||||
/**
|
||||
* The market the order is trading on (probably stored internally as a hash of the market details)
|
||||
*/
|
@ -1,3 +1,5 @@
|
||||
export * from './__generated__';
|
||||
export * from './order-event-query';
|
||||
export * from './use-order-cancel';
|
||||
export * from './use-order-submit';
|
||||
export * from './use-order-validation';
|
@ -13,6 +13,8 @@ export const ORDER_EVENT_SUB = gql`
|
||||
createdAt
|
||||
size
|
||||
price
|
||||
timeInForce
|
||||
side
|
||||
market {
|
||||
name
|
||||
decimalPlaces
|
@ -1,12 +1,19 @@
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { MarketState, MarketTradingMode, OrderType } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { VegaKeyExtended, VegaWalletContextShape } from '../context';
|
||||
import { VegaWalletContext } from '../context';
|
||||
import { VegaTxStatus } from '../use-vega-transaction';
|
||||
import type { Order } from '../vega-order-transaction-dialog';
|
||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useOrderCancel } from './use-order-cancel';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEvent_busEvents,
|
||||
} from './__generated__/OrderEvent';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
|
||||
const defaultMarket = {
|
||||
__typename: 'Market',
|
||||
@ -46,8 +53,79 @@ const defaultWalletContext = {
|
||||
};
|
||||
|
||||
function setup(context?: Partial<VegaWalletContextShape>) {
|
||||
const mocks: MockedResponse<OrderEvent> = {
|
||||
request: {
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: {
|
||||
partyId: context?.keypair?.pub || '',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
busEvents: [
|
||||
{
|
||||
type: 'Order',
|
||||
event: {
|
||||
type: 'Limit',
|
||||
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
|
||||
status: 'Active',
|
||||
rejectionReason: null,
|
||||
createdAt: '2022-07-05T14:25:47.815283706Z',
|
||||
size: '10',
|
||||
price: '300000',
|
||||
timeInForce: 'GTC',
|
||||
side: 'Buy',
|
||||
market: {
|
||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||
decimalPlaces: 5,
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Order',
|
||||
},
|
||||
__typename: 'BusEvent',
|
||||
} as OrderEvent_busEvents,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterMocks: MockedResponse<OrderEvent> = {
|
||||
request: {
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: {
|
||||
partyId: context?.keypair?.pub || '',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
busEvents: [
|
||||
{
|
||||
type: 'Order',
|
||||
event: {
|
||||
type: 'Limit',
|
||||
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
|
||||
status: 'Active',
|
||||
rejectionReason: null,
|
||||
createdAt: '2022-07-05T14:25:47.815283706Z',
|
||||
size: '10',
|
||||
price: '300000',
|
||||
timeInForce: 'GTC',
|
||||
side: 'Buy',
|
||||
market: {
|
||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||
decimalPlaces: 5,
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Order',
|
||||
},
|
||||
__typename: 'BusEvent',
|
||||
} as OrderEvent_busEvents,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider>
|
||||
<MockedProvider mocks={[mocks, filterMocks]}>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ ...defaultWalletContext, ...context }}
|
||||
>
|
||||
@ -55,6 +133,7 @@ function setup(context?: Partial<VegaWalletContextShape>) {
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
return renderHook(() => useOrderCancel(), { wrapper });
|
||||
}
|
||||
|
||||
@ -70,7 +149,7 @@ describe('useOrderCancel', () => {
|
||||
|
||||
it('should not sendTx if no keypair', async () => {
|
||||
const mockSendTx = jest.fn();
|
||||
const order: Order = {
|
||||
const order = {
|
||||
type: OrderType.Market,
|
||||
size: '10',
|
||||
price: '1234567.89',
|
||||
@ -94,7 +173,7 @@ describe('useOrderCancel', () => {
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const order: Order = {
|
||||
const order = {
|
||||
type: OrderType.Limit,
|
||||
size: '10',
|
||||
price: '1234567.89',
|
117
libs/orders/src/lib/order-hooks/use-order-cancel.tsx
Normal file
117
libs/orders/src/lib/order-hooks/use-order-cancel.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useVegaWallet, useVegaTransaction } from '@vegaprotocol/wallet';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from './__generated__/OrderEvent';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { determineId } from '@vegaprotocol/react-helpers';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
|
||||
export const useOrderCancel = () => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [updatedOrder, setUpdatedOrder] =
|
||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||
const client = useApolloClient();
|
||||
const subRef = useRef<Subscription | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
subRef.current?.unsubscribe();
|
||||
setUpdatedOrder(null);
|
||||
resetTransaction();
|
||||
};
|
||||
}, [resetTransaction]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setUpdatedOrder(null);
|
||||
subRef.current?.unsubscribe();
|
||||
}, [resetTransaction]);
|
||||
|
||||
const cancel = useCallback(
|
||||
async (order) => {
|
||||
if (!keypair) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
OrderStatus.Cancelled,
|
||||
OrderStatus.Rejected,
|
||||
OrderStatus.Expired,
|
||||
OrderStatus.Filled,
|
||||
OrderStatus.Stopped,
|
||||
].includes(order.status)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUpdatedOrder(null);
|
||||
|
||||
try {
|
||||
const res = await send({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderCancellation: {
|
||||
orderId: order.id,
|
||||
marketId: order.market.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.signature) {
|
||||
const resId = order.id ?? determineId(res.signature);
|
||||
setUpdatedOrder(null);
|
||||
// setId(resId);
|
||||
if (resId) {
|
||||
// Start a subscription looking for the newly created order
|
||||
subRef.current = client
|
||||
.subscribe<OrderEvent, OrderEventVariables>({
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: { partyId: keypair?.pub || '' },
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (!data?.busEvents?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No types available for the subscription result
|
||||
const matchingOrderEvent = data.busEvents.find((e) => {
|
||||
if (e.event.__typename !== 'Order') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return e.event.id === resId;
|
||||
});
|
||||
|
||||
if (
|
||||
matchingOrderEvent &&
|
||||
matchingOrderEvent.event.__typename === 'Order'
|
||||
) {
|
||||
setUpdatedOrder(matchingOrderEvent.event);
|
||||
subRef.current?.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[client, keypair, send]
|
||||
);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
updatedOrder,
|
||||
cancel,
|
||||
reset,
|
||||
};
|
||||
};
|
224
libs/orders/src/lib/order-hooks/use-order-submit.spec.tsx
Normal file
224
libs/orders/src/lib/order-hooks/use-order-submit.spec.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import type { Order } from '../utils';
|
||||
import type {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
VegaWalletOrderSide,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOrderSubmit } from './use-order-submit';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEvent_busEvents,
|
||||
} from './__generated__/OrderEvent';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { Market } from '../market';
|
||||
|
||||
const defaultMarket = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: MarketTradingMode.Continuous,
|
||||
state: MarketState.Active,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'quote-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
depth: {
|
||||
__typename: 'MarketDepth',
|
||||
lastTrade: {
|
||||
__typename: 'Trade',
|
||||
price: '100',
|
||||
},
|
||||
},
|
||||
} as Market;
|
||||
|
||||
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<VegaWalletContextShape>,
|
||||
market = defaultMarket
|
||||
) {
|
||||
const mocks: MockedResponse<OrderEvent> = {
|
||||
request: {
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: {
|
||||
partyId: context?.keypair?.pub || '',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
busEvents: [
|
||||
{
|
||||
type: 'Order',
|
||||
event: {
|
||||
type: 'Limit',
|
||||
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
|
||||
status: 'Active',
|
||||
rejectionReason: null,
|
||||
createdAt: '2022-07-05T14:25:47.815283706Z',
|
||||
size: '10',
|
||||
price: '300000',
|
||||
timeInForce: 'GTC',
|
||||
side: 'Buy',
|
||||
market: {
|
||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||
decimalPlaces: 5,
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Order',
|
||||
},
|
||||
__typename: 'BusEvent',
|
||||
} as OrderEvent_busEvents,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const filterMocks: MockedResponse<OrderEvent> = {
|
||||
request: {
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: {
|
||||
partyId: context?.keypair?.pub || '',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
busEvents: [
|
||||
{
|
||||
type: 'Order',
|
||||
event: {
|
||||
type: 'Limit',
|
||||
id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
|
||||
status: 'Active',
|
||||
rejectionReason: null,
|
||||
createdAt: '2022-07-05T14:25:47.815283706Z',
|
||||
size: '10',
|
||||
price: '300000',
|
||||
timeInForce: 'GTC',
|
||||
side: 'Buy',
|
||||
market: {
|
||||
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||
decimalPlaces: 5,
|
||||
__typename: 'Market',
|
||||
},
|
||||
__typename: 'Order',
|
||||
},
|
||||
__typename: 'BusEvent',
|
||||
} as OrderEvent_busEvents,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider mocks={[mocks, filterMocks]}>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ ...defaultWalletContext, ...context }}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
return renderHook(() => useOrderSubmit(market), { wrapper });
|
||||
}
|
||||
|
||||
describe('useOrderSubmit', () => {
|
||||
it('should submit a correctly formatted order', async () => {
|
||||
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve({}));
|
||||
const keypair = {
|
||||
pub: '0x123',
|
||||
} as VegaKeyExtended;
|
||||
const { result } = setup({
|
||||
sendTx: mockSendTx,
|
||||
keypairs: [keypair],
|
||||
keypair,
|
||||
});
|
||||
|
||||
const order: Order = {
|
||||
type: VegaWalletOrderType.Limit,
|
||||
size: '10',
|
||||
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
||||
side: VegaWalletOrderSide.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: VegaWalletOrderType.Limit,
|
||||
marketId: defaultMarket.id, // Market provided from hook argument
|
||||
size: '100', // size adjusted based on positionDecimalPlaces
|
||||
side: VegaWalletOrderSide.Buy,
|
||||
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
||||
price: '123456789', // Decimal removed
|
||||
expiresAt: order.expiration?.getTime() + '000000', // Nanoseconds append
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('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.txHash).toEqual(null);
|
||||
expect(result.current.transaction.error).toEqual(null);
|
||||
});
|
||||
|
||||
it('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();
|
||||
});
|
||||
|
||||
it('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();
|
||||
});
|
||||
});
|
123
libs/orders/src/lib/order-hooks/use-order-submit.ts
Normal file
123
libs/orders/src/lib/order-hooks/use-order-submit.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from './__generated__';
|
||||
import { VegaWalletOrderType, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useVegaTransaction } from '@vegaprotocol/wallet';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { Market } from '../market';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
|
||||
export const useOrderSubmit = (market: Market) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [finalizedOrder, setFinalizedOrder] =
|
||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||
const client = useApolloClient();
|
||||
const subRef = useRef<Subscription | null>(null);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setFinalizedOrder(null);
|
||||
subRef.current?.unsubscribe();
|
||||
}, [resetTransaction]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resetTransaction();
|
||||
setFinalizedOrder(null);
|
||||
subRef.current?.unsubscribe();
|
||||
};
|
||||
}, [resetTransaction]);
|
||||
|
||||
const submit = useCallback(
|
||||
async (order: Order) => {
|
||||
if (!keypair || !order.side) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFinalizedOrder(null);
|
||||
try {
|
||||
const res = await send({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderSubmission: {
|
||||
marketId: market.id,
|
||||
price:
|
||||
order.type === VegaWalletOrderType.Limit && order.price
|
||||
? removeDecimal(order.price, market.decimalPlaces)
|
||||
: undefined,
|
||||
size: removeDecimal(order.size, market.positionDecimalPlaces),
|
||||
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) {
|
||||
const resId = determineId(res.signature);
|
||||
if (resId) {
|
||||
// Start a subscription looking for the newly created order
|
||||
subRef.current = client
|
||||
.subscribe<OrderEvent, OrderEventVariables>({
|
||||
query: ORDER_EVENT_SUB,
|
||||
variables: { partyId: keypair?.pub || '' },
|
||||
})
|
||||
.subscribe(({ data }) => {
|
||||
if (!data?.busEvents?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No types available for the subscription result
|
||||
const matchingOrderEvent = data.busEvents.find((e) => {
|
||||
if (e.event.__typename !== 'Order') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return e.event.id === resId;
|
||||
});
|
||||
|
||||
if (
|
||||
matchingOrderEvent &&
|
||||
matchingOrderEvent.event.__typename === 'Order'
|
||||
) {
|
||||
setFinalizedOrder(matchingOrderEvent.event);
|
||||
subRef.current?.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[
|
||||
client,
|
||||
keypair,
|
||||
send,
|
||||
market.id,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
submit,
|
||||
reset,
|
||||
};
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
@ -11,12 +11,12 @@ import type {
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { ValidationProps } from './use-order-validation';
|
||||
import { useOrderValidation } from './use-order-validation';
|
||||
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
|
||||
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||
import type { Market } from '../market';
|
||||
|
||||
jest.mock('@vegaprotocol/wallet');
|
||||
|
||||
const market: DealTicketQuery_market = {
|
||||
const market: Market = {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
@ -59,8 +59,8 @@ const defaultWalletContext = {
|
||||
const defaultOrder = {
|
||||
market,
|
||||
step: 0.1,
|
||||
orderType: OrderType.Market,
|
||||
orderTimeInForce: OrderTimeInForce.FOK,
|
||||
orderType: VegaWalletOrderType.Market,
|
||||
orderTimeInForce: VegaWalletOrderTimeInForce.FOK,
|
||||
};
|
||||
|
||||
const ERROR = {
|
||||
@ -71,7 +71,7 @@ const ERROR = {
|
||||
MARKET_WAITING: 'Market is not active yet',
|
||||
MARKET_CONTINUOUS_LIMIT:
|
||||
'Only limit orders are permitted when market is in auction',
|
||||
MARKET_COUNTINUOUS_TIF:
|
||||
MARKET_CONTINUOUS_TIF:
|
||||
'Only GTT, GTC and GFA are permitted when market is in auction',
|
||||
FIELD_SIZE_REQ: 'An amount needs to be provided',
|
||||
FIELD_SIZE_MIN: `The amount cannot be lower than "${defaultOrder.step}"`,
|
||||
@ -135,7 +135,7 @@ it.each`
|
||||
async ({ tradingMode, errorMessage }) => {
|
||||
const { result } = setup({
|
||||
market: { ...defaultOrder.market, tradingMode },
|
||||
orderType: OrderType.Market,
|
||||
orderType: VegaWalletOrderType.Market,
|
||||
});
|
||||
expect(result.current).toEqual(errorMessage);
|
||||
}
|
||||
@ -143,21 +143,21 @@ it.each`
|
||||
|
||||
it.each`
|
||||
tradingMode | orderTimeInForce | errorMessage
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.FOK} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.IOC} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${OrderTimeInForce.GFN} | ${ERROR.MARKET_COUNTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${VegaWalletOrderTimeInForce.FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${VegaWalletOrderTimeInForce.FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${VegaWalletOrderTimeInForce.FOK} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${VegaWalletOrderTimeInForce.IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${VegaWalletOrderTimeInForce.IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${VegaWalletOrderTimeInForce.IOC} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.BatchAuction} | ${VegaWalletOrderTimeInForce.GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.MonitoringAuction} | ${VegaWalletOrderTimeInForce.GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
${MarketTradingMode.OpeningAuction} | ${VegaWalletOrderTimeInForce.GFN} | ${ERROR.MARKET_CONTINUOUS_TIF}
|
||||
`(
|
||||
'Returns an error message when submitting a limit order with a "$orderTimeInForce" value to a "$tradingMode" market',
|
||||
async ({ tradingMode, orderTimeInForce, errorMessage }) => {
|
||||
const { result } = setup({
|
||||
market: { ...defaultOrder.market, tradingMode },
|
||||
orderType: OrderType.Limit,
|
||||
orderType: VegaWalletOrderType.Limit,
|
||||
orderTimeInForce,
|
||||
});
|
||||
expect(result.current).toEqual(errorMessage);
|
@ -1,19 +1,19 @@
|
||||
import type { FieldErrors } from 'react-hook-form';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { Order } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaWallet,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
VegaWalletOrderTimeInForce as OrderTimeInForce,
|
||||
VegaWalletOrderType as OrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Order } from '../utils/get-default-order';
|
||||
import type { Market } from '../market';
|
||||
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
|
||||
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
|
||||
|
||||
export type ValidationProps = {
|
||||
step: number;
|
||||
market: DealTicketQuery_market;
|
||||
market: Market;
|
||||
orderType: OrderType;
|
||||
orderTimeInForce: OrderTimeInForce;
|
||||
fieldErrors?: FieldErrors<Order>;
|
32
libs/orders/src/lib/utils/get-default-order.ts
Normal file
32
libs/orders/src/lib/utils/get-default-order.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
VegaWalletOrderSide,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { Market } from '../market';
|
||||
|
||||
export type Order =
|
||||
| {
|
||||
size: string;
|
||||
type: VegaWalletOrderType.Market;
|
||||
timeInForce: VegaWalletOrderTimeInForce;
|
||||
side: VegaWalletOrderSide;
|
||||
price?: never;
|
||||
expiration?: never;
|
||||
}
|
||||
| {
|
||||
size: string;
|
||||
type: VegaWalletOrderType.Limit;
|
||||
timeInForce: VegaWalletOrderTimeInForce;
|
||||
side: VegaWalletOrderSide;
|
||||
price?: string;
|
||||
expiration?: Date;
|
||||
};
|
||||
|
||||
export const getDefaultOrder = (market: Market): Order => ({
|
||||
type: VegaWalletOrderType.Market,
|
||||
side: VegaWalletOrderSide.Buy,
|
||||
timeInForce: VegaWalletOrderTimeInForce.IOC,
|
||||
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||
});
|
2
libs/orders/src/lib/utils/index.ts
Normal file
2
libs/orders/src/lib/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './get-default-order';
|
||||
export * from './validate-size';
|
@ -7,7 +7,7 @@ import {
|
||||
import { LocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { WALLET_CONFIG } from '../storage-keys';
|
||||
import type { VegaConnector } from './vega-connector';
|
||||
import type { TransactionSubmission } from '../types';
|
||||
import type { TransactionSubmission } from '../wallet-types';
|
||||
|
||||
// Perhaps there should be a default ConnectorConfig that others can extend off. Do all connectors
|
||||
// need to use local storage, I don't think so...
|
||||
|
@ -2,7 +2,7 @@ import type {
|
||||
VegaKey,
|
||||
TransactionResponse,
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
import type { TransactionSubmission } from '../types';
|
||||
import type { TransactionSubmission } from '../wallet-types';
|
||||
|
||||
type ErrorResponse =
|
||||
| {
|
||||
|
@ -4,7 +4,7 @@ import type {
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
import { createContext } from 'react';
|
||||
import type { VegaConnector } from './connectors';
|
||||
import type { TransactionSubmission } from './types';
|
||||
import type { TransactionSubmission } from './wallet-types';
|
||||
|
||||
export type SendTxError =
|
||||
| {
|
||||
|
@ -2,11 +2,10 @@ export * from './context';
|
||||
export * from './use-vega-wallet';
|
||||
export * from './connectors';
|
||||
export * from './storage-keys';
|
||||
export * from './types';
|
||||
export * from './wallet-types';
|
||||
export * from './use-vega-transaction';
|
||||
export * from './use-eager-connect';
|
||||
export * from './manage-dialog';
|
||||
export * from './vega-order-transaction-dialog';
|
||||
export * from './vega-transaction-dialog';
|
||||
export * from './provider';
|
||||
export * from './order-hooks';
|
||||
export * from './connect-dialog';
|
||||
|
1
libs/wallet/src/manage-dialog/index.ts
Normal file
1
libs/wallet/src/manage-dialog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './manage-dialog';
|
@ -1,9 +1,9 @@
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { VegaWalletContext } from './context';
|
||||
import type { VegaWalletContextShape, VegaKeyExtended } from './context';
|
||||
import type { VegaManageDialogProps } from './manage-dialog';
|
||||
import { VegaManageDialog } from './manage-dialog';
|
||||
import { VegaWalletContext } from '../context';
|
||||
import type { VegaWalletContextShape, VegaKeyExtended } from '../context';
|
||||
import type { VegaManageDialogProps } from '.';
|
||||
import { VegaManageDialog } from '.';
|
||||
|
||||
let props: VegaManageDialogProps;
|
||||
let context: Partial<VegaWalletContextShape>;
|
@ -6,7 +6,7 @@ import {
|
||||
Intent,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '.';
|
||||
import { useVegaWallet } from '..';
|
||||
|
||||
export interface VegaManageDialogProps {
|
||||
dialogOpen: boolean;
|
@ -1,83 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { determineId } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import { useVegaTransaction } from '../use-vega-transaction';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { useSubscription } from '@apollo/client';
|
||||
import type {
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
OrderEvent_busEvents_event_Order,
|
||||
} from './__generated__/OrderEvent';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
import * as Sentry from '@sentry/react';
|
||||
|
||||
export const useOrderCancel = () => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { send, transaction, reset: resetTransaction } = useVegaTransaction();
|
||||
const [updatedOrder, setUpdatedOrder] =
|
||||
useState<OrderEvent_busEvents_event_Order | null>(null);
|
||||
const [id, setId] = useState('');
|
||||
|
||||
// Start a subscription looking for the newly created order
|
||||
useSubscription<OrderEvent, OrderEventVariables>(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[0].event;
|
||||
|
||||
if (matchingOrderEvent && matchingOrderEvent.__typename === 'Order') {
|
||||
setUpdatedOrder(matchingOrderEvent);
|
||||
resetTransaction();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const cancel = useCallback(
|
||||
async (order) => {
|
||||
if (!keypair) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUpdatedOrder(null);
|
||||
|
||||
try {
|
||||
const res = await send({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderCancellation: {
|
||||
orderId: order.id,
|
||||
marketId: order.market.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.signature) {
|
||||
setId(determineId(res.signature));
|
||||
}
|
||||
return res;
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[keypair, send]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setUpdatedOrder(null);
|
||||
setId('');
|
||||
}, [resetTransaction]);
|
||||
|
||||
return {
|
||||
transaction,
|
||||
finalizedOrder: updatedOrder,
|
||||
id,
|
||||
cancel,
|
||||
reset,
|
||||
};
|
||||
};
|
@ -5,7 +5,7 @@ import type { VegaKeyExtended, VegaWalletContextShape } from '.';
|
||||
import type { VegaConnector } from './connectors/vega-connector';
|
||||
import { VegaWalletContext } from './context';
|
||||
import { WALLET_KEY } from './storage-keys';
|
||||
import type { TransactionSubmission } from './types';
|
||||
import type { TransactionSubmission } from './wallet-types';
|
||||
|
||||
interface VegaWalletProviderProps {
|
||||
children: ReactNode;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { TransactionSubmission } from './types';
|
||||
import type { TransactionSubmission } from './wallet-types';
|
||||
import { useVegaWallet } from './use-vega-wallet';
|
||||
import type { SendTxError } from './context';
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './vega-order-transaction-dialog';
|
@ -1,159 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { OrderStatus, OrderType } from '@vegaprotocol/types';
|
||||
import type { VegaTxState } from '../use-vega-transaction';
|
||||
import { VegaTxStatus } from '../use-vega-transaction';
|
||||
import type { Order } from './vega-order-transaction-dialog';
|
||||
import { VegaOrderTransactionDialog } from './vega-order-transaction-dialog';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_EXPLORER_URL: 'https://test.explorer.vega.network',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('VegaOrderTransactionDialog', () => {
|
||||
it('should render when an order is successful', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
const finalizedOrder: Order = {
|
||||
status: OrderStatus.Active,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Order placed'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when transaction is requested', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Requested,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
const finalizedOrder: Order = {
|
||||
status: OrderStatus.Active,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Confirm transaction in wallet'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when transaction has error', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Error,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
const finalizedOrder: Order = {
|
||||
status: OrderStatus.Active,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Order rejected by wallet'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when an order is rejected', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
const finalizedOrder: Order = {
|
||||
status: OrderStatus.Rejected,
|
||||
rejectionReason: null,
|
||||
size: '10',
|
||||
price: '1000',
|
||||
market: null,
|
||||
type: OrderType.Limit,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={finalizedOrder}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Order failed'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when pending consensus', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Error,
|
||||
error: null,
|
||||
txHash: null,
|
||||
signature: null,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={null}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Order rejected by wallet'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render awaiting network confirmation and add link to tx in block explorer', () => {
|
||||
const transaction: VegaTxState = {
|
||||
status: VegaTxStatus.Default,
|
||||
error: null,
|
||||
txHash: 'TxHash',
|
||||
signature: null,
|
||||
};
|
||||
render(
|
||||
<VegaOrderTransactionDialog
|
||||
finalizedOrder={null}
|
||||
transaction={transaction}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('order-status-header')).toHaveTextContent(
|
||||
'Awaiting network confirmation'
|
||||
);
|
||||
expect(screen.getByTestId('tx-hash')).toHaveTextContent('TxHash');
|
||||
expect(screen.getByTestId('tx-hash')).toHaveAttribute(
|
||||
'href',
|
||||
'https://test.explorer.vega.network/txs/0xTxHash'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,160 +0,0 @@
|
||||
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { VegaTxState } from '../use-vega-transaction';
|
||||
import { VegaTxStatus } from '../use-vega-transaction';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export interface Market {
|
||||
name: string;
|
||||
positionDecimalPlaces?: number;
|
||||
decimalPlaces: number;
|
||||
}
|
||||
|
||||
export interface Order {
|
||||
status: string;
|
||||
rejectionReason: string | null;
|
||||
size: string;
|
||||
price: string;
|
||||
market: Market | null;
|
||||
type: string | null;
|
||||
}
|
||||
|
||||
interface VegaOrderTransactionDialogProps {
|
||||
transaction: VegaTxState;
|
||||
finalizedOrder: Order | null;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const VegaOrderTransactionDialog = ({
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
title = 'Order placed',
|
||||
}: VegaOrderTransactionDialogProps) => {
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
// Rejected by wallet
|
||||
if (transaction.status === VegaTxStatus.Requested) {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Confirm transaction in wallet"
|
||||
icon={<Icon name="hand-up" size={20} />}
|
||||
>
|
||||
<p>
|
||||
Please open your wallet application and confirm or reject the
|
||||
transaction
|
||||
</p>
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Transaction error
|
||||
if (transaction.status === VegaTxStatus.Error) {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Order rejected by wallet"
|
||||
icon={<Icon name="warning-sign" size={20} />}
|
||||
>
|
||||
{transaction.error && (
|
||||
<pre className="text-ui break-all whitespace-pre-wrap">
|
||||
{JSON.stringify(transaction.error, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Pending consensus
|
||||
if (!finalizedOrder) {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Awaiting network confirmation"
|
||||
icon={<Loader size="small" />}
|
||||
>
|
||||
{transaction.txHash && (
|
||||
<p className="break-all">
|
||||
Tx hash:
|
||||
<a
|
||||
className="underline"
|
||||
data-testid="tx-hash"
|
||||
href={`${VEGA_EXPLORER_URL}/txs/0x${transaction.txHash}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{transaction.txHash}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Order on network but was rejected
|
||||
if (finalizedOrder.status === 'Rejected') {
|
||||
return (
|
||||
<OrderDialogWrapper
|
||||
title="Order failed"
|
||||
icon={<Icon name="warning-sign" size={20} />}
|
||||
>
|
||||
<p data-testid="error-reason">
|
||||
{t(`Reason: ${finalizedOrder.rejectionReason}`)}
|
||||
</p>
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OrderDialogWrapper title={title} icon={<Icon name="tick" size={20} />}>
|
||||
<p>{t(`Status: ${finalizedOrder.status}`)}</p>
|
||||
{finalizedOrder.market && (
|
||||
<p>{t(`Market: ${finalizedOrder.market.name}`)}</p>
|
||||
)}
|
||||
<p>{t(`Type: ${finalizedOrder.type}`)}</p>
|
||||
<p>
|
||||
{t(
|
||||
`Amount: ${addDecimal(
|
||||
finalizedOrder.size,
|
||||
finalizedOrder.market?.positionDecimalPlaces || 0
|
||||
)}`
|
||||
)}
|
||||
</p>
|
||||
{finalizedOrder.type === 'Limit' && finalizedOrder.market && (
|
||||
<p>
|
||||
{t(
|
||||
`Price: ${addDecimalsFormatNumber(
|
||||
finalizedOrder.price,
|
||||
finalizedOrder.market.decimalPlaces
|
||||
)}`
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
interface OrderDialogWrapperProps {
|
||||
children: ReactNode;
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const OrderDialogWrapper = ({
|
||||
children,
|
||||
icon,
|
||||
title,
|
||||
}: OrderDialogWrapperProps) => {
|
||||
return (
|
||||
<div className="flex gap-12 max-w-full">
|
||||
<div className="pt-8 fill-current">{icon}</div>
|
||||
<div data-testid="order-wrapper" className="flex-1">
|
||||
<h1 data-testid="order-status-header" className="text-h4">
|
||||
{title}
|
||||
</h1>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user