feat(explorer): add stop order view (#4418)
This commit is contained in:
parent
4da0e9c368
commit
332cc302c3
@ -77,7 +77,7 @@
|
|||||||
"executor": "nx:run-commands",
|
"executor": "nx:run-commands",
|
||||||
"options": {
|
"options": {
|
||||||
"commands": [
|
"commands": [
|
||||||
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/spec-update-v0.72.0-preview.2/specs/v0.72.0-preview.2/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
|
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.72.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
fragment ExplorerStopOrderFields on StopOrder {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
trigger {
|
||||||
|
... on StopOrderPrice {
|
||||||
|
price
|
||||||
|
}
|
||||||
|
... on StopOrderTrailingPercentOffset {
|
||||||
|
trailingPercentOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
ocoLinkId
|
||||||
|
triggerDirection
|
||||||
|
order {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query ExplorerStopOrder($stopOrderId: ID!) {
|
||||||
|
stopOrder(id: $stopOrderId) {
|
||||||
|
...ExplorerStopOrderFields
|
||||||
|
}
|
||||||
|
}
|
70
apps/explorer/src/app/components/order-details/__generated__/StopOrder.ts
generated
Normal file
70
apps/explorer/src/app/components/order-details/__generated__/StopOrder.ts
generated
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ExplorerStopOrderFieldsFragment = { __typename?: 'StopOrder', id: string, status: Types.StopOrderStatus, createdAt: any, ocoLinkId?: string | null, triggerDirection: Types.StopOrderTriggerDirection, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, order?: { __typename?: 'Order', id: string } | null };
|
||||||
|
|
||||||
|
export type ExplorerStopOrderQueryVariables = Types.Exact<{
|
||||||
|
stopOrderId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ExplorerStopOrderQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, status: Types.StopOrderStatus, createdAt: any, ocoLinkId?: string | null, triggerDirection: Types.StopOrderTriggerDirection, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, order?: { __typename?: 'Order', id: string } | null } | null };
|
||||||
|
|
||||||
|
export const ExplorerStopOrderFieldsFragmentDoc = gql`
|
||||||
|
fragment ExplorerStopOrderFields on StopOrder {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
trigger {
|
||||||
|
... on StopOrderPrice {
|
||||||
|
price
|
||||||
|
}
|
||||||
|
... on StopOrderTrailingPercentOffset {
|
||||||
|
trailingPercentOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
ocoLinkId
|
||||||
|
triggerDirection
|
||||||
|
order {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const ExplorerStopOrderDocument = gql`
|
||||||
|
query ExplorerStopOrder($stopOrderId: ID!) {
|
||||||
|
stopOrder(id: $stopOrderId) {
|
||||||
|
...ExplorerStopOrderFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${ExplorerStopOrderFieldsFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useExplorerStopOrderQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useExplorerStopOrderQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useExplorerStopOrderQuery` 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 } = useExplorerStopOrderQuery({
|
||||||
|
* variables: {
|
||||||
|
* stopOrderId: // value for 'stopOrderId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useExplorerStopOrderQuery(baseOptions: Apollo.QueryHookOptions<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>(ExplorerStopOrderDocument, options);
|
||||||
|
}
|
||||||
|
export function useExplorerStopOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>(ExplorerStopOrderDocument, options);
|
||||||
|
}
|
||||||
|
export type ExplorerStopOrderQueryHookResult = ReturnType<typeof useExplorerStopOrderQuery>;
|
||||||
|
export type ExplorerStopOrderLazyQueryHookResult = ReturnType<typeof useExplorerStopOrderLazyQuery>;
|
||||||
|
export type ExplorerStopOrderQueryResult = Apollo.QueryResult<ExplorerStopOrderQuery, ExplorerStopOrderQueryVariables>;
|
@ -33,7 +33,7 @@ describe('Order TX Summary component', () => {
|
|||||||
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders nothing if the order passed lacks a price', () => {
|
it('Renders "Market Price" if the order passed lacks a price', () => {
|
||||||
const o: Order = {
|
const o: Order = {
|
||||||
marketId: '123',
|
marketId: '123',
|
||||||
side: 'SIDE_BUY',
|
side: 'SIDE_BUY',
|
||||||
@ -41,7 +41,7 @@ describe('Order TX Summary component', () => {
|
|||||||
size: '10',
|
size: '10',
|
||||||
};
|
};
|
||||||
const res = renderComponent(o);
|
const res = renderComponent(o);
|
||||||
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
expect(res.queryByText('Market Price')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders nothing if the order has an unspecified side', () => {
|
it('Renders nothing if the order has an unspecified side', () => {
|
||||||
|
@ -3,6 +3,7 @@ import type { components } from '../../../types/explorer';
|
|||||||
import PriceInMarket from '../price-in-market/price-in-market';
|
import PriceInMarket from '../price-in-market/price-in-market';
|
||||||
import { sideText } from '../order-details/lib/order-labels';
|
import { sideText } from '../order-details/lib/order-labels';
|
||||||
import SizeInMarket from '../size-in-market/size-in-market';
|
import SizeInMarket from '../size-in-market/size-in-market';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export type OrderSummaryProps = {
|
export type OrderSummaryProps = {
|
||||||
order: components['schemas']['v1OrderSubmission'];
|
order: components['schemas']['v1OrderSubmission'];
|
||||||
@ -20,7 +21,6 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => {
|
|||||||
if (
|
if (
|
||||||
!order ||
|
!order ||
|
||||||
!order.marketId ||
|
!order.marketId ||
|
||||||
!order.price ||
|
|
||||||
!order.side ||
|
!order.side ||
|
||||||
order.side === 'SIDE_UNSPECIFIED'
|
order.side === 'SIDE_UNSPECIFIED'
|
||||||
) {
|
) {
|
||||||
@ -36,10 +36,14 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => {
|
|||||||
'-'
|
'-'
|
||||||
)}
|
)}
|
||||||
<i className="text-xs">@</i>
|
<i className="text-xs">@</i>
|
||||||
|
{order.price ? (
|
||||||
<PriceInMarket
|
<PriceInMarket
|
||||||
marketId={order.marketId}
|
marketId={order.marketId}
|
||||||
price={order.price}
|
price={order.price}
|
||||||
></PriceInMarket>
|
></PriceInMarket>
|
||||||
|
) : (
|
||||||
|
t('Market Price')
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import PriceInMarket from '../../../price-in-market/price-in-market';
|
||||||
|
import StopOrderTriggerSummary from './stop-order-trigger';
|
||||||
|
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
|
|
||||||
|
const wrapperClasses =
|
||||||
|
'flex-1 max-w-xs items-center border border-vega-light-200 dark:border-vega-dark-150 rounded-md pv-2 ph-5 mb-5';
|
||||||
|
|
||||||
|
export type StopOrderType = 'RisesAbove' | 'FallsBelow' | 'OCO';
|
||||||
|
type V1OrderSetup = components['schemas']['v1StopOrderSetup'];
|
||||||
|
|
||||||
|
interface StopOrderSetupProps extends V1OrderSetup {
|
||||||
|
type: StopOrderType;
|
||||||
|
deterministicId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getExpiryTypeLabel(
|
||||||
|
expiryStrategy: V1OrderSetup['expiryStrategy']
|
||||||
|
): string {
|
||||||
|
switch (expiryStrategy) {
|
||||||
|
case 'EXPIRY_STRATEGY_CANCELS':
|
||||||
|
return t('Cancels');
|
||||||
|
case 'EXPIRY_STRATEGY_SUBMIT':
|
||||||
|
return t('Submit');
|
||||||
|
}
|
||||||
|
|
||||||
|
return expiryStrategy || t('Unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExpiryTriggerProps {
|
||||||
|
trailingPercentOffset?: string;
|
||||||
|
price?: string;
|
||||||
|
marketId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExpiryTrigger({
|
||||||
|
trailingPercentOffset,
|
||||||
|
price,
|
||||||
|
marketId,
|
||||||
|
}: ExpiryTriggerProps) {
|
||||||
|
if (price && marketId) {
|
||||||
|
return <PriceInMarket price={price} marketId={marketId} />;
|
||||||
|
}
|
||||||
|
if (trailingPercentOffset) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{formatNumberPercentage(new BigNumber(trailingPercentOffset))}{' '}
|
||||||
|
(trailing)
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMovePrefix(
|
||||||
|
type: StopOrderType,
|
||||||
|
trailingPercentOffset?: string
|
||||||
|
): string {
|
||||||
|
if (type === 'RisesAbove') {
|
||||||
|
if (trailingPercentOffset) {
|
||||||
|
return '+';
|
||||||
|
} else {
|
||||||
|
return '>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (trailingPercentOffset) {
|
||||||
|
return '-';
|
||||||
|
} else {
|
||||||
|
return '<';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TypeLabel = {
|
||||||
|
RisesAbove: t('Rises above ↗'),
|
||||||
|
FallsBelow: t('Falls below ↘'),
|
||||||
|
OCO: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export const StopOrderSetup = ({
|
||||||
|
type,
|
||||||
|
price,
|
||||||
|
orderSubmission,
|
||||||
|
expiresAt,
|
||||||
|
expiryStrategy,
|
||||||
|
trailingPercentOffset,
|
||||||
|
deterministicId,
|
||||||
|
}: StopOrderSetupProps) => {
|
||||||
|
let d = 'Unknown';
|
||||||
|
try {
|
||||||
|
d = expiresAt
|
||||||
|
? fromUnixTime(parseInt(expiresAt) / 1000000000).toLocaleString()
|
||||||
|
: t('Unknown');
|
||||||
|
} catch (e) {
|
||||||
|
d = t('Unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={wrapperClasses}>
|
||||||
|
<div className="mb-12 lg:mb-0">
|
||||||
|
<div className="bg-slate-100 text-slate-900 px-6 py-2 md:px-6 flex">
|
||||||
|
<div className="flex-1">
|
||||||
|
<strong className="font-bold mb-1">{TypeLabel[type]} </strong>
|
||||||
|
<p className=" font-xs mb-0">
|
||||||
|
{getMovePrefix(type, trailingPercentOffset)}
|
||||||
|
<ExpiryTrigger
|
||||||
|
trailingPercentOffset={trailingPercentOffset}
|
||||||
|
price={price}
|
||||||
|
marketId={orderSubmission?.marketId}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{expiresAt && expiryStrategy ? (
|
||||||
|
<div className="flex-1">
|
||||||
|
<strong className="font-bold mb-1">{t('Expiry Type')}</strong>
|
||||||
|
<p className=" font-xs mb-0">
|
||||||
|
<Tooltip description={<span>{d}</span>}>
|
||||||
|
<span>{getExpiryTypeLabel(expiryStrategy)}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<StopOrderTriggerSummary
|
||||||
|
id={deterministicId}
|
||||||
|
orderSubmission={orderSubmission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,108 @@
|
|||||||
|
import { StopOrderStatus } from '@vegaprotocol/types';
|
||||||
|
import { useExplorerStopOrderQuery } from '../../../order-details/__generated__/StopOrder';
|
||||||
|
import type { ExplorerStopOrderQuery } from '../../../order-details/__generated__/StopOrder';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { IconName } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import OrderTxSummary from '../../../order-summary/order-tx-summary';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
|
export const StatusLabel: Record<StopOrderStatus, string> = {
|
||||||
|
[StopOrderStatus.STATUS_CANCELLED]: t('Cancelled'),
|
||||||
|
[StopOrderStatus.STATUS_EXPIRED]: t('Expired'),
|
||||||
|
[StopOrderStatus.STATUS_PENDING]: t('Pending'),
|
||||||
|
[StopOrderStatus.STATUS_REJECTED]: t('Rejected'),
|
||||||
|
[StopOrderStatus.STATUS_STOPPED]: t('Stopped'),
|
||||||
|
[StopOrderStatus.STATUS_TRIGGERED]: t('Triggered'),
|
||||||
|
[StopOrderStatus.STATUS_UNSPECIFIED]: t('Status unknown'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StatusIcon: Record<StopOrderStatus, IconName> = {
|
||||||
|
[StopOrderStatus.STATUS_CANCELLED]: 'disable',
|
||||||
|
[StopOrderStatus.STATUS_EXPIRED]: 'outdated',
|
||||||
|
[StopOrderStatus.STATUS_PENDING]: 'circle',
|
||||||
|
[StopOrderStatus.STATUS_REJECTED]: 'cross',
|
||||||
|
[StopOrderStatus.STATUS_STOPPED]: 'stop',
|
||||||
|
[StopOrderStatus.STATUS_TRIGGERED]: 'tick',
|
||||||
|
[StopOrderStatus.STATUS_UNSPECIFIED]: 'help',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StatusMidColor: Record<StopOrderStatus, string> = {
|
||||||
|
[StopOrderStatus.STATUS_CANCELLED]: 'bg-red-100 text-red-900',
|
||||||
|
[StopOrderStatus.STATUS_EXPIRED]: 'bg-red-100 text-red-900',
|
||||||
|
[StopOrderStatus.STATUS_PENDING]: 'bg-yellow-100 text-yellow-900',
|
||||||
|
[StopOrderStatus.STATUS_REJECTED]: 'bg-red-100 text-red-900',
|
||||||
|
[StopOrderStatus.STATUS_STOPPED]: 'bg-red-100 text-red-900',
|
||||||
|
[StopOrderStatus.STATUS_TRIGGERED]: 'bg-green-100 text-green-900',
|
||||||
|
[StopOrderStatus.STATUS_UNSPECIFIED]: 'bg-yellow-100 text-yellow-900',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StatusBottomColor: Record<StopOrderStatus, string> = {
|
||||||
|
[StopOrderStatus.STATUS_CANCELLED]: 'bg-red-50 text-red-900 line-through',
|
||||||
|
[StopOrderStatus.STATUS_EXPIRED]: 'bg-red-50 text-red-900 line-through',
|
||||||
|
[StopOrderStatus.STATUS_PENDING]: 'bg-yellow-50 text-yellow-900',
|
||||||
|
[StopOrderStatus.STATUS_REJECTED]: 'bg-red-50 text-red-900 line-through',
|
||||||
|
[StopOrderStatus.STATUS_STOPPED]: 'bg-red-50 text-red-900 line-through',
|
||||||
|
[StopOrderStatus.STATUS_TRIGGERED]: 'bg-green-50 text-green-900',
|
||||||
|
[StopOrderStatus.STATUS_UNSPECIFIED]:
|
||||||
|
'bg-yellow-50 text-yellow-900 line-through',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getStopOrderTriggerStatus(
|
||||||
|
data?: ExplorerStopOrderQuery,
|
||||||
|
error?: ApolloError
|
||||||
|
) {
|
||||||
|
if (data && data.stopOrder) {
|
||||||
|
return data.stopOrder.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StopOrderStatus.STATUS_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StopOrderTriggerSummaryProps {
|
||||||
|
id: string;
|
||||||
|
orderSubmission?: components['schemas']['v1OrderSubmission'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
const StopOrderTriggerSummary = ({
|
||||||
|
id,
|
||||||
|
orderSubmission,
|
||||||
|
}: StopOrderTriggerSummaryProps) => {
|
||||||
|
const { data, error } = useExplorerStopOrderQuery({
|
||||||
|
variables: { stopOrderId: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
const status = getStopOrderTriggerStatus(data, error);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`${StatusMidColor[status]} px-3 py-2 md:px-6 flex space-x-4`}
|
||||||
|
>
|
||||||
|
<p className="m-0 p-0 align-top">
|
||||||
|
<Icon
|
||||||
|
size={6}
|
||||||
|
name={StatusIcon[status]}
|
||||||
|
className="inline-block mr-2"
|
||||||
|
/>
|
||||||
|
<span className="align-top">{StatusLabel[status]}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`${StatusBottomColor[status]} px-3 py-2 md:px-6 flex space-x-4`}
|
||||||
|
>
|
||||||
|
{orderSubmission && (
|
||||||
|
<p className="text-vega-grey-400 strike">
|
||||||
|
<OrderTxSummary order={orderSubmission} />
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StopOrderTriggerSummary;
|
@ -26,6 +26,11 @@ export const sharedHeaderProps = {
|
|||||||
className: 'align-top',
|
className: 'align-top',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Labels: Record<BlockExplorerTransactionResult['type'], string> = {
|
||||||
|
'Stop Orders Submission': 'Stop Order',
|
||||||
|
'Stop Orders Cancellation': 'Cancel Stop Order',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These rows are shown for every transaction type, providing a consistent set of rows for the top
|
* These rows are shown for every transaction type, providing a consistent set of rows for the top
|
||||||
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
|
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
|
||||||
@ -44,12 +49,14 @@ export const TxDetailsShared = ({
|
|||||||
const time: string = blockData?.result.block.header.time || '';
|
const time: string = blockData?.result.block.header.time || '';
|
||||||
const height: string = blockData?.result.block.header.height || txData.block;
|
const height: string = blockData?.result.block.header.height || txData.block;
|
||||||
|
|
||||||
|
const type = Labels[txData.type] || txData.type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hideTypeRow === false ? (
|
{hideTypeRow === false ? (
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||||
<TableCell>{txData.type}</TableCell>
|
<TableCell>{type}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
) : null}
|
) : null}
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
|
@ -31,6 +31,8 @@ const AccountType: Record<AccountTypes, string> = {
|
|||||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP Received Fees',
|
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP Received Fees',
|
||||||
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market Proposers',
|
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market Proposers',
|
||||||
ACCOUNT_TYPE_HOLDING: 'Holding',
|
ACCOUNT_TYPE_HOLDING: 'Holding',
|
||||||
|
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
|
||||||
|
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TransferParticipantsProps {
|
interface TransferParticipantsProps {
|
||||||
|
@ -27,6 +27,7 @@ import { TxDetailsNodeAnnounce } from './tx-node-announce';
|
|||||||
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
|
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
|
||||||
import { TxProposal } from './tx-proposal';
|
import { TxProposal } from './tx-proposal';
|
||||||
import { TxDetailsTransfer } from './tx-transfer';
|
import { TxDetailsTransfer } from './tx-transfer';
|
||||||
|
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
|
||||||
|
|
||||||
interface TxDetailsWrapperProps {
|
interface TxDetailsWrapperProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@ -116,6 +117,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
|||||||
return TxDetailsUndelegate;
|
return TxDetailsUndelegate;
|
||||||
case 'State Variable Proposal':
|
case 'State Variable Proposal':
|
||||||
return TxDetailsStateVariable;
|
return TxDetailsStateVariable;
|
||||||
|
case 'Stop Orders Submission':
|
||||||
|
return TxDetailsStopOrderSubmission;
|
||||||
case 'Transfer Funds':
|
case 'Transfer Funds':
|
||||||
return TxDetailsTransfer;
|
return TxDetailsTransfer;
|
||||||
default:
|
default:
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
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 {
|
||||||
|
getStopOrderIds,
|
||||||
|
stopOrdersSignatureToDeterministicId,
|
||||||
|
} from '../lib/deterministic-ids';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
import { StopOrderSetup } from './order/stop-order-setup';
|
||||||
|
|
||||||
|
type StopOrderSetup = components['schemas']['v1StopOrderSetup'];
|
||||||
|
|
||||||
|
interface TxDetailsOrderProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStopTypeLabel(
|
||||||
|
risesAbove: StopOrderSetup | undefined,
|
||||||
|
fallsBelow: StopOrderSetup | undefined
|
||||||
|
): string {
|
||||||
|
if (risesAbove && fallsBelow) {
|
||||||
|
return t('OCO (One Cancels Other)');
|
||||||
|
} else if (fallsBelow) {
|
||||||
|
return t('Falls Below ↘');
|
||||||
|
} else if (risesAbove) {
|
||||||
|
return t('Rises Above ↗');
|
||||||
|
} else {
|
||||||
|
return t('Stop Order');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StopMarketIdProps {
|
||||||
|
risesAbove: StopOrderSetup | undefined;
|
||||||
|
fallsBelow: StopOrderSetup | undefined;
|
||||||
|
showMarketName?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StopMarketId({
|
||||||
|
risesAbove,
|
||||||
|
fallsBelow,
|
||||||
|
showMarketName = false,
|
||||||
|
}: StopMarketIdProps) {
|
||||||
|
const raMarketId = risesAbove?.orderSubmission?.marketId;
|
||||||
|
const fbMarketId = fallsBelow?.orderSubmission?.marketId;
|
||||||
|
|
||||||
|
if (raMarketId && fbMarketId) {
|
||||||
|
if (raMarketId === fbMarketId) {
|
||||||
|
return <MarketLink id={raMarketId} showMarketName={showMarketName} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MarketLink id={raMarketId} showMarketName={showMarketName} />,
|
||||||
|
<MarketLink id={fbMarketId} showMarketName={showMarketName} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (raMarketId) {
|
||||||
|
return <MarketLink id={raMarketId} showMarketName={showMarketName} />;
|
||||||
|
} else if (fbMarketId) {
|
||||||
|
return <MarketLink id={fbMarketId} showMarketName={showMarketName} />;
|
||||||
|
} else {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export const TxDetailsStopOrderSubmission = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxDetailsOrderProps) => {
|
||||||
|
if (!txData || !txData.command.stopOrdersSubmission) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx: components['schemas']['v1StopOrdersSubmission'] =
|
||||||
|
txData.command.stopOrdersSubmission;
|
||||||
|
|
||||||
|
const orderIds = stopOrdersSignatureToDeterministicId(
|
||||||
|
txData?.signature?.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const { risesAboveId, fallsBelowId } = getStopOrderIds(
|
||||||
|
orderIds,
|
||||||
|
tx.risesAbove,
|
||||||
|
tx.fallsBelow
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Market ID')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<StopMarketId
|
||||||
|
risesAbove={tx.risesAbove}
|
||||||
|
fallsBelow={tx.fallsBelow}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Market')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<StopMarketId
|
||||||
|
risesAbove={tx.risesAbove}
|
||||||
|
fallsBelow={tx.fallsBelow}
|
||||||
|
showMarketName={true}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Trigger')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{getStopTypeLabel(tx.risesAbove, tx.fallsBelow)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{tx.fallsBelow && fallsBelowId && (
|
||||||
|
<StopOrderSetup
|
||||||
|
type={'FallsBelow'}
|
||||||
|
{...tx.fallsBelow}
|
||||||
|
deterministicId={fallsBelowId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tx.risesAbove && risesAboveId && (
|
||||||
|
<StopOrderSetup
|
||||||
|
type={'RisesAbove'}
|
||||||
|
{...tx.risesAbove}
|
||||||
|
deterministicId={risesAboveId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,4 +1,8 @@
|
|||||||
import { hexToString, txSignatureToDeterministicId } from './deterministic-ids';
|
import {
|
||||||
|
hexToString,
|
||||||
|
txSignatureToDeterministicId,
|
||||||
|
stopOrdersSignatureToDeterministicId,
|
||||||
|
} from './deterministic-ids';
|
||||||
|
|
||||||
it('txSignatureToDeterministicId Turns a known signature in to a known deterministic ID', () => {
|
it('txSignatureToDeterministicId Turns a known signature in to a known deterministic ID', () => {
|
||||||
const signature =
|
const signature =
|
||||||
@ -20,3 +24,24 @@ it('hexToString encodes a known good value as bytes', () => {
|
|||||||
const res = hexToString(hex);
|
const res = hexToString(hex);
|
||||||
expect(res).toEqual([14, 221]);
|
expect(res).toEqual([14, 221]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('stopOrdersSignatureToDeterministicId', () => {
|
||||||
|
it('should return empty object if no signature is provided', () => {
|
||||||
|
const result = stopOrdersSignatureToDeterministicId();
|
||||||
|
expect(result.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return valid deterministic ids if a signature is provided', () => {
|
||||||
|
const signature = 'deadb33f';
|
||||||
|
const result = stopOrdersSignatureToDeterministicId(signature);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(2);
|
||||||
|
|
||||||
|
expect(result[0]).toBe(
|
||||||
|
'4c45b67a8c08cbf1982883a75beaf309bf172461d04bd427623d6cd3d9ab0e91'
|
||||||
|
);
|
||||||
|
expect(result[1]).toBe(
|
||||||
|
'afe7509ff90d8f26339a0ab81e4d3e1fb6c4d44e94419aa5e13ae7659d894da1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
import { sha3_256 } from 'js-sha3';
|
import { sha3_256 } from 'js-sha3';
|
||||||
|
type StopOrderSetup = components['schemas']['v1StopOrderSetup'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes a string as bytes
|
* Encodes a string as bytes
|
||||||
@ -37,3 +39,58 @@ export function txSignatureToDeterministicId(signature: string): string {
|
|||||||
|
|
||||||
return hash.hex();
|
return hash.hex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a stop order signature string, returns the deterministic IDs of both potential
|
||||||
|
* Stop Orders. A stop order is not an order per se, but a trigger for an order. The order
|
||||||
|
* created by the stop order will have another ID based on the market event that hit the
|
||||||
|
* trigger, and as such is not deterministic.
|
||||||
|
*
|
||||||
|
* @param signature
|
||||||
|
* @returns string[]
|
||||||
|
*/
|
||||||
|
export function stopOrdersSignatureToDeterministicId(
|
||||||
|
signature?: string
|
||||||
|
): string[] {
|
||||||
|
if (!signature) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstId = txSignatureToDeterministicId(signature);
|
||||||
|
return [firstId, txSignatureToDeterministicId(firstId)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type stopSignatures = {
|
||||||
|
risesAboveId: string | undefined;
|
||||||
|
fallsBelowId: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In 0.72.10 the way stop order IDs are determined is a little tricky. It will be stabilised
|
||||||
|
* in a future release.
|
||||||
|
* @param deterministicIds Output of stopORdersSignatureToDeterministicId
|
||||||
|
* @param risesAbove Stop order setup
|
||||||
|
* @param fallsBelow Stop order setup
|
||||||
|
* @returns Object containing the deterministic IDs of the stop orders
|
||||||
|
*/
|
||||||
|
export function getStopOrderIds(
|
||||||
|
deterministicIds: string[],
|
||||||
|
risesAbove: StopOrderSetup | undefined,
|
||||||
|
fallsBelow: StopOrderSetup | undefined
|
||||||
|
) {
|
||||||
|
if (risesAbove && fallsBelow) {
|
||||||
|
return {
|
||||||
|
risesAboveId: deterministicIds[0],
|
||||||
|
fallsBelowId: deterministicIds[1],
|
||||||
|
};
|
||||||
|
} else if (!fallsBelow && risesAbove) {
|
||||||
|
return {
|
||||||
|
risesAboveId: deterministicIds[0],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
fallsBelowId: deterministicIds[0] || undefined,
|
||||||
|
risesAboveId: deterministicIds[1] || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,6 +34,7 @@ export type FilterOption =
|
|||||||
| 'Protocol Upgrade'
|
| 'Protocol Upgrade'
|
||||||
| 'Register new Node'
|
| 'Register new Node'
|
||||||
| 'State Variable Proposal'
|
| 'State Variable Proposal'
|
||||||
|
| 'Stop Orders Submission'
|
||||||
| 'Stop Orders Cancellation'
|
| 'Stop Orders Cancellation'
|
||||||
| 'Submit Oracle Data'
|
| 'Submit Oracle Data'
|
||||||
| 'Submit Order'
|
| 'Submit Order'
|
||||||
@ -54,6 +55,7 @@ export const PrimaryFilterOptions: FilterOption[] = [
|
|||||||
'Delegate',
|
'Delegate',
|
||||||
'Liquidity Provision Order',
|
'Liquidity Provision Order',
|
||||||
'Proposal',
|
'Proposal',
|
||||||
|
'Stop Orders Submission',
|
||||||
'Stop Orders Cancellation',
|
'Stop Orders Cancellation',
|
||||||
'Submit Oracle Data',
|
'Submit Oracle Data',
|
||||||
'Submit Order',
|
'Submit Order',
|
||||||
|
@ -50,6 +50,25 @@ const displayString: StringMap = {
|
|||||||
'Stop Orders Cancellation': 'Cancel stop',
|
'Stop Orders Cancellation': 'Cancel stop',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getLabelForStopOrderType(
|
||||||
|
orderType: string,
|
||||||
|
command: components['schemas']['v1InputData']
|
||||||
|
): string {
|
||||||
|
if (command.stopOrdersSubmission) {
|
||||||
|
if (
|
||||||
|
command.stopOrdersSubmission.risesAbove &&
|
||||||
|
command.stopOrdersSubmission.fallsBelow
|
||||||
|
) {
|
||||||
|
return 'Stop ⇅';
|
||||||
|
} else if (command.stopOrdersSubmission.risesAbove) {
|
||||||
|
return 'Stop ↗';
|
||||||
|
} else if (command.stopOrdersSubmission.fallsBelow) {
|
||||||
|
return 'Stop ↘';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'Stop';
|
||||||
|
}
|
||||||
|
|
||||||
export function getLabelForOrderType(
|
export function getLabelForOrderType(
|
||||||
orderType: string,
|
orderType: string,
|
||||||
command: components['schemas']['v1InputData']
|
command: components['schemas']['v1InputData']
|
||||||
@ -185,6 +204,9 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
|||||||
} else if (type === 'Order' && command) {
|
} else if (type === 'Order' && command) {
|
||||||
type = getLabelForOrderType(orderType, command);
|
type = getLabelForOrderType(orderType, command);
|
||||||
colours = 'text-white dark-text-white bg-vega-blue dark:bg-vega-blue';
|
colours = 'text-white dark-text-white bg-vega-blue dark:bg-vega-blue';
|
||||||
|
} else if (type === 'Stop' && command) {
|
||||||
|
type = getLabelForStopOrderType(orderType, command);
|
||||||
|
colours = 'text-white dark-text-white bg-vega-blue dark:bg-vega-blue';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'Vote on Proposal') {
|
if (type === 'Vote on Proposal') {
|
||||||
|
@ -55,7 +55,7 @@ export const TxsInfiniteList = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-scroll">
|
<div>
|
||||||
<table className={className} data-testid="transactions-list">
|
<table className={className} data-testid="transactions-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="w-full mb-3 text-vega-dark-300 uppercase text-left">
|
<tr className="w-full mb-3 text-vega-dark-300 uppercase text-left">
|
||||||
|
6
apps/explorer/src/types/explorer.d.ts
vendored
6
apps/explorer/src/types/explorer.d.ts
vendored
@ -920,6 +920,8 @@ export interface components {
|
|||||||
* - ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: Per asset reward account for fees received by liquidity providers
|
* - ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: Per asset reward account for fees received by liquidity providers
|
||||||
* - ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: Per asset reward account for market proposers when the market goes above some trading threshold
|
* - ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: Per asset reward account for market proposers when the market goes above some trading threshold
|
||||||
* - ACCOUNT_TYPE_HOLDING: Per asset account for holding in-flight unfilled orders' funds
|
* - ACCOUNT_TYPE_HOLDING: Per asset account for holding in-flight unfilled orders' funds
|
||||||
|
* - ACCOUNT_TYPE_LP_LIQUIDITY_FEES: Network controlled liquidity provider's account, per market, to hold accrued liquidity fees.
|
||||||
|
* - ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: Network controlled liquidity fees bonus distribution account, per market.
|
||||||
* @default ACCOUNT_TYPE_UNSPECIFIED
|
* @default ACCOUNT_TYPE_UNSPECIFIED
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
@ -941,7 +943,9 @@ export interface components {
|
|||||||
| 'ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES'
|
| 'ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES'
|
||||||
| 'ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES'
|
| 'ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES'
|
||||||
| 'ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS'
|
| 'ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS'
|
||||||
| 'ACCOUNT_TYPE_HOLDING';
|
| 'ACCOUNT_TYPE_HOLDING'
|
||||||
|
| 'ACCOUNT_TYPE_LP_LIQUIDITY_FEES'
|
||||||
|
| 'ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION';
|
||||||
/** Vega representation of an external asset */
|
/** Vega representation of an external asset */
|
||||||
readonly vegaAssetDetails: {
|
readonly vegaAssetDetails: {
|
||||||
/** @description Vega built-in asset. */
|
/** @description Vega built-in asset. */
|
||||||
|
Loading…
Reference in New Issue
Block a user