fix(#623): order fixes

* feat: format size and volume with positionDecimalPlaces

* fix: pass in calculated step so client side validation works

* fix: add requested state to order dialog, handle user rejected error

* feat: add test case for user rejecting tx

* feat: add test case for order list fractional sizes

* feat: add test case for fractional volume in positions table

* fix: deal ticket tests imports

* feat: add test case for trades fractional size

* fix: props for orderbook tests

* fix: add missing positionDecimalPlaces fields to mock functions

* fix: improve error handling of service error with type guard
This commit is contained in:
Matthew Russell 2022-06-23 15:00:58 -07:00 committed by GitHub
parent a9df2f425d
commit d5727baffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 275 additions and 185 deletions

View File

@ -12,6 +12,7 @@ export const generateOrderBook = (
const marketDepth: MarketDepth_market = {
id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e',
decimalPlaces: 5,
positionDecimalPlaces: 0,
data: {
staticMidPrice: '826337',
marketTradingMode: MarketTradingMode.Continuous,

View File

@ -18,6 +18,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
id: 'c9f5acd348796011c075077e4d58d9b7f1689b7c1c8e030a5e886b83aa96923d',
name: 'AAVEDAI Monthly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
@ -46,6 +47,7 @@ export const generateOrders = (override?: PartialDeep<Orders>): Orders => {
id: '5a4b0b9e9c0629f0315ec56fcb7bd444b0c6e4da5ec7677719d502626658a376',
name: 'Tesla Quarterly (30 Jun 2022)',
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {

View File

@ -25,6 +25,7 @@ export const generatePositions = (
market: { __typename: 'Market', id: '123' },
},
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
instrument: {
id: '',
@ -78,6 +79,7 @@ export const generatePositions = (
},
},
decimalPlaces: 5,
positionDecimalPlaces: 0,
tradableInstrument: {
instrument: {
id: '',

View File

@ -12,6 +12,7 @@ export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
positionDecimalPlaces: 0,
__typename: 'Market',
},
__typename: 'Trade',
@ -24,6 +25,7 @@ export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
positionDecimalPlaces: 0,
__typename: 'Market',
},
__typename: 'Trade',
@ -36,6 +38,7 @@ export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
positionDecimalPlaces: 0,
__typename: 'Market',
},
__typename: 'Trade',

View File

@ -109,7 +109,7 @@ interface TradeGridProps {
export const TradeGrid = ({ market }: TradeGridProps) => {
const wrapperClasses = classNames(
'h-full max-h-full',
'grid gap-4 grid-cols-[1fr_375px_460px] grid-rows-[min-content_1fr_200px]',
'grid gap-4 grid-cols-[1fr_375px_460px] grid-rows-[min-content_1fr_300px]',
'bg-black-10 dark:bg-white-10',
'text-ui'
);

View File

@ -1,108 +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;
}
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;
}

View File

@ -1,3 +1,2 @@
export * from './DealTicketQuery';
export * from './MarketInfoQuery';
export * from './OrderEvent';

View File

@ -12,22 +12,17 @@ export interface DealTicketAmountProps {
price?: string;
}
const getAmountComponent = (type: OrderType) => {
switch (type) {
export const DealTicketAmount = ({
orderType,
...props
}: DealTicketAmountProps) => {
switch (orderType) {
case OrderType.Market:
return DealTicketMarketAmount;
return <DealTicketMarketAmount {...props} />;
case OrderType.Limit:
return DealTicketLimitAmount;
return <DealTicketLimitAmount {...props} />;
default: {
throw new Error('Invalid ticket type');
}
}
};
export const DealTicketAmount = ({
orderType,
...props
}: DealTicketAmountProps) => {
const AmountComponent = getAmountComponent(orderType);
return <AmountComponent {...props} />;
};

View File

@ -37,6 +37,10 @@ export const DealTicketManager = ({
return Intent.Danger;
}
if (status === VegaTxStatus.Requested) {
return Intent.Warning;
}
if (status === VegaTxStatus.Error) {
return Intent.Danger;
}
@ -47,6 +51,8 @@ export const DealTicketManager = ({
useEffect(() => {
if (transaction.status !== VegaTxStatus.Default) {
setOrderDialogOpen(true);
} else {
setOrderDialogOpen(false);
}
}, [transaction.status]);

View File

@ -6,13 +6,14 @@ import {
import { addDecimal } from '@vegaprotocol/react-helpers';
import { fireEvent, render, screen, act } from '@testing-library/react';
import { DealTicket } from './deal-ticket';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
import type { Order } from '../utils/get-default-order';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
const market: DealTicketQuery_market = {
__typename: 'Market',
id: 'market-id',
name: 'market-name',
decimalPlaces: 2,
positionDecimalPlaces: 1,
tradingMode: MarketTradingMode.Continuous,
@ -24,6 +25,12 @@ const market: DealTicketQuery_market = {
product: {
__typename: 'Future',
quoteName: 'quote-name',
settlementAsset: {
__typename: 'Asset',
id: 'asset-id',
name: 'asset-name',
symbol: 'asset-symbol',
},
},
},
},

View File

@ -77,7 +77,7 @@ export const DealTicket = ({
/>
<DealTicketAmount
orderType={orderType}
step={0.02}
step={step}
register={register}
price={
market.depth.lastTrade

View File

@ -1,9 +1,13 @@
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { OrderEvent_busEvents_event_Order } from './__generated__/OrderEvent';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import {
addDecimal,
addDecimalsFormatNumber,
t,
} from '@vegaprotocol/react-helpers';
import type { VegaTxState } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet';
import type { OrderEvent_busEvents_event_Order } from '../hooks/__generated__/OrderEvent';
interface OrderDialogProps {
transaction: VegaTxState;
@ -14,9 +18,22 @@ export const OrderDialog = ({
transaction,
finalizedOrder,
}: OrderDialogProps) => {
// TODO: When wallets support confirming transactions return UI for 'awaiting confirmation' step
// Rejected by wallet
if (transaction.status === VegaTxStatus.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
@ -72,7 +89,14 @@ export const OrderDialog = ({
<p>{t(`Market: ${finalizedOrder.market.name}`)}</p>
)}
<p>{t(`Type: ${finalizedOrder.type}`)}</p>
<p>{t(`Amount: ${finalizedOrder.size}`)}</p>
<p>
{t(
`Amount: ${addDecimal(
finalizedOrder.size,
finalizedOrder.market?.positionDecimalPlaces || 0
)}`
)}
</p>
{finalizedOrder.type === 'Limit' && finalizedOrder.market && (
<p>
{t(

View File

@ -36,6 +36,12 @@ export interface OrderEvent_busEvents_event_Order_market {
* 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 {

View File

@ -4,12 +4,12 @@ import type { Order } from '../utils/get-default-order';
import { OrderType, useVegaWallet } from '@vegaprotocol/wallet';
import { determineId, removeDecimal } from '@vegaprotocol/react-helpers';
import { useVegaTransaction } from '@vegaprotocol/wallet';
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
import type {
OrderEvent,
OrderEventVariables,
OrderEvent_busEvents_event_Order,
} from '../components/__generated__/OrderEvent';
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
} from './__generated__/OrderEvent';
const ORDER_EVENT_SUB = gql`
subscription OrderEvent($partyId: ID!) {
@ -29,6 +29,7 @@ const ORDER_EVENT_SUB = gql`
market {
name
decimalPlaces
positionDecimalPlaces
}
}
}

View File

@ -132,6 +132,12 @@ export interface MarketDepth_market {
* 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;
/**
* marketData for the given market
*/

View File

@ -55,6 +55,12 @@ export interface MarketDepthSubscription_marketDepthUpdate_market {
* Market ID
*/
id: string;
/**
* 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;
/**
* marketData for the given market
*/

View File

@ -16,6 +16,7 @@ const MARKET_DEPTH_QUERY = gql`
market(id: $marketId) {
id
decimalPlaces
positionDecimalPlaces
data {
staticMidPrice
marketTradingMode
@ -52,6 +53,7 @@ export const MARKET_DEPTH_SUBSCRIPTION_QUERY = gql`
marketDepthUpdate(marketId: $marketId) {
market {
id
positionDecimalPlaces
data {
staticMidPrice
marketTradingMode

View File

@ -82,6 +82,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
<Orderbook
{...orderbookData}
decimalPlaces={data?.decimalPlaces ?? 0}
positionDecimalPlaces={data?.positionDecimalPlaces ?? 0}
resolution={resolution}
onResolutionChange={(resolution: number) => setResolution(resolution)}
/>

View File

@ -5,6 +5,7 @@ import {
CumulativeVol,
addDecimalsFormatNumber,
VolumeType,
addDecimal,
} from '@vegaprotocol/react-helpers';
interface OrderbookRowProps {
@ -15,6 +16,7 @@ interface OrderbookRowProps {
cumulativeRelativeAsk?: number;
cumulativeRelativeBid?: number;
decimalPlaces: number;
positionDecimalPlaces: number;
indicativeVolume?: string;
price: string;
relativeAsk?: number;
@ -30,6 +32,7 @@ export const OrderbookRow = React.memo(
cumulativeRelativeAsk,
cumulativeRelativeBid,
decimalPlaces,
positionDecimalPlaces,
indicativeVolume,
price,
relativeAsk,
@ -40,6 +43,7 @@ export const OrderbookRow = React.memo(
<Vol
testId={`bid-vol-${price}`}
value={bid}
valueFormatted={addDecimal(bid, positionDecimalPlaces)}
relativeValue={relativeBid}
type={VolumeType.bid}
/>
@ -51,6 +55,7 @@ export const OrderbookRow = React.memo(
<Vol
testId={`ask-vol-${price}`}
value={ask}
valueFormatted={addDecimal(ask, positionDecimalPlaces)}
relativeValue={relativeAsk}
type={VolumeType.ask}
/>

View File

@ -22,6 +22,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -35,6 +36,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -44,6 +46,7 @@ describe('Orderbook', () => {
result.rerender(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData({
...params,
numberOfSellRows: params.numberOfSellRows - 1,
@ -60,6 +63,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -69,6 +73,7 @@ describe('Orderbook', () => {
result.rerender(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData({
...params,
bestStaticBidPrice: params.bestStaticBidPrice + 1,
@ -86,6 +91,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -98,6 +104,7 @@ describe('Orderbook', () => {
result.rerender(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData({
...params,
numberOfSellRows: params.numberOfSellRows - 1,
@ -114,6 +121,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -134,6 +142,7 @@ describe('Orderbook', () => {
const result = render(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData(params)}
onResolutionChange={onResolutionChange}
/>
@ -153,6 +162,7 @@ describe('Orderbook', () => {
result.rerender(
<Orderbook
decimalPlaces={decimalPlaces}
positionDecimalPlaces={0}
{...generateMockData({
...params,
resolution: 10,

View File

@ -19,6 +19,7 @@ import { Icon, Splash } from '@vegaprotocol/ui-toolkit';
import type { OrderbookData, OrderbookRowData } from './orderbook-data';
interface OrderbookProps extends OrderbookData {
decimalPlaces: number;
positionDecimalPlaces: number;
resolution: number;
onResolutionChange: (resolution: number) => void;
}
@ -100,6 +101,7 @@ export const Orderbook = ({
indicativeVolume,
indicativePrice,
decimalPlaces,
positionDecimalPlaces,
resolution,
onResolutionChange,
}: OrderbookProps) => {
@ -298,6 +300,7 @@ export const Orderbook = ({
<OrderbookRow
price={(BigInt(data.price) / BigInt(resolution)).toString()}
decimalPlaces={decimalPlaces - Math.log10(resolution)}
positionDecimalPlaces={positionDecimalPlaces}
bid={data.bid}
relativeBid={data.relativeBid}
cumulativeBid={data.cumulativeVol.bid}

View File

@ -52,6 +52,12 @@ export interface OrderFields_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -52,6 +52,12 @@ export interface OrderSub_orders_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -52,6 +52,12 @@ export interface Orders_party_orders_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -25,6 +25,7 @@ const marketOrder: Orders_party_orders = {
id: 'market-id',
name: 'market-name',
decimalPlaces: 2,
positionDecimalPlaces: 0,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
@ -54,6 +55,7 @@ const limitOrder: Orders_party_orders = {
id: 'market-id',
name: 'market-name',
decimalPlaces: 2,
positionDecimalPlaces: 2,
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
@ -62,11 +64,11 @@ const limitOrder: Orders_party_orders = {
},
},
},
size: '10',
size: '1000',
type: OrderType.Limit,
status: OrderStatus.Active,
side: Side.Sell,
remaining: '5',
remaining: '500',
price: '12345',
timeInForce: OrderTimeInForce.GTT,
createdAt: new Date('2022-3-3').toISOString(),
@ -122,10 +124,10 @@ it('Correct formatting applied for GTT limit order', async () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
limitOrder.market?.tradableInstrument.instrument.code,
'-10',
'-10.00',
limitOrder.type,
limitOrder.status,
'5',
'5.00',
formatNumber(limitOrder.price, limitOrder.market?.decimalPlaces ?? 0),
`${limitOrder.timeInForce}: ${getDateTimeFormat().format(
new Date(limitOrder.expiresAt ?? '')

View File

@ -1,11 +1,17 @@
import { OrderTimeInForce, OrderStatus, Side } from '@vegaprotocol/types';
import type { Orders_party_orders } from './__generated__/Orders';
import { formatNumber, getDateTimeFormat } from '@vegaprotocol/react-helpers';
import {
addDecimal,
formatNumber,
getDateTimeFormat,
t,
} from '@vegaprotocol/react-helpers';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type { ValueFormatterParams } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { forwardRef } from 'react';
import BigNumber from 'bignumber.js';
interface OrderListProps {
data: Orders_party_orders[] | null;
@ -23,16 +29,18 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
getRowId={({ data }) => data.id}
>
<AgGridColumn
headerName="Market"
headerName={t('Market')}
field="market.tradableInstrument.instrument.code"
/>
<AgGridColumn
headerName="Amount"
headerName={t('Amount')}
field="size"
cellClass="font-mono"
valueFormatter={({ value, data }: ValueFormatterParams) => {
const prefix = data.side === Side.Buy ? '+' : '-';
return prefix + value;
return (
prefix + addDecimal(value, data.market.positionDecimalPlaces)
);
}}
/>
<AgGridColumn field="type" />
@ -47,11 +55,18 @@ export const OrderList = forwardRef<AgGridReact, OrderListProps>(
}}
/>
<AgGridColumn
headerName="Filled"
headerName={t('Filled')}
field="remaining"
cellClass="font-mono"
valueFormatter={({ data }: ValueFormatterParams) => {
return `${Number(data.size) - Number(data.remaining)}/${data.size}`;
const dps = data.market.positionDecimalPlaces;
const size = new BigNumber(data.size);
const remaining = new BigNumber(data.remaining);
const fills = size.minus(remaining);
return `${addDecimal(fills.toString(), dps)}/${addDecimal(
size.toString(),
dps
)}`;
}}
/>
<AgGridColumn

View File

@ -13,6 +13,7 @@ const ORDER_FRAGMENT = gql`
id
name
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
code

View File

@ -136,6 +136,12 @@ export interface PositionDetails_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -136,6 +136,12 @@ export interface PositionSubscribe_positions_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -136,6 +136,12 @@ export interface Positions_party_positions_market {
* 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;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -27,6 +27,7 @@ const POSITIONS_FRAGMENT = gql`
}
}
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
id

View File

@ -5,7 +5,7 @@ import { MarketTradingMode } from '@vegaprotocol/types';
const singleRow: Positions_party_positions = {
realisedPNL: '520000000',
openVolume: '100',
openVolume: '10000',
unrealisedPNL: '895000',
averageEntryPrice: '1129935',
market: {
@ -17,6 +17,7 @@ const singleRow: Positions_party_positions = {
__typename: 'MarketData',
market: { __typename: 'Market', id: '123' },
},
positionDecimalPlaces: 2,
decimalPlaces: 5,
tradableInstrument: {
instrument: {
@ -90,7 +91,7 @@ it('Correct formatting applied', async () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
singleRow.market.tradableInstrument.instrument.code,
'+100',
'+100.00',
'11.29935',
'11.38885',
'+5,200.000',

View File

@ -88,8 +88,11 @@ export const PositionsTable = forwardRef<AgGridReact, PositionsTableProps>(
<AgGridColumn
headerName={t('Amount')}
field="openVolume"
valueFormatter={({ value }: PositionsTableValueFormatterParams) =>
volumePrefix(value)
valueFormatter={({
value,
data,
}: PositionsTableValueFormatterParams) =>
volumePrefix(addDecimal(value, data.market.positionDecimalPlaces))
}
/>
<AgGridColumn

View File

@ -1,19 +1,6 @@
import once from 'lodash/once';
import { getUserLocale } from './utils';
/**
* Returns a number prefixed with either a '-' or a '+'. The open volume field
* already comes with a '-' if negative so we only need to actually prefix if
* its a positive value
*/
export function volumePrefix(value: string): string {
if (value === '0' || value.startsWith('-')) {
return value;
}
return '+' + value;
}
export const getTimeFormat = once(
() =>
new Intl.DateTimeFormat(getUserLocale(), {

View File

@ -1,4 +1,5 @@
export * from './date';
export * from './number';
export * from './truncate';
export * from './size';
export * from './utils';

View File

@ -0,0 +1,12 @@
/**
* Returns a number prefixed with either a '-' or a '+'. The open volume field
* already comes with a '-' if negative so we only need to actually prefix if
* its a positive value
*/
export function volumePrefix(value: string): string {
if (value === '0' || value.startsWith('-')) {
return value;
}
return '+' + value;
}

View File

@ -9,6 +9,7 @@ export enum VolumeType {
}
export interface VolProps {
value: number | bigint | null | undefined;
valueFormatted: string;
relativeValue?: number;
type: VolumeType;
testId?: string;
@ -22,7 +23,7 @@ export const BID_COLOR = 'darkgreen';
export const ASK_COLOR = 'maroon';
export const Vol = React.memo(
({ value, relativeValue, type, testId }: VolProps) => {
({ value, valueFormatted, relativeValue, type, testId }: VolProps) => {
if ((!value && value !== 0) || isNaN(Number(value))) {
return <div data-testid="vol">-</div>;
}
@ -38,7 +39,7 @@ export const Vol = React.memo(
backgroundColor: type === VolumeType.bid ? BID_COLOR : ASK_COLOR,
}}
></div>
<PriceCell value={value} valueFormatted={value.toString()} />
<PriceCell value={value} valueFormatted={valueFormatted} />
</div>
);
}

View File

@ -30,6 +30,12 @@ export interface TradeFields_market {
* 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 TradeFields {

View File

@ -30,6 +30,12 @@ export interface Trades_market_trades_market {
* 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 Trades_market_trades {

View File

@ -30,6 +30,12 @@ export interface TradesSub_trades_market {
* 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 TradesSub_trades {

View File

@ -16,6 +16,7 @@ const TRADES_FRAGMENT = gql`
market {
id
decimalPlaces
positionDecimalPlaces
}
}
`;

View File

@ -7,12 +7,13 @@ const trade: TradeFields = {
__typename: 'Trade',
id: 'trade-id',
price: '111122200',
size: '20',
size: '2000',
createdAt: new Date('2022-04-06T19:00:00').toISOString(),
market: {
__typename: 'Market',
id: 'market-id',
decimalPlaces: 2,
positionDecimalPlaces: 2,
},
};
@ -34,7 +35,7 @@ it('Number and data columns are formatted', async () => {
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'1,111,222.00',
'20',
'20.00',
getDateTimeFormat().format(new Date(trade.createdAt)),
];
cells.forEach((cell, i) => {

View File

@ -4,6 +4,7 @@ import { forwardRef, useMemo } from 'react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import type { TradeFields } from './__generated__/TradeFields';
import {
addDecimal,
addDecimalsFormatNumber,
getDateTimeFormat,
t,
@ -73,6 +74,9 @@ export const TradesTable = forwardRef<AgGridReact, TradesTableProps>(
<AgGridColumn
headerName={t('Size')}
field="size"
valueFormatter={({ value, data }: ValueFormatterParams) => {
return addDecimal(value, data.market.positionDecimalPlaces);
}}
cellClass={changeCellClass('size')}
/>
<AgGridColumn

View File

@ -87,27 +87,28 @@ export class RestConnector implements VegaConnector {
async sendTx(body: TransactionSubmission) {
try {
return await this.service.commandSyncPost(body);
const res = await this.service.commandSyncPost(body);
return res;
} catch (err) {
return this.handleSendTxError(err);
}
}
private handleSendTxError(err: unknown) {
if (typeof err === 'object' && err && 'body' in err) {
const unpexpectedError = { error: 'Something went wrong' };
if (isServiceError(err)) {
if (err.code === 401) {
return { error: 'User rejected' };
}
try {
// @ts-ignore Not sure why TS can't infer that 'body' does indeed exist on object
return JSON.parse(err.body);
return JSON.parse(err.body ?? '');
} catch {
// Unexpected response
return {
error: 'Something went wrong',
};
return unpexpectedError;
}
} else {
return {
error: 'Something went wrong',
};
return unpexpectedError;
}
}
@ -132,3 +133,17 @@ export class RestConnector implements VegaConnector {
LocalStorage.removeItem(this.configKey);
}
}
interface ServiceError {
code: number;
body: string | undefined;
headers: object;
}
export const isServiceError = (err: unknown): err is ServiceError => {
// Some responses don't contain body object
if (typeof err === 'object' && err !== null && 'code' in err) {
return true;
}
return false;
};

View File

@ -2,7 +2,11 @@ import { act, renderHook } from '@testing-library/react-hooks';
import type { VegaWalletContextShape } from './context';
import { VegaWalletContext } from './context';
import type { ReactNode } from 'react';
import { useVegaTransaction, VegaTxStatus } from './use-vega-transaction';
import {
initialState,
useVegaTransaction,
VegaTxStatus,
} from './use-vega-transaction';
import type { OrderSubmission } from './types';
const defaultWalletContext = {
@ -94,3 +98,14 @@ it('Returns the signature if successful', async () => {
successObj.tx.signature.value
);
});
it('Resets transaction state if user rejects', async () => {
const mockSendTx = jest
.fn()
.mockReturnValue(Promise.resolve({ error: 'User rejected' }));
const { result } = setup({ sendTx: mockSendTx });
await act(async () => {
result.current.send({} as OrderSubmission);
});
expect(result.current.transaction).toEqual(initialState);
});

View File

@ -44,6 +44,10 @@ export const useVegaTransaction = () => {
[setTransaction]
);
const reset = useCallback(() => {
setTransaction(initialState);
}, [setTransaction]);
const send = useCallback(
async (tx: TransactionSubmission) => {
setTransaction({
@ -61,7 +65,12 @@ export const useVegaTransaction = () => {
}
if ('error' in res) {
handleError(res);
// Close dialog if user rejects the transaction
if (res.error === 'User rejected') {
reset();
} else {
handleError(res);
}
return null;
} else if ('errors' in res) {
handleError(res);
@ -79,12 +88,8 @@ export const useVegaTransaction = () => {
return null;
},
[sendTx, handleError, setTransaction]
[sendTx, handleError, setTransaction, reset]
);
const reset = useCallback(() => {
setTransaction(initialState);
}, [setTransaction]);
return { send, transaction, reset };
};

View File

@ -29,7 +29,7 @@
"@sentry/nextjs": "^6.19.3",
"@sentry/react": "^6.19.2",
"@sentry/tracing": "^6.19.2",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.12",
"@vegaprotocol/vegawallet-service-api-client": "^0.4.13",
"@walletconnect/ethereum-provider": "^1.7.5",
"@web3-react/core": "8.0.20-beta.0",
"@web3-react/metamask": "8.0.16-beta.0",

View File

@ -6750,10 +6750,10 @@
"@typescript-eslint/types" "5.22.0"
eslint-visitor-keys "^3.0.0"
"@vegaprotocol/vegawallet-service-api-client@^0.4.12":
version "0.4.12"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.12.tgz#65551b9a4d2e00b2c2e9ca9619d95453954a0dbf"
integrity sha512-Z680W8rsjz2U8R/gss7+hI0eik0euDJLlh7LzWGXUJxUC3XWO9rwJmzlqN/ZlEB4L9OzSLTSZsvlBAGwiHzUSQ==
"@vegaprotocol/vegawallet-service-api-client@^0.4.13":
version "0.4.13"
resolved "https://registry.yarnpkg.com/@vegaprotocol/vegawallet-service-api-client/-/vegawallet-service-api-client-0.4.13.tgz#fb98ec0179ea6cc27e991ef3d3338327eca4f3c4"
integrity sha512-YK6DsDKgvb+n9QwvKYSBQ51TDon0lGpLsNdNUa4oywIjubzWGVE4g98GrEmcP+UB/AfZzLA6A9ul7F/+TSep5Q==
dependencies:
es6-promise "^4.2.4"
url-parse "^1.4.3"