Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
979614ccfb | ||
|
|
50959b4c50 | ||
|
|
a8c2f4e025 | ||
|
|
2cea73c567 | ||
|
|
b0a54be408 | ||
|
|
d1a036e53c | ||
|
|
9ac199f59a | ||
|
|
332cc302c3 | ||
|
|
4da0e9c368 | ||
|
|
13e1f99767 | ||
|
|
5c18c898b0 | ||
|
|
4fe81cc4aa |
@@ -1,6 +1,6 @@
|
||||
# Related issues 🔗
|
||||
|
||||
Closes #[Issue number here]
|
||||
Issue: #[Issue number here]
|
||||
|
||||
# Description ℹ️
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ on:
|
||||
- types
|
||||
- utils
|
||||
- i18n
|
||||
- wallet
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
||||
@@ -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) => {
|
||||
'-'
|
||||
)}
|
||||
<i className="text-xs">@</i>
|
||||
<PriceInMarket
|
||||
marketId={order.marketId}
|
||||
price={order.price}
|
||||
></PriceInMarket>
|
||||
{order.price ? (
|
||||
<PriceInMarket
|
||||
marketId={order.marketId}
|
||||
price={order.price}
|
||||
></PriceInMarket>
|
||||
) : (
|
||||
t('Market Price')
|
||||
)}
|
||||
</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;
|
||||
@@ -1,10 +1,12 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import SizeInMarket from '../../../size-in-market/size-in-market';
|
||||
|
||||
export interface TxDetailsOrderIcebergDetailsProps {
|
||||
iceberg: components['schemas']['v1IcebergOpts'];
|
||||
size: components['schemas']['v1OrderSubmission']['size'];
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +30,7 @@ export interface TxDetailsOrderIcebergDetailsProps {
|
||||
export const TxOrderIcebergDetails = ({
|
||||
iceberg,
|
||||
size,
|
||||
marketId,
|
||||
}: TxDetailsOrderIcebergDetailsProps) => {
|
||||
return (
|
||||
<div
|
||||
@@ -36,15 +39,28 @@ export const TxOrderIcebergDetails = ({
|
||||
>
|
||||
<Tooltip description={t('Iceberg: Minimum visible size')}>
|
||||
<span className="align-bottom text-vega-orange-650">
|
||||
{iceberg.minimumVisibleSize || '-'}
|
||||
{marketId ? (
|
||||
<SizeInMarket
|
||||
size={iceberg.minimumVisibleSize}
|
||||
marketId={marketId}
|
||||
/>
|
||||
) : (
|
||||
iceberg.minimumVisibleSize
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip description={t('Iceberg: Total size')}>
|
||||
<span className="text-sm text-vega-blue-600 mx-3">{size}</span>
|
||||
<span className="text-sm text-vega-blue-600 mx-3">
|
||||
{marketId ? <SizeInMarket size={size} marketId={marketId} /> : size}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip description={t('Iceberg: Visible peak')}>
|
||||
<span className="align-top text-vega-yellow-600">
|
||||
{iceberg.peakSize || '-'}
|
||||
{marketId ? (
|
||||
<SizeInMarket size={iceberg.peakSize} marketId={marketId} />
|
||||
) : (
|
||||
iceberg.peakSize
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,11 @@ export const sharedHeaderProps = {
|
||||
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
|
||||
* 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 ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{txData.type}</TableCell>
|
||||
<TableCell>{type}</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -31,6 +31,8 @@ const AccountType: Record<AccountTypes, string> = {
|
||||
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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -81,7 +81,11 @@ export const TxDetailsOrder = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Iceberg details')}</TableCell>
|
||||
<TableCell>
|
||||
<TxOrderIcebergDetails iceberg={iceberg} size={size} />
|
||||
<TxOrderIcebergDetails
|
||||
iceberg={iceberg}
|
||||
size={size}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
|
||||
@@ -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', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -55,7 +55,7 @@ export const TxsInfiniteList = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-scroll">
|
||||
<div>
|
||||
<table className={className} data-testid="transactions-list">
|
||||
<thead>
|
||||
<tr className="w-full mb-3 text-vega-dark-300 uppercase text-left">
|
||||
|
||||
@@ -1,4 +1,34 @@
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExplorerOracleDataSource on OracleSpec {
|
||||
...ExplorerOracleDataConnection
|
||||
dataSourceSpec {
|
||||
spec {
|
||||
id
|
||||
@@ -34,6 +64,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
key {
|
||||
name
|
||||
type
|
||||
numberDecimalPlaces
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
@@ -49,38 +80,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection(pagination: { first: 50 }) {
|
||||
pageInfo {
|
||||
@@ -97,6 +96,5 @@ query ExplorerOracleSpecs {
|
||||
query ExplorerOracleSpecById($id: ID!) {
|
||||
oracleSpec(oracleSpecId: $id) {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
|
||||
+38
-41
@@ -3,24 +3,55 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } };
|
||||
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
|
||||
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
|
||||
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } } } | null> | null } | null };
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
|
||||
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
|
||||
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
|
||||
|
||||
export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataSource on OracleSpec {
|
||||
...ExplorerOracleDataConnection
|
||||
dataSourceSpec {
|
||||
spec {
|
||||
id
|
||||
@@ -56,6 +87,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
key {
|
||||
name
|
||||
type
|
||||
numberDecimalPlaces
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
@@ -70,40 +102,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
export const ExplorerOracleSpecsDocument = gql`
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection(pagination: {first: 50}) {
|
||||
@@ -149,11 +148,9 @@ export const ExplorerOracleSpecByIdDocument = gql`
|
||||
query ExplorerOracleSpecById($id: ID!) {
|
||||
oracleSpec(oracleSpecId: $id) {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
${ExplorerOracleDataSourceFragmentDoc}
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
${ExplorerOracleDataSourceFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleSpecByIdQuery__
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { OracleData } from './oracle-data';
|
||||
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
import { type ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
|
||||
function renderComponent(data: ExplorerOracleDataConnectionFragment) {
|
||||
type DataConnection = ExplorerOracleDataConnectionFragment['dataConnection'];
|
||||
|
||||
function renderComponent(
|
||||
data: ExplorerOracleDataConnectionFragment['dataConnection']
|
||||
) {
|
||||
return <OracleData data={data} />;
|
||||
}
|
||||
|
||||
describe('Oracle Data view', () => {
|
||||
it('Renders nothing when data is null', () => {
|
||||
const res = render(
|
||||
renderComponent(null as unknown as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
const res = render(renderComponent(null as unknown as DataConnection));
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing when dataConnection is empty', () => {
|
||||
const res = render(
|
||||
renderComponent({} as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
const res = render(renderComponent({} as DataConnection));
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
it('Renders nothing when dataConnection has no edges', () => {
|
||||
@@ -26,7 +26,7 @@ describe('Oracle Data view', () => {
|
||||
dataConnection: {
|
||||
edges: null,
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
} as DataConnection)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
@@ -37,7 +37,7 @@ describe('Oracle Data view', () => {
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
} as unknown as ExplorerOracleDataConnectionFragment)
|
||||
} as unknown as DataConnection)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
@@ -47,20 +47,18 @@ describe('Oracle Data view', () => {
|
||||
it('Renders details component when there is data', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
externalData: {
|
||||
data: {
|
||||
broadcastAt: '2022-01-01',
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
externalData: {
|
||||
data: {
|
||||
broadcastAt: '2022-01-01',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
},
|
||||
],
|
||||
} as DataConnection)
|
||||
);
|
||||
expect(res.getByText('Broadcast data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import filter from 'recursive-key-filter';
|
||||
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
|
||||
interface OracleDataTypeProps {
|
||||
data: ExplorerOracleDataConnectionFragment;
|
||||
data: ExplorerOracleDataConnectionFragment['dataConnection'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,12 +14,7 @@ interface OracleDataTypeProps {
|
||||
* that Does The Job, rather than because it's good.
|
||||
*/
|
||||
export function OracleData({ data }: OracleDataTypeProps) {
|
||||
if (
|
||||
!data ||
|
||||
!data.dataConnection ||
|
||||
!data.dataConnection.edges?.length ||
|
||||
data.dataConnection.edges.length > 1
|
||||
) {
|
||||
if (!data || !data.edges?.length || data.edges.length > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,7 +22,7 @@ export function OracleData({ data }: OracleDataTypeProps) {
|
||||
<details data-testid="oracle-data">
|
||||
<summary>{t('Broadcast data')}</summary>
|
||||
<ul>
|
||||
{data.dataConnection.edges.map((d) => {
|
||||
{data.edges.map((d) => {
|
||||
if (!d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
|
||||
const m = markets.find((m) => {
|
||||
const p = m.tradableInstrument.instrument.product;
|
||||
if (
|
||||
p.dataSourceSpecForSettlementData.id === id ||
|
||||
p.dataSourceSpecForTradingTermination.id === id
|
||||
p?.dataSourceSpecForSettlementData?.id === id ||
|
||||
p?.dataSourceSpecForTradingTermination?.id === id
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -36,15 +36,9 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
|
||||
});
|
||||
|
||||
if (m && m.id) {
|
||||
const type =
|
||||
id ===
|
||||
m.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
|
||||
.id
|
||||
? 'Settlement for'
|
||||
: 'Termination for';
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{type}</TableHeader>
|
||||
<TableHeader scope="row">{getLabel(id, m)}</TableHeader>
|
||||
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
|
||||
<MarketLink id={m.id} />
|
||||
</TableCell>
|
||||
@@ -61,3 +55,14 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function getLabel(
|
||||
id: string,
|
||||
m: ExplorerOracleForMarketsMarketFragment | null
|
||||
): string {
|
||||
const settlementId =
|
||||
m?.tradableInstrument?.instrument?.product?.dataSourceSpecForSettlementData
|
||||
?.id || null;
|
||||
|
||||
return id === settlementId ? 'Settlement for' : 'Termination for';
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export type SourceType =
|
||||
interface OracleDetailsProps {
|
||||
id: string;
|
||||
dataSource: ExplorerOracleDataSourceFragment;
|
||||
dataConnection?: ExplorerOracleDataConnectionFragment;
|
||||
dataConnection: ExplorerOracleDataConnectionFragment['dataConnection'];
|
||||
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
|
||||
showBroadcasts?: boolean;
|
||||
}
|
||||
@@ -41,8 +41,7 @@ export const OracleDetails = ({
|
||||
showBroadcasts = false,
|
||||
}: OracleDetailsProps) => {
|
||||
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
|
||||
const reportsCount: number =
|
||||
dataConnection?.dataConnection.edges?.length || 0;
|
||||
const reportsCount: number = dataConnection.edges?.length || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -34,11 +34,15 @@ const Oracles = () => {
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dataConnection = o?.node.dataConnection;
|
||||
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
dataConnection={dataConnection}
|
||||
showBroadcasts={false}
|
||||
/>
|
||||
<details>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const Oracle = () => {
|
||||
<OracleDetails
|
||||
id={id || ''}
|
||||
dataSource={data?.oracleSpec}
|
||||
dataConnection={data?.oracleSpec}
|
||||
dataConnection={data?.oracleSpec.dataConnection}
|
||||
showBroadcasts={true}
|
||||
/>
|
||||
<details>
|
||||
|
||||
+5
-1
@@ -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. */
|
||||
|
||||
@@ -20,6 +20,10 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
getDateFormatForSpecifiedDays,
|
||||
getProposalFromTitle,
|
||||
getProposalInformationFromTable,
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
longProposalDescription,
|
||||
proposalChangeType,
|
||||
submitUniqueRawProposal,
|
||||
validateProposalDetailsDiff,
|
||||
@@ -43,7 +46,6 @@ const proposalDetailsTitle = 'proposal-title';
|
||||
const proposalDetailsDescription = 'proposal-description';
|
||||
const openProposals = 'open-proposals';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const proposalDescriptionToggle = 'proposal-description-toggle';
|
||||
const voteBreakdownToggle = 'vote-breakdown-toggle';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
@@ -71,10 +73,13 @@ describe(
|
||||
|
||||
// 3001-VOTE-050 3001-VOTE-054 3001-VOTE-055 3002-PROP-019
|
||||
it('Newly created raw proposal details - shows proposal title and full description', function () {
|
||||
const proposalDescription =
|
||||
'I propose that everyone evaluate the following IPFS document and vote Yes if they agree. bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si';
|
||||
const proposalDetails = longProposalDescription;
|
||||
|
||||
createRawProposal();
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({
|
||||
proposalTitle: 'raw proposal with long description',
|
||||
proposalDescription: proposalDetails,
|
||||
});
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
cy.getByTestId(openProposals).within(() => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
@@ -85,12 +90,17 @@ describe(
|
||||
'contain.text',
|
||||
rawProposal.rationale.title
|
||||
);
|
||||
cy.getByTestId(proposalDescriptionToggle).click();
|
||||
cy.getByTestId('proposal-description-toggle');
|
||||
cy.getByTestId(proposalDetailsDescription)
|
||||
.find('p')
|
||||
.should('have.text', proposalDescription);
|
||||
});
|
||||
cy.getByTestId(proposalDetailsDescription).within(() => {
|
||||
cy.get('p').should('not.have.text', 'Hyperlink text');
|
||||
cy.getByTestId('show-more-btn').click();
|
||||
cy.get('p')
|
||||
.invoke('text')
|
||||
.should('have.have.length', 2194) // Full description is displayed
|
||||
.and('contain', 'Hyperlink text');
|
||||
cy.get('a').should('have.attr', 'href');
|
||||
});
|
||||
|
||||
// 3001-VOTE-008
|
||||
getProposalInformationFromTable('ID')
|
||||
.invoke('text')
|
||||
@@ -361,34 +371,6 @@ describe(
|
||||
stakingPageDisassociateAllTokens();
|
||||
});
|
||||
|
||||
it('Error message should be displayed if error returned from wallet when voting', function () {
|
||||
const errorMsg =
|
||||
'Application error: party has already submitted the maximum number of transactions of this type per epoch (3)';
|
||||
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
cy.intercept('POST', '/api/v2/requests', {
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code: 2001,
|
||||
message: 'Application error',
|
||||
data: 'party has already submitted the maximum number of transactions of this type per epoch (3)',
|
||||
},
|
||||
id: '-PK5EGmErnjLhAmzMeclC',
|
||||
});
|
||||
cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
|
||||
cy.getByTestId('vote-buttons').contains('for').click();
|
||||
cy.getByTestId('dialog-title').should(
|
||||
'have.text',
|
||||
'Transaction failed'
|
||||
);
|
||||
cy.getByTestId('Error').should('have.text', errorMsg);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to see successor market details with new and updated values', function () {
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
@@ -425,7 +407,8 @@ describe(
|
||||
// 3003-PMAN-011 3003-PMAN-012
|
||||
cy.getByTestId(marketDataToggle).click();
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.contains('Key details').click();
|
||||
// Assert that all toggles are removed
|
||||
cy.getByTestId('accordion-toggle').should('not.exist');
|
||||
validateProposalDetailsDiff(
|
||||
'Name',
|
||||
proposalChangeType.UPDATED,
|
||||
@@ -449,7 +432,6 @@ describe(
|
||||
'Opening auction'
|
||||
);
|
||||
|
||||
cy.contains('Instrument').click();
|
||||
validateProposalDetailsDiff(
|
||||
'Market Name',
|
||||
proposalChangeType.UPDATED,
|
||||
@@ -457,7 +439,6 @@ describe(
|
||||
'Test market 1'
|
||||
);
|
||||
|
||||
cy.contains('Metadata').click();
|
||||
validateProposalDetailsDiff(
|
||||
'Sector',
|
||||
proposalChangeType.UPDATED,
|
||||
|
||||
@@ -274,3 +274,6 @@ export enum proposalChangeType {
|
||||
UPDATED = 'Updated',
|
||||
ADDED = 'Added',
|
||||
}
|
||||
|
||||
export const longProposalDescription =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam bibendum orci augue, vel imperdiet augue ultrices sed. In hac habitasse platea dictumst. Sed eget elit vitae nisl tincidunt faucibus. Donec pellentesque mauris nec viverra blandit. Aenean eros diam, tempor eu luctus nec, rhoncus non massa. Mauris libero diam, mattis et enim ut, lobortis pharetra elit. Phasellus vel metus accumsan, rhoncus tellus finibus, blandit mi. In sollicitudin ex ac tortor ornare interdum. Sed est ipsum, vestibulum eget dolor vel, porta luctus elit. Fusce justo nibh, placerat eget sollicitudin eleifend, rhoncus id lorem. Fusce vitae magna vel urna faucibus accumsan quis id purus.\nPraesent convallis dolor sed ante ultricies tempor. Proin sed risus ut libero euismod semper. Duis quis quam sed lacus viverra blandit vel scelerisque diam. Donec interdum, ipsum eget imperdiet ornare, risus augue faucibus lectus, ullamcorper scelerisque erat sapien in purus. Nunc molestie tincidunt felis dignissim vestibulum. Quisque quis ornare enim, non dignissim lectus. Mauris mollis, massa ut maximus consectetur, sem mi lobortis quam, vel malesuada eros tortor nec ex. Cras ac nunc sed erat malesuada varius a quis nulla. Curabitur cursus nec sem sit amet aliquet. Ut tristique tortor neque, a dignissim lectus dictum vel. Praesent sollicitudin bibendum vulputate.\nAenean bibendum tristique diam laoreet posuere. Curabitur ornare lectus ut diam ultricies, ut sodales eros lacinia. Maecenas mauris turpis, gravida non arcu ac, interdum auctor sapien. Vestibulum sed tortor quam. Interdum et malesuada fames ac ante ipsum primis in faucibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec finibus pulvinar magna, non laoreet lectus molestie nec. Vestibulum tempus mattis vehicula. Praesent in orci lectus. In commodo sollicitudin lacus, et lobortis eros placerat vitae. Proin mi libero, feugiat id pretium posuere, rhoncus ut augue. Cras massa tortor, rutrum sed ex vitae, posuere pretium augue. Donec pellentesque suscipit dignissim. Vivamus convallis a odio vitae sodales. Nullam non eleifend mauris, sed iaculis lectus. Cras facilisis justo at ante.\n[Hyperlink text](https://dweb.link/ipfs/bafybeigwwctpv37xdcwacqxvekr6e4kaemqsrv34em6glkbiceo3fcy4si)';
|
||||
|
||||
@@ -22,6 +22,8 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
@@ -29,3 +31,4 @@ LC_ALL="en_US.UTF-8"
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
@@ -30,3 +30,4 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
@@ -22,3 +22,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
@@ -22,3 +22,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -21,3 +21,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -18,3 +18,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -23,3 +23,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -20,3 +20,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
AppFailure,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
@@ -109,8 +110,17 @@ const Web3Container = ({
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
const {
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
@@ -139,10 +149,33 @@ const Web3Container = ({
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
@@ -275,7 +308,7 @@ const AppContainer = () => {
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="grid min-h-full text-white">
|
||||
<div className="min-h-full text-white grid">
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
export const injected = new InjectedConnector();
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
@@ -43,11 +44,23 @@ jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
|
||||
links: {
|
||||
explorer: 'explorer',
|
||||
concepts: 'concepts',
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal
|
||||
restData={{}}
|
||||
proposal={proposal as ProposalQuery['proposal']}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { VoteButtons } from './vote-buttons';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
@@ -67,7 +68,7 @@ describe('Vote buttons', () => {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
render(
|
||||
<AppStateProvider>
|
||||
|
||||
@@ -114,6 +114,7 @@ describe('Raw proposal form', () => {
|
||||
{
|
||||
pubKey,
|
||||
sendTx: mockSendTx,
|
||||
links: { explorer: 'explorer' },
|
||||
} as unknown as VegaWalletContextShape
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import type { PubKey, VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote';
|
||||
import { UserVoteDocument } from '../components/vote-details/__generated__/Vote';
|
||||
@@ -21,7 +21,7 @@ export const mockWalletContext = {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
const mockEthereumConfig = {
|
||||
network_id: '3',
|
||||
|
||||
@@ -1,475 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import {
|
||||
MarketState,
|
||||
MarketStateMapping,
|
||||
PropertyKeyType,
|
||||
} from '@vegaprotocol/types';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import {
|
||||
chainIdQuery,
|
||||
statisticsQuery,
|
||||
createDataConnection,
|
||||
oracleSpecDataConnectionQuery,
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
marketsDataQuery,
|
||||
createMarketsDataFragment,
|
||||
assetQuery,
|
||||
networkParamsQuery,
|
||||
nodeGuardQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
|
||||
describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
const settlementDataProperty = 'settlement-data-property';
|
||||
const settlementDataPropertyKey = {
|
||||
__typename: 'PropertyKey' as const,
|
||||
name: settlementDataProperty,
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
numberDecimalPlaces: 2,
|
||||
};
|
||||
const settlementDataSourceData: DataSourceDefinition = {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
filters: [
|
||||
{
|
||||
__typename: 'Filter',
|
||||
key: settlementDataPropertyKey,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const rowSelector =
|
||||
'[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row';
|
||||
|
||||
const assetsResult = assetQuery();
|
||||
// @ts-ignore asset definitely exists
|
||||
const settlementAsset = assetsResult.assetsConnection.edges[0].node;
|
||||
|
||||
const settledMarket = createMarketFragment({
|
||||
id: '0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: subDays(new Date(), 4).toISOString(),
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: 'market-1-trading-termination-oracle-id',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
settlementAsset,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const terminatedMarket = createMarketFragment({
|
||||
id: '1',
|
||||
state: MarketState.STATE_TRADING_TERMINATED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: null, // market
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
metadata: {
|
||||
tags: [
|
||||
`settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`,
|
||||
],
|
||||
},
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const delayedSettledMarket = createMarketFragment({
|
||||
id: '2',
|
||||
state: MarketState.STATE_TRADING_TERMINATED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: null, // market
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
metadata: {
|
||||
tags: [
|
||||
`settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`,
|
||||
],
|
||||
},
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const unknownMarket = createMarketFragment({
|
||||
id: '3',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
});
|
||||
|
||||
const closedMarketsResult = [
|
||||
{
|
||||
node: settledMarket,
|
||||
},
|
||||
{
|
||||
node: terminatedMarket,
|
||||
},
|
||||
{
|
||||
node: delayedSettledMarket,
|
||||
},
|
||||
{ node: unknownMarket },
|
||||
{
|
||||
node: createMarketFragment({ id: '4', state: MarketState.STATE_PENDING }),
|
||||
},
|
||||
{
|
||||
node: createMarketFragment({ id: '5', state: MarketState.STATE_ACTIVE }),
|
||||
},
|
||||
];
|
||||
|
||||
const settledMarketData = createMarketsDataFragment({
|
||||
market: {
|
||||
id: settledMarket.id,
|
||||
},
|
||||
bestBidPrice: '1000',
|
||||
bestOfferPrice: '2000',
|
||||
markPrice: '1500',
|
||||
});
|
||||
|
||||
const closedMarketsDataResult = [
|
||||
{
|
||||
node: {
|
||||
data: settledMarketData,
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: terminatedMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: delayedSettledMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: unknownMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const specDataConnection = createDataConnection();
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'Markets',
|
||||
marketsQuery({
|
||||
marketsConnection: {
|
||||
edges: closedMarketsResult,
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'MarketsData',
|
||||
marketsDataQuery({
|
||||
marketsConnection: {
|
||||
edges: closedMarketsDataResult,
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'OracleSpecDataConnection',
|
||||
oracleSpecDataConnectionQuery()
|
||||
);
|
||||
});
|
||||
|
||||
cy.mockSubscription();
|
||||
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
it('renders a settled market', () => {
|
||||
const expectedMarkets = closedMarketsResult.filter((edge) => {
|
||||
return [
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
].includes(edge.node.state);
|
||||
});
|
||||
const product = settledMarket.tradableInstrument.instrument.product;
|
||||
|
||||
// rows should be filtered to only include settled/terminated markets
|
||||
cy.get(rowSelector).should('have.length', expectedMarkets.length);
|
||||
|
||||
// check each column in the first row renders correctly
|
||||
// 6001-MARK-001
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="code"]')
|
||||
.find('[data-testid="market-code"]')
|
||||
.should('have.text', settledMarket.tradableInstrument.instrument.code);
|
||||
|
||||
// 6001-MARK-071
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[title="Future"]')
|
||||
.should('have.text', 'Futr');
|
||||
|
||||
// 6001-MARK-002
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="name"]')
|
||||
.should('have.text', settledMarket.tradableInstrument.instrument.name);
|
||||
|
||||
// 6001-MARK-003
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', MarketStateMapping[settledMarket.state]);
|
||||
|
||||
// 6001-MARK-004
|
||||
// 6001-MARK-005
|
||||
// 6001-MARK-009
|
||||
// 6001-MARK-008
|
||||
// 6001-MARK-010
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(href).to.match(
|
||||
new RegExp(
|
||||
`/oracles/${product.dataSourceSpecForTradingTermination.id}`
|
||||
)
|
||||
);
|
||||
})
|
||||
.should('have.text', '4 days ago')
|
||||
.should(
|
||||
'have.attr',
|
||||
'title',
|
||||
getDateTimeFormat().format(
|
||||
new Date(settledMarket.marketTimestamps.close)
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-011
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="bestBidPrice"]')
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.bestBidPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-012
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="bestOfferPrice"]')
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.bestOfferPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-013
|
||||
cy.get(rowSelector).first().find('[col-id="markPrice"]').should(
|
||||
'have.text',
|
||||
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.markPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-014
|
||||
// 6001-MARK-015
|
||||
// 6001-MARK-016
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementDataOracleId"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(href).to.match(
|
||||
new RegExp(`/oracles/${product.dataSourceSpecForSettlementData.id}`)
|
||||
);
|
||||
})
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
// @ts-ignore cannot deep un-partial
|
||||
specDataConnection.externalData.data.data[0].value,
|
||||
settlementDataPropertyKey.numberDecimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-018
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementAsset"]')
|
||||
.should('have.text', product.settlementAsset.symbol);
|
||||
|
||||
// 6001-MARK-020
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button svg')
|
||||
.should('exist');
|
||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||
cy.get(rowSelector)
|
||||
.find('[col-id="successorMarket"]')
|
||||
.first()
|
||||
.should('have.text', '-');
|
||||
}
|
||||
});
|
||||
|
||||
// test market list for market in terminated state
|
||||
it('renders a terminated market', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(1)
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', MarketStateMapping[terminatedMarket.state]);
|
||||
|
||||
// 6001-MARK-006
|
||||
// 6001-MARK-007
|
||||
cy.get(rowSelector)
|
||||
.eq(1)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Expected in 4 days');
|
||||
});
|
||||
|
||||
it('renders a terminated market which was expected to have settled', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(2)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.should('have.class', 'text-danger')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Expected 2 days ago');
|
||||
});
|
||||
|
||||
it('renders terminated market which doesnt have settlement date metadata', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(3)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Unknown');
|
||||
});
|
||||
|
||||
it('can open asset detail dialog', () => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Asset', assetsResult);
|
||||
});
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementAsset"]')
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
// 6001-MARK-019
|
||||
cy.get('[data-testid="dialog-title"]').should(
|
||||
'have.text',
|
||||
`Asset details - ${settlementAsset.symbol}`
|
||||
);
|
||||
|
||||
cy.get('[data-testid="dialog-close"]').click();
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
|
||||
.should('have.text', 'Copy Market ID');
|
||||
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(1)
|
||||
.find('a')
|
||||
.then(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(/\/markets\/0/.test(href || '')).to.equal(true);
|
||||
})
|
||||
.should('have.text', 'View on Explorer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-034
|
||||
cy.getByTestId('tab-closed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
@@ -33,9 +33,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must see the price unit', function () {
|
||||
// 7002-SORD-018
|
||||
cy.getByTestId(orderPriceField)
|
||||
.siblings('label')
|
||||
.should('have.text', 'Price (DAI)');
|
||||
cy.getByTestId(orderPriceField).next().should('have.text', 'DAI');
|
||||
});
|
||||
|
||||
it('must see warning when placing an order with expiry date in past', () => {
|
||||
|
||||
@@ -1,59 +1,25 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { ledgerEntriesQuery } from '@vegaprotocol/mock';
|
||||
import { partyAssetsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
describe('Portfolio page', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'LedgerEntries', ledgerEntriesQuery());
|
||||
aliasGQLQuery(req, 'PartyAssets', partyAssetsQuery());
|
||||
});
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
describe('Ledger entries', () => {
|
||||
it('List should be properly rendered', () => {
|
||||
it('Download form should be properly rendered', () => {
|
||||
// 7007-LEEN-001
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('"Ledger entries"').click();
|
||||
const headers = [
|
||||
'Sender',
|
||||
'Account type',
|
||||
'Market',
|
||||
'Receiver',
|
||||
'Account type',
|
||||
'Market',
|
||||
'Transfer type',
|
||||
'Quantity',
|
||||
'Asset',
|
||||
'Sender account balance',
|
||||
'Receiver account balance',
|
||||
'Vega time',
|
||||
];
|
||||
cy.getByTestId('tab-ledger-entries').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
.getByTestId('ledger-download-button')
|
||||
.should('be.visible');
|
||||
});
|
||||
cy.get(
|
||||
'[data-testid="tab-ledger-entries"] .ag-center-cols-container .ag-row'
|
||||
).should('have.length', ledgerEntriesQuery().ledgerEntries.edges.length);
|
||||
});
|
||||
|
||||
it('account filters should be callable', () => {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('"Ledger entries"').click();
|
||||
cy.get('[role="columnheader"][col-id="fromAccountType"]').realHover();
|
||||
cy.get(
|
||||
'[role="columnheader"][col-id="fromAccountType"] .ag-header-cell-menu-button'
|
||||
).click();
|
||||
cy.get('fieldset.ag-simple-filter-body-wrapper')
|
||||
.should('be.visible')
|
||||
.within((fields) => {
|
||||
cy.wrap(fields).find('label').should('have.length', 18);
|
||||
});
|
||||
cy.getByTestId('"Ledger entries"').click();
|
||||
cy.get('fieldset.ag-simple-filter-body-wrapper').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
|
||||
# Cosmic elevator flags
|
||||
@@ -19,6 +21,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
|
||||
@@ -21,6 +21,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
@@ -21,3 +21,4 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -22,6 +22,7 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -23,6 +23,7 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
DocsLinks,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
@@ -17,16 +18,32 @@ export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
});
|
||||
|
||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
const { error, VEGA_URL, MAINTENANCE_PAGE } = useEnvironment((store) => ({
|
||||
error: store.error,
|
||||
VEGA_URL: store.VEGA_URL,
|
||||
MAINTENANCE_PAGE: store.MAINTENANCE_PAGE,
|
||||
}));
|
||||
const {
|
||||
error,
|
||||
VEGA_URL,
|
||||
VEGA_ENV,
|
||||
VEGA_WALLET_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
MAINTENANCE_PAGE,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
if (MAINTENANCE_PAGE) {
|
||||
return <MaintenancePage />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!DocsLinks
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
@@ -40,7 +57,21 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Web3Provider>
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
</Web3Provider>
|
||||
</NodeGuard>
|
||||
</NetworkLoader>
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { LedgerManager } from '@vegaprotocol/ledger';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { LedgerExportForm } from '@vegaprotocol/ledger';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { PartyAssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { usePartyAssetsQuery } from '@vegaprotocol/assets';
|
||||
|
||||
export const LedgerContainer = () => {
|
||||
const VEGA_URL = useEnvironment((store) => store.VEGA_URL);
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const gridStore = useLedgerStore((store) => store.gridStore);
|
||||
const updateGridStore = useLedgerStore((store) => store.updateGridStore);
|
||||
|
||||
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||
updateGridStore(colState);
|
||||
const { data, loading } = usePartyAssetsQuery({
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const assets = (data?.party?.accountsConnection?.edges ?? [])
|
||||
.map<PartyAssetFieldsFragment>(
|
||||
(item) => item?.node?.asset ?? ({} as PartyAssetFieldsFragment)
|
||||
)
|
||||
.reduce((aggr, item) => {
|
||||
if ('id' in item && 'symbol' in item) {
|
||||
aggr[item.id as string] = item.symbol as string;
|
||||
}
|
||||
return aggr;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
@@ -26,11 +33,31 @@ export const LedgerContainer = () => {
|
||||
);
|
||||
}
|
||||
|
||||
return <LedgerManager partyId={pubKey} gridProps={gridStoreCallbacks} />;
|
||||
};
|
||||
if (!VEGA_URL) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('Environment not configured')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
const useLedgerStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_ledger_store',
|
||||
})
|
||||
);
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="relative flex items-center justify-center w-full h-full">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!Object.keys(assets).length) {
|
||||
return (
|
||||
<Splash>
|
||||
<p>{t('No ledger entries to export')}</p>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LedgerExportForm partyId={pubKey} vegaUrl={VEGA_URL} assets={assets} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from './sidebar';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
|
||||
jest.mock('../node-health', () => ({
|
||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||
@@ -31,16 +32,20 @@ jest.mock('../welcome-dialog', () => ({
|
||||
GetStarted: () => <div data-testid="get-started" />,
|
||||
}));
|
||||
|
||||
const walletContext = {
|
||||
pubKeys: [{ publicKey: 'pubkey' }],
|
||||
} as VegaWalletContextShape;
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
(path) => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -58,11 +63,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders ticket and info on market pages', () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -79,11 +84,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders selected state', async () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||
@@ -107,13 +112,13 @@ describe('Sidebar', () => {
|
||||
describe('SidebarContent', () => {
|
||||
it('renders the correct content', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
@@ -133,13 +138,13 @@ describe('SidebarContent', () => {
|
||||
|
||||
it('closes sidebar if market id is required but not present', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
@@ -46,7 +47,13 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return <GetWalletButton className="justify-between" />;
|
||||
return (
|
||||
<GetWalletButton
|
||||
className="justify-between"
|
||||
chromeExtensionUrl={CHROME_EXTENSION_URL}
|
||||
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
|
||||
/>
|
||||
);
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
InjectedConnector,
|
||||
SnapConnector,
|
||||
DEFAULT_SNAP_ID,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
export const jsonRpc = new JsonRpcConnector();
|
||||
@@ -15,8 +18,13 @@ if (typeof window !== 'undefined') {
|
||||
view = new ViewConnector();
|
||||
}
|
||||
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
jsonRpc,
|
||||
view,
|
||||
snap,
|
||||
};
|
||||
|
||||
@@ -24,3 +24,26 @@ query Assets {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment PartyAssetFields on Asset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
status
|
||||
}
|
||||
|
||||
query PartyAssets($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
type
|
||||
asset {
|
||||
...PartyAssetFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+63
-1
@@ -10,6 +10,15 @@ export type AssetsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type AssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, status: Types.AssetStatus, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } } | null> | null } | null };
|
||||
|
||||
export type PartyAssetFieldsFragment = { __typename?: 'Asset', id: string, name: string, symbol: string, status: Types.AssetStatus };
|
||||
|
||||
export type PartyAssetsQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type PartyAssetsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string, symbol: string, status: Types.AssetStatus } } } | null> | null } | null } | null };
|
||||
|
||||
export const AssetListFieldsFragmentDoc = gql`
|
||||
fragment AssetListFields on Asset {
|
||||
id
|
||||
@@ -28,6 +37,14 @@ export const AssetListFieldsFragmentDoc = gql`
|
||||
status
|
||||
}
|
||||
`;
|
||||
export const PartyAssetFieldsFragmentDoc = gql`
|
||||
fragment PartyAssetFields on Asset {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
status
|
||||
}
|
||||
`;
|
||||
export const AssetsDocument = gql`
|
||||
query Assets {
|
||||
assetsConnection {
|
||||
@@ -65,4 +82,49 @@ export function useAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<Ass
|
||||
}
|
||||
export type AssetsQueryHookResult = ReturnType<typeof useAssetsQuery>;
|
||||
export type AssetsLazyQueryHookResult = ReturnType<typeof useAssetsLazyQuery>;
|
||||
export type AssetsQueryResult = Apollo.QueryResult<AssetsQuery, AssetsQueryVariables>;
|
||||
export type AssetsQueryResult = Apollo.QueryResult<AssetsQuery, AssetsQueryVariables>;
|
||||
export const PartyAssetsDocument = gql`
|
||||
query PartyAssets($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
id
|
||||
accountsConnection {
|
||||
edges {
|
||||
node {
|
||||
type
|
||||
asset {
|
||||
...PartyAssetFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PartyAssetFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __usePartyAssetsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `usePartyAssetsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `usePartyAssetsQuery` 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 } = usePartyAssetsQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function usePartyAssetsQuery(baseOptions: Apollo.QueryHookOptions<PartyAssetsQuery, PartyAssetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(PartyAssetsDocument, options);
|
||||
}
|
||||
export function usePartyAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyAssetsQuery, PartyAssetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(PartyAssetsDocument, options);
|
||||
}
|
||||
export type PartyAssetsQueryHookResult = ReturnType<typeof usePartyAssetsQuery>;
|
||||
export type PartyAssetsLazyQueryHookResult = ReturnType<typeof usePartyAssetsLazyQuery>;
|
||||
export type PartyAssetsQueryResult = Apollo.QueryResult<PartyAssetsQuery, PartyAssetsQueryVariables>;
|
||||
@@ -0,0 +1,47 @@
|
||||
import merge from 'lodash/merge';
|
||||
import type { PartyAssetsQuery } from './__generated__/Assets';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
export const partyAssetsQuery = (
|
||||
override?: PartialDeep<PartyAssetsQuery>
|
||||
): PartyAssetsQuery => {
|
||||
const defaultAssets: PartyAssetsQuery = {
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: 'partyId',
|
||||
accountsConnection: {
|
||||
edges: partyAccountFields.map((node) => ({
|
||||
__typename: 'AccountEdge',
|
||||
node,
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
return merge(defaultAssets, override);
|
||||
};
|
||||
|
||||
const partyAccountFields = [
|
||||
{
|
||||
__typename: 'AccountBalance',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id',
|
||||
symbol: 'tEURO',
|
||||
name: 'Euro',
|
||||
status: Types.AssetStatus.STATUS_ENABLED,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'AccountBalance',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: 'asset-id-2',
|
||||
symbol: 'tDAI',
|
||||
name: 'DAI',
|
||||
status: Types.AssetStatus.STATUS_ENABLED,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
@@ -2,6 +2,7 @@
|
||||
export * from '../accounts/src/lib/accounts.mock';
|
||||
export * from '../assets/src/lib/asset.mock';
|
||||
export * from '../assets/src/lib/assets.mock';
|
||||
export * from '../assets/src/lib/party-assets.mock';
|
||||
export * from '../candles-chart/src/lib/candles.mock';
|
||||
export * from '../candles-chart/src/lib/chart.mock';
|
||||
export * from '../deal-ticket/src/hooks/estimate-order.mock';
|
||||
@@ -10,7 +11,6 @@ export * from '../environment/src/utils/node.mock';
|
||||
export * from '../environment/src/components/node-guard/node-guard.mock';
|
||||
export * from '../fills/src/lib/fills.mock';
|
||||
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
||||
export * from '../ledger/src/lib/ledger-entries.mock';
|
||||
export * from '../market-depth/src/lib/market-depth.mock';
|
||||
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
||||
export * from '../markets/src/lib/market-candles.mock';
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
side: Side;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const DealTicketButton = ({ side, label }: Props) => {
|
||||
const buttonClasses = classNames(
|
||||
'px-10 py-2 uppercase rounded-md text-white w-full',
|
||||
{
|
||||
'bg-market-red': side === Side.SIDE_SELL,
|
||||
'bg-market-green-550': side === Side.SIDE_BUY,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<button type="submit" data-testid="place-order" className={buttonClasses}>
|
||||
{label || t('Place order')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
@@ -16,7 +13,6 @@ import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
||||
TOTAL_MARGIN_AVAILABLE,
|
||||
@@ -25,114 +21,54 @@ import {
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
export interface DealTicketFeeDetailPros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetail = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: DealTicketFeeDetailPros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DealTicketFeeDetailsProps {
|
||||
assetSymbol: string;
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: Market;
|
||||
notionalSize: string | null;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
assetSymbol,
|
||||
order,
|
||||
market,
|
||||
notionalSize,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
const feeEstimate = useEstimateFees(order);
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
</>
|
||||
<KeyValue
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -209,7 +145,7 @@ export const DealTicketMarginDetails = ({
|
||||
BigInt(marginAccountBalance);
|
||||
|
||||
deductionFromCollateral = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
indent
|
||||
label={t('Deduction from collateral')}
|
||||
value={formatRange(
|
||||
@@ -236,7 +172,7 @@ export const DealTicketMarginDetails = ({
|
||||
/>
|
||||
);
|
||||
projectedMargin = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Projected margin')}
|
||||
value={formatRange(
|
||||
marginEstimate?.bestCase.initialLevel,
|
||||
@@ -308,7 +244,7 @@ export const DealTicketMarginDetails = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Margin required')}
|
||||
value={formatRange(
|
||||
marginRequiredBestCase,
|
||||
@@ -324,7 +260,7 @@ export const DealTicketMarginDetails = ({
|
||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Total margin available')}
|
||||
indent
|
||||
value={formatValue(totalMarginAvailable, assetDecimals)}
|
||||
@@ -342,7 +278,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{deductionFromCollateral}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Current margin allocation')}
|
||||
indent
|
||||
onClick={
|
||||
@@ -358,7 +294,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{projectedMargin}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Liquidation price estimate')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const DealTicketSizeIceberg = ({
|
||||
const renderPeakSizeError = () => {
|
||||
if (peakSizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-peak-error-message-size-limit">
|
||||
<TradingInputError testId="deal-ticket-peak-error-message">
|
||||
{peakSizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
@@ -44,7 +44,7 @@ export const DealTicketSizeIceberg = ({
|
||||
const renderMinimumSizeError = () => {
|
||||
if (minimumVisibleSizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-minimum-error-message-size-limit">
|
||||
<TradingInputError testId="deal-ticket-minimum-error-message">
|
||||
{minimumVisibleSizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateMarket } from '../../test-helpers';
|
||||
import { StopOrder } from './deal-ticket-stop-order';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useDealTicketFormValues,
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { FeatureFlags } from '@vegaprotocol/environment';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@@ -57,7 +58,7 @@ const orderSideBuy = 'order-side-SIDE_BUY';
|
||||
const orderSideSell = 'order-side-SIDE_SELL';
|
||||
|
||||
const triggerDirectionRisesAbove = 'triggerDirection-risesAbove';
|
||||
// const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||
const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||
|
||||
const expiryStrategySubmit = 'expiryStrategy-submit';
|
||||
const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||
@@ -65,6 +66,7 @@ const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||
const triggerTypePrice = 'triggerType-price';
|
||||
const triggerTypeTrailingPercentOffset = 'triggerType-trailingPercentOffset';
|
||||
|
||||
const oco = 'oco';
|
||||
const expire = 'expire';
|
||||
const datePicker = 'date-picker-field';
|
||||
const timeInForce = 'order-tif';
|
||||
@@ -76,6 +78,8 @@ const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
const ocoPostfix = (id: string, postfix = true) => (postfix ? `${id}-oco` : id);
|
||||
|
||||
describe('StopOrder', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
@@ -107,6 +111,7 @@ describe('StopOrder', () => {
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId(expire).dataset.state).toEqual('unchecked');
|
||||
expect(screen.getByTestId(oco).dataset.state).toEqual('unchecked');
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
@@ -115,6 +120,32 @@ describe('StopOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calculate notional for market limit', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '10');
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional100.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('calculates notional for limit order', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
// price trigger is selected but it's empty, calculate base on size and marketPrice prop
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional20.00 BTC'
|
||||
);
|
||||
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '3');
|
||||
// calculate base on size and price trigger
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional30.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', async () => {
|
||||
const values: Partial<StopOrderFormValues> = {
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -125,6 +156,11 @@ describe('StopOrder', () => {
|
||||
expire: true,
|
||||
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS,
|
||||
expiresAt: '2023-07-27T16:43:27.000',
|
||||
oco: true,
|
||||
ocoType: Schema.OrderType.TYPE_LIMIT,
|
||||
ocoSize: '0.2',
|
||||
ocoPrice: '300.23',
|
||||
ocoTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
};
|
||||
|
||||
useDealTicketFormValues.setState({
|
||||
@@ -143,10 +179,22 @@ describe('StopOrder', () => {
|
||||
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue(
|
||||
values.size as string
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(values.timeInForce);
|
||||
expect(screen.getByTestId(timeInForce)).toHaveValue(values.timeInForce);
|
||||
expect(screen.getByTestId(priceInput)).toHaveDisplayValue(
|
||||
values.price as string
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ocoPostfix(sizeInput))).toHaveDisplayValue(
|
||||
values.ocoSize as string
|
||||
);
|
||||
expect(screen.getByTestId(ocoPostfix(timeInForce))).toHaveValue(
|
||||
values.ocoTimeInForce
|
||||
);
|
||||
expect(screen.getByTestId(ocoPostfix(priceInput))).toHaveDisplayValue(
|
||||
values.ocoPrice as string
|
||||
);
|
||||
expect(screen.getByTestId('ocoTypeLimit').dataset.state).toEqual('checked');
|
||||
|
||||
expect(screen.getByTestId(expire).dataset.state).toEqual('checked');
|
||||
expect(screen.getByTestId(expiryStrategyCancel).dataset.state).toEqual(
|
||||
'checked'
|
||||
@@ -154,6 +202,9 @@ describe('StopOrder', () => {
|
||||
expect(screen.getByTestId(datePicker)).toHaveDisplayValue(
|
||||
values.expiresAt as string
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
expect(screen.getByTestId(oco).dataset.state).toEqual('unchecked');
|
||||
});
|
||||
|
||||
it('does not submit if no wallet connected', async () => {
|
||||
@@ -174,145 +225,239 @@ describe('StopOrder', () => {
|
||||
expect(submit).toBeCalled();
|
||||
});
|
||||
|
||||
it('validates size field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'size', ocoValue: false },
|
||||
{ fieldName: 'ocoSize', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
expect(getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(sizeInput), '0.01');
|
||||
expect(getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
await userEvent.clear(getByTestId(sizeInput));
|
||||
await userEvent.type(getByTestId(sizeInput), '0.1');
|
||||
expect(queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates price field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'price', ocoValue: false },
|
||||
{ fieldName: 'ocoPrice', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
// price error message should not show if size has error
|
||||
// expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
// await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(priceInput), '0.001');
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to market order type error should disappear
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
expect(queryByTestId(priceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to limit type
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(priceInput), '0.001');
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(priceInput));
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
await userEvent.clear(getByTestId(priceInput));
|
||||
await userEvent.type(getByTestId(priceInput), '0.01');
|
||||
expect(queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger price field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'triggerPrice', ocoValue: false },
|
||||
{ fieldName: 'ocoTriggerPrice', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
await userEvent.click(screen.getByTestId(triggerDirectionFallsBelow));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
await userEvent.click(getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to price trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypePrice));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.click(getByTestId(triggerTypePrice));
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '0.001');
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using value causing immediate trigger
|
||||
await userEvent.clear(screen.getByTestId(triggerPriceInput));
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.01');
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
expect(
|
||||
screen.queryByTestId(triggerPriceWarningMessage)
|
||||
).toBeInTheDocument();
|
||||
await userEvent.clear(getByTestId(triggerPriceInput));
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '0.01');
|
||||
expect(queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
expect(queryByTestId(triggerPriceWarningMessage)).toBeInTheDocument();
|
||||
|
||||
// change to correct value
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '2');
|
||||
expect(screen.queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '2');
|
||||
expect(queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger trailing percentage offset field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'trailingPercentageOffset', ocoValue: false },
|
||||
{ fieldName: 'ocoTrailingPercentageOffset', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
|
||||
// should not show error with default form values
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
await userEvent.click(getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.09'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.1'
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(getByTestId(triggerTrailingPercentOffsetInput), '0.1');
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
|
||||
// to big value should be invalid
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.91'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.9'
|
||||
);
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('sync oco trigger', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('checked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('checked');
|
||||
await userEvent.click(screen.getByTestId(triggerDirectionFallsBelow));
|
||||
expect(
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('unchecked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('unchecked');
|
||||
await userEvent.click(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow))
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('checked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('checked');
|
||||
});
|
||||
|
||||
it('disables submit expiry strategy when OCO selected', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await userEvent.click(screen.getByTestId(expiryStrategySubmit));
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
'unchecked'
|
||||
);
|
||||
expect(screen.getByTestId(expiryStrategySubmit)).toBeDisabled();
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
await userEvent.click(screen.getByTestId(expiryStrategySubmit));
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId(expiryStrategySubmit)).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('sets expiry time/date to now if expiry is changed to checked', async () => {
|
||||
const now = 24 * 60 * 60 * 1000;
|
||||
render(generateJsx());
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => now);
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
|
||||
// expiry time/date was empty it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
|
||||
// set to the value in the past (now - 1s)
|
||||
fireEvent.change(screen.getByTestId<HTMLInputElement>(datePicker), {
|
||||
target: { value: formatForInput(new Date(now - 1000)) },
|
||||
});
|
||||
expect(
|
||||
new Date(
|
||||
screen.getByTestId<HTMLInputElement>(datePicker).value
|
||||
).getTime() + 1000
|
||||
).toEqual(now);
|
||||
|
||||
// switch expiry off and on
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
// expiry time/date was in the past it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
OrderSubmissionBody,
|
||||
StopOrdersSubmission,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
formatForInput,
|
||||
formatValue,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
@@ -19,6 +23,9 @@ import {
|
||||
TradingInputError as InputError,
|
||||
TradingSelect as Select,
|
||||
Tooltip,
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -31,6 +38,7 @@ import {
|
||||
REDUCE_ONLY_TOOLTIP,
|
||||
stopSubmit,
|
||||
getNotionalSize,
|
||||
getAssetUnit,
|
||||
} from './deal-ticket';
|
||||
import { TypeToggle } from './type-selector';
|
||||
import {
|
||||
@@ -41,9 +49,10 @@ import {
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { StopOrderFormValues } from '../../hooks/use-form-values';
|
||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import { validateExpiration } from '../../utils';
|
||||
import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
@@ -78,7 +87,7 @@ const Trigger = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
assetSymbol,
|
||||
quoteName,
|
||||
oco,
|
||||
marketPrice,
|
||||
decimalPlaces,
|
||||
@@ -86,7 +95,7 @@ const Trigger = ({
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
assetSymbol: string;
|
||||
quoteName: string;
|
||||
oco?: boolean;
|
||||
marketPrice?: string | null;
|
||||
decimalPlaces: number;
|
||||
@@ -181,7 +190,7 @@ const Trigger = ({
|
||||
data-testid={`triggerPrice${oco ? '-oco' : ''}`}
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={assetSymbol}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
@@ -249,7 +258,7 @@ const Trigger = ({
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
appendElement={<Pill size="xs">%</Pill>}
|
||||
data-testid={`triggerTrailingPercentOffset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
@@ -311,10 +320,14 @@ const Size = ({
|
||||
control,
|
||||
sizeStep,
|
||||
oco,
|
||||
isLimitType,
|
||||
assetUnit,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
sizeStep: string;
|
||||
oco?: boolean;
|
||||
isLimitType: boolean;
|
||||
assetUnit?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
@@ -332,7 +345,7 @@ const Size = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-size${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className={isLimitType ? 'mb-4' : 'mb-2'}>
|
||||
<FormGroup labelFor={id} label={t(`Size`)} compact>
|
||||
<Input
|
||||
id={id}
|
||||
@@ -341,6 +354,7 @@ const Size = ({
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
|
||||
data-testid={id}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
@@ -394,12 +408,8 @@ const Price = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-price${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor={id}
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact={true}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<FormGroup labelFor={id} label={t('Price')} compact={true}>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
@@ -409,6 +419,7 @@ const Price = ({
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -434,17 +445,17 @@ const TimeInForce = ({
|
||||
oco?: boolean;
|
||||
}) => (
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
name={oco ? 'ocoTimeInForce' : 'timeInForce'}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const id = `select-time-in-force${oco ? '-oco' : ''}`;
|
||||
const id = `order-tif${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<FormGroup label={t('Time in force')} labelFor={id} compact={true}>
|
||||
<Select
|
||||
id={id}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
data-testid={id}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
>
|
||||
@@ -486,6 +497,255 @@ const ReduceOnly = () => (
|
||||
/>
|
||||
);
|
||||
|
||||
const NotionalAndFees = ({
|
||||
market,
|
||||
marketPrice,
|
||||
side,
|
||||
size,
|
||||
price,
|
||||
timeInForce,
|
||||
triggerPrice,
|
||||
triggerType,
|
||||
type,
|
||||
}: Pick<
|
||||
OrderSubmissionBody['orderSubmission'],
|
||||
'side' | 'size' | 'timeInForce' | 'type' | 'price'
|
||||
> &
|
||||
Pick<StopOrderProps, 'market' | 'marketPrice'> &
|
||||
Pick<StopOrderFormValues, 'triggerType' | 'triggerPrice'>) => {
|
||||
const { quoteName, settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
const derivedPrice = getDerivedPrice(
|
||||
{
|
||||
type,
|
||||
price,
|
||||
},
|
||||
type === Schema.OrderType.TYPE_MARKET && isPriceTrigger && triggerPrice
|
||||
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||
: marketPrice || '0'
|
||||
);
|
||||
|
||||
const notionalSize = getNotionalSize(
|
||||
derivedPrice,
|
||||
size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
price: derivedPrice,
|
||||
side,
|
||||
size,
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
assetSymbol={asset.symbol}
|
||||
market={market}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const formatSizeAtPrice = ({
|
||||
assetUnit,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces,
|
||||
price,
|
||||
quoteName,
|
||||
side,
|
||||
size,
|
||||
type,
|
||||
}: Pick<StopOrderFormValues, 'price' | 'side' | 'size' | 'type'> & {
|
||||
assetUnit?: string;
|
||||
decimalPlaces: number;
|
||||
positionDecimalPlaces: number;
|
||||
quoteName: string;
|
||||
}) =>
|
||||
`${formatValue(
|
||||
removeDecimal(size, positionDecimalPlaces),
|
||||
positionDecimalPlaces
|
||||
)} ${assetUnit} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
removeDecimal(price || '0', decimalPlaces),
|
||||
decimalPlaces
|
||||
)} ${quoteName}`
|
||||
}`;
|
||||
const formatTrigger = ({
|
||||
decimalPlaces,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
quoteName,
|
||||
}: Pick<
|
||||
StopOrderFormValues,
|
||||
| 'triggerDirection'
|
||||
| 'triggerType'
|
||||
| 'triggerPrice'
|
||||
| 'triggerTrailingPercentOffset'
|
||||
> & {
|
||||
decimalPlaces: number;
|
||||
quoteName: string;
|
||||
}) =>
|
||||
`${
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
? t('above')
|
||||
: t('below')
|
||||
} ${
|
||||
triggerType === 'price'
|
||||
? `${formatValue(
|
||||
removeDecimal(triggerPrice || '', decimalPlaces),
|
||||
decimalPlaces
|
||||
)} ${quoteName}`
|
||||
: `${(Number(triggerTrailingPercentOffset) || 0).toFixed(1)}% ${t(
|
||||
'trailing'
|
||||
)}`
|
||||
}`;
|
||||
|
||||
const SubmitButton = ({
|
||||
assetUnit,
|
||||
market,
|
||||
oco,
|
||||
ocoPrice,
|
||||
ocoSize,
|
||||
ocoTriggerPrice,
|
||||
ocoTriggerTrailingPercentOffset,
|
||||
ocoTriggerType,
|
||||
ocoType,
|
||||
price,
|
||||
side,
|
||||
size,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
type,
|
||||
}: Pick<
|
||||
StopOrderFormValues,
|
||||
| 'oco'
|
||||
| 'ocoPrice'
|
||||
| 'ocoSize'
|
||||
| 'ocoTriggerPrice'
|
||||
| 'ocoTriggerTrailingPercentOffset'
|
||||
| 'ocoTriggerType'
|
||||
| 'ocoType'
|
||||
| 'price'
|
||||
| 'side'
|
||||
| 'size'
|
||||
| 'triggerDirection'
|
||||
| 'triggerPrice'
|
||||
| 'triggerTrailingPercentOffset'
|
||||
| 'triggerType'
|
||||
| 'type'
|
||||
> &
|
||||
Pick<StopOrderProps, 'market'> & { assetUnit?: string }) => {
|
||||
const { quoteName } = market.tradableInstrument.instrument.product;
|
||||
const risesAbove =
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE;
|
||||
const subLabel = oco ? (
|
||||
<>
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price: risesAbove ? price : ocoPrice,
|
||||
quoteName,
|
||||
side,
|
||||
size: risesAbove ? size : ocoSize,
|
||||
type,
|
||||
})}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection:
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE,
|
||||
triggerPrice: risesAbove ? triggerPrice : ocoTriggerPrice,
|
||||
triggerTrailingPercentOffset: risesAbove
|
||||
? triggerTrailingPercentOffset
|
||||
: ocoTriggerTrailingPercentOffset,
|
||||
triggerType: risesAbove ? triggerType : ocoTriggerType,
|
||||
})}
|
||||
<br />
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price: !risesAbove ? price : ocoPrice,
|
||||
quoteName,
|
||||
side,
|
||||
size: !risesAbove ? size : ocoSize,
|
||||
type: ocoType,
|
||||
})}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection:
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW,
|
||||
triggerPrice: !risesAbove ? triggerPrice : ocoTriggerPrice,
|
||||
triggerTrailingPercentOffset: !risesAbove
|
||||
? triggerTrailingPercentOffset
|
||||
: ocoTriggerTrailingPercentOffset,
|
||||
triggerType: !risesAbove ? triggerType : ocoTriggerType,
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price,
|
||||
quoteName,
|
||||
side,
|
||||
size,
|
||||
type,
|
||||
})}
|
||||
<br />
|
||||
{t('Trigger')}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
data-testid="place-order"
|
||||
type="submit"
|
||||
className="w-full"
|
||||
subLabel={subLabel}
|
||||
>
|
||||
{t(
|
||||
oco
|
||||
? 'Place OCO stop order'
|
||||
: type === Schema.OrderType.TYPE_MARKET
|
||||
? 'Place market stop order'
|
||||
: 'Place limit stop order'
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setType = useDealTicketFormValues((state) => state.setType);
|
||||
@@ -521,50 +781,40 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
},
|
||||
[market.id, market.decimalPlaces, market.positionDecimalPlaces, submit]
|
||||
);
|
||||
const side = watch('side');
|
||||
const expire = watch('expire');
|
||||
const triggerType = watch('triggerType');
|
||||
const triggerPrice = watch('triggerPrice');
|
||||
const timeInForce = watch('timeInForce');
|
||||
const rawPrice = watch('price');
|
||||
const rawSize = watch('size');
|
||||
const oco = watch('oco');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
if (size && rawSize !== size) {
|
||||
setValue('size', size);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, rawSize, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const price = storedFormValues?.[dealTicketType]?.price;
|
||||
if (price && rawPrice !== price) {
|
||||
setValue('price', price);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, rawPrice, setValue]);
|
||||
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
const size = removeDecimal(rawSize, market.positionDecimalPlaces);
|
||||
const price =
|
||||
marketPrice &&
|
||||
getDerivedPrice(
|
||||
{
|
||||
type,
|
||||
price: rawPrice && removeDecimal(rawPrice, market.decimalPlaces),
|
||||
},
|
||||
type === Schema.OrderType.TYPE_MARKET && isPriceTrigger && triggerPrice
|
||||
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||
: marketPrice
|
||||
);
|
||||
|
||||
const notionalSize = getNotionalSize(
|
||||
price,
|
||||
size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
const oco = watch('oco');
|
||||
const ocoPrice = watch('ocoPrice');
|
||||
const ocoSize = watch('ocoSize');
|
||||
const ocoTimeInForce = watch('ocoTimeInForce');
|
||||
const ocoTriggerPrice = watch('ocoTriggerPrice');
|
||||
const ocoTriggerTrailingPercentOffset = watch(
|
||||
'ocoTriggerTrailingPercentOffset'
|
||||
);
|
||||
const ocoTriggerType = watch('ocoTriggerType');
|
||||
const ocoType = watch('ocoType');
|
||||
const price = watch('price');
|
||||
const side = watch('side');
|
||||
const size = watch('size');
|
||||
const timeInForce = watch('timeInForce');
|
||||
const triggerDirection = watch('triggerDirection');
|
||||
const triggerPrice = watch('triggerPrice');
|
||||
const triggerTrailingPercentOffset = watch('triggerTrailingPercentOffset');
|
||||
const triggerType = watch('triggerType');
|
||||
|
||||
useEffect(() => {
|
||||
const storedSize = storedFormValues?.[dealTicketType]?.size;
|
||||
if (storedSize && size !== storedSize) {
|
||||
setValue('size', storedSize);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, size, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedPrice = storedFormValues?.[dealTicketType]?.price;
|
||||
if (storedPrice && price !== storedPrice) {
|
||||
setValue('price', storedPrice);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, price, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((value, { name, type }) => {
|
||||
@@ -573,8 +823,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
return () => subscription.unsubscribe();
|
||||
}, [watch, market.id, updateStoredFormValues]);
|
||||
|
||||
const { quoteName, settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { quoteName } = market.tradableInstrument.instrument.product;
|
||||
const assetUnit = getAssetUnit(
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
@@ -584,6 +836,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control,
|
||||
});
|
||||
|
||||
const normalizedPrice = price && removeDecimal(price, market.decimalPlaces);
|
||||
const normalizedSize =
|
||||
size && removeDecimal(size, market.positionDecimalPlaces);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={isReadOnly || !pubKey ? stopSubmit : handleSubmit(onSubmit)}
|
||||
@@ -620,18 +876,34 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
isLimitType={type === Schema.OrderType.TYPE_LIMIT}
|
||||
assetUnit={assetUnit}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} />
|
||||
<NotionalAndFees
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
price={normalizedPrice}
|
||||
side={side}
|
||||
size={normalizedSize}
|
||||
timeInForce={timeInForce}
|
||||
triggerPrice={triggerPrice}
|
||||
triggerType={triggerType}
|
||||
type={type}
|
||||
/>
|
||||
<TimeInForce control={control} />
|
||||
<div className="flex justify-end pb-3 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -682,12 +954,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_MARKET}
|
||||
id={`ocoTypeMarket`}
|
||||
id="ocoTypeMarket"
|
||||
label={'Market'}
|
||||
/>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_LIMIT}
|
||||
id={`ocoTypeLimit`}
|
||||
id="ocoTypeLimit"
|
||||
label={'Limit'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
@@ -699,12 +971,19 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
oco
|
||||
/>
|
||||
<hr className="mb-2 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
assetUnit={assetUnit}
|
||||
oco
|
||||
isLimitType={ocoType === Schema.OrderType.TYPE_LIMIT}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
@@ -712,7 +991,19 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
quoteName={quoteName}
|
||||
oco
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} oco />
|
||||
<NotionalAndFees
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
price={ocoPrice && removeDecimal(ocoPrice, market.decimalPlaces)}
|
||||
side={side}
|
||||
size={
|
||||
ocoSize && removeDecimal(ocoSize, market.positionDecimalPlaces)
|
||||
}
|
||||
timeInForce={ocoTimeInForce}
|
||||
triggerPrice={ocoTriggerPrice}
|
||||
triggerType={ocoTriggerType}
|
||||
type={ocoType}
|
||||
/>
|
||||
<TimeInForce control={control} oco />
|
||||
<div className="flex justify-end mb-2 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -728,11 +1019,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
return (
|
||||
<Checkbox
|
||||
onCheckedChange={(value) => {
|
||||
const now = Date.now();
|
||||
if (
|
||||
value &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
(!expiresAt || new Date(expiresAt).getTime() < now)
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
setValue('expiresAt', formatForInput(new Date(now)), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
@@ -803,19 +1095,24 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</>
|
||||
)}
|
||||
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
price: price || undefined,
|
||||
side,
|
||||
size,
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={asset.symbol}
|
||||
<SubmitButton
|
||||
assetUnit={assetUnit}
|
||||
market={market}
|
||||
oco={oco}
|
||||
ocoPrice={ocoPrice}
|
||||
ocoSize={ocoSize}
|
||||
ocoTriggerPrice={ocoTriggerPrice}
|
||||
ocoTriggerTrailingPercentOffset={ocoTriggerTrailingPercentOffset}
|
||||
ocoTriggerType={ocoTriggerType}
|
||||
ocoType={ocoType}
|
||||
price={price}
|
||||
side={side}
|
||||
size={size}
|
||||
triggerDirection={triggerDirection}
|
||||
triggerPrice={triggerPrice}
|
||||
triggerTrailingPercentOffset={triggerTrailingPercentOffset}
|
||||
triggerType={triggerType}
|
||||
type={type}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateMarket, generateMarketData } from '../../test-helpers';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
@@ -14,6 +20,7 @@ import {
|
||||
} from '../../hooks/use-form-values';
|
||||
import * as positionsTools from '@vegaprotocol/positions';
|
||||
import { OrdersDocument } from '@vegaprotocol/orders';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@@ -314,7 +321,7 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('iceberg')).toBeChecked();
|
||||
});
|
||||
|
||||
it('should set values for a non-persistent iceberg order and disable post only checkbox', () => {
|
||||
it('should set values for a non-persistent order and disable post only checkbox', () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -357,6 +364,7 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
|
||||
expect(screen.getByTestId('post-only')).not.toBeChecked();
|
||||
expect(screen.getByTestId('iceberg')).not.toBeChecked();
|
||||
expect(screen.getByTestId('iceberg')).toBeDisabled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
@@ -473,4 +481,150 @@ describe('DealTicket', () => {
|
||||
Object.keys(Schema.OrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
it('validates size field', async () => {
|
||||
render(generateJsx());
|
||||
const sizeErrorMessage = 'deal-ticket-error-message-size';
|
||||
const sizeInput = 'order-size';
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates price field', async () => {
|
||||
const priceErrorMessage = 'deal-ticket-error-message-price';
|
||||
const priceInput = 'order-price';
|
||||
const submitButton = 'place-order';
|
||||
const orderTypeMarket = 'order-type-Market';
|
||||
const orderTypeLimit = 'order-type-Limit';
|
||||
render(generateJsx());
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to market order type error should disappear
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to limit type
|
||||
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(priceInput));
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates iceberg field', async () => {
|
||||
const peakSizeErrorMessage = 'deal-ticket-peak-error-message';
|
||||
const minimumSizeErrorMessage = 'deal-ticket-minimum-error-message';
|
||||
const sizeInput = 'order-size';
|
||||
const peakSizeInput = 'order-peak-size';
|
||||
const minimumSizeInput = 'order-minimum-size';
|
||||
const submitButton = 'place-order';
|
||||
|
||||
render(generateJsx());
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GFA
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('iceberg'));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
// validate empty fields
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '0.01');
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '0.01');
|
||||
|
||||
// validate value smaller than step
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(peakSizeInput));
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '0.5');
|
||||
await userEvent.clear(screen.getByTestId(minimumSizeInput));
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '0.7');
|
||||
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
|
||||
// validate value higher than size
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||
// validate peak higher than minimum
|
||||
expect(screen.queryByTestId(peakSizeErrorMessage)).toBeNull();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(peakSizeInput));
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '1');
|
||||
await userEvent.clear(screen.getByTestId(minimumSizeInput));
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '1');
|
||||
|
||||
// validate correct values
|
||||
expect(screen.queryByTestId(peakSizeErrorMessage)).toBeNull();
|
||||
expect(screen.queryByTestId(minimumSizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('sets expiry time/date to now if expiry is changed to checked', async () => {
|
||||
const datePicker = 'date-picker-field';
|
||||
const now = 24 * 60 * 60 * 1000;
|
||||
render(generateJsx());
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => now);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
|
||||
// expiry time/date was empty it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
|
||||
// set to the value in the past (now - 1s)
|
||||
fireEvent.change(screen.getByTestId<HTMLInputElement>(datePicker), {
|
||||
target: { value: formatForInput(new Date(now - 1000)) },
|
||||
});
|
||||
expect(
|
||||
new Date(
|
||||
screen.getByTestId<HTMLInputElement>(datePicker).value
|
||||
).getTime() + 1000
|
||||
).toEqual(now);
|
||||
|
||||
// switch expiry off and on
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GFA
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
// expiry time/date was in the past it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { FormEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { Controller, useController, useForm } from 'react-hook-form';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
DealTicketMarginDetails,
|
||||
@@ -23,6 +22,8 @@ import {
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
validateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
formatValue,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
@@ -46,7 +48,10 @@ import {
|
||||
validateType,
|
||||
} from '../../utils';
|
||||
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
|
||||
import { SummaryValidationType } from '../../constants';
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
SummaryValidationType,
|
||||
} from '../../constants';
|
||||
import type {
|
||||
Market,
|
||||
MarketData,
|
||||
@@ -68,6 +73,7 @@ import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@@ -118,6 +124,11 @@ const getDefaultValues = (
|
||||
...storedValues,
|
||||
});
|
||||
|
||||
export const getAssetUnit = (tags?: string[] | null) =>
|
||||
tags
|
||||
?.find((tag) => tag.startsWith('base:') || tag.startsWith('ticker:'))
|
||||
?.replace(/^[^:]*:/, '');
|
||||
|
||||
export const DealTicket = ({
|
||||
market,
|
||||
onMarketClick,
|
||||
@@ -257,6 +268,10 @@ export const DealTicket = ({
|
||||
const assetSymbol =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
const assetUnit = getAssetUnit(
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const summaryError = useMemo(() => {
|
||||
if (!pubKey) {
|
||||
return {
|
||||
@@ -338,6 +353,7 @@ export const DealTicket = ({
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -384,18 +400,16 @@ export const DealTicket = ({
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
deps: ['peakSize', 'minimumVisibleSize'],
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
compact
|
||||
>
|
||||
<div className={isLimitType ? 'mb-4' : 'mb-2'}>
|
||||
<FormGroup label={t('Size')} labelFor="order-size" compact>
|
||||
<Input
|
||||
id="input-order-size-limit"
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
@@ -411,7 +425,7 @@ export const DealTicket = ({
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
@@ -424,14 +438,15 @@ export const DealTicket = ({
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<div className="mb-2">
|
||||
<FormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
label={t('Price')}
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
@@ -449,6 +464,22 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetails
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
@@ -465,17 +496,18 @@ export const DealTicket = ({
|
||||
onSelect={(value) => {
|
||||
// If GTT is selected and no expiresAt time is set, or its
|
||||
// behind current time then reset the value to current time
|
||||
const now = Date.now();
|
||||
if (
|
||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
(!expiresAt || new Date(expiresAt).getTime() < now)
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
setValue('expiresAt', formatForInput(new Date(now)), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
|
||||
// iceberg orders must be persistent orders, so if user
|
||||
// switches to to a non persisten tif value, remove iceberg selection
|
||||
// switches to a non persistent tif value, remove iceberg selection
|
||||
if (iceberg && isNonPersistentOrder(value)) {
|
||||
setValue('iceberg', false);
|
||||
}
|
||||
@@ -487,7 +519,7 @@ export const DealTicket = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT &&
|
||||
{isLimitType &&
|
||||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
@@ -569,7 +601,7 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<>
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
@@ -624,15 +656,29 @@ export const DealTicket = ({
|
||||
pubKey={pubKey}
|
||||
onDeposit={onDeposit}
|
||||
/>
|
||||
<DealTicketButton side={side} />
|
||||
<DealTicketFeeDetails
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
<Button
|
||||
data-testid="place-order"
|
||||
type="submit"
|
||||
className="w-full"
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
subLabel={`${formatValue(
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
)} ${assetUnit} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
normalizedOrder.price,
|
||||
market.decimalPlaces
|
||||
)} ${quoteName}`
|
||||
}`}
|
||||
>
|
||||
{t(
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'Place market order'
|
||||
: 'Place limit order'
|
||||
)}
|
||||
</Button>
|
||||
<DealTicketMarginDetails
|
||||
onMarketClick={onMarketClick}
|
||||
assetSymbol={assetSymbol}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface KeyValuePros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const KeyValue = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -408,6 +408,12 @@ function compileFeatureFlags(): FeatureFlags {
|
||||
process.env['NX_PRODUCT_PERPETUALS']
|
||||
) as string
|
||||
),
|
||||
METAMASK_SNAPS: TRUTHY.includes(
|
||||
windowOrDefault(
|
||||
'NX_METAMASK_SNAPS',
|
||||
process.env['NX_METAMASK_SNAPS']
|
||||
) as string
|
||||
),
|
||||
};
|
||||
const EXPLORER_FLAGS = {
|
||||
EXPLORER_ASSETS: TRUTHY.includes(
|
||||
|
||||
@@ -18,7 +18,11 @@ export type Environment = z.infer<typeof envSchema>;
|
||||
export type FeatureFlags = z.infer<typeof featureFlagsSchema>;
|
||||
export type CosmicElevatorFlags = Pick<
|
||||
FeatureFlags,
|
||||
'ICEBERG_ORDERS' | 'STOP_ORDERS' | 'SUCCESSOR_MARKETS' | 'PRODUCT_PERPETUALS'
|
||||
| 'ICEBERG_ORDERS'
|
||||
| 'STOP_ORDERS'
|
||||
| 'SUCCESSOR_MARKETS'
|
||||
| 'PRODUCT_PERPETUALS'
|
||||
| 'METAMASK_SNAPS'
|
||||
>;
|
||||
export type Configuration = z.infer<typeof tomlConfigSchema>;
|
||||
export const CUSTOM_NODE_KEY = 'custom' as const;
|
||||
|
||||
@@ -77,6 +77,7 @@ const COSMIC_ELEVATOR_FLAGS = {
|
||||
STOP_ORDERS: z.optional(z.boolean()),
|
||||
ICEBERG_ORDERS: z.optional(z.boolean()),
|
||||
PRODUCT_PERPETUALS: z.optional(z.boolean()),
|
||||
METAMASK_SNAPS: z.optional(z.boolean()),
|
||||
};
|
||||
|
||||
const EXPLORER_FLAGS = {
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './lib/ledger-manager';
|
||||
export * from './lib/__generated__/LedgerEntries';
|
||||
export * from './lib/ledger-export-form';
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
fragment LedgerEntry on AggregatedLedgerEntry {
|
||||
vegaTime
|
||||
quantity
|
||||
assetId
|
||||
transferType
|
||||
toAccountType
|
||||
toAccountMarketId
|
||||
toAccountPartyId
|
||||
toAccountBalance
|
||||
fromAccountType
|
||||
fromAccountMarketId
|
||||
fromAccountPartyId
|
||||
fromAccountBalance
|
||||
}
|
||||
|
||||
query LedgerEntries(
|
||||
$partyId: ID!
|
||||
$pagination: Pagination
|
||||
$dateRange: DateRange
|
||||
$fromAccountType: [AccountType!]
|
||||
$toAccountType: [AccountType!]
|
||||
) {
|
||||
ledgerEntries(
|
||||
filter: {
|
||||
FromAccountFilter: {
|
||||
partyIds: [$partyId]
|
||||
accountTypes: $fromAccountType
|
||||
}
|
||||
ToAccountFilter: { partyIds: [$partyId], accountTypes: $toAccountType }
|
||||
}
|
||||
pagination: $pagination
|
||||
dateRange: $dateRange
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...LedgerEntry
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type LedgerEntryFragment = { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string };
|
||||
|
||||
export type LedgerEntriesQueryVariables = Types.Exact<{
|
||||
partyId: Types.Scalars['ID'];
|
||||
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||
dateRange?: Types.InputMaybe<Types.DateRange>;
|
||||
fromAccountType?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
|
||||
toAccountType?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
|
||||
}>;
|
||||
|
||||
|
||||
export type LedgerEntriesQuery = { __typename?: 'Query', ledgerEntries: { __typename?: 'AggregatedLedgerEntriesConnection', edges: Array<{ __typename?: 'AggregatedLedgerEntriesEdge', cursor: string, node: { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string } } | null>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
|
||||
|
||||
export const LedgerEntryFragmentDoc = gql`
|
||||
fragment LedgerEntry on AggregatedLedgerEntry {
|
||||
vegaTime
|
||||
quantity
|
||||
assetId
|
||||
transferType
|
||||
toAccountType
|
||||
toAccountMarketId
|
||||
toAccountPartyId
|
||||
toAccountBalance
|
||||
fromAccountType
|
||||
fromAccountMarketId
|
||||
fromAccountPartyId
|
||||
fromAccountBalance
|
||||
}
|
||||
`;
|
||||
export const LedgerEntriesDocument = gql`
|
||||
query LedgerEntries($partyId: ID!, $pagination: Pagination, $dateRange: DateRange, $fromAccountType: [AccountType!], $toAccountType: [AccountType!]) {
|
||||
ledgerEntries(
|
||||
filter: {FromAccountFilter: {partyIds: [$partyId], accountTypes: $fromAccountType}, ToAccountFilter: {partyIds: [$partyId], accountTypes: $toAccountType}}
|
||||
pagination: $pagination
|
||||
dateRange: $dateRange
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...LedgerEntry
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
}
|
||||
}
|
||||
${LedgerEntryFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useLedgerEntriesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useLedgerEntriesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useLedgerEntriesQuery` 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 } = useLedgerEntriesQuery({
|
||||
* variables: {
|
||||
* partyId: // value for 'partyId'
|
||||
* pagination: // value for 'pagination'
|
||||
* dateRange: // value for 'dateRange'
|
||||
* fromAccountType: // value for 'fromAccountType'
|
||||
* toAccountType: // value for 'toAccountType'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useLedgerEntriesQuery(baseOptions: Apollo.QueryHookOptions<LedgerEntriesQuery, LedgerEntriesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<LedgerEntriesQuery, LedgerEntriesQueryVariables>(LedgerEntriesDocument, options);
|
||||
}
|
||||
export function useLedgerEntriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<LedgerEntriesQuery, LedgerEntriesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<LedgerEntriesQuery, LedgerEntriesQueryVariables>(LedgerEntriesDocument, options);
|
||||
}
|
||||
export type LedgerEntriesQueryHookResult = ReturnType<typeof useLedgerEntriesQuery>;
|
||||
export type LedgerEntriesLazyQueryHookResult = ReturnType<typeof useLedgerEntriesLazyQuery>;
|
||||
export type LedgerEntriesQueryResult = Apollo.QueryResult<LedgerEntriesQuery, LedgerEntriesQueryVariables>;
|
||||
@@ -1,77 +0,0 @@
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { assetsMapProvider } from '@vegaprotocol/assets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
|
||||
import type {
|
||||
LedgerEntriesQuery,
|
||||
LedgerEntriesQueryVariables,
|
||||
LedgerEntryFragment,
|
||||
} from './__generated__/LedgerEntries';
|
||||
import { LedgerEntriesDocument } from './__generated__/LedgerEntries';
|
||||
|
||||
export type LedgerEntry = LedgerEntryFragment & {
|
||||
asset: Asset | null | undefined;
|
||||
marketSender: Market | null | undefined;
|
||||
marketReceiver: Market | null | undefined;
|
||||
};
|
||||
|
||||
type Edge = LedgerEntriesQuery['ledgerEntries']['edges'][number];
|
||||
|
||||
const isLedgerEntryEdge = (entry: Edge): entry is NonNullable<Edge> =>
|
||||
entry !== null;
|
||||
|
||||
const getData = (responseData: LedgerEntriesQuery | null) => {
|
||||
return (
|
||||
responseData?.ledgerEntries?.edges
|
||||
.filter(isLedgerEntryEdge)
|
||||
.map((edge) => edge.node) || []
|
||||
);
|
||||
};
|
||||
|
||||
const ledgerEntriesOnlyProvider = makeDataProvider<
|
||||
LedgerEntriesQuery,
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
never,
|
||||
LedgerEntriesQueryVariables
|
||||
>({
|
||||
query: LedgerEntriesDocument,
|
||||
getData,
|
||||
additionalContext: {
|
||||
isEnlargedTimeout: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const ledgerEntriesProvider = makeDerivedDataProvider<
|
||||
LedgerEntry[],
|
||||
never,
|
||||
LedgerEntriesQueryVariables
|
||||
>(
|
||||
[
|
||||
ledgerEntriesOnlyProvider,
|
||||
(callback, client) => assetsMapProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData) => {
|
||||
const entries = partsData[0] as ReturnType<typeof getData>;
|
||||
const assets = partsData[1] as Record<string, Asset>;
|
||||
const markets = partsData[2] as Record<string, Market>;
|
||||
return entries.map((entry) => {
|
||||
const asset = entry.assetId
|
||||
? (assets as Record<string, Asset>)[entry.assetId]
|
||||
: null;
|
||||
const marketSender = entry.fromAccountMarketId
|
||||
? markets[entry.fromAccountMarketId]
|
||||
: null;
|
||||
const marketReceiver = entry.toAccountMarketId
|
||||
? markets[entry.toAccountMarketId]
|
||||
: null;
|
||||
return { ...entry, asset, marketSender, marketReceiver };
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,247 +0,0 @@
|
||||
import type {
|
||||
LedgerEntriesQuery,
|
||||
LedgerEntryFragment,
|
||||
} from './__generated__/LedgerEntries';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
export const ledgerEntriesQuery = (
|
||||
override?: PartialDeep<LedgerEntriesQuery>
|
||||
): LedgerEntriesQuery => {
|
||||
const defaultResult: LedgerEntriesQuery = {
|
||||
__typename: 'Query',
|
||||
ledgerEntries: {
|
||||
__typename: 'AggregatedLedgerEntriesConnection',
|
||||
edges: ledgerEntries.map((node) => ({
|
||||
__typename: 'AggregatedLedgerEntriesEdge',
|
||||
node,
|
||||
cursor: 'cursor-1',
|
||||
})),
|
||||
pageInfo: {
|
||||
startCursor:
|
||||
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDE3OjI3OjU2LjczNDM2NFoifQ==',
|
||||
endCursor:
|
||||
'eyJ2ZWdhX3RpbWUiOiIyMDIyLTExLTIzVDEzOjExOjE2LjU0NjM2M1oifQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
__typename: 'PageInfo',
|
||||
},
|
||||
},
|
||||
};
|
||||
return merge(defaultResult, override);
|
||||
};
|
||||
|
||||
export const ledgerEntries: LedgerEntryFragment[] = [
|
||||
{
|
||||
vegaTime: '1669224476734364000',
|
||||
quantity: '0',
|
||||
assetId: 'asset-id',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: 'market-1',
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: 'market-0',
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669221452175594000',
|
||||
quantity: '0',
|
||||
assetId: 'asset-id-2',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: 'market-0',
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: 'market-2',
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209347054198000',
|
||||
quantity: '0',
|
||||
assetId: 'asset-id',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: 'market-3',
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: 'market-2',
|
||||
fromAccountPartyId: 'sender party id',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209345512806000',
|
||||
quantity: '0',
|
||||
assetId: 'asset-id',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209316163397000',
|
||||
quantity: '0',
|
||||
assetId: 'asset-id-2',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209299051286000',
|
||||
quantity: '1326783',
|
||||
assetId: 'asset-id-2',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MTM_WIN,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209151328614000',
|
||||
quantity: '0',
|
||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209117655380000',
|
||||
quantity: '0',
|
||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209082788024000',
|
||||
quantity: '1326783',
|
||||
assetId: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MTM_WIN,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '1669209076546363000',
|
||||
quantity: '0',
|
||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_HIGH,
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '2022-11-24T13:36:42.13989Z',
|
||||
quantity: '9078407730948615',
|
||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_LOW,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
fromAccountMarketId:
|
||||
'0942d767cb2cb5a795e14216e8e53c2b6f75e46dc1732c5aeda8a5aba4ad193d',
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
|
||||
{
|
||||
vegaTime: '2022-11-24T13:35:49.257039Z',
|
||||
quantity: '263142253070974',
|
||||
assetId: 'cee709223217281d7893b650850ae8ee8a18b7539b5658f9b4cc24de95dd18ad',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_MARGIN_LOW,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
fromAccountMarketId:
|
||||
'0942d767cb2cb5a795e14216e8e53c2b6f75e46dc1732c5aeda8a5aba4ad193d',
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '2022-11-24T12:41:22.054428Z',
|
||||
quantity: '1000000000',
|
||||
assetId: '4e4e80abff30cab933b8c4ac6befc618372eb76b2cbddc337eff0b4a3a4d25b8',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: null,
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '2022-11-24T12:39:11.516154Z',
|
||||
quantity: '1000000000000',
|
||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: null,
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '2022-11-24T12:37:26.832226Z',
|
||||
quantity: '10000000000000000000000',
|
||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: null,
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
{
|
||||
vegaTime: '2022-11-24T12:24:52.844901Z',
|
||||
quantity: '49390000000000000000000',
|
||||
assetId: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
|
||||
transferType: Schema.TransferType.TRANSFER_TYPE_DEPOSIT,
|
||||
toAccountType: Schema.AccountType.ACCOUNT_TYPE_EXTERNAL,
|
||||
toAccountMarketId: null,
|
||||
toAccountPartyId: 'network',
|
||||
fromAccountType: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountMarketId: null,
|
||||
fromAccountPartyId:
|
||||
'2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
__typename: 'AggregatedLedgerEntry',
|
||||
toAccountBalance: '0',
|
||||
fromAccountBalance: '0',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,215 @@
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { createDownloadUrl, LedgerExportForm } from './ledger-export-form';
|
||||
import { formatForInput, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
|
||||
const vegaUrl = 'https://vega-url.co.uk/querystuff';
|
||||
|
||||
const mockResponse = {
|
||||
headers: { get: jest.fn() },
|
||||
blob: () => '',
|
||||
};
|
||||
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
||||
|
||||
const assetsMock = {
|
||||
['a'.repeat(64)]: 'symbol asset-id',
|
||||
['b'.repeat(64)]: 'symbol asset-id-2',
|
||||
};
|
||||
|
||||
describe('LedgerExportForm', () => {
|
||||
const partyId = 'c'.repeat(64);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-08-10T10:10:10.000Z'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be properly rendered', async () => {
|
||||
render(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('symbol asset-id')).toBeInTheDocument();
|
||||
|
||||
// userEvent does not work with faked timers
|
||||
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||
|
||||
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||
Object.keys(assetsMock)[0]
|
||||
}&dateRange.startTimestamp=1691057410000000000`
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('assetID should be properly change request url', async () => {
|
||||
render(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('symbol asset-id')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByTestId('select-ledger-asset'), {
|
||||
target: { value: Object.keys(assetsMock)[1] },
|
||||
});
|
||||
|
||||
expect(screen.getByText('symbol asset-id-2')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||
|
||||
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||
Object.keys(assetsMock)[1]
|
||||
}&dateRange.startTimestamp=1691057410000000000`
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('date-from should properly change request url', async () => {
|
||||
const newDate = new Date(1691230210000);
|
||||
render(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByLabelText('Date from')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Date from'), {
|
||||
target: { value: formatForInput(newDate) },
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('date-from')).toHaveValue(
|
||||
`${formatForInput(newDate)}.000`
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||
|
||||
expect(screen.getByTestId('download-spinner')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||
Object.keys(assetsMock)[0]
|
||||
}&dateRange.startTimestamp=${toNanoSeconds(newDate)}`
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('date-to should properly change request url', async () => {
|
||||
const newDate = new Date(1691230210000);
|
||||
render(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText('Date to')).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Date to'), {
|
||||
target: { value: formatForInput(newDate) },
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('date-to')).toHaveValue(
|
||||
`${formatForInput(newDate)}.000`
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('ledger-download-button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${
|
||||
Object.keys(assetsMock)[0]
|
||||
}&dateRange.startTimestamp=1691057410000000000&dateRange.endTimestamp=${toNanoSeconds(
|
||||
newDate
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDownloadUrl', () => {
|
||||
const fromTimestamp = 1690848000000;
|
||||
const toTimestamp = 1691107200000;
|
||||
const args = {
|
||||
protohost: 'https://vega-url.co.uk',
|
||||
partyId: 'a'.repeat(64),
|
||||
assetId: 'b'.repeat(64),
|
||||
dateFrom: new Date(fromTimestamp).toISOString(),
|
||||
};
|
||||
|
||||
it('formats url with without an end date', () => {
|
||||
expect(createDownloadUrl(args)).toEqual(
|
||||
`${args.protohost}/api/v2/ledgerentry/export?partyId=${
|
||||
args.partyId
|
||||
}&assetId=${args.assetId}&dateRange.startTimestamp=${toNanoSeconds(
|
||||
args.dateFrom
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('formats url with with an end date', () => {
|
||||
const dateTo = new Date(toTimestamp).toISOString();
|
||||
expect(
|
||||
createDownloadUrl({
|
||||
...args,
|
||||
dateTo,
|
||||
})
|
||||
).toEqual(
|
||||
`${args.protohost}/api/v2/ledgerentry/export?partyId=${
|
||||
args.partyId
|
||||
}&assetId=${args.assetId}&dateRange.startTimestamp=${toNanoSeconds(
|
||||
args.dateFrom
|
||||
)}&dateRange.endTimestamp=${toNanoSeconds(dateTo)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if invalid args are provided', () => {
|
||||
// invalid url
|
||||
expect(() => {
|
||||
// @ts-ignore override z.infer type
|
||||
createDownloadUrl({ ...args, protohost: 'foo' });
|
||||
}).toThrow();
|
||||
|
||||
// invalid partyId
|
||||
expect(() => {
|
||||
// @ts-ignore override z.infer type
|
||||
createDownloadUrl({ ...args, partyId: 'z'.repeat(64) });
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Button,
|
||||
Loader,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingSelect,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { toNanoSeconds, VEGA_ID_REGEX } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
const DEFAULT_EXPORT_FILE_NAME = 'ledger_entries.csv';
|
||||
|
||||
const getProtoHost = (vegaurl: string) => {
|
||||
const loc = new URL(vegaurl);
|
||||
return `${loc.protocol}//${loc.host}`;
|
||||
};
|
||||
|
||||
const downloadSchema = z.object({
|
||||
protohost: z.string().url().nonempty(),
|
||||
partyId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||
assetId: z.string().regex(VEGA_ID_REGEX).nonempty(),
|
||||
dateFrom: z.string().nonempty(),
|
||||
dateTo: z.string().optional(),
|
||||
});
|
||||
|
||||
export const createDownloadUrl = (args: z.infer<typeof downloadSchema>) => {
|
||||
// check args from form inputs
|
||||
downloadSchema.parse(args);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('partyId', args.partyId);
|
||||
params.append('assetId', args.assetId);
|
||||
params.append('dateRange.startTimestamp', toNanoSeconds(args.dateFrom));
|
||||
|
||||
if (args.dateTo) {
|
||||
params.append('dateRange.endTimestamp', toNanoSeconds(args.dateTo));
|
||||
}
|
||||
|
||||
const url = new URL(args.protohost);
|
||||
url.pathname = '/api/v2/ledgerentry/export';
|
||||
url.search = params.toString();
|
||||
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
interface Props {
|
||||
partyId: string;
|
||||
vegaUrl: string;
|
||||
assets: Record<string, string>;
|
||||
}
|
||||
|
||||
export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
const now = useRef(new Date());
|
||||
const [dateFrom, setDateFrom] = useState(() => {
|
||||
return formatForInput(subDays(now.current, 7));
|
||||
});
|
||||
const [dateTo, setDateTo] = useState('');
|
||||
const maxFromDate = formatForInput(new Date(dateTo || now.current));
|
||||
const maxToDate = formatForInput(now.current);
|
||||
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
|
||||
const protohost = getProtoHost(vegaUrl);
|
||||
const disabled = Boolean(!assetId || isDownloading);
|
||||
|
||||
const assetDropDown = (
|
||||
<TradingSelect
|
||||
id="select-ledger-asset"
|
||||
value={assetId}
|
||||
onChange={(e) => {
|
||||
setAssetId(e.target.value);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="select-ledger-asset"
|
||||
disabled={isDownloading}
|
||||
>
|
||||
{Object.keys(assets).map((assetKey) => (
|
||||
<option key={assetKey} value={assetKey}>
|
||||
{assets[assetKey]}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
);
|
||||
|
||||
const startDownload = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const link = createDownloadUrl({
|
||||
protohost,
|
||||
partyId,
|
||||
assetId,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
});
|
||||
setIsDownloading(true);
|
||||
const resp = await fetch(link);
|
||||
const { headers } = resp;
|
||||
const nameHeader = headers.get('content-disposition');
|
||||
const filename = nameHeader?.split('=').pop() ?? DEFAULT_EXPORT_FILE_NAME;
|
||||
const blob = await resp.blob();
|
||||
if (blob) {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
link.click();
|
||||
}
|
||||
} catch (err) {
|
||||
localLoggerFactory({ application: 'ledger' }).error('Download file', err);
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!protohost || Object.keys(assets).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={startDownload} className="p-4 w-[350px]">
|
||||
<h2 className="mb-4">{t('Export ledger entries')}</h2>
|
||||
<TradingFormGroup label={t('Select asset')} labelFor="asset">
|
||||
{assetDropDown}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Date from')} labelFor="date-from">
|
||||
<TradingInput
|
||||
type="datetime-local"
|
||||
data-testid="date-from"
|
||||
id="date-from"
|
||||
value={dateFrom}
|
||||
onChange={(e) => setDateFrom(e.target.value)}
|
||||
disabled={disabled}
|
||||
max={maxFromDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Date to')} labelFor="date-to">
|
||||
<TradingInput
|
||||
type="datetime-local"
|
||||
data-testid="date-to"
|
||||
id="date-to"
|
||||
value={dateTo}
|
||||
onChange={(e) => setDateTo(e.target.value)}
|
||||
disabled={disabled}
|
||||
max={maxToDate}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="relative text-sm" title={t('Download all to .csv file')}>
|
||||
{isDownloading && (
|
||||
<div
|
||||
className="absolute flex items-center justify-center w-full h-full"
|
||||
data-testid="download-spinner"
|
||||
>
|
||||
<Loader size="small" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
fill
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
data-testid="ledger-download-button"
|
||||
>
|
||||
{t('Download')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { LedgerExportLink } from './ledger-export-link';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { ledgerEntries } from './ledger-entries.mock';
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
|
||||
const VEGA_URL = 'https://vega-url.co.uk/querystuff';
|
||||
const mockEnvironment = jest.fn(() => VEGA_URL);
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: jest.fn(() => mockEnvironment()),
|
||||
}));
|
||||
|
||||
const asset = {
|
||||
id: 'assetID',
|
||||
name: 'assetName',
|
||||
symbol: 'assetSymbol',
|
||||
decimals: 1,
|
||||
quantum: '1',
|
||||
status: Types.AssetStatus,
|
||||
source: {
|
||||
__typename: 'ERC20',
|
||||
contractAddress: 'contractAddres',
|
||||
lifetimeLimit: 'lifetimeLimit',
|
||||
withdrawThreshold: 'withdraw',
|
||||
},
|
||||
};
|
||||
|
||||
describe('LedgerExportLink', () => {
|
||||
const partyId = 'partyId';
|
||||
const entries = ledgerEntries.map((entry) => {
|
||||
return {
|
||||
...entry,
|
||||
asset: entry.assetId
|
||||
? {
|
||||
...asset,
|
||||
id: entry.assetId,
|
||||
name: `name ${entry.assetId}`,
|
||||
symbol: `symbol ${entry.assetId}`,
|
||||
}
|
||||
: null,
|
||||
marketSender: null,
|
||||
marketReceiver: null,
|
||||
} as LedgerEntry;
|
||||
});
|
||||
|
||||
it('should be properly rendered', async () => {
|
||||
render(<LedgerExportLink partyId={partyId} entries={entries} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link')).toHaveAttribute(
|
||||
'href',
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=asset-id`
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('button', { name: /^symbol asset-id/ })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should be properly change link url', async () => {
|
||||
render(<LedgerExportLink partyId={partyId} entries={entries} />);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('button', { name: /^symbol asset-id/ })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
act(() => {
|
||||
userEvent.click(screen.getByRole('button', { name: /^symbol asset-id/ }));
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
});
|
||||
act(() => {
|
||||
userEvent.click(
|
||||
screen.getByRole('menuitem', { name: /^symbol asset-id-2/ })
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('link')).toHaveAttribute(
|
||||
'href',
|
||||
`https://vega-url.co.uk/api/v2/ledgerentry/export?partyId=${partyId}&assetId=asset-id-2`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Link,
|
||||
Button,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
const getProtoHost = (vegaurl: string) => {
|
||||
const loc = new URL(vegaurl);
|
||||
return `${loc.protocol}//${loc.host}`;
|
||||
};
|
||||
|
||||
export const LedgerExportLink = ({
|
||||
partyId,
|
||||
entries,
|
||||
}: {
|
||||
partyId: string;
|
||||
entries: LedgerEntry[];
|
||||
}) => {
|
||||
const assets = entries.reduce((aggr, item) => {
|
||||
if (item.asset && !(item.asset.id in aggr)) {
|
||||
aggr[item.asset.id] = item.asset.symbol;
|
||||
}
|
||||
return aggr;
|
||||
}, {} as Record<string, string>);
|
||||
const [assetId, setAssetId] = useState(Object.keys(assets)[0]);
|
||||
const VEGA_URL = useEnvironment((store) => store.VEGA_URL);
|
||||
const protohost = VEGA_URL ? getProtoHost(VEGA_URL) : '';
|
||||
|
||||
const assetDropDown = useMemo(() => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={<DropdownMenuTrigger>{assets[assetId]}</DropdownMenuTrigger>}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{Object.keys(assets).map((assetKey) => (
|
||||
<DropdownMenuItem
|
||||
key={assetKey}
|
||||
onSelect={() => setAssetId(assetKey)}
|
||||
>
|
||||
{assets[assetKey]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}, [assetId, assets]);
|
||||
|
||||
if (!protohost || !entries || entries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="flex shrink items-stretch gap-2 p-2">
|
||||
<div className="flex items-center">Export all</div>
|
||||
{assetDropDown}
|
||||
<Link
|
||||
className="text-sm"
|
||||
title={t('Download all to .csv file')}
|
||||
href={`${protohost}/api/v2/ledgerentry/export?partyId=${partyId}&assetId=${assetId}`}
|
||||
>
|
||||
<Button size="sm">{t('Download')}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { subDays, formatRFC3339 } from 'date-fns';
|
||||
import { ledgerEntriesProvider } from './ledger-entries-data-provider';
|
||||
import type { LedgerEntriesQueryVariables } from './__generated__/LedgerEntries';
|
||||
import { LedgerTable } from './ledger-table';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { LedgerExportLink } from './ledger-export-link';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
|
||||
export interface Filter {
|
||||
vegaTime?: {
|
||||
value: Schema.DateRange;
|
||||
};
|
||||
fromAccountType?: { value: Types.AccountType[] };
|
||||
toAccountType?: { value: Types.AccountType[] };
|
||||
}
|
||||
const defaultFilter = {
|
||||
vegaTime: {
|
||||
value: { start: formatRFC3339(subDays(Date.now(), 7)) },
|
||||
},
|
||||
};
|
||||
|
||||
export const LedgerManager = ({
|
||||
partyId,
|
||||
gridProps,
|
||||
}: {
|
||||
partyId: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}) => {
|
||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||
|
||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
||||
() => ({
|
||||
partyId,
|
||||
dateRange: filter?.vegaTime?.value,
|
||||
pagination: {
|
||||
first: 10,
|
||||
},
|
||||
}),
|
||||
[partyId, filter?.vegaTime?.value]
|
||||
);
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: ledgerEntriesProvider,
|
||||
variables,
|
||||
skip: !variables.partyId,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
|
||||
setFilter(updatedFilter);
|
||||
gridProps.onFilterChanged(event);
|
||||
},
|
||||
[gridProps]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LedgerTable
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||
{...gridProps}
|
||||
onFilterChanged={onFilterChanged}
|
||||
/>
|
||||
{data && <LedgerExportLink entries={data} partyId={partyId} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,215 +0,0 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
fromNanoSeconds,
|
||||
getDateTimeFormat,
|
||||
truncateByChars,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaValueFormatterParams,
|
||||
TypedDataAgGrid,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
DateRangeFilter,
|
||||
MarketNameCell,
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
AccountTypeMapping,
|
||||
DescriptionTransferTypeMapping,
|
||||
TransferTypeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
import { useMemo } from 'react';
|
||||
import { formatRFC3339, subDays } from 'date-fns';
|
||||
|
||||
export const TransferTooltipCellComponent = ({
|
||||
value,
|
||||
}: {
|
||||
value: Types.TransferType;
|
||||
}) => {
|
||||
return (
|
||||
<p className="max-w-sm px-4 py-2 z-20 rounded text-sm break-word">
|
||||
{value ? DescriptionTransferTypeMapping[value] : ''}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultValue = { start: formatRFC3339(subDays(Date.now(), 7)) };
|
||||
const dateRangeFilterParams = {
|
||||
maxNextDays: 0,
|
||||
defaultValue,
|
||||
};
|
||||
const defaultColDef = {
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
tooltipComponent: TransferTooltipCellComponent,
|
||||
filterParams: {
|
||||
...dateRangeFilterParams,
|
||||
buttons: ['reset'],
|
||||
},
|
||||
};
|
||||
|
||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||
|
||||
export const LedgerTable = (props: LedgerEntryProps) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Sender'),
|
||||
field: 'fromAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'fromAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketSender.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) =>
|
||||
value && (
|
||||
<MarketNameCell value={value} data={data?.marketSender as Market} />
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver'),
|
||||
field: 'toAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'toAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) =>
|
||||
value && (
|
||||
<MarketNameCell
|
||||
value={value}
|
||||
data={data?.marketReceiver as Market}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t('Transfer type'),
|
||||
field: 'transferType',
|
||||
tooltipField: 'transferType',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: TransferTypeMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Quantity'),
|
||||
field: 'quantity',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'assetId',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || '',
|
||||
},
|
||||
{
|
||||
headerName: t('Sender account balance'),
|
||||
field: 'fromAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver account balance'),
|
||||
field: 'toAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Vega time'),
|
||||
field: 'vegaTime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
|
||||
filterParams: dateRangeFilterParams,
|
||||
filter: DateRangeFilter,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Generated
+2
@@ -4414,6 +4414,8 @@ export type StopOrderFilter = {
|
||||
dateRange?: InputMaybe<DateRange>;
|
||||
/** Zero or more expiry strategies to filter by */
|
||||
expiryStrategy?: InputMaybe<Array<StopOrderExpiryStrategy>>;
|
||||
/** Filter for live stop orders only */
|
||||
liveOnly?: InputMaybe<Scalars['Boolean']>;
|
||||
/** Zero or more market IDs to filter by */
|
||||
markets?: InputMaybe<Array<Scalars['ID']>>;
|
||||
/** Zero or more party IDs to filter by */
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
export const IconMetaMask = ({ size = 16 }: { size: number }) => (
|
||||
<svg viewBox="0 0 47 47" fill="none" height={size}>
|
||||
<g>
|
||||
<path
|
||||
d="m40.632 6.969-14.136 10.62 2.628-6.259L40.632 6.97Z"
|
||||
fill="#E17726"
|
||||
stroke="#E17726"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m8.024 6.969 14.01 10.72-2.502-6.359L8.024 6.97ZM35.542 31.594l-3.761 5.834 8.054 2.251 2.307-7.958-6.6-.127ZM6.528 31.721 8.82 39.68l8.04-2.251-3.747-5.834-6.586.127Z"
|
||||
fill="#E27625"
|
||||
stroke="#E27625"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m16.428 21.738-2.237 3.427 7.97.368-.266-8.709-5.467 4.914ZM32.229 21.738l-5.552-5.012-.181 8.807 7.97-.368-2.237-3.427ZM16.861 37.428l4.824-2.365-4.152-3.285-.672 5.65ZM26.971 35.063l4.81 2.365-.657-5.65-4.153 3.285Z"
|
||||
fill="#E27625"
|
||||
stroke="#E27625"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m31.78 37.428-4.81-2.365.392 3.172-.042 1.345 4.46-2.152ZM16.861 37.428l4.475 2.152-.028-1.345.377-3.172-4.824 2.365Z"
|
||||
fill="#D5BFB2"
|
||||
stroke="#D5BFB2"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m21.42 29.682-4-1.19 2.825-1.316 1.174 2.506ZM27.236 29.682l1.175-2.506 2.838 1.317-4.013 1.19Z"
|
||||
fill="#233447"
|
||||
stroke="#233447"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m16.861 37.427.7-5.834-4.447.128 3.747 5.706ZM31.096 31.593l.685 5.834 3.761-5.706-4.446-.128ZM34.465 25.165l-7.97.368.741 4.15 1.175-2.507 2.838 1.317 3.216-3.328ZM17.42 28.493l2.825-1.317 1.175 2.506.74-4.149-7.97-.368 3.23 3.328Z"
|
||||
fill="#CC6228"
|
||||
stroke="#CC6228"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m14.19 25.165 3.343 6.613-.112-3.285-3.23-3.328ZM31.25 28.493l-.126 3.285 3.342-6.613-3.216 3.328ZM22.161 25.533l-.741 4.149.937 4.9.21-6.458-.406-2.591ZM26.495 25.533l-.391 2.577.196 6.471.937-4.9-.741-4.148Z"
|
||||
fill="#E27525"
|
||||
stroke="#E27525"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m27.237 29.682-.937 4.9.671.481 4.153-3.285.126-3.285-4.013 1.19ZM17.42 28.493l.112 3.285 4.153 3.285.671-.481-.937-4.9-3.999-1.19Z"
|
||||
fill="#F5841F"
|
||||
stroke="#F5841F"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m27.32 39.58.042-1.345-.363-.312h-5.342l-.35.312.029 1.345-4.475-2.152 1.566 1.303 3.175 2.223h5.439l3.188-2.224 1.552-1.302-4.46 2.152Z"
|
||||
fill="#C0AC9D"
|
||||
stroke="#C0AC9D"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m26.97 35.063-.67-.482h-3.944l-.67.482-.378 3.172.35-.312h5.34l.364.312-.391-3.172Z"
|
||||
fill="#161616"
|
||||
stroke="#161616"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m41.234 18.283 1.188-5.863-1.79-5.451-13.66 10.266 5.257 4.503 7.425 2.195 1.636-1.94-.713-.524 1.132-1.048-.867-.68 1.133-.878-.741-.58ZM6.234 12.42l1.203 5.863-.77.58 1.147.878-.867.68L8.08 21.47l-.713.524 1.636 1.94 7.425-2.195 5.257-4.503L8.025 6.97l-1.79 5.452Z"
|
||||
fill="#763E1A"
|
||||
stroke="#763E1A"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="m39.654 23.933-7.425-2.195 2.237 3.427-3.342 6.613 4.419-.057h6.6l-2.49-7.788ZM16.428 21.738l-7.425 2.195-2.475 7.788h6.586l4.418.056-3.342-6.612 2.238-3.427ZM26.495 25.533l.476-8.298 2.153-5.905h-9.592l2.153 5.905.476 8.298.181 2.605.014 6.443H26.3l.014-6.443.182-2.605Z"
|
||||
fill="#F5841F"
|
||||
stroke="#F5841F"
|
||||
strokeWidth="0.223"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IconArrowDown } from './svg-icons/icon-arrow-down';
|
||||
import { IconArrowLeft } from './svg-icons/icon-arrow-left';
|
||||
import { IconArrowUp } from './svg-icons/icon-arrow-up';
|
||||
import { IconArrowRight } from './svg-icons/icon-arrow-right';
|
||||
import { IconArrowTopRight } from './svg-icons/icon-arrow-top-right';
|
||||
import { IconArrowUp } from './svg-icons/icon-arrow-up';
|
||||
import { IconBreakdown } from './svg-icons/icon-breakdown';
|
||||
import { IconBullet } from './svg-icons/icon-bullet';
|
||||
import { IconChevronDown } from './svg-icons/icon-chevron-down';
|
||||
@@ -20,28 +20,29 @@ import { IconGlobe } from './svg-icons/icon-globe';
|
||||
import { IconInfo } from './svg-icons/icon-info';
|
||||
import { IconKebab } from './svg-icons/icon-kebab';
|
||||
import { IconLinkedIn } from './svg-icons/icon-linkedin';
|
||||
import { IconMetaMask } from './svg-icons/icon-metamask';
|
||||
import { IconMinus } from './svg-icons/icon-minus';
|
||||
import { IconMoon } from './svg-icons/icon-moon';
|
||||
import { IconOpenExternal } from './svg-icons/icon-open-external';
|
||||
import { IconQuestionMark } from './svg-icons/icon-question-mark';
|
||||
import { IconPlus } from './svg-icons/icon-plus';
|
||||
import { IconQuestionMark } from './svg-icons/icon-question-mark';
|
||||
import { IconSearch } from './svg-icons/icon-search';
|
||||
import { IconStar } from './svg-icons/icon-star';
|
||||
import { IconTick } from './svg-icons/icon-tick';
|
||||
import { IconTicket } from './svg-icons/icon-ticket';
|
||||
import { IconTransfer } from './svg-icons/icon-transfer';
|
||||
import { IconTrendUp } from './svg-icons/icon-trend-up';
|
||||
import { IconTrendDown } from './svg-icons/icon-trend-down';
|
||||
import { IconTrendUp } from './svg-icons/icon-trend-up';
|
||||
import { IconTwitter } from './svg-icons/icon-twitter';
|
||||
import { IconVote } from './svg-icons/icon-vote';
|
||||
import { IconWithdraw } from './svg-icons/icon-withdraw';
|
||||
import { IconSearch } from './svg-icons/icon-search';
|
||||
|
||||
export enum VegaIconNames {
|
||||
ARROW_DOWN = 'arrow-down',
|
||||
ARROW_LEFT = 'arrow-left',
|
||||
ARROW_UP = 'arrow-up',
|
||||
ARROW_RIGHT = 'arrow-right',
|
||||
ARROW_TOP_RIGHT = 'arrow-top-right',
|
||||
ARROW_UP = 'arrow-up',
|
||||
BREAKDOWN = 'breakdown',
|
||||
BULLET = 'bullet',
|
||||
CHEVRON_DOWN = 'chevron-down',
|
||||
@@ -59,18 +60,19 @@ export enum VegaIconNames {
|
||||
INFO = 'info',
|
||||
KEBAB = 'kebab',
|
||||
LINKEDIN = 'linkedin',
|
||||
METAMASK = 'metamask',
|
||||
MINUS = 'minus',
|
||||
MOON = 'moon',
|
||||
OPEN_EXTERNAL = 'open-external',
|
||||
QUESTION_MARK = 'question-mark',
|
||||
PLUS = 'plus',
|
||||
QUESTION_MARK = 'question-mark',
|
||||
SEARCH = 'search',
|
||||
STAR = 'star',
|
||||
TICK = 'tick',
|
||||
TICKET = 'ticket',
|
||||
TRANSFER = 'transfer',
|
||||
TREND_UP = 'trend-up',
|
||||
TREND_DOWN = 'trend-down',
|
||||
TREND_UP = 'trend-up',
|
||||
TWITTER = 'twitter',
|
||||
VOTE = 'vote',
|
||||
WITHDRAW = 'withdraw',
|
||||
@@ -82,38 +84,39 @@ export const VegaIconNameMap: Record<
|
||||
> = {
|
||||
'arrow-down': IconArrowDown,
|
||||
'arrow-left': IconArrowLeft,
|
||||
'arrow-up': IconArrowUp,
|
||||
'arrow-right': IconArrowRight,
|
||||
'arrow-top-right': IconArrowTopRight,
|
||||
breakdown: IconBreakdown,
|
||||
bullet: IconBullet,
|
||||
'arrow-up': IconArrowUp,
|
||||
'chevron-down': IconChevronDown,
|
||||
'chevron-left': IconChevronLeft,
|
||||
'chevron-up': IconChevronUp,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
'open-external': IconOpenExternal,
|
||||
'question-mark': IconQuestionMark,
|
||||
'trend-down': IconTrendDown,
|
||||
'trend-up': IconTrendUp,
|
||||
breakdown: IconBreakdown,
|
||||
bullet: IconBullet,
|
||||
cog: IconCog,
|
||||
copy: IconCopy,
|
||||
cross: IconCross,
|
||||
deposit: IconDeposit,
|
||||
edit: IconEdit,
|
||||
'exclaimation-mark': IconExclaimationMark,
|
||||
eye: IconEye,
|
||||
forum: IconForum,
|
||||
globe: IconGlobe,
|
||||
info: IconInfo,
|
||||
kebab: IconKebab,
|
||||
linkedin: IconLinkedIn,
|
||||
metamask: IconMetaMask,
|
||||
minus: IconMinus,
|
||||
moon: IconMoon,
|
||||
'open-external': IconOpenExternal,
|
||||
plus: IconPlus,
|
||||
'question-mark': IconQuestionMark,
|
||||
search: IconSearch,
|
||||
star: IconStar,
|
||||
tick: IconTick,
|
||||
ticket: IconTicket,
|
||||
transfer: IconTransfer,
|
||||
'trend-up': IconTrendUp,
|
||||
'trend-down': IconTrendDown,
|
||||
twitter: IconTwitter,
|
||||
vote: IconVote,
|
||||
withdraw: IconWithdraw,
|
||||
|
||||
@@ -68,7 +68,11 @@ export const ShowMore = ({
|
||||
|
||||
{!expanded && (
|
||||
<div className="mt-1 text-center">
|
||||
<Button size={'sm'} onClick={() => setExpanded(true)}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
onClick={() => setExpanded(true)}
|
||||
data-testid="show-more-btn"
|
||||
>
|
||||
{t('Show more')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -80,13 +80,14 @@ const getAffixElement = ({
|
||||
appendIconName,
|
||||
appendIconDescription,
|
||||
}: Pick<TradingInputProps, keyof AffixProps>) => {
|
||||
const position = prependIconName || prependElement ? 'pre' : 'post';
|
||||
|
||||
const className = classNames(
|
||||
['fill-black dark:fill-white', 'absolute', 'z-10'],
|
||||
'absolute z-10 top-0 bottom-0 flex items-center',
|
||||
{
|
||||
'left-3': position === 'pre',
|
||||
'right-3': position === 'post',
|
||||
'fill-black dark:fill-white': prependIconName || appendIconName,
|
||||
'left-3': prependIconName,
|
||||
'right-3': appendIconName,
|
||||
'left-1': prependElement,
|
||||
'right-1': appendElement,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -161,7 +162,7 @@ export const TradingInput = forwardRef<HTMLInputElement, TradingInputProps>(
|
||||
|
||||
if (element) {
|
||||
return (
|
||||
<div className="flex items-center relative">
|
||||
<div className="relative">
|
||||
{hasPrepended && element}
|
||||
{input}
|
||||
{hasAppended && element}
|
||||
|
||||
@@ -15,8 +15,9 @@ export const ethereumAddress = (value: string) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i;
|
||||
export const vegaPublicKey = (value: string) => {
|
||||
if (!/^[A-Fa-f0-9]{64}$/i.test(value)) {
|
||||
if (!VEGA_ID_REGEX.test(value)) {
|
||||
return t('Invalid Vega key');
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"name": "@vegaprotocol/wallet",
|
||||
"version": "0.0.1"
|
||||
"version": "0.0.1",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
"tsConfig": "libs/wallet/tsconfig.lib.json",
|
||||
"project": "libs/wallet/package.json",
|
||||
"entryFile": "libs/wallet/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"format": ["esm", "cjs"],
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/wallet/README.md",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
export const ChromeIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
data-testid="chrome-logo"
|
||||
>
|
||||
<g clipPath="url(#clip0_3681_24659)">
|
||||
<path
|
||||
d="M15.9987 9.99963L26.3893 9.99964C25.3364 8.17534 23.8217 6.6604 21.9976 5.60716C20.1735 4.55391 18.1042 3.99949 15.9979 3.99964C13.8915 3.99979 11.8223 4.5545 9.99837 5.608C8.1744 6.66149 6.65995 8.17664 5.6073 10.0011L10.8026 18.9996L10.8072 18.9984C10.2787 18.0871 9.99984 17.0525 9.99865 15.999C9.99747 14.9454 10.274 13.9102 10.8005 12.9977C11.3269 12.0851 12.0847 11.3275 12.9973 10.8011C13.9099 10.2748 14.9451 9.99832 15.9987 9.99963Z"
|
||||
fill="url(#paint0_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M21.1974 18.9989L16.0021 27.9974C18.1084 27.9977 20.1777 27.4435 22.0019 26.3904C23.8261 25.3373 25.3409 23.8224 26.3939 21.9982C27.447 20.174 28.0012 18.1047 28.0008 15.9983C28.0004 13.892 27.4455 11.8228 26.3918 9.99898L16.0012 9.99899L15.9999 10.0036C17.0534 10.0016 18.0889 10.2773 19.0018 10.8031C19.9148 11.3288 20.673 12.0859 21.2001 12.9981C21.7272 13.9103 22.0044 14.9454 22.004 15.9989C22.0035 17.0524 21.7253 18.0872 21.1974 18.9989Z"
|
||||
fill="url(#paint1_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M10.8044 19.0016L5.60914 10.0031C4.5557 11.8271 4.00106 13.8963 4.00098 16.0026C4.0009 18.109 4.55539 20.1782 5.60869 22.0023C6.66199 23.8264 8.17698 25.341 10.0013 26.3938C11.8257 27.4467 13.895 28.0007 16.0014 28.0001L21.1967 19.0015L21.1933 18.9981C20.6683 19.9115 19.9118 20.6703 19 21.1981C18.0882 21.7259 17.0534 22.004 15.9999 22.0043C14.9464 22.0047 13.9114 21.7273 12.9992 21.2001C12.0871 20.6729 11.3301 19.9146 10.8044 19.0016Z"
|
||||
fill="url(#paint2_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M16 22C19.3137 22 22 19.3137 22 16C22 12.6863 19.3137 10 16 10C12.6863 10 10 12.6863 10 16C10 19.3137 12.6863 22 16 22Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16 20.75C18.6234 20.75 20.75 18.6234 20.75 16C20.75 13.3766 18.6234 11.25 16 11.25C13.3766 11.25 11.25 13.3766 11.25 16C11.25 18.6234 13.3766 20.75 16 20.75Z"
|
||||
fill="#1A73E8"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3681_24659"
|
||||
x1="25.093"
|
||||
y1="9.25084"
|
||||
x2="14.702"
|
||||
y2="27.2485"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#D93025" />
|
||||
<stop offset="1" stopColor="#EA4335" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_3681_24659"
|
||||
x1="27.073"
|
||||
y1="11.4997"
|
||||
x2="6.29104"
|
||||
y2="11.4997"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#FCC934" />
|
||||
<stop offset="1" stopColor="#FBBC04" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_3681_24659"
|
||||
x1="17.2992"
|
||||
y1="27.2508"
|
||||
x2="6.90819"
|
||||
y2="9.25305"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#1E8E3E" />
|
||||
<stop offset="1" stopColor="#34A853" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3681_24659">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ExternalLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ExternalLink,
|
||||
@@ -7,12 +6,15 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { MozillaIcon } from './mozilla-icon';
|
||||
import { ChromeIcon } from './chrome-icon';
|
||||
import { useVegaWallet } from '../use-vega-wallet';
|
||||
|
||||
export const ConnectDialogTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<h1
|
||||
data-testid="wallet-dialog-title"
|
||||
className="text-2xl uppercase mb-6 font-alpha calt"
|
||||
className="mb-6 text-2xl uppercase font-alpha calt"
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
@@ -24,6 +26,7 @@ export const ConnectDialogContent = ({ children }: { children: ReactNode }) => {
|
||||
};
|
||||
|
||||
export const ConnectDialogFooter = () => {
|
||||
const { links } = useVegaWallet();
|
||||
const wrapperClasses = classNames(
|
||||
'flex justify-center gap-4 mt-4',
|
||||
'px-4 md:px-8 pt-4 md:pt-6',
|
||||
@@ -32,335 +35,37 @@ export const ConnectDialogFooter = () => {
|
||||
);
|
||||
return (
|
||||
<footer className={wrapperClasses}>
|
||||
<ExternalLink
|
||||
href={ExternalLinks.VEGA_WALLET_URL_ABOUT}
|
||||
className="underline"
|
||||
>
|
||||
<ExternalLink href={links.about} className="underline">
|
||||
{t('About the Vega wallet')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
|
||||
</ExternalLink>
|
||||
{ExternalLinks.VEGA_WALLET_BROWSER_LIST && (
|
||||
<>
|
||||
{' | '}
|
||||
<ExternalLink
|
||||
href={ExternalLinks.VEGA_WALLET_BROWSER_LIST}
|
||||
className="underline"
|
||||
>
|
||||
{t('Supported browsers')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
|
||||
</ExternalLink>
|
||||
</>
|
||||
)}
|
||||
{' | '}
|
||||
<ExternalLink href={links.browserList} className="underline">
|
||||
{t('Supported browsers')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_TOP_RIGHT} />
|
||||
</ExternalLink>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChromeIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="28"
|
||||
height="28"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
data-testid="chrome-logo"
|
||||
>
|
||||
<g clipPath="url(#clip0_3681_24659)">
|
||||
<path
|
||||
d="M15.9987 9.99963L26.3893 9.99964C25.3364 8.17534 23.8217 6.6604 21.9976 5.60716C20.1735 4.55391 18.1042 3.99949 15.9979 3.99964C13.8915 3.99979 11.8223 4.5545 9.99837 5.608C8.1744 6.66149 6.65995 8.17664 5.6073 10.0011L10.8026 18.9996L10.8072 18.9984C10.2787 18.0871 9.99984 17.0525 9.99865 15.999C9.99747 14.9454 10.274 13.9102 10.8005 12.9977C11.3269 12.0851 12.0847 11.3275 12.9973 10.8011C13.9099 10.2748 14.9451 9.99832 15.9987 9.99963Z"
|
||||
fill="url(#paint0_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M21.1974 18.9989L16.0021 27.9974C18.1084 27.9977 20.1777 27.4435 22.0019 26.3904C23.8261 25.3373 25.3409 23.8224 26.3939 21.9982C27.447 20.174 28.0012 18.1047 28.0008 15.9983C28.0004 13.892 27.4455 11.8228 26.3918 9.99898L16.0012 9.99899L15.9999 10.0036C17.0534 10.0016 18.0889 10.2773 19.0018 10.8031C19.9148 11.3288 20.673 12.0859 21.2001 12.9981C21.7272 13.9103 22.0044 14.9454 22.004 15.9989C22.0035 17.0524 21.7253 18.0872 21.1974 18.9989Z"
|
||||
fill="url(#paint1_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M10.8044 19.0016L5.60914 10.0031C4.5557 11.8271 4.00106 13.8963 4.00098 16.0026C4.0009 18.109 4.55539 20.1782 5.60869 22.0023C6.66199 23.8264 8.17698 25.341 10.0013 26.3938C11.8257 27.4467 13.895 28.0007 16.0014 28.0001L21.1967 19.0015L21.1933 18.9981C20.6683 19.9115 19.9118 20.6703 19 21.1981C18.0882 21.7259 17.0534 22.004 15.9999 22.0043C14.9464 22.0047 13.9114 21.7273 12.9992 21.2001C12.0871 20.6729 11.3301 19.9146 10.8044 19.0016Z"
|
||||
fill="url(#paint2_linear_3681_24659)"
|
||||
/>
|
||||
<path
|
||||
d="M16 22C19.3137 22 22 19.3137 22 16C22 12.6863 19.3137 10 16 10C12.6863 10 10 12.6863 10 16C10 19.3137 12.6863 22 16 22Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16 20.75C18.6234 20.75 20.75 18.6234 20.75 16C20.75 13.3766 18.6234 11.25 16 11.25C13.3766 11.25 11.25 13.3766 11.25 16C11.25 18.6234 13.3766 20.75 16 20.75Z"
|
||||
fill="#1A73E8"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3681_24659"
|
||||
x1="25.093"
|
||||
y1="9.25084"
|
||||
x2="14.702"
|
||||
y2="27.2485"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#D93025" />
|
||||
<stop offset="1" stopColor="#EA4335" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_3681_24659"
|
||||
x1="27.073"
|
||||
y1="11.4997"
|
||||
x2="6.29104"
|
||||
y2="11.4997"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#FCC934" />
|
||||
<stop offset="1" stopColor="#FBBC04" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_3681_24659"
|
||||
x1="17.2992"
|
||||
y1="27.2508"
|
||||
x2="6.90819"
|
||||
y2="9.25305"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#1E8E3E" />
|
||||
<stop offset="1" stopColor="#34A853" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3681_24659">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(4 4)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const MozillaIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
data-testid="mozilla-logo"
|
||||
>
|
||||
<g clipPath="url(#clip0_3681_24667)">
|
||||
<path
|
||||
d="M22.4398 7.79786C21.9502 6.62017 20.9585 5.34873 20.1798 4.94687C20.8136 6.1893 21.1804 7.43561 21.3205 8.36575C21.3205 8.36758 21.3212 8.37212 21.3227 8.3845C20.0489 5.20951 17.889 3.92926 16.1252 1.1417C16.0361 1.00075 15.9468 0.85942 15.8598 0.710452C15.8155 0.634372 15.7742 0.556628 15.7358 0.477389C15.6625 0.335913 15.606 0.186371 15.5674 0.0317953C15.5676 0.0244774 15.5652 0.017323 15.5604 0.0117374C15.5557 0.00615177 15.549 0.00253868 15.5418 0.00160783C15.5349 -0.000373182 15.5275 -0.000373182 15.5206 0.00160783C15.519 0.00217033 15.5167 0.00399845 15.515 0.0046547C15.5125 0.00563908 15.5094 0.00788908 15.5068 0.0093422C15.508 0.0076547 15.5107 0.00385783 15.5115 0.0029672C12.6816 1.66028 11.7216 4.72614 11.6334 6.26003C10.5034 6.33772 9.42293 6.75406 8.53298 7.45478C8.43981 7.37608 8.3424 7.30253 8.24119 7.23447C7.98442 6.33615 7.97351 5.38538 8.20959 4.4814C7.05234 5.00833 6.15225 5.84125 5.49787 6.57672H5.49267C5.04609 6.01108 5.07759 4.14517 5.10305 3.75559C5.0977 3.73145 4.76991 3.92575 4.72697 3.95505C4.3329 4.23633 3.96449 4.55194 3.62606 4.89817C3.24095 5.28871 2.88907 5.71069 2.57409 6.15972C2.57409 6.16028 2.57377 6.16094 2.57358 6.1615C2.57358 6.16089 2.57391 6.16028 2.57409 6.15972C1.8497 7.18625 1.33595 8.34618 1.06252 9.57245C1.05712 9.59687 1.05258 9.62219 1.04733 9.6468C1.02614 9.74598 0.949828 10.2421 0.936469 10.3499C0.935438 10.3582 0.934969 10.3662 0.933984 10.3745C0.835324 10.8874 0.774224 11.4069 0.751172 11.9287C0.751172 11.9479 0.75 11.967 0.75 11.9862C0.750187 18.2072 5.79394 23.2501 12.0154 23.2501C17.5872 23.2501 22.2135 19.2053 23.1192 13.8924C23.1383 13.7482 23.1536 13.6033 23.1704 13.4578C23.3943 11.5261 23.1456 9.49572 22.4398 7.79786ZM9.45562 16.6148C9.50831 16.6399 9.55781 16.6675 9.61191 16.6916C9.61416 16.6931 9.61725 16.6949 9.61955 16.6963C9.56449 16.67 9.50984 16.6428 9.45562 16.6148ZM21.3236 8.38726L21.3221 8.37634C21.3227 8.38033 21.3234 8.3845 21.324 8.38848L21.3236 8.38726Z"
|
||||
fill="url(#paint0_linear_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M22.4397 7.79776C21.9501 6.62007 20.9584 5.34864 20.1797 4.94678C20.8135 6.1892 21.1803 7.43551 21.3204 8.36565C21.3204 8.36293 21.321 8.3679 21.3221 8.37625C21.3227 8.38023 21.3234 8.3844 21.324 8.38839C22.3869 11.2698 21.8078 14.1999 20.9734 15.9903C19.6825 18.7607 16.5571 21.5999 11.6652 21.4614C6.37978 21.3117 1.7235 17.39 0.854297 12.2536C0.695906 11.4436 0.854297 11.0323 0.933984 10.3746C0.836906 10.8816 0.799922 11.0281 0.751172 11.9289C0.751172 11.9481 0.75 11.9671 0.75 11.9864C0.750094 18.2071 5.79384 23.25 12.0153 23.25C17.5871 23.25 22.2134 19.2052 23.1191 13.8923C23.1382 13.7481 23.1535 13.6032 23.1703 13.4577C23.3942 11.526 23.1455 9.49562 22.4397 7.79776Z"
|
||||
fill="url(#paint1_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M22.4397 7.79776C21.9501 6.62007 20.9584 5.34864 20.1797 4.94678C20.8135 6.1892 21.1803 7.43551 21.3204 8.36565C21.3204 8.36293 21.321 8.3679 21.3221 8.37625C21.3227 8.38023 21.3234 8.3844 21.324 8.38839C22.3869 11.2698 21.8078 14.1999 20.9734 15.9903C19.6825 18.7607 16.5571 21.5999 11.6652 21.4614C6.37978 21.3117 1.7235 17.39 0.854297 12.2536C0.695906 11.4436 0.854297 11.0323 0.933984 10.3746C0.836906 10.8816 0.799922 11.0281 0.751172 11.9289C0.751172 11.9481 0.75 11.9671 0.75 11.9864C0.750094 18.2071 5.79384 23.25 12.0153 23.25C17.5871 23.25 22.2134 19.2052 23.1191 13.8923C23.1382 13.7481 23.1535 13.6032 23.1703 13.4577C23.3942 11.526 23.1455 9.49562 22.4397 7.79776Z"
|
||||
fill="url(#paint2_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M16.965 9.12184C16.9896 9.13909 17.0119 9.15625 17.035 9.1734C16.7523 8.67164 16.4002 8.21224 15.9892 7.80878C12.4874 4.3074 15.071 0.216811 15.5067 0.00906055C15.5079 0.00737305 15.5106 0.00357617 15.5114 0.00268555C12.6815 1.66 11.7215 4.72586 11.6333 6.25975C11.7646 6.25065 11.8954 6.23964 12.029 6.23964C14.1408 6.23964 15.9801 7.40073 16.965 9.12184Z"
|
||||
fill="url(#paint3_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M12.0361 9.82097C12.0176 10.1012 11.0276 11.0675 10.6814 11.0675C7.47799 11.0675 6.95801 13.0051 6.95801 13.0051C7.0999 14.6368 8.23587 15.9805 9.61165 16.6915C9.67441 16.7239 9.73793 16.7532 9.80149 16.7822C9.91047 16.8304 10.0208 16.8756 10.1324 16.9175C10.6041 17.0845 11.0982 17.1798 11.5982 17.2002C17.2129 17.4635 18.3007 10.488 14.2488 8.4623C15.2864 8.28183 16.3635 8.69916 16.965 9.12169C15.9801 7.40072 14.1408 6.23962 12.0291 6.23962C11.8955 6.23962 11.7647 6.25064 11.6334 6.25973C10.5033 6.33742 9.42291 6.75376 8.53296 7.45448C8.70471 7.5998 8.89859 7.79405 9.30705 8.19642C10.0712 8.94956 12.0319 9.72947 12.0361 9.82097Z"
|
||||
fill="url(#paint4_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M12.0361 9.82097C12.0176 10.1012 11.0276 11.0675 10.6814 11.0675C7.47799 11.0675 6.95801 13.0051 6.95801 13.0051C7.0999 14.6368 8.23587 15.9805 9.61165 16.6915C9.67441 16.7239 9.73793 16.7532 9.80149 16.7822C9.91047 16.8304 10.0208 16.8756 10.1324 16.9175C10.6041 17.0845 11.0982 17.1798 11.5982 17.2002C17.2129 17.4635 18.3007 10.488 14.2488 8.4623C15.2864 8.28183 16.3635 8.69916 16.965 9.12169C15.9801 7.40072 14.1408 6.23962 12.0291 6.23962C11.8955 6.23962 11.7647 6.25064 11.6334 6.25973C10.5033 6.33742 9.42291 6.75376 8.53296 7.45448C8.70471 7.5998 8.89859 7.79405 9.30705 8.19642C10.0712 8.94956 12.0319 9.72947 12.0361 9.82097Z"
|
||||
fill="url(#paint5_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M8.00739 7.07982C8.08584 7.13043 8.16368 7.18199 8.24087 7.23451C7.98411 6.33619 7.97319 5.38542 8.20928 4.48145C7.05203 5.00837 6.15193 5.84129 5.49756 6.57676C5.5517 6.57521 7.18571 6.54582 8.00739 7.07982Z"
|
||||
fill="url(#paint6_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M0.853976 12.2536C1.72318 17.3901 6.37946 21.3118 11.6649 21.4614C16.5568 21.5999 19.6822 18.7605 20.9731 15.9904C21.8075 14.1997 22.3866 11.2701 21.3237 8.38842L21.3233 8.3872L21.3218 8.37628C21.3206 8.36793 21.3199 8.36296 21.3201 8.36568C21.3201 8.36751 21.3208 8.37206 21.3223 8.38443C21.7219 10.9935 20.3947 13.5212 18.3199 15.2302L18.3137 15.2449C14.271 18.5366 10.4024 17.2309 9.61913 16.6964C9.56408 16.67 9.50939 16.6427 9.45507 16.6147C7.0981 15.4884 6.12441 13.3411 6.3332 11.4996C4.34302 11.4996 3.66441 9.82101 3.66441 9.82101C3.66441 9.82101 5.45124 8.54699 7.8062 9.65503C9.98729 10.6813 12.0356 9.8211 12.0359 9.82101C12.0317 9.72951 10.071 8.9496 9.30667 8.19651C8.89824 7.79414 8.70432 7.60012 8.53257 7.45457C8.4394 7.37587 8.34198 7.30232 8.24077 7.23426C8.16349 7.18188 8.08566 7.13031 8.00729 7.07957C7.18566 6.54557 5.5516 6.57496 5.49746 6.57637H5.49226C5.04568 6.01073 5.07718 4.14482 5.10263 3.75524C5.09729 3.7311 4.76949 3.9254 4.72655 3.9547C4.33248 4.23598 3.96408 4.55159 3.62565 4.89782C3.24052 5.28846 2.88865 5.71053 2.57368 6.15965C2.57368 6.16021 2.57335 6.16087 2.57316 6.16143C2.57316 6.16082 2.57349 6.16021 2.57368 6.15965C1.84929 7.18619 1.33553 8.34611 1.0621 9.57238C1.05671 9.59681 0.656632 11.3462 0.853976 12.2536Z"
|
||||
fill="url(#paint7_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M15.9894 7.80883C16.4004 8.21229 16.7525 8.67169 17.0352 9.17345C17.0972 9.22005 17.1552 9.2665 17.2043 9.31183C19.7582 11.665 18.4201 14.9931 18.3203 15.2303C20.395 13.5212 21.7222 10.9936 21.3227 8.38445C20.0489 5.20951 17.8889 3.92926 16.1251 1.1417C16.0361 1.00075 15.9468 0.85942 15.8598 0.710452C15.8155 0.634372 15.7741 0.556628 15.7357 0.477389C15.6625 0.335913 15.6059 0.186371 15.5673 0.0317953C15.5676 0.0244774 15.5651 0.017323 15.5604 0.0117374C15.5556 0.00615177 15.549 0.00253868 15.5417 0.00160783C15.5348 -0.000373182 15.5275 -0.000373182 15.5206 0.00160783C15.519 0.00217033 15.5166 0.00399845 15.515 0.0046547C15.5125 0.00563908 15.5093 0.00788908 15.5067 0.0093422C15.0712 0.216905 12.4876 4.3075 15.9894 7.80883Z"
|
||||
fill="url(#paint8_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M17.2043 9.31181C17.1551 9.26648 17.0972 9.22003 17.0352 9.17343C17.0123 9.15618 16.9898 9.13903 16.9652 9.12187C16.3637 8.69934 15.2866 8.28201 14.2489 8.46248C18.3008 10.4881 17.2131 17.4637 11.5984 17.2004C11.0984 17.18 10.6043 17.0847 10.1326 16.9177C10.021 16.8757 9.91066 16.8306 9.80166 16.7824C9.7381 16.7534 9.67458 16.7241 9.61182 16.6917C9.61407 16.6932 9.61716 16.6949 9.61946 16.6964C10.4027 17.2307 14.2713 18.5365 18.314 15.2448L18.3202 15.2302C18.42 14.9932 19.7581 11.665 17.2043 9.31181Z"
|
||||
fill="url(#paint9_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M6.95794 13.0051C6.95794 13.0051 7.47793 11.0675 10.6813 11.0675C11.0276 11.0675 12.0177 10.1012 12.036 9.82096C12.0543 9.54074 9.98756 10.6812 7.80633 9.65497C5.45138 8.54694 3.66455 9.82096 3.66455 9.82096C3.66455 9.82096 4.34316 11.4995 6.33333 11.4995C6.1246 13.341 7.09828 15.4886 9.45521 16.6147C9.50789 16.6399 9.55739 16.6674 9.61149 16.6915C8.2358 15.9805 7.09983 14.6368 6.95794 13.0051Z"
|
||||
fill="url(#paint10_radial_3681_24667)"
|
||||
/>
|
||||
<path
|
||||
d="M22.4396 7.79786C21.95 6.62017 20.9583 5.34873 20.1796 4.94687C20.8134 6.1893 21.1802 7.43561 21.3203 8.36575C21.3203 8.36758 21.321 8.37212 21.3225 8.3845C20.0487 5.20951 17.8888 3.92926 16.125 1.1417C16.0359 1.00075 15.9466 0.85942 15.8596 0.710452C15.8153 0.634372 15.774 0.556628 15.7356 0.477389C15.6623 0.335913 15.6058 0.186371 15.5672 0.0317953C15.5675 0.0244774 15.565 0.017323 15.5602 0.0117374C15.5555 0.00615177 15.5489 0.00253868 15.5416 0.00160783C15.5347 -0.000373182 15.5274 -0.000373182 15.5205 0.00160783C15.5189 0.00217033 15.5165 0.00399845 15.5148 0.0046547C15.5123 0.00563908 15.5092 0.00788908 15.5066 0.0093422C15.5078 0.0076547 15.5105 0.00385783 15.5113 0.0029672C12.6814 1.66028 11.7214 4.72614 11.6332 6.26003C11.7645 6.25094 11.8953 6.23992 12.0289 6.23992C14.1408 6.23992 15.9801 7.40101 16.9649 9.12198C16.3634 8.69945 15.2863 8.28212 14.2486 8.46259C18.3005 10.4882 17.2127 17.4638 11.598 17.2005C11.098 17.1801 10.6039 17.0848 10.1322 16.9178C10.0207 16.8758 9.91032 16.8307 9.80133 16.7825C9.73777 16.7535 9.67425 16.7242 9.61148 16.6918C9.61373 16.6933 9.61683 16.6951 9.61912 16.6965C9.56407 16.67 9.50938 16.6427 9.45506 16.6148C9.50775 16.6399 9.55725 16.6675 9.61134 16.6916C8.23556 15.9806 7.09959 14.6369 6.9577 13.0052C6.9577 13.0052 7.47769 11.0676 10.6811 11.0676C11.0274 11.0676 12.0174 10.1013 12.0358 9.82108C12.0316 9.72958 10.0709 8.94967 9.30656 8.19658C8.89814 7.7942 8.70422 7.60019 8.53247 7.45464C8.43929 7.37594 8.34188 7.30239 8.24067 7.23433C7.98391 6.33601 7.97299 5.38524 8.20908 4.48126C7.05183 5.00819 6.15173 5.84111 5.49736 6.57658H5.49216C5.04558 6.01094 5.07708 4.14503 5.10253 3.75545C5.09719 3.73131 4.76939 3.92561 4.72645 3.9549C4.33238 4.23619 3.96398 4.5518 3.62555 4.89803C3.24054 5.28863 2.88878 5.71066 2.57391 6.15972C2.57391 6.16028 2.57358 6.16094 2.57339 6.1615C2.57339 6.16089 2.57372 6.16028 2.57391 6.15972C1.84952 7.18625 1.33576 8.34618 1.06233 9.57245C1.05694 9.59687 1.05239 9.62219 1.04714 9.6468C1.02595 9.74598 0.930609 10.2493 0.917297 10.3572C0.916266 10.3655 0.918281 10.349 0.917297 10.3572C0.830351 10.8773 0.774875 11.4022 0.751172 11.929C0.751172 11.9482 0.75 11.9672 0.75 11.9865C0.75 18.2072 5.79375 23.2501 12.0152 23.2501C17.587 23.2501 22.2133 19.2053 23.119 13.8924C23.1381 13.7482 23.1534 13.6033 23.1702 13.4578C23.3941 11.5261 23.1454 9.49572 22.4396 7.79786ZM21.322 8.37634C21.3226 8.38033 21.3233 8.3845 21.3239 8.38848L21.3235 8.38726L21.322 8.37634Z"
|
||||
fill="url(#paint11_linear_3681_24667)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3681_24667"
|
||||
x1="20.3814"
|
||||
y1="3.60386"
|
||||
x2="2.29295"
|
||||
y2="21.0528"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.05" stopColor="#FFF44F" />
|
||||
<stop offset="0.37" stopColor="#FF980E" />
|
||||
<stop offset="0.53" stopColor="#FF3647" />
|
||||
<stop offset="0.7" stopColor="#E31587" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint1_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(16.3404 2.59171) scale(23.0401 23.4281)"
|
||||
>
|
||||
<stop offset="0.13" stopColor="#FFBD4F" />
|
||||
<stop offset="0.28" stopColor="#FF980E" />
|
||||
<stop offset="0.47" stopColor="#FF3750" />
|
||||
<stop offset="0.78" stopColor="#EB0878" />
|
||||
<stop offset="0.86" stopColor="#E50080" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint2_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(9.65968 12.2681) scale(23.6161 23.4281)"
|
||||
>
|
||||
<stop offset="0.3" stopColor="#960E18" />
|
||||
<stop offset="0.35" stopColor="#B11927" stopOpacity="0.74" />
|
||||
<stop offset="0.43" stopColor="#DB293D" stopOpacity="0.34" />
|
||||
<stop offset="0.5" stopColor="#F5334B" stopOpacity="0.09" />
|
||||
<stop offset="0.53" stopColor="#FF3750" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint3_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(14.2261 -1.0978) scale(7.56236 12.839)"
|
||||
>
|
||||
<stop offset="0.13" stopColor="#FFF44F" />
|
||||
<stop offset="0.53" stopColor="#FF980E" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint4_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(9.2356 18.3163) scale(10.007 10.9678)"
|
||||
>
|
||||
<stop offset="0.35" stopColor="#3A8EE6" />
|
||||
<stop offset="0.67" stopColor="#9059FF" />
|
||||
<stop offset="1" stopColor="#C139E6" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint5_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(10.9455 9.859) scale(5.31373 6.47101)"
|
||||
>
|
||||
<stop offset="0.21" stopColor="#9059FF" stopOpacity="0" />
|
||||
<stop offset="0.97" stopColor="#6E008B" stopOpacity="0.6" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint6_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(11.2585 1.72838) scale(7.95561 7.98388)"
|
||||
>
|
||||
<stop offset="0.1" stopColor="#FFE226" />
|
||||
<stop offset="0.79" stopColor="#FF7139" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint7_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(18.5242 -3.50921) scale(37.9818 31.8836)"
|
||||
>
|
||||
<stop offset="0.11" stopColor="#FFF44F" />
|
||||
<stop offset="0.46" stopColor="#FF980E" />
|
||||
<stop offset="0.72" stopColor="#FF3647" />
|
||||
<stop offset="0.9" stopColor="#E31587" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint8_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4.41888 7.02007) rotate(77.3946) scale(12.0503 52.1278)"
|
||||
>
|
||||
<stop stopColor="#FFF44F" />
|
||||
<stop offset="0.3" stopColor="#FF980E" />
|
||||
<stop offset="0.57" stopColor="#FF3647" />
|
||||
<stop offset="0.74" stopColor="#E31587" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint9_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(11.3407 4.60001) scale(21.8071 21.424)"
|
||||
>
|
||||
<stop offset="0.14" stopColor="#FFF44F" />
|
||||
<stop offset="0.48" stopColor="#FF980E" />
|
||||
<stop offset="0.66" stopColor="#FF3647" />
|
||||
<stop offset="0.9" stopColor="#E31587" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="paint10_radial_3681_24667"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(17.0005 5.8529) scale(26.2114 23.4492)"
|
||||
>
|
||||
<stop offset="0.09" stopColor="#FFF44F" />
|
||||
<stop offset="0.63" stopColor="#FF980E" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint11_linear_3681_24667"
|
||||
x1="18.75"
|
||||
y1="3.25511"
|
||||
x2="4.28552"
|
||||
y2="19.0592"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.17" stopColor="#FFF44F" stopOpacity="0.8" />
|
||||
<stop offset="0.6" stopColor="#FFF44F" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3681_24667">
|
||||
<rect width="24" height="24" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const BrowserIcon = () => {
|
||||
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
|
||||
export const BrowserIcon = ({
|
||||
chromeExtensionUrl,
|
||||
mozillaExtensionUrl,
|
||||
}: {
|
||||
chromeExtensionUrl: string;
|
||||
mozillaExtensionUrl: string;
|
||||
}) => {
|
||||
const isItChrome = window.navigator.userAgent.includes('Chrome');
|
||||
const isItMozilla =
|
||||
window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
return (
|
||||
<div className="absolute right-1 top-0 h-8 flex items-center">
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
{!isItChrome && !isItMozilla ? (
|
||||
<>
|
||||
<a href={MOZILLA_EXTENSION_URL} target="_blank" rel="noreferrer">
|
||||
<a href={mozillaExtensionUrl} target="_blank" rel="noreferrer">
|
||||
<MozillaIcon />
|
||||
</a>{' '}
|
||||
<a href={CHROME_EXTENSION_URL} target="_blank" rel="noreferrer">
|
||||
<a href={chromeExtensionUrl} target="_blank" rel="noreferrer">
|
||||
<ChromeIcon />
|
||||
</a>
|
||||
</>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { VegaWalletConfig } from '../provider';
|
||||
import { VegaWalletProvider } from '../provider';
|
||||
import {
|
||||
VegaConnectDialog,
|
||||
CLOSE_DELAY,
|
||||
useVegaWalletDialogStore,
|
||||
} from './connect-dialog';
|
||||
import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog';
|
||||
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
||||
import type { VegaConnectDialogProps } from '..';
|
||||
import {
|
||||
ClientErrors,
|
||||
@@ -15,7 +13,6 @@ import {
|
||||
ViewConnector,
|
||||
WalletError,
|
||||
} from '../connectors';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ChainIdQuery } from './__generated__/ChainId';
|
||||
import { ChainIdDocument } from './__generated__/ChainId';
|
||||
import {
|
||||
@@ -28,26 +25,14 @@ import {
|
||||
const mockUpdateDialogOpen = jest.fn();
|
||||
const mockCloseVegaDialog = jest.fn();
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
let mockIsDesktopRunning = true;
|
||||
|
||||
jest.mock('../use-is-wallet-service-running', () => ({
|
||||
useIsWalletServiceRunning: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockIsDesktopRunning),
|
||||
}));
|
||||
|
||||
// @ts-ignore ignore mock implementation
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_ENV: 'TESTNET',
|
||||
VEGA_URL: 'https://vega-node.url',
|
||||
VEGA_NETWORKS: JSON.stringify({}),
|
||||
VEGA_WALLET_URL: mockVegaWalletUrl,
|
||||
GIT_BRANCH: 'test',
|
||||
GIT_COMMIT_HASH: 'abcdef',
|
||||
GIT_ORIGIN_URL: 'https://github.com/test/repo',
|
||||
HOSTED_WALLET_URL: mockHostedWalletUrl,
|
||||
}));
|
||||
|
||||
let defaultProps: VegaConnectDialogProps;
|
||||
|
||||
const INITIAL_KEY = 'some-key';
|
||||
@@ -59,7 +44,9 @@ const connectors = {
|
||||
jsonRpc,
|
||||
view,
|
||||
injected,
|
||||
snap: undefined,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
defaultProps = {
|
||||
@@ -73,12 +60,24 @@ beforeEach(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const mockVegaWalletUrl = 'http://mock.wallet.com';
|
||||
const mockHostedWalletUrl = 'http://mock.hosted.com';
|
||||
|
||||
const mockChainId = 'chain-id';
|
||||
|
||||
function generateJSX(props?: Partial<VegaConnectDialogProps>) {
|
||||
const defaultConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://vegaservice.xyz',
|
||||
links: {
|
||||
explorer: 'explorer-link',
|
||||
concepts: 'concepts-link',
|
||||
chromeExtensionUrl: 'chrome-link',
|
||||
mozillaExtensionUrl: 'mozilla-link',
|
||||
},
|
||||
};
|
||||
|
||||
function generateJSX(
|
||||
props?: Partial<VegaConnectDialogProps>,
|
||||
config?: Partial<VegaWalletConfig>
|
||||
) {
|
||||
const chainIdMock: MockedResponse<ChainIdQuery> = {
|
||||
request: {
|
||||
query: ChainIdDocument,
|
||||
@@ -93,7 +92,7 @@ function generateJSX(props?: Partial<VegaConnectDialogProps>) {
|
||||
};
|
||||
return (
|
||||
<MockedProvider mocks={[chainIdMock]}>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider config={{ ...defaultConfig, ...config }}>
|
||||
<VegaConnectDialog {...defaultProps} {...props} />
|
||||
</VegaWalletProvider>
|
||||
</MockedProvider>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
Dialog,
|
||||
Intent,
|
||||
@@ -14,15 +13,17 @@ import type { ReactNode } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { VegaConnector } from '../connectors';
|
||||
import type { Connectors, VegaConnector } from '../connectors';
|
||||
import {
|
||||
DEFAULT_SNAP_ID,
|
||||
InjectedConnector,
|
||||
JsonRpcConnector,
|
||||
SnapConnector,
|
||||
ViewConnector,
|
||||
requestSnap,
|
||||
} from '../connectors';
|
||||
import { JsonRpcConnectorForm } from './json-rpc-connector-form';
|
||||
import { ViewConnectorForm } from './view-connector-form';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
BrowserIcon,
|
||||
ConnectDialogContent,
|
||||
@@ -38,33 +39,18 @@ import { useVegaWallet } from '../use-vega-wallet';
|
||||
import { InjectedConnectorForm } from './injected-connector-form';
|
||||
import { isBrowserWalletInstalled } from '../utils';
|
||||
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
|
||||
import { useIsSnapRunning } from '../use-is-snap-running';
|
||||
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
|
||||
|
||||
export const CLOSE_DELAY = 1700;
|
||||
type Connectors = { [key: string]: VegaConnector };
|
||||
export type WalletType = 'injected' | 'jsonRpc' | 'view';
|
||||
|
||||
export type WalletType = 'injected' | 'jsonRpc' | 'view' | 'snap';
|
||||
|
||||
export interface VegaConnectDialogProps {
|
||||
connectors: Connectors;
|
||||
riskMessage?: ReactNode;
|
||||
}
|
||||
|
||||
export interface VegaWalletDialogStore {
|
||||
vegaWalletDialogOpen: boolean;
|
||||
updateVegaWalletDialog: (open: boolean) => void;
|
||||
openVegaWalletDialog: () => void;
|
||||
closeVegaWalletDialog: () => void;
|
||||
}
|
||||
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
(set) => ({
|
||||
vegaWalletDialogOpen: false,
|
||||
updateVegaWalletDialog: (open: boolean) =>
|
||||
set({ vegaWalletDialogOpen: open }),
|
||||
openVegaWalletDialog: () => set({ vegaWalletDialogOpen: true }),
|
||||
closeVegaWalletDialog: () => set({ vegaWalletDialogOpen: false }),
|
||||
})
|
||||
);
|
||||
|
||||
export const VegaConnectDialog = ({
|
||||
connectors,
|
||||
riskMessage,
|
||||
@@ -119,12 +105,12 @@ const ConnectDialogContainer = ({
|
||||
appChainId: string;
|
||||
riskMessage?: ReactNode;
|
||||
}) => {
|
||||
const { VEGA_WALLET_URL } = useEnvironment();
|
||||
const { vegaUrl, vegaWalletServiceUrl } = useVegaWallet();
|
||||
const closeDialog = useVegaWalletDialogStore(
|
||||
(store) => store.closeVegaWalletDialog
|
||||
);
|
||||
const [selectedConnector, setSelectedConnector] = useState<VegaConnector>();
|
||||
const [walletUrl, setWalletUrl] = useState(VEGA_WALLET_URL || '');
|
||||
const [walletUrl, setWalletUrl] = useState(vegaWalletServiceUrl);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSelectedConnector(undefined);
|
||||
@@ -143,7 +129,6 @@ const ConnectDialogContainer = ({
|
||||
|
||||
const handleSelect = (type: WalletType) => {
|
||||
const connector = connectors[type];
|
||||
connector.url = walletUrl;
|
||||
|
||||
if (!connector) {
|
||||
// we should never get here unless connectors are not configured correctly
|
||||
@@ -155,17 +140,29 @@ const ConnectDialogContainer = ({
|
||||
// Immediately connect on selection if jsonRpc is selected, we can't do this
|
||||
// for rest because we need to show an authentication form
|
||||
if (connector instanceof JsonRpcConnector) {
|
||||
connector.url = walletUrl;
|
||||
jsonRpcConnect(connector, appChainId);
|
||||
} else if (connector instanceof InjectedConnector) {
|
||||
injectedConnect(connector, appChainId);
|
||||
} else if (connector instanceof SnapConnector) {
|
||||
// Set the nodeAddress to send tx's to, normally this is handled by
|
||||
// the vega wallet
|
||||
connector.nodeAddress = new URL(vegaUrl).origin;
|
||||
injectedConnect(connector, appChainId);
|
||||
}
|
||||
};
|
||||
|
||||
const isDesktopWalletRunning = useIsWalletServiceRunning(
|
||||
walletUrl,
|
||||
connectors,
|
||||
connectors['jsonRpc'],
|
||||
appChainId
|
||||
);
|
||||
|
||||
const isSnapRunning = useIsSnapRunning(
|
||||
DEFAULT_SNAP_ID,
|
||||
Boolean(connectors['snap'])
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectDialogContent>
|
||||
@@ -181,10 +178,12 @@ const ConnectDialogContainer = ({
|
||||
/>
|
||||
) : (
|
||||
<ConnectorList
|
||||
connectors={connectors}
|
||||
walletUrl={walletUrl}
|
||||
setWalletUrl={setWalletUrl}
|
||||
onSelect={handleSelect}
|
||||
isDesktopWalletRunning={isDesktopWalletRunning}
|
||||
isSnapRunning={isSnapRunning}
|
||||
/>
|
||||
)}
|
||||
</ConnectDialogContent>
|
||||
@@ -194,27 +193,34 @@ const ConnectDialogContainer = ({
|
||||
};
|
||||
|
||||
const ConnectorList = ({
|
||||
connectors,
|
||||
onSelect,
|
||||
walletUrl,
|
||||
setWalletUrl,
|
||||
isDesktopWalletRunning,
|
||||
isSnapRunning,
|
||||
}: {
|
||||
connectors: Connectors;
|
||||
onSelect: (type: WalletType) => void;
|
||||
walletUrl: string;
|
||||
setWalletUrl: (value: string) => void;
|
||||
isDesktopWalletRunning: boolean | null;
|
||||
isSnapRunning: boolean | null;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { pubKey, links } = useVegaWallet();
|
||||
const title = isBrowserWalletInstalled()
|
||||
? t('Connect Vega wallet')
|
||||
: t('Get a Vega wallet');
|
||||
|
||||
const extendedText = (
|
||||
<>
|
||||
<div className="w-full h-full flex justify-center items-center gap-1 text-base">
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Connect')}
|
||||
</div>
|
||||
<BrowserIcon />
|
||||
<BrowserIcon
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -235,9 +241,51 @@ const ConnectorList = ({
|
||||
onClick={() => onSelect('injected')}
|
||||
/>
|
||||
) : (
|
||||
<GetWalletButton />
|
||||
<GetWalletButton
|
||||
chromeExtensionUrl={links.chromeExtensionUrl}
|
||||
mozillaExtensionUrl={links.mozillaExtensionUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{connectors['snap'] !== undefined ? (
|
||||
<div>
|
||||
{isSnapRunning ? (
|
||||
<ConnectionOption
|
||||
type="snap"
|
||||
text={
|
||||
<>
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Connect via Vega MetaMask Snap')}
|
||||
</div>
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
onSelect('snap');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ConnectionOption
|
||||
type="snap"
|
||||
text={
|
||||
<>
|
||||
<div className="flex items-center justify-center w-full h-full text-base gap-1">
|
||||
{t('Install Vega MetaMask Snap')}
|
||||
</div>
|
||||
<div className="absolute top-0 flex items-center h-8 right-1">
|
||||
<VegaIcon name={VegaIconNames.METAMASK} size={24} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
onClick={() => {
|
||||
requestSnap(DEFAULT_SNAP_ID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<ConnectionOption
|
||||
type="view"
|
||||
@@ -282,7 +330,10 @@ const SelectedForm = ({
|
||||
onConnect: () => void;
|
||||
riskMessage?: ReactNode;
|
||||
}) => {
|
||||
if (connector instanceof InjectedConnector) {
|
||||
if (
|
||||
connector instanceof InjectedConnector ||
|
||||
connector instanceof SnapConnector
|
||||
) {
|
||||
return (
|
||||
<InjectedConnectorForm
|
||||
status={injectedState.status}
|
||||
@@ -320,30 +371,43 @@ const SelectedForm = ({
|
||||
throw new Error('No connector selected');
|
||||
};
|
||||
|
||||
export const GetWalletButton = ({ className }: { className?: string }) => {
|
||||
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
|
||||
export const GetWalletButton = ({
|
||||
chromeExtensionUrl,
|
||||
mozillaExtensionUrl,
|
||||
className,
|
||||
}: {
|
||||
chromeExtensionUrl?: string;
|
||||
mozillaExtensionUrl?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const isItChrome = window.navigator.userAgent.includes('Chrome');
|
||||
const isItMozilla =
|
||||
window.navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
|
||||
const onClick = () => {
|
||||
if (isItMozilla) {
|
||||
window.open(MOZILLA_EXTENSION_URL, '_blank');
|
||||
window.open(mozillaExtensionUrl, '_blank');
|
||||
return;
|
||||
}
|
||||
if (isItChrome) {
|
||||
window.open(CHROME_EXTENSION_URL, '_blank');
|
||||
window.open(chromeExtensionUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
<div className="flex items-center justify-center gap-1 text-base">
|
||||
<div className="flex items-center justify-center text-base gap-1">
|
||||
{t('Get the Vega Wallet')}
|
||||
<Pill size="xxs" intent={Intent.Info}>
|
||||
ALPHA
|
||||
</Pill>
|
||||
</div>
|
||||
<BrowserIcon />
|
||||
{chromeExtensionUrl && mozillaExtensionUrl && (
|
||||
<BrowserIcon
|
||||
chromeExtensionUrl={chromeExtensionUrl}
|
||||
mozillaExtensionUrl={mozillaExtensionUrl}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -398,7 +462,7 @@ const ConnectionOption = ({
|
||||
icon={icon}
|
||||
fill
|
||||
>
|
||||
<span className="flex justify-center items-center text-base">{text}</span>
|
||||
<span className="flex items-center justify-center text-base">{text}</span>
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
@@ -454,7 +518,7 @@ const CustomUrlInput = ({
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
/>
|
||||
{isDesktopWalletRunning !== null && (
|
||||
<p className="mb-6 text-sm pt-2">
|
||||
<p className="pt-2 mb-6 text-sm">
|
||||
{isDesktopWalletRunning ? (
|
||||
<button
|
||||
className="underline text-default"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user