Feat/470 edit order (#742)
* 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 * fix: #470 add use order edit hook * fix: #470 edit order button * 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: #470 open edit order modal and update storybook * feat: #470 edit modals set up * 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 * fix: #470 merge with new order hooks * fix: #470 fix use-order-edit no red update order-list with code * fix: #470 invert order show price last in dialog * fix: #470 able to edit order * fix: #470 fix dialog transition * fix: #656 #609 show Continuous trading and market state from trade grid header * fix: #603 filter out rejected markets * fix: #603 filter out rejected markets * fix: #470 revert to 17.0.2 react * fix: #470 revert to 17.0.2 react * fix: #603 filter out rejected markets & dialog lg width * fix: #609 show trading mode Continuous Trading and hide market state * fix: #656 modify order validation to trade when suspended * fix: #656 fix use order validation tests * fix: #656 format volume no * fix: format volume with positionDecimalPlaces * fix: tests don't need to be async * fix: md:w-[720px] to prevent dialog overflow * fix: add market state translations * fix: imprt type validation props * fix: #470 working edit submit on GTC not on GTT as it is missing expiresAt * Update libs/orders/src/lib/order-hooks/use-order-validation.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update libs/orders/src/lib/order-hooks/use-order-validation.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update libs/orders/src/lib/order-hooks/use-order-validation.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update libs/orders/src/lib/order-hooks/use-order-validation.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update libs/orders/src/lib/order-hooks/use-order-validation.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update libs/orders/src/lib/order-hooks/use-order-validation.spec.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update apps/trading/pages/markets/__generated__/Market.ts Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * fix: fix warning messages based on feedback * fix: capitalize trading mode * fix: capitalize trading mode * fix: remove line 72 on markets.cy.ts * fix: don't show trigger if unspecified * fix: format last price and shrink 0 on warning icon * fix: order sizes must be whole numbers for this market and input warning size 20 * fix: order sizes must be whole numbers for this market and input warning size 20 * fix: format market list * fix: #470 fix expiresAt and price unmarshall values * fix: #470 fix expiresAt and price unmarshall values * fix: #470 add extra test on editing order * fix: pass child react node for order edit on vega tx default * fix: status and rejection reason optional * fix: add header transalations and remove commented line * fix: simplify get list of markets * fix: check if order.market undefined * fix: remove cast and check market id Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com> Co-authored-by: Matthew Russell <mattrussell36@gmail.com> Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
This commit is contained in:
parent
4670d5e6cf
commit
c0532a8507
@ -1,5 +1,4 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -20,15 +19,13 @@ const MARKETS_QUERY = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const marketList = ({ markets }: MarketsLanding) =>
|
||||
orderBy(
|
||||
markets?.filter(
|
||||
({ marketTimestamps, tradingMode }) =>
|
||||
marketTimestamps.open && tradingMode === MarketTradingMode.Continuous
|
||||
) || [],
|
||||
['state', 'marketTimestamps.open', 'id'],
|
||||
const getMarketList = ({ markets = [] }: MarketsLanding) => {
|
||||
return orderBy(
|
||||
markets,
|
||||
['marketTimestamps.open', 'id'],
|
||||
['asc', 'asc', 'asc']
|
||||
);
|
||||
};
|
||||
|
||||
export function Index() {
|
||||
const { replace } = useRouter();
|
||||
@ -39,7 +36,7 @@ export function Index() {
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const marketId = marketList(data)[0]?.id;
|
||||
const marketId = getMarketList(data)[0]?.id;
|
||||
|
||||
// If a default market is found, go to it with the landing dialog open
|
||||
if (marketId) {
|
||||
|
102
libs/orders/src/lib/components/order-list/order-edit-dialog.tsx
Normal file
102
libs/orders/src/lib/components/order-list/order-edit-dialog.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import {
|
||||
addDecimal,
|
||||
t,
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { OrderType } from '@vegaprotocol/types';
|
||||
import { FormGroup, Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import Icon from 'react-syntax-highlighter';
|
||||
import { OrderDialogWrapper } from '@vegaprotocol/wallet';
|
||||
import type { Order } from '@vegaprotocol/wallet';
|
||||
|
||||
interface OrderEditDialogProps {
|
||||
title: string;
|
||||
order: Order | null;
|
||||
edit: (body: Order) => Promise<unknown>;
|
||||
}
|
||||
|
||||
interface FormFields {
|
||||
entryPrice: string;
|
||||
}
|
||||
|
||||
export const OrderEditDialog = ({
|
||||
order,
|
||||
title,
|
||||
edit,
|
||||
}: OrderEditDialogProps) => {
|
||||
const headerClassName = 'text-h5 font-bold text-black dark:text-white';
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
} = useForm<FormFields>({
|
||||
defaultValues: {
|
||||
entryPrice: order?.price
|
||||
? addDecimal(order?.price, order?.market?.decimalPlaces ?? 0)
|
||||
: '',
|
||||
},
|
||||
});
|
||||
if (!order) return null;
|
||||
return (
|
||||
<OrderDialogWrapper title={title} icon={<Icon name="hand-up" size={20} />}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{order.market && (
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Market`)}</p>
|
||||
<p>{t(`${order.market.name}`)}</p>
|
||||
</div>
|
||||
)}
|
||||
{order.type === OrderType.Limit && order.market && (
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Last price`)}</p>
|
||||
<p>
|
||||
{addDecimalsFormatNumber(order.price, order.market.decimalPlaces)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Amount remaining`)}</p>
|
||||
<p
|
||||
className={
|
||||
order.side === 'Buy'
|
||||
? 'text-dark-green dark:text-vega-green'
|
||||
: 'text-red dark:text-vega-red'
|
||||
}
|
||||
>
|
||||
{order.side === 'Buy' ? '+' : '-'}
|
||||
{order.size}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 py-12">
|
||||
<form
|
||||
onSubmit={handleSubmit(async (data) => {
|
||||
await edit({
|
||||
...order,
|
||||
price: data.entryPrice,
|
||||
});
|
||||
})}
|
||||
data-testid="edit-order"
|
||||
>
|
||||
<FormGroup label={t('Entry price')} labelFor="entryPrice">
|
||||
<Input
|
||||
{...register('entryPrice', { required: t('Required') })}
|
||||
id="entryPrice"
|
||||
type="text"
|
||||
/>
|
||||
{errors.entryPrice?.message && (
|
||||
<InputError intent="danger" className="mt-4">
|
||||
{errors.entryPrice.message}
|
||||
</InputError>
|
||||
)}
|
||||
</FormGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
{t('Update')}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</OrderDialogWrapper>
|
||||
);
|
||||
};
|
@ -16,7 +16,12 @@ const generateJsx = (
|
||||
return (
|
||||
<MockedProvider>
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<OrderListTable data={orders} cancel={jest.fn()} />
|
||||
<OrderListTable
|
||||
data={orders}
|
||||
cancel={jest.fn()}
|
||||
setEditOrderDialogOpen={jest.fn()}
|
||||
setEditOrder={jest.fn()}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
</MockedProvider>
|
||||
);
|
||||
@ -36,7 +41,7 @@ describe('OrderListTable', () => {
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(10);
|
||||
expect(headers).toHaveLength(11);
|
||||
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
||||
'Market',
|
||||
'Amount',
|
||||
@ -47,6 +52,7 @@ describe('OrderListTable', () => {
|
||||
'Time In Force',
|
||||
'Created At',
|
||||
'Updated At',
|
||||
'Edit',
|
||||
'Cancel',
|
||||
]);
|
||||
});
|
||||
@ -67,6 +73,7 @@ describe('OrderListTable', () => {
|
||||
marketOrder.timeInForce,
|
||||
getDateTimeFormat().format(new Date(marketOrder.createdAt)),
|
||||
'-',
|
||||
'Edit',
|
||||
'Cancel',
|
||||
];
|
||||
cells.forEach((cell, i) =>
|
||||
@ -92,6 +99,7 @@ describe('OrderListTable', () => {
|
||||
)}`,
|
||||
getDateTimeFormat().format(new Date(limitOrder.createdAt)),
|
||||
'-',
|
||||
'Edit',
|
||||
'Cancel',
|
||||
];
|
||||
cells.forEach((cell, i) =>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { OrderType, OrderStatus } from '@vegaprotocol/types';
|
||||
import { OrderType, OrderStatus, OrderTimeInForce } from '@vegaprotocol/types';
|
||||
import { OrderList, OrderListTable } from './order-list';
|
||||
import { useState } from 'react';
|
||||
import type { Order, VegaTxState } from '@vegaprotocol/wallet';
|
||||
@ -15,7 +15,16 @@ const Template: Story = (args) => {
|
||||
const cancel = () => Promise.resolve();
|
||||
return (
|
||||
<div style={{ height: 1000 }}>
|
||||
<OrderListTable data={args.data} cancel={cancel} />
|
||||
<OrderListTable
|
||||
data={args.data}
|
||||
cancel={cancel}
|
||||
setEditOrderDialogOpen={() => {
|
||||
return;
|
||||
}}
|
||||
setEditOrder={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -39,12 +48,22 @@ const Template2: Story = (args) => {
|
||||
price: '1000',
|
||||
market: { name: 'ETH/DAI (30 Jun 2022)', decimalPlaces: 5 },
|
||||
type: OrderType.Limit,
|
||||
timeInForce: OrderTimeInForce.GTC,
|
||||
};
|
||||
const reset = () => null;
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: 1000 }}>
|
||||
<OrderListTable data={args.data} cancel={cancel} />
|
||||
<OrderListTable
|
||||
data={args.data}
|
||||
cancel={cancel}
|
||||
setEditOrderDialogOpen={() => {
|
||||
return;
|
||||
}}
|
||||
setEditOrder={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<VegaTransactionDialog
|
||||
orderDialogOpen={open}
|
||||
|
@ -12,6 +12,8 @@ import { forwardRef, useState } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useOrderCancel } from '../../order-hooks/use-order-cancel';
|
||||
import { VegaTransactionDialog } from '@vegaprotocol/wallet';
|
||||
import { useOrderEdit } from '../../order-hooks/use-order-edit';
|
||||
import { OrderEditDialog } from './order-edit-dialog';
|
||||
|
||||
interface OrderListProps {
|
||||
data: Orders_party_orders[] | null;
|
||||
@ -21,11 +23,22 @@ interface OrderListProps {
|
||||
export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
({ data, showCancelled = true }, ref) => {
|
||||
const [cancelOrderDialogOpen, setCancelOrderDialogOpen] = useState(false);
|
||||
const [editOrderDialogOpen, setEditOrderDialogOpen] = useState(false);
|
||||
const [editOrder, setEditOrder] = useState<Orders_party_orders | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const { transaction, updatedOrder, reset, cancel } = useOrderCancel();
|
||||
const {
|
||||
transaction: editTransaction,
|
||||
updatedOrder: editedOrder,
|
||||
reset: resetEdit,
|
||||
edit,
|
||||
} = useOrderEdit();
|
||||
const ordersData = showCancelled
|
||||
? data
|
||||
: data?.filter((o) => o.status !== OrderStatus.Cancelled) || null;
|
||||
const getDialogTitle = (status?: string) => {
|
||||
const getCancelDialogTitle = (status?: string) => {
|
||||
switch (status) {
|
||||
case OrderStatus.Cancelled:
|
||||
return 'Order cancelled';
|
||||
@ -37,18 +50,51 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
return 'Cancellation failed';
|
||||
}
|
||||
};
|
||||
const getEditDialogTitle = () =>
|
||||
editedOrder
|
||||
? t(
|
||||
`Order ${
|
||||
editOrder?.market?.tradableInstrument.instrument.code ?? ''
|
||||
} updated`
|
||||
)
|
||||
: t(
|
||||
`Edit ${
|
||||
editOrder?.market?.tradableInstrument.instrument.code ?? ''
|
||||
} order`
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<OrderListTable data={ordersData} cancel={cancel} ref={ref} />
|
||||
<OrderListTable
|
||||
data={ordersData}
|
||||
cancel={cancel}
|
||||
ref={ref}
|
||||
setEditOrderDialogOpen={setEditOrderDialogOpen}
|
||||
setEditOrder={setEditOrder}
|
||||
/>
|
||||
<VegaTransactionDialog
|
||||
key={`cancel-order-dialog-${transaction.txHash}`}
|
||||
orderDialogOpen={cancelOrderDialogOpen}
|
||||
setOrderDialogOpen={setCancelOrderDialogOpen}
|
||||
finalizedOrder={updatedOrder}
|
||||
transaction={transaction}
|
||||
reset={reset}
|
||||
title={getDialogTitle(updatedOrder?.status)}
|
||||
title={getCancelDialogTitle(updatedOrder?.status)}
|
||||
finalizedOrder={updatedOrder}
|
||||
/>
|
||||
<VegaTransactionDialog
|
||||
key={`edit-order-dialog-${transaction.txHash}`}
|
||||
orderDialogOpen={editOrderDialogOpen}
|
||||
setOrderDialogOpen={setEditOrderDialogOpen}
|
||||
transaction={editTransaction}
|
||||
reset={resetEdit}
|
||||
title={getEditDialogTitle()}
|
||||
finalizedOrder={editedOrder}
|
||||
>
|
||||
<OrderEditDialog
|
||||
title={getEditDialogTitle()}
|
||||
order={editOrder}
|
||||
edit={edit}
|
||||
/>
|
||||
</VegaTransactionDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -57,10 +103,12 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
|
||||
interface OrderListTableProps {
|
||||
data: Orders_party_orders[] | null;
|
||||
cancel: (body?: unknown) => Promise<unknown>;
|
||||
setEditOrderDialogOpen: (value: boolean) => void;
|
||||
setEditOrder: (order: Orders_party_orders | null) => void;
|
||||
}
|
||||
|
||||
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
({ data, cancel }, ref) => {
|
||||
({ data, cancel, setEditOrderDialogOpen, setEditOrder }, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
@ -147,6 +195,34 @@ export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
|
||||
return value ? getDateTimeFormat().format(new Date(value)) : '-';
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="edit"
|
||||
cellRenderer={({ data }: ICellRendererParams) => {
|
||||
if (
|
||||
![
|
||||
OrderStatus.Cancelled,
|
||||
OrderStatus.Rejected,
|
||||
OrderStatus.Expired,
|
||||
OrderStatus.Filled,
|
||||
OrderStatus.Stopped,
|
||||
].includes(data.status)
|
||||
) {
|
||||
return (
|
||||
<Button
|
||||
data-testid="edit"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setEditOrderDialogOpen(true);
|
||||
setEditOrder(data);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="cancel"
|
||||
cellRenderer={({ data }: ICellRendererParams) => {
|
||||
|
@ -15,6 +15,10 @@ export interface OrderEvent_busEvents_event_TimeUpdate {
|
||||
|
||||
export interface OrderEvent_busEvents_event_Order_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Market full name
|
||||
*/
|
||||
|
@ -14,8 +14,10 @@ export const ORDER_EVENT_SUB = gql`
|
||||
size
|
||||
price
|
||||
timeInForce
|
||||
expiresAt
|
||||
side
|
||||
market {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export const useOrderCancel = () => {
|
||||
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
|
||||
|
221
libs/orders/src/lib/order-hooks/use-order-edit.spec.tsx
Normal file
221
libs/orders/src/lib/order-hooks/use-order-edit.spec.tsx
Normal file
@ -0,0 +1,221 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import type {
|
||||
VegaKeyExtended,
|
||||
VegaWalletContextShape,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
VegaWalletOrderSide,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { VegaTxStatus, VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useOrderEdit } from './use-order-edit';
|
||||
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 {
|
||||
MarketTradingMode,
|
||||
MarketState,
|
||||
OrderTimeInForce,
|
||||
} from '@vegaprotocol/types';
|
||||
import type {
|
||||
OrderAmendmentBodyOrderAmendment,
|
||||
OrderAmendmentBody,
|
||||
} from '@vegaprotocol/vegawallet-service-api-client';
|
||||
|
||||
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>) {
|
||||
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(() => useOrderEdit(), { wrapper });
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const order = {
|
||||
id: 'order-id',
|
||||
type: VegaWalletOrderType.Limit,
|
||||
size: '10',
|
||||
timeInForce: OrderTimeInForce.GTT, // order timeInForce is transformed to wallet timeInForce
|
||||
side: VegaWalletOrderSide.Buy,
|
||||
price: '1234567.89',
|
||||
expiration: new Date('2022-01-01'),
|
||||
expiresAt: new Date('2022-01-01'),
|
||||
status: VegaTxStatus.Pending,
|
||||
rejectionReason: null,
|
||||
market: {
|
||||
id: 'market-id',
|
||||
decimalPlaces: 2,
|
||||
name: 'ETHDAI',
|
||||
positionDecimalPlaces: 2,
|
||||
},
|
||||
};
|
||||
|
||||
describe('useOrderEdit', () => {
|
||||
it('should edit 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,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.edit(order);
|
||||
});
|
||||
|
||||
expect(mockSendTx).toHaveBeenCalledWith({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderAmendment: {
|
||||
orderId: 'order-id',
|
||||
marketId: defaultMarket.id, // Market provided from hook argument
|
||||
timeInForce: VegaWalletOrderTimeInForce.GTT,
|
||||
price: { value: '123456789' }, // Decimal removed
|
||||
sizeDelta: 0,
|
||||
expiresAt: { value: order.expiration?.getTime() + '000000' }, // Nanoseconds append
|
||||
} as unknown as OrderAmendmentBodyOrderAmendment,
|
||||
} as OrderAmendmentBody);
|
||||
});
|
||||
|
||||
it('has the correct default state', () => {
|
||||
const { result } = setup();
|
||||
expect(typeof result.current.edit).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.edit(order);
|
||||
});
|
||||
expect(mockSendTx).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
119
libs/orders/src/lib/order-hooks/use-order-edit.tsx
Normal file
119
libs/orders/src/lib/order-hooks/use-order-edit.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import type { Order } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletOrderTimeInForce } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { ORDER_EVENT_SUB } from './order-event-query';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import type {
|
||||
OrderEvent_busEvents_event_Order,
|
||||
OrderEvent,
|
||||
OrderEventVariables,
|
||||
} from './__generated__';
|
||||
import * as Sentry from '@sentry/react';
|
||||
|
||||
export const useOrderEdit = () => {
|
||||
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);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetTransaction();
|
||||
setUpdatedOrder(null);
|
||||
subRef.current?.unsubscribe();
|
||||
}, [resetTransaction]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
resetTransaction();
|
||||
setUpdatedOrder(null);
|
||||
subRef.current?.unsubscribe();
|
||||
};
|
||||
}, [resetTransaction]);
|
||||
|
||||
const edit = useCallback(
|
||||
async (order: Order) => {
|
||||
if (!keypair || !order.market || !order.market.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUpdatedOrder(null);
|
||||
|
||||
try {
|
||||
const res = await send({
|
||||
pubKey: keypair.pub,
|
||||
propagate: true,
|
||||
orderAmendment: {
|
||||
orderId: order.id,
|
||||
marketId: order.market.id,
|
||||
price: {
|
||||
value: removeDecimal(order.price, order.market?.decimalPlaces),
|
||||
},
|
||||
timeInForce: VegaWalletOrderTimeInForce[order.timeInForce],
|
||||
sizeDelta: 0,
|
||||
expiresAt: order.expiresAt
|
||||
? {
|
||||
value:
|
||||
// Wallet expects timestamp in nanoseconds,
|
||||
// we don't have that level of accuracy so just append 6 zeroes
|
||||
new Date(order.expiresAt).getTime().toString() + '000000',
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.signature) {
|
||||
const resId = order.id ?? determineId(res.signature);
|
||||
setUpdatedOrder(null);
|
||||
|
||||
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,
|
||||
edit,
|
||||
reset,
|
||||
};
|
||||
};
|
@ -5,6 +5,7 @@ import {
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import type { Market } from '../market';
|
||||
import type { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export type Order =
|
||||
| {
|
||||
@ -14,6 +15,9 @@ export type Order =
|
||||
side: VegaWalletOrderSide;
|
||||
price?: never;
|
||||
expiration?: never;
|
||||
rejectionReason: string | null;
|
||||
status?: OrderStatus;
|
||||
market?: Market | null;
|
||||
}
|
||||
| {
|
||||
size: string;
|
||||
@ -22,6 +26,9 @@ export type Order =
|
||||
side: VegaWalletOrderSide;
|
||||
price?: string;
|
||||
expiration?: Date;
|
||||
rejectionReason: string | null;
|
||||
status?: OrderStatus;
|
||||
market?: Market | null;
|
||||
};
|
||||
|
||||
export const getDefaultOrder = (market: Market): Order => ({
|
||||
@ -29,4 +36,6 @@ export const getDefaultOrder = (market: Market): Order => ({
|
||||
side: VegaWalletOrderSide.Buy,
|
||||
timeInForce: VegaWalletOrderTimeInForce.IOC,
|
||||
size: String(toDecimal(market.positionDecimalPlaces)),
|
||||
rejectionReason: null,
|
||||
market: null,
|
||||
});
|
||||
|
10
libs/types/src/__generated__/globalTypes.ts
generated
10
libs/types/src/__generated__/globalTypes.ts
generated
@ -293,16 +293,6 @@ export enum WithdrawalStatus {
|
||||
Rejected = "Rejected",
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination constructs to support cursor based pagination in the API
|
||||
*/
|
||||
export interface Pagination {
|
||||
first?: number | null;
|
||||
after?: string | null;
|
||||
last?: number | null;
|
||||
before?: string | null;
|
||||
}
|
||||
|
||||
//==============================================================
|
||||
// END Enums and Input Objects
|
||||
//==============================================================
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './__generated__/globalTypes';
|
||||
export * from './candle';
|
||||
export * from './pagination';
|
||||
|
6
libs/types/src/pagination.ts
Normal file
6
libs/types/src/pagination.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface Pagination {
|
||||
first?: number;
|
||||
after?: string;
|
||||
last?: number;
|
||||
before?: string;
|
||||
}
|
@ -17,6 +17,7 @@ export interface VegaTransactionDialogProps {
|
||||
transaction: VegaTxState;
|
||||
reset: () => void;
|
||||
title?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const getDialogIntent = (
|
||||
@ -45,6 +46,7 @@ export const VegaTransactionDialog = ({
|
||||
transaction,
|
||||
reset,
|
||||
title = '',
|
||||
children,
|
||||
}: VegaTransactionDialogProps) => {
|
||||
// open / close dialog
|
||||
useEffect(() => {
|
||||
@ -75,6 +77,7 @@ export const VegaTransactionDialog = ({
|
||||
transaction={transaction}
|
||||
finalizedOrder={finalizedOrder}
|
||||
title={title}
|
||||
children={children}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
@ -84,15 +87,22 @@ interface VegaDialogProps {
|
||||
transaction: VegaTxState;
|
||||
finalizedOrder: Order | null;
|
||||
title: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const VegaDialog = ({
|
||||
transaction,
|
||||
finalizedOrder,
|
||||
title,
|
||||
children,
|
||||
}: VegaDialogProps) => {
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const headerClassName = 'text-h5 font-bold text-black dark:text-white';
|
||||
|
||||
if (children && transaction.status === VegaTxStatus.Default) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
// Rejected by wallet
|
||||
if (transaction.status === VegaTxStatus.Requested) {
|
||||
return (
|
||||
@ -178,6 +188,17 @@ export const VegaDialog = ({
|
||||
<p className={headerClassName}>{t(`Status`)}</p>
|
||||
<p>{t(`${finalizedOrder.status}`)}</p>
|
||||
</div>
|
||||
{finalizedOrder.type === OrderType.Limit && finalizedOrder.market && (
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Price`)}</p>
|
||||
<p>
|
||||
{addDecimalsFormatNumber(
|
||||
finalizedOrder.price,
|
||||
finalizedOrder.market.decimalPlaces
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Amount`)}</p>
|
||||
<p
|
||||
@ -193,17 +214,6 @@ export const VegaDialog = ({
|
||||
`}
|
||||
</p>
|
||||
</div>
|
||||
{finalizedOrder.type === OrderType.Limit && finalizedOrder.market && (
|
||||
<div>
|
||||
<p className={headerClassName}>{t(`Price`)}</p>
|
||||
<p>
|
||||
{addDecimalsFormatNumber(
|
||||
finalizedOrder.price,
|
||||
finalizedOrder.market.decimalPlaces
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
{transaction.txHash && (
|
||||
@ -231,7 +241,7 @@ interface OrderDialogWrapperProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const OrderDialogWrapper = ({
|
||||
export const OrderDialogWrapper = ({
|
||||
children,
|
||||
icon,
|
||||
title,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { OrderTimeInForce } from '@vegaprotocol/types';
|
||||
import type {
|
||||
DelegateSubmissionBody,
|
||||
OrderCancellationBody,
|
||||
@ -41,14 +42,18 @@ export interface Market {
|
||||
name: string;
|
||||
positionDecimalPlaces?: number;
|
||||
decimalPlaces: number;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface Order {
|
||||
status: string;
|
||||
rejectionReason: string | null;
|
||||
id?: string;
|
||||
status?: string;
|
||||
rejectionReason?: string | null;
|
||||
size: string;
|
||||
price: string;
|
||||
market: Market | null;
|
||||
type: string | null;
|
||||
side?: string;
|
||||
timeInForce: OrderTimeInForce;
|
||||
expiresAt?: Date | string | null;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
"@sentry/react": "^6.19.2",
|
||||
"@sentry/tracing": "^6.19.2",
|
||||
"@testing-library/user-event": "^14.2.1",
|
||||
"@vegaprotocol/vegawallet-service-api-client": "0.4.14",
|
||||
"@vegaprotocol/vegawallet-service-api-client": "0.4.15",
|
||||
"@walletconnect/ethereum-provider": "^1.7.5",
|
||||
"@web3-react/core": "8.0.20-beta.0",
|
||||
"@web3-react/metamask": "8.0.16-beta.0",
|
||||
|
@ -6693,10 +6693,10 @@
|
||||
"@typescript-eslint/types" "5.22.0"
|
||||
eslint-visitor-keys "^3.0.0"
|
||||
|
||||
"@vegaprotocol/vegawallet-service-api-client@0.4.14":
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.14.tgz#cdec296644380f95397688e10b753af328c38147"
|
||||
integrity sha512-xQ/Dg4Bg+3LSHybYHV83i3G7i407Jj8ROElblZ2TTHTW9iHbBhbd/EHtWfUF2C6R6U27+JUZExnFPcZlvNXprA==
|
||||
"@vegaprotocol/vegawallet-service-api-client@^0.4.15":
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.15.tgz#b303fec121b9b334a678161a6f66b360aeed5f0d"
|
||||
integrity sha512-YwJkUgFvFqpA1xPYQ30ILGddgzjwD9lclsu1GvwK2AUX/8e3iUcXyr37wLd/t8mDZ7P3Zb2AsuLJP8uZ6E1GHQ==
|
||||
dependencies:
|
||||
es6-promise "^4.2.4"
|
||||
url-parse "^1.4.3"
|
||||
|
Loading…
Reference in New Issue
Block a user