diff --git a/apps/explorer/project.json b/apps/explorer/project.json index eece002b4..4f374de6e 100644 --- a/apps/explorer/project.json +++ b/apps/explorer/project.json @@ -77,7 +77,7 @@ "executor": "nx:run-commands", "options": { "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" ] } }, diff --git a/apps/explorer/src/app/components/order-details/StopOrder.graphql b/apps/explorer/src/app/components/order-details/StopOrder.graphql new file mode 100644 index 000000000..17d269ee4 --- /dev/null +++ b/apps/explorer/src/app/components/order-details/StopOrder.graphql @@ -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 + } +} diff --git a/apps/explorer/src/app/components/order-details/__generated__/StopOrder.ts b/apps/explorer/src/app/components/order-details/__generated__/StopOrder.ts new file mode 100644 index 000000000..5a55c3642 --- /dev/null +++ b/apps/explorer/src/app/components/order-details/__generated__/StopOrder.ts @@ -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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ExplorerStopOrderDocument, options); + } +export function useExplorerStopOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ExplorerStopOrderDocument, options); + } +export type ExplorerStopOrderQueryHookResult = ReturnType; +export type ExplorerStopOrderLazyQueryHookResult = ReturnType; +export type ExplorerStopOrderQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/apps/explorer/src/app/components/order-summary/order-tx-summary.spec.tsx b/apps/explorer/src/app/components/order-summary/order-tx-summary.spec.tsx index 3d302d910..88e2270dd 100644 --- a/apps/explorer/src/app/components/order-summary/order-tx-summary.spec.tsx +++ b/apps/explorer/src/app/components/order-summary/order-tx-summary.spec.tsx @@ -33,7 +33,7 @@ describe('Order TX Summary component', () => { 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 = { marketId: '123', side: 'SIDE_BUY', @@ -41,7 +41,7 @@ describe('Order TX Summary component', () => { size: '10', }; 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', () => { diff --git a/apps/explorer/src/app/components/order-summary/order-tx-summary.tsx b/apps/explorer/src/app/components/order-summary/order-tx-summary.tsx index 77d20ede5..6ece5a4f0 100644 --- a/apps/explorer/src/app/components/order-summary/order-tx-summary.tsx +++ b/apps/explorer/src/app/components/order-summary/order-tx-summary.tsx @@ -3,6 +3,7 @@ import type { components } from '../../../types/explorer'; import PriceInMarket from '../price-in-market/price-in-market'; import { sideText } from '../order-details/lib/order-labels'; import SizeInMarket from '../size-in-market/size-in-market'; +import { t } from '@vegaprotocol/i18n'; export type OrderSummaryProps = { order: components['schemas']['v1OrderSubmission']; @@ -20,7 +21,6 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => { if ( !order || !order.marketId || - !order.price || !order.side || order.side === 'SIDE_UNSPECIFIED' ) { @@ -36,10 +36,14 @@ const OrderTxSummary = ({ order }: OrderSummaryProps) => { '-' )}  @  - + {order.price ? ( + + ) : ( + t('Market Price') + )} ); }; diff --git a/apps/explorer/src/app/components/txs/details/order/stop-order-setup.tsx b/apps/explorer/src/app/components/txs/details/order/stop-order-setup.tsx new file mode 100644 index 000000000..fadef46ed --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/order/stop-order-setup.tsx @@ -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 ; + } + if (trailingPercentOffset) { + return ( + + {formatNumberPercentage(new BigNumber(trailingPercentOffset))}{' '} + (trailing) + + ); + } + + 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 ( +
+
+
+
+ {TypeLabel[type]} +

+ {getMovePrefix(type, trailingPercentOffset)} + +

+
+ + {expiresAt && expiryStrategy ? ( +
+ {t('Expiry Type')} +

+ {d}}> + {getExpiryTypeLabel(expiryStrategy)} + +

+
+ ) : null} +
+ +
+
+ ); +}; diff --git a/apps/explorer/src/app/components/txs/details/order/stop-order-trigger.tsx b/apps/explorer/src/app/components/txs/details/order/stop-order-trigger.tsx new file mode 100644 index 000000000..e55d9a519 --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/order/stop-order-trigger.tsx @@ -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.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.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.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.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 ( + <> +
+

+ + {StatusLabel[status]} +

+
+ +
+ {orderSubmission && ( +

+ +

+ )} +
+ + ); +}; + +export default StopOrderTriggerSummary; diff --git a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx index 656266b9b..b9b202adf 100644 --- a/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx +++ b/apps/explorer/src/app/components/txs/details/shared/tx-details-shared.tsx @@ -26,6 +26,11 @@ export const sharedHeaderProps = { className: 'align-top', }; +const Labels: Record = { + '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 * 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 height: string = blockData?.result.block.header.height || txData.block; + const type = Labels[txData.type] || txData.type; + return ( <> {hideTypeRow === false ? ( {t('Type')} - {txData.type} + {type} ) : null} diff --git a/apps/explorer/src/app/components/txs/details/transfer/blocks/transfer-participants.tsx b/apps/explorer/src/app/components/txs/details/transfer/blocks/transfer-participants.tsx index 7fa30ec68..3992696da 100644 --- a/apps/explorer/src/app/components/txs/details/transfer/blocks/transfer-participants.tsx +++ b/apps/explorer/src/app/components/txs/details/transfer/blocks/transfer-participants.tsx @@ -31,6 +31,8 @@ const AccountType: Record = { ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP Received Fees', ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market Proposers', ACCOUNT_TYPE_HOLDING: 'Holding', + ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution', + ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees', }; interface TransferParticipantsProps { diff --git a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx index 509deb263..651eaf4ae 100644 --- a/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx +++ b/apps/explorer/src/app/components/txs/details/tx-details-wrapper.tsx @@ -27,6 +27,7 @@ import { TxDetailsNodeAnnounce } from './tx-node-announce'; import { TxDetailsStateVariable } from './tx-state-variable-proposal'; import { TxProposal } from './tx-proposal'; import { TxDetailsTransfer } from './tx-transfer'; +import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission'; interface TxDetailsWrapperProps { txData: BlockExplorerTransactionResult | undefined; @@ -116,6 +117,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) { return TxDetailsUndelegate; case 'State Variable Proposal': return TxDetailsStateVariable; + case 'Stop Orders Submission': + return TxDetailsStopOrderSubmission; case 'Transfer Funds': return TxDetailsTransfer; default: diff --git a/apps/explorer/src/app/components/txs/details/tx-stop-order-submission.tsx b/apps/explorer/src/app/components/txs/details/tx-stop-order-submission.tsx new file mode 100644 index 000000000..0aa4e65f2 --- /dev/null +++ b/apps/explorer/src/app/components/txs/details/tx-stop-order-submission.tsx @@ -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 ; + } else { + return ( + <> + , + + + ); + } + } else if (raMarketId) { + return ; + } else if (fbMarketId) { + return ; + } 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 ( + <> + + + + {t('Market ID')} + + + + + + {t('Market')} + + + + + + {t('Trigger')} + + {getStopTypeLabel(tx.risesAbove, tx.fallsBelow)} + + + +
+ {tx.fallsBelow && fallsBelowId && ( + + )} + + {tx.risesAbove && risesAboveId && ( + + )} +
+ + ); +}; diff --git a/apps/explorer/src/app/components/txs/lib/deterministic-ids.spec.ts b/apps/explorer/src/app/components/txs/lib/deterministic-ids.spec.ts index 6b6cbd1cc..ce62a4538 100644 --- a/apps/explorer/src/app/components/txs/lib/deterministic-ids.spec.ts +++ b/apps/explorer/src/app/components/txs/lib/deterministic-ids.spec.ts @@ -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', () => { const signature = @@ -20,3 +24,24 @@ it('hexToString encodes a known good value as bytes', () => { const res = hexToString(hex); 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' + ); + }); +}); diff --git a/apps/explorer/src/app/components/txs/lib/deterministic-ids.ts b/apps/explorer/src/app/components/txs/lib/deterministic-ids.ts index e51639599..8dc87e9c6 100644 --- a/apps/explorer/src/app/components/txs/lib/deterministic-ids.ts +++ b/apps/explorer/src/app/components/txs/lib/deterministic-ids.ts @@ -1,4 +1,6 @@ +import type { components } from '../../../../types/explorer'; import { sha3_256 } from 'js-sha3'; +type StopOrderSetup = components['schemas']['v1StopOrderSetup']; /** * Encodes a string as bytes @@ -37,3 +39,58 @@ export function txSignatureToDeterministicId(signature: string): string { 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, + }; + } +} diff --git a/apps/explorer/src/app/components/txs/tx-filter.tsx b/apps/explorer/src/app/components/txs/tx-filter.tsx index b0add2253..aeb7e6061 100644 --- a/apps/explorer/src/app/components/txs/tx-filter.tsx +++ b/apps/explorer/src/app/components/txs/tx-filter.tsx @@ -34,6 +34,7 @@ export type FilterOption = | 'Protocol Upgrade' | 'Register new Node' | 'State Variable Proposal' + | 'Stop Orders Submission' | 'Stop Orders Cancellation' | 'Submit Oracle Data' | 'Submit Order' @@ -54,6 +55,7 @@ export const PrimaryFilterOptions: FilterOption[] = [ 'Delegate', 'Liquidity Provision Order', 'Proposal', + 'Stop Orders Submission', 'Stop Orders Cancellation', 'Submit Oracle Data', 'Submit Order', diff --git a/apps/explorer/src/app/components/txs/tx-order-type.tsx b/apps/explorer/src/app/components/txs/tx-order-type.tsx index ad2f53bea..87b9d6473 100644 --- a/apps/explorer/src/app/components/txs/tx-order-type.tsx +++ b/apps/explorer/src/app/components/txs/tx-order-type.tsx @@ -50,6 +50,25 @@ const displayString: StringMap = { '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( orderType: string, command: components['schemas']['v1InputData'] @@ -185,6 +204,9 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => { } else if (type === 'Order' && command) { type = getLabelForOrderType(orderType, command); 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') { diff --git a/apps/explorer/src/app/components/txs/txs-infinite-list.tsx b/apps/explorer/src/app/components/txs/txs-infinite-list.tsx index 23fc54d12..f0fb780f6 100644 --- a/apps/explorer/src/app/components/txs/txs-infinite-list.tsx +++ b/apps/explorer/src/app/components/txs/txs-infinite-list.tsx @@ -55,7 +55,7 @@ export const TxsInfiniteList = ({ } return ( -
+
diff --git a/apps/explorer/src/types/explorer.d.ts b/apps/explorer/src/types/explorer.d.ts index 1ac6e98d1..49e391b37 100644 --- a/apps/explorer/src/types/explorer.d.ts +++ b/apps/explorer/src/types/explorer.d.ts @@ -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_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_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 * @enum {string} */ @@ -941,7 +943,9 @@ export interface components { | 'ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES' | 'ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES' | '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 */ readonly vegaAssetDetails: { /** @description Vega built-in asset. */