feat(explorer): add liquidity provision submission and amend view (#2663)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
This commit is contained in:
parent
0702a97b6d
commit
df6b9023f1
@ -60,10 +60,15 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
|||||||
market: {
|
market: {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
id: '789',
|
id: '789',
|
||||||
|
state: 'STATUS_ACTIVE',
|
||||||
decimalPlaces: '5',
|
decimalPlaces: '5',
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
quoteName: '123',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -37,10 +37,15 @@ const mock = {
|
|||||||
market: {
|
market: {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
id: '789',
|
id: '789',
|
||||||
|
state: 'STATE_ACTIVE',
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
tradableInstrument: {
|
tradableInstrument: {
|
||||||
instrument: {
|
instrument: {
|
||||||
name: 'TEST',
|
name: 'TEST',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
quoteName: '123',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,8 @@ describe('Lib: getBlockTime', () => {
|
|||||||
it('Returns a known date string', () => {
|
it('Returns a known date string', () => {
|
||||||
const mockBlockTime = '1669223762';
|
const mockBlockTime = '1669223762';
|
||||||
const usRes = getBlockTime(mockBlockTime, 'en-US');
|
const usRes = getBlockTime(mockBlockTime, 'en-US');
|
||||||
expect(usRes).toEqual('11/23/2022, 5:16:02 PM');
|
expect(usRes).toContain('11/23/2022');
|
||||||
|
expect(usRes).toContain('5:16:02');
|
||||||
|
expect(usRes).toContain('PM');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
export const ErrorCodes = new Map([
|
export const ErrorCodes = new Map([
|
||||||
[51, 'Transaction failed validation'],
|
[51, 'Transaction failed validation'],
|
||||||
[60, 'Transaction could not be decoded'],
|
[60, 'Transaction could not be decoded'],
|
||||||
[70, 'Internal error'],
|
[70, 'Error'],
|
||||||
[80, 'Unknown command'],
|
[80, 'Unknown command'],
|
||||||
[89, 'Rejected as spam'],
|
[89, 'Rejected as spam'],
|
||||||
[0, 'Success'],
|
[0, 'Success'],
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
query ExplorerSettlementAssetForMarket($id: ID!) {
|
||||||
|
market(id: $id) {
|
||||||
|
id
|
||||||
|
decimalPlaces
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
settlementAsset {
|
||||||
|
decimals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
|
||||||
|
id: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const ExplorerSettlementAssetForMarketDocument = gql`
|
||||||
|
query ExplorerSettlementAssetForMarket($id: ID!) {
|
||||||
|
market(id: $id) {
|
||||||
|
id
|
||||||
|
decimalPlaces
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
settlementAsset {
|
||||||
|
decimals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useExplorerSettlementAssetForMarketQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useExplorerSettlementAssetForMarketQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useExplorerSettlementAssetForMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useExplorerSettlementAssetForMarketQuery({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useExplorerSettlementAssetForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
|
||||||
|
}
|
||||||
|
export function useExplorerSettlementAssetForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
|
||||||
|
}
|
||||||
|
export type ExplorerSettlementAssetForMarketQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketQuery>;
|
||||||
|
export type ExplorerSettlementAssetForMarketLazyQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketLazyQuery>;
|
||||||
|
export type ExplorerSettlementAssetForMarketQueryResult = Apollo.QueryResult<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>;
|
@ -0,0 +1,131 @@
|
|||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { Side } from '@vegaprotocol/types';
|
||||||
|
import type { LiquidityOrder } from '@vegaprotocol/types';
|
||||||
|
import { PeggedReference } from '@vegaprotocol/types';
|
||||||
|
import { LiquidityProvisionDetailsRow } from './liquidity-provision-details-row';
|
||||||
|
import type { VegaSide } from './liquidity-provision-details-row';
|
||||||
|
|
||||||
|
describe('LiquidityProvisionDetails component', () => {
|
||||||
|
function renderComponent(
|
||||||
|
order: LiquidityOrder,
|
||||||
|
side: VegaSide,
|
||||||
|
normaliseProportionsTo: number,
|
||||||
|
marketId: string
|
||||||
|
) {
|
||||||
|
return render(
|
||||||
|
<MockedProvider>
|
||||||
|
<table>
|
||||||
|
<tbody data-testid="container">
|
||||||
|
<LiquidityProvisionDetailsRow
|
||||||
|
order={order}
|
||||||
|
marketId={marketId}
|
||||||
|
normaliseProportionsTo={normaliseProportionsTo}
|
||||||
|
side={side}
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders null for an order with no proportion', () => {
|
||||||
|
const mockOrder = {
|
||||||
|
offset: '1',
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(
|
||||||
|
mockOrder as LiquidityOrder,
|
||||||
|
Side.SIDE_BUY,
|
||||||
|
100,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
expect(res.getByTestId('container')).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders null for a null order', () => {
|
||||||
|
const res = renderComponent(
|
||||||
|
null as unknown as LiquidityOrder,
|
||||||
|
Side.SIDE_BUY,
|
||||||
|
100,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
expect(res.getByTestId('container')).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a row when the order is as expected', () => {
|
||||||
|
const mockOrder = {
|
||||||
|
offset: '1',
|
||||||
|
proportion: 20,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(
|
||||||
|
mockOrder as LiquidityOrder,
|
||||||
|
Side.SIDE_BUY,
|
||||||
|
100,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
// Row test ids and keys are based on the side, reference and proportion
|
||||||
|
expect(res.getByTestId('SIDE_BUY-20-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('+1')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Mid')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('20%')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalises offsets when normaliseToProportion is not 100', () => {
|
||||||
|
const mockOrder = {
|
||||||
|
offset: '1',
|
||||||
|
proportion: 20,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(
|
||||||
|
mockOrder as LiquidityOrder,
|
||||||
|
Side.SIDE_SELL,
|
||||||
|
50,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||||
|
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Best Bid')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a missing offset gracefully (should not happen)', () => {
|
||||||
|
const mockOrder = {
|
||||||
|
proportion: 20,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(
|
||||||
|
mockOrder as LiquidityOrder,
|
||||||
|
Side.SIDE_SELL,
|
||||||
|
50,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||||
|
expect(res.getByTestId('SIDE_SELL-40-')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('-')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a missing reference gracefully (should not happen)', () => {
|
||||||
|
const mockOrder = {
|
||||||
|
offset: '1',
|
||||||
|
proportion: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(
|
||||||
|
mockOrder as LiquidityOrder,
|
||||||
|
Side.SIDE_SELL,
|
||||||
|
50,
|
||||||
|
'123'
|
||||||
|
);
|
||||||
|
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
|
||||||
|
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('-')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,77 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import type { components } from '../../../../../../types/explorer';
|
||||||
|
import { TableRow } from '../../../../table';
|
||||||
|
import { LiquidityProvisionOffset } from './liquidity-provision-offset';
|
||||||
|
|
||||||
|
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
|
||||||
|
export type VegaSide = components['schemas']['vegaSide'];
|
||||||
|
|
||||||
|
export type LiquidityProvisionOrder =
|
||||||
|
components['schemas']['vegaLiquidityOrder'];
|
||||||
|
|
||||||
|
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
|
||||||
|
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
|
||||||
|
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
|
||||||
|
PEGGED_REFERENCE_MID: t('Mid'),
|
||||||
|
PEGGED_REFERENCE_UNSPECIFIED: '-',
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LiquidityProvisionDetailsRowProps = {
|
||||||
|
order?: LiquidityProvisionOrder;
|
||||||
|
marketId?: string;
|
||||||
|
side: VegaSide;
|
||||||
|
// If this is
|
||||||
|
normaliseProportionsTo: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Note: offset is formatted by settlement asset on the market, assuming that is available
|
||||||
|
* Note: Due to the mix of references (MID vs BEST_X), it's not possible to correctly order
|
||||||
|
* the orders by their actual distance from a midpoint. This would require us knowing
|
||||||
|
* the best bid (now or at placement) and the mid. Getting the data for *now* would be
|
||||||
|
* misleading for LP submissions in the past. There is no API for getting <mid />
|
||||||
|
* at the time of a transaction.
|
||||||
|
*/
|
||||||
|
export function LiquidityProvisionDetailsRow({
|
||||||
|
normaliseProportionsTo,
|
||||||
|
order,
|
||||||
|
side,
|
||||||
|
marketId,
|
||||||
|
}: LiquidityProvisionDetailsRowProps) {
|
||||||
|
if (!order || !order.proportion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proportion =
|
||||||
|
normaliseProportionsTo === 100
|
||||||
|
? order.proportion
|
||||||
|
: Math.round((order.proportion / normaliseProportionsTo) * 100);
|
||||||
|
|
||||||
|
const key = `${side}-${proportion}-${order.offset ? order.offset : ''}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow modifier="bordered" key={key} data-testid={key}>
|
||||||
|
<td className="text-right px-2">
|
||||||
|
{order.offset && marketId ? (
|
||||||
|
<LiquidityProvisionOffset
|
||||||
|
offset={order.offset}
|
||||||
|
side={side}
|
||||||
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
{proportion === order.proportion
|
||||||
|
? `${proportion}%`
|
||||||
|
: `${proportion}% (normalised from: ${order.proportion}%)`}{' '}
|
||||||
|
</td>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { LiquidityProvisionMid } from './liquidity-provision-mid';
|
||||||
|
|
||||||
|
describe('LiquidityProvisionMid component', () => {
|
||||||
|
function renderComponent() {
|
||||||
|
return render(
|
||||||
|
<table>
|
||||||
|
<tbody data-testid="container">
|
||||||
|
<LiquidityProvisionMid />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders a basic row that spans the whole table', () => {
|
||||||
|
const res = renderComponent();
|
||||||
|
const display = res.getByTestId('mid-display');
|
||||||
|
expect(res.getByTestId('mid')).toBeInTheDocument();
|
||||||
|
expect(display).toBeInTheDocument();
|
||||||
|
expect(display).toHaveAttribute('colspan', '3');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
import { TableRow } from '../../../../table';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a LiquidityProvision table, this row is the midpoint. Above our LP orders on the
|
||||||
|
* buy side, below are LP orders on the sell side. This component simply divides them.
|
||||||
|
*
|
||||||
|
* There is no API that can give us the mid price when the order was created, and even
|
||||||
|
* if there was it isn't clear that would be appropriate for this centre row. So instead
|
||||||
|
* it's a simple divider.
|
||||||
|
*/
|
||||||
|
export function LiquidityProvisionMid() {
|
||||||
|
return (
|
||||||
|
<TableRow modifier="bordered" data-testid="mid">
|
||||||
|
<td data-testid="mid-display" colSpan={3} className="bg-white"></td>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { ExplorerSettlementAssetForMarketDocument } from '../__generated__/Explorer-settlement-asset';
|
||||||
|
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||||
|
import type { VegaSide } from './liquidity-provision-details-row';
|
||||||
|
import {
|
||||||
|
getFormattedOffset,
|
||||||
|
LiquidityProvisionOffset,
|
||||||
|
} from './liquidity-provision-offset';
|
||||||
|
const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
|
||||||
|
market: {
|
||||||
|
id: '123',
|
||||||
|
__typename: 'Market',
|
||||||
|
decimalPlaces: 5,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
product: {
|
||||||
|
settlementAsset: {
|
||||||
|
decimals: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('LiquidityProvisionOffset component', () => {
|
||||||
|
function renderComponent(
|
||||||
|
offset: string,
|
||||||
|
side: VegaSide,
|
||||||
|
marketId: string,
|
||||||
|
mocks: MockedResponse[]
|
||||||
|
) {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<LiquidityProvisionOffset
|
||||||
|
offset={offset}
|
||||||
|
side={side}
|
||||||
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders a simple row before market data comes in', () => {
|
||||||
|
const res = renderComponent('1', 'SIDE_BUY', '123', []);
|
||||||
|
expect(res.getByText('+1')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('replaces unformatted with formatted if the market data comes in', () => {
|
||||||
|
const mock = {
|
||||||
|
request: {
|
||||||
|
query: ExplorerSettlementAssetForMarketDocument,
|
||||||
|
variables: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: decimalsMock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = renderComponent('1', 'SIDE_BUY', '123', [mock]);
|
||||||
|
expect(res.getByText('+1')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getFormattedOffset returns the unformatted offset if there is not enough data', () => {
|
||||||
|
const res = getFormattedOffset('1', {});
|
||||||
|
expect(res).toEqual('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getFormattedOffset decimal formats a number if it comes in with market data', () => {
|
||||||
|
const res = getFormattedOffset('1', decimalsMock);
|
||||||
|
expect(res).toEqual('0.00001');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,61 @@
|
|||||||
|
import { useExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
|
||||||
|
import type { VegaSide } from './liquidity-provision-details-row';
|
||||||
|
|
||||||
|
export type LiquidityProvisionOffsetProps = {
|
||||||
|
side: VegaSide;
|
||||||
|
offset: string;
|
||||||
|
marketId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correctly formats an LP's offset according to the market settlement decimal places.
|
||||||
|
* Initially this will appear unformatted, then when the query loads in the proper formatted
|
||||||
|
* value will be displayed
|
||||||
|
*
|
||||||
|
* @see getFormattedOffset
|
||||||
|
*/
|
||||||
|
export function LiquidityProvisionOffset({
|
||||||
|
side,
|
||||||
|
offset,
|
||||||
|
marketId,
|
||||||
|
}: LiquidityProvisionOffsetProps) {
|
||||||
|
const { data } = useExplorerSettlementAssetForMarketQuery({
|
||||||
|
variables: {
|
||||||
|
id: marketId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// getFormattedOffset handles missing results/loading states
|
||||||
|
const formattedOffset = getFormattedOffset(offset, data);
|
||||||
|
|
||||||
|
const label = side === 'SIDE_BUY' ? '+' : '-';
|
||||||
|
const className = side === 'SIDE_BUY' ? 'text-vega-green' : 'text-vega-pink';
|
||||||
|
return <span className={className}>{`${label}${formattedOffset}`}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the work of formatting the number now we have the settlement decimal places.
|
||||||
|
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
|
||||||
|
* this function will return the unformatted number
|
||||||
|
*
|
||||||
|
* @see LiquidityProvisionOffset
|
||||||
|
* @param data the result of a ExplorerSettlementAssetForMarketQuery
|
||||||
|
* @param offset the unformatted offset
|
||||||
|
* @returns string the offset of this lp order formatted with the settlement decimal places
|
||||||
|
*/
|
||||||
|
export function getFormattedOffset(
|
||||||
|
offset: string,
|
||||||
|
data?: ExplorerSettlementAssetForMarketQuery
|
||||||
|
) {
|
||||||
|
const decimals =
|
||||||
|
data?.market?.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.decimals;
|
||||||
|
|
||||||
|
if (!decimals) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addDecimalsFormatNumber(offset, decimals);
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import type { LiquidityOrder } from '@vegaprotocol/types';
|
||||||
|
import { PeggedReference } from '@vegaprotocol/types';
|
||||||
|
import type { LiquiditySubmission } from '../tx-liquidity-submission';
|
||||||
|
import {
|
||||||
|
LiquidityProvisionDetails,
|
||||||
|
sumProportions,
|
||||||
|
} from './liquidity-provision-details';
|
||||||
|
|
||||||
|
function mockProportion(proportion: number): LiquidityOrder {
|
||||||
|
return {
|
||||||
|
proportion,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
offset: '1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
describe('sumProportions function', () => {
|
||||||
|
it('returns 0 if the side is undefined', () => {
|
||||||
|
const side: LiquidityOrder[] = undefined as unknown as LiquidityOrder[];
|
||||||
|
const res = sumProportions(side);
|
||||||
|
|
||||||
|
expect(res).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 0 if the side is empty', () => {
|
||||||
|
const side: LiquidityOrder[] = [];
|
||||||
|
const res = sumProportions(side);
|
||||||
|
|
||||||
|
expect(res).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sums 1 item correctly (under 100%)', () => {
|
||||||
|
const side: LiquidityOrder[] = [mockProportion(10)];
|
||||||
|
const res = sumProportions(side);
|
||||||
|
|
||||||
|
expect(res).toEqual(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sums 2 item correctly (exactly 100%)', () => {
|
||||||
|
const side: LiquidityOrder[] = [mockProportion(50), mockProportion(50)];
|
||||||
|
const res = sumProportions(side);
|
||||||
|
|
||||||
|
expect(res).toEqual(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sums 3 item correctly to over 100%', () => {
|
||||||
|
const side: LiquidityOrder[] = [
|
||||||
|
mockProportion(20),
|
||||||
|
mockProportion(40),
|
||||||
|
mockProportion(50),
|
||||||
|
];
|
||||||
|
const res = sumProportions(side);
|
||||||
|
|
||||||
|
expect(res).toEqual(110);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LiquidityProvisionDetails component', () => {
|
||||||
|
function renderComponent(provision: LiquiditySubmission) {
|
||||||
|
return render(
|
||||||
|
<MockedProvider>
|
||||||
|
<LiquidityProvisionDetails provision={provision} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
it('handles an LP with no buys or sells by returning empty (should never happen)', () => {
|
||||||
|
const mock: LiquiditySubmission = {};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.container).toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an LP with no sells by just rendering buys', () => {
|
||||||
|
const mock: LiquiditySubmission = {
|
||||||
|
marketId: '123',
|
||||||
|
buys: [
|
||||||
|
{
|
||||||
|
offset: '1',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an LP with no buys by just rendering sells', () => {
|
||||||
|
const mock: LiquiditySubmission = {
|
||||||
|
marketId: '123',
|
||||||
|
sells: [
|
||||||
|
{
|
||||||
|
offset: '1',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_SELL-50-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an LP with sells by just rendering buys', () => {
|
||||||
|
const mock: LiquiditySubmission = {
|
||||||
|
marketId: '123',
|
||||||
|
buys: [
|
||||||
|
{
|
||||||
|
offset: '1',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an LP with both sides', () => {
|
||||||
|
const mock: LiquiditySubmission = {
|
||||||
|
marketId: '123',
|
||||||
|
buys: [
|
||||||
|
{
|
||||||
|
offset: '1',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sells: [
|
||||||
|
{
|
||||||
|
offset: '4',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 50,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.getByText('Price offset')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Price reference')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Proportion')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_SELL-50-4')).toBeInTheDocument();
|
||||||
|
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalises proportions when they do not total 100%', () => {
|
||||||
|
const mock: LiquiditySubmission = {
|
||||||
|
marketId: '123',
|
||||||
|
buys: [
|
||||||
|
{
|
||||||
|
offset: '1',
|
||||||
|
proportion: 25,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: '2',
|
||||||
|
proportion: 30,
|
||||||
|
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(mock);
|
||||||
|
expect(res.getByText('45% (normalised from: 25%)')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('55% (normalised from: 30%)')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,94 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import type { LiquiditySubmission } from '../tx-liquidity-submission';
|
||||||
|
import { TableRow } from '../../../table';
|
||||||
|
import { LiquidityProvisionMid } from './components/liquidity-provision-mid';
|
||||||
|
import { LiquidityProvisionDetailsRow } from './components/liquidity-provision-details-row';
|
||||||
|
import { Side } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
|
||||||
|
|
||||||
|
export type LiquidityProvisionOrder =
|
||||||
|
components['schemas']['vegaLiquidityOrder'];
|
||||||
|
|
||||||
|
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
|
||||||
|
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
|
||||||
|
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
|
||||||
|
PEGGED_REFERENCE_MID: t('Mid'),
|
||||||
|
PEGGED_REFERENCE_UNSPECIFIED: '-',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a side of a liquidity provision order, returns the total
|
||||||
|
* It should be 100%, but it isn't always and if it isn't the proportion
|
||||||
|
* reported for each order should be scaled
|
||||||
|
*
|
||||||
|
* @returns number
|
||||||
|
*/
|
||||||
|
export function sumProportions(
|
||||||
|
side: LiquiditySubmission['buys'] | LiquiditySubmission['sells']
|
||||||
|
): number {
|
||||||
|
if (!side || side.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return side.reduce((total, o) => total + (o.proportion || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LiquidityProvisionDetailsProps = {
|
||||||
|
provision: LiquiditySubmission;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a table displaying all buys and sells in this LP. It is valid for there
|
||||||
|
* to be no buys or sells.
|
||||||
|
*
|
||||||
|
* It might seem logical to turn proportions in to values based on the total commitment
|
||||||
|
* but based on the current API structure it is awkward, and given that non-LP orders
|
||||||
|
* will change the amount that is actually deployed vs assigned to a level, we decided
|
||||||
|
* not to bother going down that route.
|
||||||
|
*/
|
||||||
|
export function LiquidityProvisionDetails({
|
||||||
|
provision,
|
||||||
|
}: LiquidityProvisionDetailsProps) {
|
||||||
|
if (!provision.buys?.length && !provision.sells?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// We need to do some additional calcs if these aren't both 100
|
||||||
|
const buyTotal = sumProportions(provision.buys);
|
||||||
|
const sellTotal = sumProportions(provision.sells);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<th className="px-2 pb-1">{t('Price offset')}</th>
|
||||||
|
<th className="px-2 pb-1">{t('Price reference')}</th>
|
||||||
|
<th className="px-2 pb-1">{t('Proportion')}</th>
|
||||||
|
</TableRow>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{provision.buys?.map((b, i) => (
|
||||||
|
<LiquidityProvisionDetailsRow
|
||||||
|
order={b}
|
||||||
|
marketId={provision.marketId}
|
||||||
|
side={Side.SIDE_BUY}
|
||||||
|
key={`SIDE_BUY-${i}`}
|
||||||
|
normaliseProportionsTo={buyTotal}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<LiquidityProvisionMid />
|
||||||
|
{provision.sells?.map((s, i) => (
|
||||||
|
<LiquidityProvisionDetailsRow
|
||||||
|
order={s}
|
||||||
|
marketId={provision.marketId}
|
||||||
|
side={Side.SIDE_SELL}
|
||||||
|
key={`SIDE_SELL-${i}`}
|
||||||
|
normaliseProportionsTo={sellTotal}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
@ -5,7 +5,6 @@ import { TxDetailsOrder } from './tx-order';
|
|||||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
import { TxDetailsHeartbeat } from './tx-hearbeat';
|
import { TxDetailsHeartbeat } from './tx-hearbeat';
|
||||||
import { TxDetailsLPAmend } from './tx-lp-amend';
|
|
||||||
import { TxDetailsGeneric } from './tx-generic';
|
import { TxDetailsGeneric } from './tx-generic';
|
||||||
import { TxDetailsBatch } from './tx-batch';
|
import { TxDetailsBatch } from './tx-batch';
|
||||||
import { TxDetailsChainEvent } from './tx-chain-event';
|
import { TxDetailsChainEvent } from './tx-chain-event';
|
||||||
@ -17,6 +16,8 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
|
|||||||
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
|
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
|
||||||
import { TxDetailsDelegate } from './tx-delegation';
|
import { TxDetailsDelegate } from './tx-delegation';
|
||||||
import { TxDetailsUndelegate } from './tx-undelegation';
|
import { TxDetailsUndelegate } from './tx-undelegation';
|
||||||
|
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
|
||||||
|
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
|
||||||
|
|
||||||
interface TxDetailsWrapperProps {
|
interface TxDetailsWrapperProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@ -83,8 +84,6 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
|||||||
return TxDetailsOrderAmend;
|
return TxDetailsOrderAmend;
|
||||||
case 'Validator Heartbeat':
|
case 'Validator Heartbeat':
|
||||||
return TxDetailsHeartbeat;
|
return TxDetailsHeartbeat;
|
||||||
case 'Amend LiquidityProvision Order':
|
|
||||||
return TxDetailsLPAmend;
|
|
||||||
case 'Batch Market Instructions':
|
case 'Batch Market Instructions':
|
||||||
return TxDetailsBatch;
|
return TxDetailsBatch;
|
||||||
case 'Chain Event':
|
case 'Chain Event':
|
||||||
@ -93,6 +92,10 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
|||||||
return TxDetailsNodeVote;
|
return TxDetailsNodeVote;
|
||||||
case 'Withdraw':
|
case 'Withdraw':
|
||||||
return TxDetailsWithdrawSubmission;
|
return TxDetailsWithdrawSubmission;
|
||||||
|
case 'Liquidity Provision Order':
|
||||||
|
return TxDetailsLiquiditySubmission;
|
||||||
|
case 'Amend Liquidity Provision Order':
|
||||||
|
return TxDetailsLiquidityAmendment;
|
||||||
case 'Delegate':
|
case 'Delegate':
|
||||||
return TxDetailsDelegate;
|
return TxDetailsDelegate;
|
||||||
case 'Undelegate':
|
case 'Undelegate':
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import { MarketLink } from '../../links';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
|
||||||
|
import PriceInMarket from '../../price-in-market/price-in-market';
|
||||||
|
|
||||||
|
export type LiquidityAmendment =
|
||||||
|
components['schemas']['v1LiquidityProvisionAmendment'];
|
||||||
|
|
||||||
|
interface TxDetailsLiquidityAmendmentProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An existing liquidity order is being amended. This uses
|
||||||
|
* exactly the same details as the creation
|
||||||
|
*/
|
||||||
|
export const TxDetailsLiquidityAmendment = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsLiquidityAmendmentProps) => {
|
||||||
|
if (!txData || !txData.command.liquidityProvisionAmendment) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amendment: LiquidityAmendment =
|
||||||
|
txData.command.liquidityProvisionAmendment;
|
||||||
|
const marketId: string = amendment.marketId || '-';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableWithTbody className="mb-8">
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Market')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MarketLink id={marketId} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{amendment.commitmentAmount ? (
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Commitment amount')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<PriceInMarket
|
||||||
|
price={amendment.commitmentAmount}
|
||||||
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : null}
|
||||||
|
{amendment.fee ? (
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Fee')}</TableCell>
|
||||||
|
<TableCell>{amendment.fee}%</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : null}
|
||||||
|
</TableWithTbody>
|
||||||
|
|
||||||
|
<LiquidityProvisionDetails provision={amendment} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,72 @@
|
|||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import { MarketLink } from '../../links/';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
|
||||||
|
import PriceInMarket from '../../price-in-market/price-in-market';
|
||||||
|
|
||||||
|
export type LiquiditySubmission =
|
||||||
|
components['schemas']['v1LiquidityProvisionSubmission'];
|
||||||
|
|
||||||
|
interface TxDetailsLiquiditySubmissionProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Someone cancelled an order
|
||||||
|
*/
|
||||||
|
export const TxDetailsLiquiditySubmission = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsLiquiditySubmissionProps) => {
|
||||||
|
if (!txData || !txData.command.liquidityProvisionSubmission) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submission: LiquiditySubmission =
|
||||||
|
txData.command.liquidityProvisionSubmission;
|
||||||
|
const marketId: string = submission.marketId || '-';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableWithTbody className="mb-8">
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Market')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<MarketLink id={marketId} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{submission.commitmentAmount ? (
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Commitment amount')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<PriceInMarket
|
||||||
|
price={submission.commitmentAmount}
|
||||||
|
marketId={marketId}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : null}
|
||||||
|
{submission.fee ? (
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Fee')}</TableCell>
|
||||||
|
<TableCell>{submission.fee}%</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : null}
|
||||||
|
</TableWithTbody>
|
||||||
|
|
||||||
|
<LiquidityProvisionDetails provision={submission} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,41 +0,0 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
|
||||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
|
||||||
import { MarketLink } from '../../links/';
|
|
||||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
|
||||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
|
||||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
|
||||||
|
|
||||||
interface TxDetailsOrderProps {
|
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
|
||||||
pubKey: string | undefined;
|
|
||||||
blockData: TendermintBlocksResponse | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies changes to the shape of a users Liquidity Commitment order for
|
|
||||||
* a specific market. So far this only displays the market, which is only
|
|
||||||
* because it's very easy to do so.
|
|
||||||
*/
|
|
||||||
export const TxDetailsLPAmend = ({
|
|
||||||
txData,
|
|
||||||
pubKey,
|
|
||||||
blockData,
|
|
||||||
}: TxDetailsOrderProps) => {
|
|
||||||
if (!txData) {
|
|
||||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const marketId = txData.command.liquidityProvisionAmendment?.marketId || '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableWithTbody className="mb-8">
|
|
||||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
|
||||||
<TableRow modifier="bordered">
|
|
||||||
<TableCell>{t('Market')}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<MarketLink id={marketId} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableWithTbody>
|
|
||||||
);
|
|
||||||
};
|
|
@ -2,6 +2,7 @@ import { TxsInfiniteList } from './txs-infinite-list';
|
|||||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||||
|
import { Side } from '@vegaprotocol/types';
|
||||||
|
|
||||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||||
return Array.from(Array(number)).map((_) => ({
|
return Array.from(Array(number)).map((_) => ({
|
||||||
@ -24,7 +25,7 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
|||||||
'b4d0a070f5cc73a7d53b23d6f63f8cb52e937ed65d2469a3af4cc1e80e155fcf',
|
'b4d0a070f5cc73a7d53b23d6f63f8cb52e937ed65d2469a3af4cc1e80e155fcf',
|
||||||
price: '14525946',
|
price: '14525946',
|
||||||
size: '54',
|
size: '54',
|
||||||
side: 'SIDE_SELL',
|
side: Side.SIDE_SELL,
|
||||||
timeInForce: 'TIME_IN_FORCE_GTT',
|
timeInForce: 'TIME_IN_FORCE_GTT',
|
||||||
expiresAt: '1664966445481288736',
|
expiresAt: '1664966445481288736',
|
||||||
type: 'TYPE_LIMIT',
|
type: 'TYPE_LIMIT',
|
||||||
|
Loading…
Reference in New Issue
Block a user