Compare commits

..
Author SHA1 Message Date
Matthew Russell 2534315f1a chore: make team stats aggregation epochs 192 2024-02-01 09:16:27 -08:00
Art 02bd031bee fix(trading): mark as updated when tx confirmed (#5715) 2024-02-01 13:27:21 +00:00
Art bd97651dd3 fix(trading): game cards are not showing (#5713) 2024-02-01 12:04:36 +00:00
Bartłomiej Głownia 516b3e5b93 feat(deal-ticket): fix margin calculations in DealTicketMarginDetails for isolated margin mode (#5709) 2024-02-01 12:52:45 +01:00
Matthew Russellandbwallacee 42a98b6a35 fix(trading): teams snags (#5707)
Co-authored-by: bwallacee <ben@vega.xyz>
2024-02-01 10:38:57 +00:00
Art 2002731c52 feat(trading): gas fee estimation for withdraw transaction (#5668) 2024-02-01 09:07:13 +01:00
a49139f127 fix(trading): closed markets filter (#5691) (#5697)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-31 14:34:43 +00:00
Edd e216b23472 feat(explorer): updatemarginmode tx view (#5690) 2024-01-31 14:26:35 +00:00
e52ae97233 feat(trading): competitions (#5621)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
Co-authored-by: Ben <ben@vega.xyz>
2024-01-31 14:21:29 +00:00
1780f6fa7f feat(trading): margin estimate update (#5664)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: daro-maj <119658839+daro-maj@users.noreply.github.com>
2024-01-31 14:05:30 +00:00
Ben 8b91592b93 chore(trading): update to preview 7 binaries (#5689) 2024-01-31 12:42:29 +01:00
m.ray 508274268d fix(trading): show user inactive lose streak in remaining epochs (#5667) 2024-01-31 10:54:16 +00:00
Edd 2a40d9ec4d feat(explorer): support for more external chains (#5687) 2024-01-31 10:36:58 +00:00
Art efb746f373 fix(candles-chart): ignore candles from before market open date (#5678) 2024-01-30 14:32:55 +00:00
Ben 6d2f367987 chore(trading): update vega binaries to v0.74.0-preview.6 (#5686) 2024-01-30 13:31:06 +00:00
m.ray 6b7bbc9c94 fix(trading): market id use text-xs in key details (#5684) 2024-01-29 15:02:03 +00:00
Art 9153677a33 fix(markets): key details panel crashing (#5683) 2024-01-29 13:22:17 +00:00
m.ray cff1818940 chore(trading, governance, explorer): add liquidation strategy to market info (#5642) 2024-01-29 13:13:19 +01:00
Zohar Etzioni d05dd6e4cb Merge pull request #5669 from vegaprotocol/feat/use-camelCase-marketId-in-updateMarginMode-tx
feat(trading): use camel case marketId in updateMarginMode tx
2024-01-25 14:46:54 +00:00
Bartłomiej Głownia 4ef789e00a feat(trading): use camel case marketId in updateMarginMode tx 2024-01-25 15:09:09 +01:00
m.ray 0bd13a5f7b fix(trading): adjust full screen for mobile dialogs (#5666) 2024-01-25 13:32:14 +00:00
67d38ff03e fix(trading): error guards not working when poll interval is supplied (#5661)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-25 07:59:40 +00:00
138 changed files with 2376 additions and 1586 deletions
+1 -1
View File
@@ -205,7 +205,7 @@ jobs:
# run tests
#----------------------------------------------
- name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 1 --dist loadfile --durations=45
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 1 --dist loadfile --durations=45
working-directory: apps/trading/e2e
#----------------------------------------------
# upload traces
-2
View File
@@ -1,5 +1,4 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
@@ -9,7 +8,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
-1
View File
@@ -1,6 +1,5 @@
# App configuration variables
NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
-1
View File
@@ -2,7 +2,6 @@
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.governance.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET_MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
NX_VEGA_ENV=TESTNET
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
-1
View File
@@ -4,7 +4,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
-1
View File
@@ -3,7 +3,6 @@ NX_TENDERMINT_URL=http://localhost:26607/
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
@@ -1,34 +0,0 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type EthExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
};
export const EthExplorerLink = ({
id,
type,
...props
}: EthExplorerLinkProps) => {
const link = `${DATA_SOURCES.ethExplorerUrl}/${type}/${id}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,34 @@
import type { ChainIdMapping } from './external-chain';
import { SUPPORTED_CHAIN_IDS, SUPPORTED_CHAIN_LABELS } from './external-chain';
export const SUPPORTED_CHAIN_ICON_URLS: ChainIdMapping = {
'1': '/assets/chain-eth-logo.svg',
'100': '/assets/chain-gno-logo.svg',
'42161': '/assets/chain-arb-logo.svg',
'11155111': '/assets/chain-eth-logo.svg',
};
export type ExternalChainIconProps = {
chainId?: string;
};
export const ExternalChainIcon = ({
// If chainID is not provided, default to a non-existent chain
chainId = '-1',
}: ExternalChainIconProps) => {
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
const url = SUPPORTED_CHAIN_ICON_URLS[chainId];
const alt = SUPPORTED_CHAIN_LABELS[chainId];
return (
<img
src={url}
className="inline-block w-4 h-4 mr-1 dark:invert"
alt={alt}
title={alt}
/>
);
} else {
return null;
}
};
@@ -0,0 +1,38 @@
export type ChainIdMapping = {
[K in typeof SUPPORTED_CHAIN_IDS[number]]: string;
};
export const SUPPORTED_CHAIN_IDS: string[] = ['1', '100', '42161', '11155111'];
export const SUPPORTED_CHAIN_LABELS: ChainIdMapping = {
'1': 'Ethereum',
'100': 'Gnosis',
'42161': 'Arbitrum',
'11155111': 'Sepolia',
};
export function getExternalExplorerLink(chainId: string, type: string) {
if (SUPPORTED_CHAIN_IDS.includes(chainId)) {
switch (chainId) {
case '1':
return 'https://etherscan.io';
case '100':
return 'https://gnosisscan.io';
case '42161':
return 'https://arbiscan.io';
case '11155111':
return 'https://sepolia.etherscan.io';
default:
return '#';
}
} else {
return '#';
}
}
export function getExternalChainLabel(chainId?: string) {
if (chainId && SUPPORTED_CHAIN_IDS.includes(chainId)) {
return SUPPORTED_CHAIN_LABELS[chainId];
} else {
return 'Custom Chain';
}
}
@@ -0,0 +1,41 @@
import Hash from '../hash';
import { getExternalExplorerLink } from './external-chain';
import { ExternalChainIcon } from './external-chain-icon';
export enum EthExplorerLinkTypes {
block = 'block',
address = 'address',
tx = 'tx',
}
export type ExternalExplorerLinkProps = Partial<typeof HTMLAnchorElement> & {
id: string;
type: EthExplorerLinkTypes;
// Defaults to Ethereum Mainnet, as chain support was added late
chain?: string;
code?: boolean;
};
export const ExternalExplorerLink = ({
id,
type,
chain = '1',
code = false,
...props
}: ExternalExplorerLinkProps) => {
const link = `${getExternalExplorerLink(chain, type)}/${type}/${id}${
code ? '#code' : ''
}`;
return (
<a
className="underline external font-mono"
target="_blank"
rel="noopener noreferrer"
{...props}
href={link}
>
<ExternalChainIcon chainId={chain} />
<Hash text={id} />
</a>
);
};
@@ -1,6 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidationStrategyInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MarginScalingFactorsPanel,
@@ -94,6 +95,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
</>
)
)}
<h2 className={headerClassName}>{t('Liquidation strategy')}</h2>
<LiquidationStrategyInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
@@ -1,9 +1,10 @@
import { TableCell, TableRow } from '../../../table';
import { t } from '@vegaprotocol/i18n';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../links/external-explorer-link/external-chain';
import type { components } from '../../../../../types/explorer';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { BigNumber } from 'ethers';
@@ -44,10 +45,10 @@ export const TxDetailsContractCall = ({
},
});
if (!contractCall || !contractCall.result) {
if (!contractCall) {
return null;
}
const chainLabel = getExternalChainLabel(contractCall.sourceChainId);
return (
<>
{contractCall.specId && (
@@ -64,9 +65,12 @@ export const TxDetailsContractCall = ({
)}
{contractCall.blockHeight && (
<TableRow modifier="bordered">
<TableCell>{t('ETH block')}</TableCell>
<TableCell>
<EthExplorerLink
{chainLabel} {t('block')}
</TableCell>
<TableCell>
<ExternalExplorerLink
chain={contractCall.sourceChainId}
id={contractCall.blockHeight}
type={EthExplorerLinkTypes.block}
/>
@@ -75,14 +79,24 @@ export const TxDetailsContractCall = ({
)}
{data?.oracleSpec?.dataSourceSpec && (
<OracleEthSource
chain={contractCall.sourceChainId}
sourceType={data.oracleSpec.dataSourceSpec.spec.data.sourceType}
/>
)}
<TableRow modifier="bordered">
<TableCell>{t('Result')}</TableCell>
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell>
</TableRow>
{contractCall.error && (
<TableRow modifier="bordered">
<TableCell>{t('Call error')}</TableCell>
<TableCell>{contractCall.error}</TableCell>
</TableRow>
)}
{contractCall.result && (
<TableRow modifier="bordered">
<TableCell>{t('Result')}</TableCell>
<TableCell>{decodeEthCallResult(contractCall.result)}</TableCell>
</TableRow>
)}
</>
);
};
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
assetLimitsUpdated: components['schemas']['vegaERC20AssetLimitsUpdated'];
@@ -42,7 +42,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventErc20AssetListProps {
assetList: components['schemas']['vegaERC20AssetList'];
@@ -32,7 +32,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={assetList.assetSource}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink, PartyLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventProps {
deposit: components['schemas']['vegaERC20Deposit'];
@@ -36,7 +36,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { AssetLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventWithdrawalProps {
withdrawal: components['schemas']['vegaERC20Withdrawal'];
@@ -35,7 +35,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeDepositProps {
deposit: components['schemas']['vegaStakeDeposited'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={deposit.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeRemoveProps {
remove: components['schemas']['vegaStakeRemoved'];
@@ -39,7 +39,7 @@ export const TxDetailsChainEventStakeRemove = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={remove.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -3,9 +3,9 @@ import { t } from '@vegaprotocol/i18n';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
} from '../../../links/external-explorer-link/external-explorer-link';
interface TxDetailsChainEventStakeTotalSupplyProps {
update: components['schemas']['vegaStakeTotalSupply'];
@@ -38,7 +38,7 @@ export const TxDetailsChainEventStakeTotalSupply = ({
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={update.tokenAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -41,6 +41,7 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
ACCOUNT_TYPE_ORDER_MARGIN: 'Order Margin',
};
interface TransferParticipantsProps {
@@ -32,6 +32,7 @@ import { TxDetailsCreateReferralSet } from './tx-create-referral-set';
import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
import { TxDetailsJoinTeam } from './tx-join-team';
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -133,6 +134,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsApplyReferralCode;
case 'Join Team':
return TxDetailsJoinTeam;
case 'Update Margin Mode':
return TxDetailsUpdateMarginMode;
default:
return TxDetailsGeneric;
}
@@ -5,9 +5,9 @@ import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
} from '../../links/external-explorer-link/external-explorer-link';
import { BlockLink } from '../../links';
type EthKeyRotate = components['schemas']['v1EthereumKeyRotateSubmission'];
@@ -46,7 +46,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('Old Address')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
type={EthExplorerLinkTypes.address}
id={k.currentAddress}
/>
@@ -57,7 +57,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('New Address')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
type={EthExplorerLinkTypes.address}
id={k.newAddress}
/>
@@ -68,7 +68,7 @@ export const TxDetailsEthKeyRotate = ({
<TableRow modifier="bordered">
<TableCell>{t('Submitter address')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
type={EthExplorerLinkTypes.address}
id={k.submitterAddress}
/>
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
} from '../../links/external-explorer-link/external-explorer-link';
import { NodeLink } from '../../links';
type Command = components['schemas']['v1IssueSignatures'];
@@ -57,7 +57,7 @@ export const TxDetailsIssueSignatures = ({
<TableRow modifier="bordered">
<TableCell>{t('ETH key')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={cmd.submitter}
type={EthExplorerLinkTypes.address}
/>
@@ -6,9 +6,9 @@ import { TableRow, TableCell, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
} from '../../links/external-explorer-link/external-explorer-link';
import { PartyLink } from '../../links';
import Hash from '../../links/hash';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
@@ -72,7 +72,7 @@ export const TxDetailsNodeAnnounce = ({
<TableRow modifier="bordered">
<TableCell>{t('Ethereum Address')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
type={EthExplorerLinkTypes.address}
id={cmd.ethereumAddress}
/>
@@ -8,9 +8,9 @@ import { useExplorerNodeVoteQuery } from './__generated__/Node-vote';
import { PartyLink } from '../../links';
import { Time } from '../../time';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
} from '../../links/external-explorer-link/external-explorer-link';
interface TxDetailsNodeVoteProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -143,7 +143,7 @@ export function TxHash({ hash }: TxDetailsEthTxHashProps) {
<TableRow modifier="bordered">
<TableCell>Ethereum TX:</TableCell>
<TableCell>
<EthExplorerLink id={hash} type={EthExplorerLinkTypes.tx} />
<ExternalExplorerLink id={hash} type={EthExplorerLinkTypes.tx} />
</TableCell>
</TableRow>
);
@@ -8,7 +8,7 @@ import GovernanceAssetBalance from '../../asset-balance/governance-asset-balance
import type { components } from '../../../../types/explorer';
export const methodText: Record<
components['schemas']['UndelegateSubmissionMethod'],
components['schemas']['v1UndelegateSubmissionMethod'],
string
> = {
METHOD_NOW: 'Immediate',
@@ -0,0 +1,60 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { MarketLink } from '../../links';
interface TxDetailsUpdateMarginModeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
type Mode = components['schemas']['UpdateMarginModeMode'];
const MarginModeLabels: Record<Mode, string> = {
MODE_CROSS_MARGIN: t('Cross margin'),
MODE_ISOLATED_MARGIN: t('Isolated margin'),
MODE_UNSPECIFIED: t('Unspecified'),
};
export const TxDetailsUpdateMarginMode = ({
txData,
pubKey,
blockData,
}: TxDetailsUpdateMarginModeProps) => {
if (!txData || !txData.command.updateMarginMode) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const u: components['schemas']['v1UpdateMarginMode'] =
txData.command.updateMarginMode;
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
{u.marketId && (
<TableRow modifier="bordered">
<TableCell>{t('Market ID')}</TableCell>
<TableCell>
<MarketLink id={u.marketId} />
</TableCell>
</TableRow>
)}
{u.mode && (
<TableRow modifier="bordered">
<TableCell>{t('New margin mode')}</TableCell>
<TableCell>{MarginModeLabels[u.mode]}</TableCell>
</TableRow>
)}
{u.marginFactor && (
<TableRow modifier="bordered">
<TableCell>{t('Margin factor')}</TableCell>
<TableCell>{u.marginFactor}</TableCell>
</TableRow>
)}
</TableWithTbody>
);
};
@@ -4,9 +4,9 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../links/eth-explorer-link/eth-explorer-link';
} from '../../links/external-explorer-link/external-explorer-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import AssetBalance from '../../asset-balance/asset-balance';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
@@ -57,7 +57,7 @@ export const TxDetailsWithdrawSubmission = ({
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<EthExplorerLink
<ExternalExplorerLink
id={w.ext.erc20.receiverAddress}
type={EthExplorerLinkTypes.address}
/>
@@ -44,6 +44,7 @@ export type FilterOption =
| 'Transfer Funds'
| 'Undelegate'
| 'Update Referral Set'
| 'Update Margin Mode'
| 'Validator Heartbeat'
| 'Vote on Proposal'
| 'Withdraw';
@@ -59,6 +60,7 @@ export const filterOptions: Record<string, FilterOption[]> = {
'Stop Orders Submission',
'Stop Orders Cancellation',
'Submit Order',
'Update Margin Mode',
],
'Transfers and Withdrawals': [
'Transfer Funds',
@@ -1,6 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
interface TxOrderTypeProps {
orderType: string;
@@ -188,6 +189,9 @@ export function getLabelForChainEvent(
}
return t('Multisig update');
} else if (chainEvent.contractCall) {
if (chainEvent.contractCall.error) {
return t('Call error');
}
return t('Contract call');
}
return t('Chain Event');
@@ -257,6 +261,11 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
data-testid="tx-type"
className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`}
>
{command?.chainEvent && (
<ExternalChainIcon
chainId={command?.chainEvent?.contractCall?.sourceChainId}
/>
)}
{type}
</div>
);
-1
View File
@@ -14,7 +14,6 @@ export const ENV = {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
@@ -78,6 +78,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
}
}
}
sourceChainId
filters {
key {
name
@@ -118,6 +119,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
address
requiredConfirmations
method
sourceChainId
filters {
key {
type
@@ -67,6 +67,7 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
sourceType {
... on EthCallSpec {
address
sourceChainId
}
... on DataSourceSpecConfiguration {
signers {
@@ -5,19 +5,19 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
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 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?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 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?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 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?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 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?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, sourceChainId: number, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | 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> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | 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 {
@@ -101,6 +101,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
}
}
}
sourceChainId
filters {
key {
name
@@ -141,6 +142,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
address
requiredConfirmations
method
sourceChainId
filters {
key {
type
@@ -9,12 +9,12 @@ export type ExplorerOracleFutureFragment = { __typename?: 'Future', dataSourceSp
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string, sourceChainId: number } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
@@ -90,6 +90,7 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
sourceType {
... on EthCallSpec {
address
sourceChainId
}
... on DataSourceSpecConfiguration {
signers {
@@ -28,7 +28,7 @@ export function isInternalSourceType(s: SourceType) {
export function getExternalType(s: SourceType) {
if (s.sourceType.__typename === 'EthCallSpec') {
return 'Ethereum Contract Call';
return 'Contract Call';
} else {
return 'External Data';
}
@@ -1,18 +1,24 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
} from '../../../components/links/external-explorer-link/external-explorer-link';
import { getExternalChainLabel } from '../../../components/links/external-explorer-link/external-chain';
import { t } from 'i18next';
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
chain?: string;
}
/**
* Given an Oracle that sources data from Ethereum, this component will render
* a link to the smart contract and some basic details
*/
export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
export function OracleEthSource({
sourceType,
chain = '1',
}: OracleDetailsEthSourceProps) {
if (
sourceType.__typename !== 'DataSourceDefinitionExternal' ||
sourceType.sourceType.__typename !== 'EthCallSpec'
@@ -26,11 +32,20 @@ export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
return null;
}
const chainLabel = getExternalChainLabel(chain);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Ethereum Contract</TableHeader>
<TableHeader scope="row">
{chainLabel} {t('Contract')}
</TableHeader>
<TableCell modifier="bordered">
<EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />
<ExternalExplorerLink
chain={chain}
id={address}
type={EthExplorerLinkTypes.address}
code={true}
/>
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</TableCell>
@@ -1,8 +1,8 @@
import { PartyLink } from '../../../components/links';
import {
EthExplorerLink,
ExternalExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
} from '../../../components/links/external-explorer-link/external-explorer-link';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import { remove0x } from '@vegaprotocol/utils';
@@ -37,13 +37,15 @@ export function getAddressLink(signer: Signer) {
}
if (signer.__typename === 'ETHAddress') {
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
return (
<ExternalExplorerLink id={address} type={EthExplorerLinkTypes.address} />
);
} else if (signer.__typename === 'PubKey' && address.length !== 64) {
// This is a hack: some older oracles were submitted before proper checks stopped
// ETH addresses being returned as Vega addresses
// Hacky 0x prefixing as a bonus
return (
<EthExplorerLink
<ExternalExplorerLink
id={`0x${remove0x(address)}`}
type={EthExplorerLinkTypes.address}
/>
@@ -61,7 +63,10 @@ interface OracleDetailsSignersProps {
/**
* Given an Oracle, this component will render either a link to Ethereum
* or the Vega party depending on which type is specified
* or the Vega party depending on which type is specified.
*
* Note that this won't be shown for external contract calls as they do not
* have a signer in the same way.
*/
export function OracleSigners({ sourceType }: OracleDetailsSignersProps) {
if (sourceType.__typename !== 'DataSourceDefinitionExternal') {
@@ -42,6 +42,11 @@ export const OracleDetails = ({
dataConnection,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const chain =
dataSource.dataSourceSpec.spec.data.sourceType.sourceType.__typename ===
'EthCallSpec'
? dataSource.dataSourceSpec.spec.data.sourceType.sourceType.sourceChainId.toString()
: undefined;
return (
<div>
@@ -60,7 +65,7 @@ export const OracleDetails = ({
</TableCell>
</TableRow>
<OracleSigners sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} chain={chain} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Filter')}</TableHeader>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns:xodm="http://www.corel.com/coreldraw/odm/2003" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 2500 2500" style="enable-background:new 0 0 2500 2500;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#213147;}
.st2{fill:#12AAFF;}
.st3{fill:#9DCCED;}
.st4{fill:#FFFFFF;}
</style>
<g id="Layer_x0020_1">
<g id="_2405588477232">
<rect class="st0" width="2500" height="2500"></rect>
<g>
<g>
<path class="st1" d="M226,760v980c0,63,33,120,88,152l849,490c54,31,121,31,175,0l849-490c54-31,88-89,88-152V760 c0-63-33-120-88-152l-849-490c-54-31-121-31-175,0L314,608c-54,31-87,89-87,152H226z"></path>
<g>
<g>
<g>
<path class="st2" d="M1435,1440l-121,332c-3,9-3,19,0,29l208,571l241-139l-289-793C1467,1422,1442,1422,1435,1440z"></path>
</g>
<g>
<path class="st2" d="M1678,882c-7-18-32-18-39,0l-121,332c-3,9-3,19,0,29l341,935l241-139L1678,883V882z"></path>
</g>
</g>
</g>
<g>
<path class="st3" d="M1250,155c6,0,12,2,17,5l918,530c11,6,17,18,17,30v1060c0,12-7,24-17,30l-918,530c-5,3-11,5-17,5 s-12-2-17-5l-918-530c-11-6-17-18-17-30V719c0-12,7-24,17-30l918-530c5-3,11-5,17-5l0,0V155z M1250,0c-33,0-65,8-95,25L237,555 c-59,34-95,96-95,164v1060c0,68,36,130,95,164l918,530c29,17,62,25,95,25s65-8,95-25l918-530c59-34,95-96,95-164V719 c0-68-36-130-95-164L1344,25c-29-17-62-25-95-25l0,0H1250z"></path>
</g>
<polygon class="st1" points="642,2179 727,1947 897,2088 738,2234 "></polygon>
<g>
<path class="st4" d="M1172,644H939c-17,0-33,11-39,27L401,2039l241,139l550-1507c5-14-5-28-19-28L1172,644z"></path>
<path class="st4" d="M1580,644h-233c-17,0-33,11-39,27L738,2233l241,139l620-1701c5-14-5-28-19-28V644z"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#136485"/><path fill="#FFF" d="M10.436 31.006a16.008 16.008 0 0 1-5.604-3.548l.147-.257c2.388-3.773 4.533-7.678 6.148-11.85 1.713-4.425 3.084-8.967 4.39-13.527.117-.407.256-.807.384-1.21.138.158.188.305.23.454.82 2.926 1.613 5.86 2.464 8.776 1.55 5.313 3.73 10.353 6.617 15.077.337.55.91 1.472 1.72 2.762a15.988 15.988 0 0 1-6.035 3.554 4320.193 4320.193 0 0 0-5.002-15.17l-.154-.002c-1.166 3.277-2.934 8.257-5.305 14.941zM16.075.049h-.124L16 0l.075.049z"/></g></svg>

After

Width:  |  Height:  |  Size: 622 B

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"
viewBox="0 0 784.37 1277.39"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1421394342400">
<g>
<polygon fill="#343434" fill-rule="nonzero" points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 "/>
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 "/>
<polygon fill="#3C3C3B" fill-rule="nonzero" points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 "/>
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,1277.38 392.07,956.52 -0,724.89 "/>
<polygon fill="#141414" fill-rule="nonzero" points="392.07,882.29 784.13,650.54 392.07,472.33 "/>
<polygon fill="#393939" fill-rule="nonzero" points="0,650.54 392.07,882.29 392.07,472.33 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 428 428" style="enable-background:new 0 0 428 428;" xml:space="preserve">
<path style="fill:#00193C;" d="M125.8,243.7c12.3,0,24.3-4.1,34-11.6l-78-78c-18.8,24.3-14.3,59.3,10,78.1
C101.6,239.6,113.5,243.7,125.8,243.7L125.8,243.7z"/>
<path style="fill:#00193C;" d="M357.8,188c0-12.3-4.1-24.3-11.6-34l-78,78c24.3,18.8,59.2,14.3,78-10
C353.7,212.3,357.8,200.3,357.8,188z"/>
<path style="fill:#00193C;" d="M397.1,103.1l-34.5,34.5c27.8,33.3,23.4,82.9-9.9,110.7c-29.2,24.4-71.6,24.4-100.8,0L214,286.2
l-37.8-37.8c-33.3,27.8-82.9,23.4-110.7-9.9c-24.4-29.2-24.4-71.6,0-100.8L47.8,120L31,103.1C10.7,136.5,0,174.9,0,214
c0,118.2,95.8,214,214,214s214-95.8,214-214C428.1,174.9,417.3,136.5,397.1,103.1z"/>
<path style="fill:#00193C;" d="M368.8,66.3c-81.5-85.5-216.9-88.7-302.4-7.2c-2.5,2.4-4.9,4.8-7.2,7.2c-5.3,5.6-10.3,11.4-15,17.5
L214,253.7L383.8,83.8C379.2,77.7,374.1,71.9,368.8,66.3z M214,28c50,0,96.6,19.3,131.6,54.5L214,214.1L82.4,82.5
C117.4,47.3,164,28,214,28z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+317 -33
View File
@@ -28,7 +28,7 @@ export interface paths {
'/transactions': {
/**
* List transactions
* @description List transactions from the Vega blockchain
* @description List transactions from the Vega blockchain from the newest to the oldest transactions.
*/
get: operations['BlockExplorer_ListTransactions'];
};
@@ -117,15 +117,31 @@ export interface components {
| 'EXPIRY_STRATEGY_CANCELS'
| 'EXPIRY_STRATEGY_SUBMIT';
/**
* @description - METHOD_NOW: Undelegate straight away, losing all rewards for the current epoch.
* - METHOD_AT_END_OF_EPOCH: Undelegate at the end of an epoch, retaining all rewards for the current epoch.
* @default METHOD_UNSPECIFIED
* - SIZE_OVERRIDE_SETTING_UNSPECIFIED: Never valid
* - SIZE_OVERRIDE_SETTING_NONE: No override, the size within the contained normal order submission will be used
* - SIZE_OVERRIDE_SETTING_POSITION: Use the total position of the trader
* @default SIZE_OVERRIDE_SETTING_UNSPECIFIED
* @enum {string}
*/
readonly UndelegateSubmissionMethod:
| 'METHOD_UNSPECIFIED'
| 'METHOD_NOW'
| 'METHOD_AT_END_OF_EPOCH';
readonly StopOrderSizeOverrideSetting:
| 'SIZE_OVERRIDE_SETTING_UNSPECIFIED'
| 'SIZE_OVERRIDE_SETTING_NONE'
| 'SIZE_OVERRIDE_SETTING_POSITION';
readonly StopOrderSizeOverrideValue: {
/** Scaling percentage of the current positions size */
readonly percentage?: string;
};
/**
* - MODE_UNSPECIFIED: Never valid.
* - MODE_CROSS_MARGIN: Cross margin mode - margin is dynamically acquired and released as a position is marked to market
* - MODE_ISOLATED_MARGIN: Isolated margin mode - margin for any newly opened position volume is transferred to the margin account when the trade is executed
* @default MODE_UNSPECIFIED
* @enum {string}
*/
readonly UpdateMarginModeMode:
| 'MODE_UNSPECIFIED'
| 'MODE_CROSS_MARGIN'
| 'MODE_ISOLATED_MARGIN';
readonly blockexplorerapiv1Transaction: {
/**
* Height of the block the transaction was found in
@@ -307,6 +323,30 @@ export interface components {
readonly stopOrdersSubmission?: readonly components['schemas']['v1StopOrdersSubmission'][];
/** @description List of order submissions to be processed sequentially. */
readonly submissions?: readonly components['schemas']['v1OrderSubmission'][];
/** Update margin mode instruction */
readonly updateMarginMode?: readonly components['schemas']['v1UpdateMarginMode'][];
};
/**
* @description Command that allows a token holder to submit a batch governance proposal that can be voted on by any other token holders, and eventually enacted on the Vega network.
* For example this command can be used to propose a new market and a network parameter change with it.
*/
readonly v1BatchProposalSubmission: {
/** @description Rationale behind a proposal. */
readonly rationale?: components['schemas']['vegaProposalRationale'];
/** @description Arbitrary human-readable reference identifying the proposal. */
readonly reference?: string;
/** @description Proposal terms containing the type and details of the proposal, as well as time spans for voting and enactment. */
readonly terms?: components['schemas']['v1BatchProposalSubmissionTerms'];
};
/** Terms for a batch governance proposal submission */
readonly v1BatchProposalSubmissionTerms: {
/** @description List of individual changes included in the batch proposal. */
readonly changes?: readonly components['schemas']['vegaBatchProposalTermsChange'][];
/**
* Format: int64
* @description Closing timestamp in Unix time; adheres to `minClose` and `maxClose` limits.
*/
readonly closingTimestamp?: string;
};
/** @description A validator command sent automatically that provides information of events that have happened on foreign chains. */
readonly v1ChainEvent: {
@@ -347,6 +387,12 @@ export interface components {
readonly team?: components['schemas']['v1CreateReferralSetTeam'];
};
readonly v1CreateReferralSetTeam: {
/**
* @description List of public keys that are allowed to join the team.
* Only applicable to closed teams. Removing a party from the allow list does not remove
* the party from the team.
*/
readonly allowList?: readonly string[];
/** @description External link to an avatar for the team. */
readonly avatarUrl?: string;
/** @description Whether or not the team is closed to new party members. */
@@ -432,6 +478,8 @@ export interface components {
readonly applyReferralCode?: components['schemas']['v1ApplyReferralCode'];
/** @description Command to submit a batch of order instructions. */
readonly batchMarketInstructions?: components['schemas']['v1BatchMarketInstructions'];
/** @description Command to submit a batch governance proposal. */
readonly batchProposalSubmission?: components['schemas']['v1BatchProposalSubmission'];
/**
* Format: uint64
* @description Block height which has been used to calculate the transaction proof-of-work.
@@ -449,6 +497,8 @@ export interface components {
readonly ethereumKeyRotateSubmission?: components['schemas']['v1EthereumKeyRotateSubmission'];
/** @description Command to request signatures to amend the multisig-control contract. */
readonly issueSignatures?: components['schemas']['v1IssueSignatures'];
/** @description Command to join a team. */
readonly joinTeam?: components['schemas']['v1JoinTeam'];
/** @description Validator command sent manually by a node operator to rotate their node's Vega keys. */
readonly keyRotateSubmission?: components['schemas']['v1KeyRotateSubmission'];
/** @description Command to amend a liquidity commitment. */
@@ -489,6 +539,10 @@ export interface components {
readonly transfer?: components['schemas']['commandsv1Transfer'];
/** @description Command to remove tokens delegated to a validator. */
readonly undelegateSubmission?: components['schemas']['v1UndelegateSubmission'];
/** @description Command to update the margin mode of a party in a market. */
readonly updateMarginMode?: components['schemas']['v1UpdateMarginMode'];
/** @description Command to update a party's profile. */
readonly updatePartyProfile?: components['schemas']['v1UpdatePartyProfile'];
/** @description Command to update a referral set. */
readonly updateReferralSet?: components['schemas']['v1UpdateReferralSet'];
/** @description Validator command sent automatically to signal regular participation in the network. */
@@ -525,6 +579,11 @@ export interface components {
/** @description Node ID of the validator node that will be signed in or out of the smart contract. */
readonly validatorNodeId?: string;
};
/** @description Command that allows the submitter to join a team or change teams if they are already a member of a team. */
readonly v1JoinTeam: {
/** @description ID of the team to join, this is the same as the referral code used to generate the team. */
readonly id?: string;
};
/** @description A validator command sent manually that allows a node operator to indicate to the network that their node's Vega key will be rotated. */
readonly v1KeyRotateSubmission: {
/** @description Hash of the node's current Vega public key. */
@@ -686,6 +745,17 @@ export interface components {
readonly peggedReference?: components['schemas']['vegaPeggedReference'];
/** @description New price for the order. This field is an unsigned integer scaled to the market's decimal places. */
readonly price?: string;
/**
* Format: uint64
* @description New size for the order.
* Amending the size causes the size and remaining part of the order to be changed by the difference between the original and amended size.
* - Specifying a size smaller than the current size leaves the order at its current order book position.
* - Specifying a size larger than the current size removes and reinserts the order at the back of the price level.
* - Specifying a size that results in the remaining part of the order being reduced to zero cancels the order.
* This field is an unsigned integer scaled to the market's decimal places.
* If specified, size_delta must be set to 0.
*/
readonly size?: string;
/**
* Format: int64
* @description Amend the size for the order by the delta specified:
@@ -693,6 +763,7 @@ export interface components {
* - To increase the size from the current value, set a positive integer value
* - To leave the size unchanged set a value of zero
* This field needs to be scaled using the market's position decimal places.
* If specified, size must not be set.
*/
readonly sizeDelta?: string;
/** @description New time in force for the order. */
@@ -863,6 +934,10 @@ export interface components {
readonly orderSubmission?: components['schemas']['v1OrderSubmission'];
/** @description Order will be submitted if the last traded price on the market breaches the given price. */
readonly price?: string;
/** Indicates if this order is linked to an order or position to derive the order size */
readonly sizeOverrideSetting?: components['schemas']['StopOrderSizeOverrideSetting'];
/** If this order is linked to a position, provide an optional scaling factor */
readonly sizeOverrideValue?: components['schemas']['StopOrderSizeOverrideValue'];
/** @description Order will be submitted if the last traded price has moved the given percent from the highest/lowest mark price since the stop order was submitted. */
readonly trailingPercentOffset?: string;
};
@@ -909,10 +984,42 @@ export interface components {
*/
readonly amount?: string;
/** @description Method of delegation. */
readonly method?: components['schemas']['UndelegateSubmissionMethod'];
readonly method?: components['schemas']['v1UndelegateSubmissionMethod'];
/** @description Node ID to undelegate stake from. */
readonly nodeId?: string;
};
/**
* @description - METHOD_NOW: Undelegate straight away, losing all rewards for the current epoch.
* - METHOD_AT_END_OF_EPOCH: Undelegate at the end of an epoch, retaining all rewards for the current epoch.
* @default METHOD_UNSPECIFIED
* @enum {string}
*/
readonly v1UndelegateSubmissionMethod:
| 'METHOD_UNSPECIFIED'
| 'METHOD_NOW'
| 'METHOD_AT_END_OF_EPOCH';
readonly v1UpdateMarginMode: {
/** Margin factor to use for margin in isolated mode. It is a multiplier that defines how much margin needs to be set aside */
readonly marginFactor?: string;
/** @description Market to change margin mode for. */
readonly marketId?: string;
/** @description Margin mode to use. */
readonly mode?: components['schemas']['UpdateMarginModeMode'];
};
/**
* @description Command to associate metadata to a public key, known as a party ID.
* Partial update is not supported, meaning previous values must be included in
* the update, otherwise they are removed.
*/
readonly v1UpdatePartyProfile: {
/** @description Alias given to the party. It must be unique network-wide. */
readonly alias?: string;
/**
* @description Freeform data to associate to the party.
* Support a maximum of 10 entries.
*/
readonly metadata?: readonly components['schemas']['vegaMetadata'][];
};
/**
* @description A command that allows the referrer of a referral set to update team details for a referral set.
* Any field that is left unset or has a default value indicates that this field on the original referral set will be left unchanged.
@@ -926,9 +1033,18 @@ export interface components {
readonly team?: components['schemas']['v1UpdateReferralSetTeam'];
};
readonly v1UpdateReferralSetTeam: {
/**
* @description List of public keys that are allowed to join the team.
* Only applicable to closed teams. Removing a party from the allow list does not remove
* the party from the team.
*/
readonly allowList?: readonly string[];
/** @description New link to an avatar for the team. */
readonly avatarUrl?: string;
/** @description Whether or not the team is closed to new party members. */
/**
* @description Whether or not the team is closed to new party members. When closed, only parties specified in the allow list can
* join the team.
*/
readonly closed?: boolean;
/** @description New name of the team. */
readonly name?: string;
@@ -1014,6 +1130,7 @@ export interface components {
* - ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: Per asset market reward account given for return volatility
* - ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: Per asset market reward account given to validators by their ranking
* - ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: Per asset account for pending fee referral reward payouts
* - ACCOUNT_TYPE_ORDER_MARGIN: Per asset market account for party in isolated margin mode
* @default ACCOUNT_TYPE_UNSPECIFIED
* @enum {string}
*/
@@ -1045,7 +1162,8 @@ export interface components {
| 'ACCOUNT_TYPE_REWARD_RELATIVE_RETURN'
| 'ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY'
| 'ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING'
| 'ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD';
| 'ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD'
| 'ACCOUNT_TYPE_ORDER_MARGIN';
/** Vega representation of an external asset */
readonly vegaAssetDetails: {
/** @description Vega built-in asset. */
@@ -1071,6 +1189,42 @@ export interface components {
/** @description Minimum economically meaningful amount in the asset. */
readonly quantum?: string;
};
/** Terms change for a batch governance proposal */
readonly vegaBatchProposalTermsChange: {
/** @description Proposal change to cancel a governance initiated transfe. */
readonly cancelTransfer?: components['schemas']['vegaCancelTransfer'];
/**
* Format: int64
* @description Timestamp as Unix time in seconds when proposal terms gets enacted if proposal passed the vote,
* constrained by `minEnact` and `maxEnact` network parameters.
*/
readonly enactmentTimestamp?: string;
/**
* @description Proposal change for a freeform request, which can be voted on but does not change the behaviour of the system,
* and can be used to gauge community sentiment.
*/
readonly newFreeform?: components['schemas']['vegaNewFreeform'];
/** @description Proposal change for creating new futures market. */
readonly newMarket?: components['schemas']['vegaNewMarket'];
/** @description Proposal change for creating new spot market. */
readonly newSpotMarket?: components['schemas']['vegaNewSpotMarket'];
/** @description Proposal change for a governance initiated transfer. */
readonly newTransfer?: components['schemas']['vegaNewTransfer'];
/** @description Proposal change for updating an asset. */
readonly updateAsset?: components['schemas']['vegaUpdateAsset'];
/** @description Proposal change for modifying an existing futures market. */
readonly updateMarket?: components['schemas']['vegaUpdateMarket'];
/** @description Proposal change for updating the state of a market. */
readonly updateMarketState?: components['schemas']['vegaUpdateMarketState'];
/** @description Proposal change for updating Vega network parameters. */
readonly updateNetworkParameter?: components['schemas']['vegaUpdateNetworkParameter'];
/** @description Proposal change for updating the referral program. */
readonly updateReferralProgram?: components['schemas']['vegaUpdateReferralProgram'];
/** @description Proposal change for modifying an existing spot market. */
readonly updateSpotMarket?: components['schemas']['vegaUpdateSpotMarket'];
/** @description Proposal change for updating the volume discount program. */
readonly updateVolumeDiscountProgram?: components['schemas']['vegaUpdateVolumeDiscountProgram'];
};
readonly vegaBenefitTier: {
/**
* @description Required number of epochs a party must have been in a referral set to
@@ -1125,6 +1279,44 @@ export interface components {
/** @description ID of the governance transfer proposal. */
readonly transferId?: string;
};
/** @description Mark price configuration parameters. */
readonly vegaCompositePriceConfiguration: {
/** @description Cash amount, in asset decimals, used for the calculation of the mark price from the order book. */
readonly cashAmount?: string;
/** @description Which method is used for the calculation of the composite price for the market. */
readonly compositePriceType?: components['schemas']['vegaCompositePriceType'];
/** @description Additional price sources to be used for index price calculation. */
readonly dataSourcesSpec?: readonly components['schemas']['vegaDataSourceDefinition'][];
/** List of each price source and its corresponding binding */
readonly dataSourcesSpecBinding?: readonly components['schemas']['vegaSpecBindingForCompositePrice'][];
/**
* Format: uint64
* @description Decay power used for the calculation of mark price.
*/
readonly decayPower?: string;
/** @description Decay weight used for calculation of mark price. */
readonly decayWeight?: string;
/**
* @description For how long a price source is considered valid. One entry for each data source
* such that the first is for the trade based mark price, the second is for the book based price
* the third is for the first oracle, followed by more oracle data source staleness tolerance.
*/
readonly sourceStalenessTolerance?: readonly string[];
/** @description Weights for each composite price data source. */
readonly sourceWeights?: readonly string[];
};
/**
* @description - COMPOSITE_PRICE_TYPE_WEIGHTED: Composite price is calculated as a weighted average of the underlying mark prices.
* - COMPOSITE_PRICE_TYPE_MEDIAN: Composite price is calculated as a median of the underlying mark prices.
* - COMPOSITE_PRICE_TYPE_LAST_TRADE: Composite price is calculated as the last trade price.
* @default COMPOSITE_PRICE_TYPE_UNSPECIFIED
* @enum {string}
*/
readonly vegaCompositePriceType:
| 'COMPOSITE_PRICE_TYPE_UNSPECIFIED'
| 'COMPOSITE_PRICE_TYPE_WEIGHTED'
| 'COMPOSITE_PRICE_TYPE_MEDIAN'
| 'COMPOSITE_PRICE_TYPE_LAST_TRADE';
/**
* @description Represents the top level object that handles data sources.
* Data source definition can be external or internal, with whatever
@@ -1197,21 +1389,17 @@ export interface components {
readonly tradingTerminationProperty?: string;
};
/**
* Describes which property of the data source data is to be
* used as settlement data and which to use as the trading terminated trigger
* @description Describes which properties of the data source data is to be
* used for settlement.
*/
readonly vegaDataSourceSpecToPerpetualBinding: {
/**
* @description Name of the property in the source data that should be used as settlement data.
* If it is set to "prices.BTC.value", then the perpetual market will use the value of
* this property as settlement data.
* @description Name of the property in the source data that should be used for settlement data.
* If it is set to "prices.BTC.value" for example, then the perpetual market will use the value of
* this property to get settlement data.
*/
readonly settlementDataProperty?: string;
/**
* @description Name of the property in the source data that should be used as settlement data.
* If it is set to "prices.BTC.value", then the perpetual market will use the value of
* this property as settlement data.
*/
/** @description Name of the property in the source data that should be used to determine the perpetual's settlement schedule. */
readonly settlementScheduleProperty?: string;
};
/**
@@ -1493,6 +1681,11 @@ export interface components {
* Format: uint64
*/
readonly requiredConfirmations?: string;
/**
* Format: uint64
* @description The ID of the EVM based chain which is to be used to source the oracle data.
*/
readonly sourceChainId?: string;
/** @description Conditions for determining when to call the contract method. */
readonly trigger?: components['schemas']['vegaEthCallTrigger'];
};
@@ -1519,6 +1712,11 @@ export interface components {
* @description Result of contract call, packed according to the ABI stored in the associated data source spec.
*/
readonly result?: string;
/**
* Format: uint64
* @description Source chain for this chain event.
*/
readonly sourceChainId?: string;
/** @description ID of the data source spec that triggered this contract call. */
readonly specId?: string;
};
@@ -1597,6 +1795,42 @@ export interface components {
readonly tolerance?: string;
readonly value?: components['schemas']['vegaStateVarValue'];
};
/** @description Liquidation strategy used when the network holds a position resulting from position resolution. */
readonly vegaLiquidationStrategy: {
/** @description Fraction of the open position the market will try to close in a single attempt; range 0 through 1. */
readonly disposalFraction?: string;
/**
* Format: int64
* @description Interval, in seconds, at which the network will attempt to close its position.
*/
readonly disposalTimeStep?: string;
/**
* Format: uint64
* @description Size of the position that the network will try to close in a single attempt.
*/
readonly fullDisposalSize?: string;
/** @description Max fraction of the total volume of the orderbook, within liquidity bounds, that the network can use to close its position; range 0 through 1. */
readonly maxFractionConsumed?: string;
};
/** @description Market settings that describe how the liquidity fee is calculated. */
readonly vegaLiquidityFeeSettings: {
/** @description Constant liquidity fee used when using the constant fee method. */
readonly feeConstant?: string;
/** @description Method used to calculate the market's liquidity fee. */
readonly method?: components['schemas']['vegaLiquidityFeeSettingsMethod'];
};
/**
* @description - METHOD_MARGINAL_COST: Fee is the smallest value of all bids, such that liquidity providers with nominated fees less than or equal to this value still have sufficient commitment to fulfil the market's target stake.
* - METHOD_WEIGHTED_AVERAGE: Fee is the weighted average of all liquidity providers' nominated fees, weighted by their committment.
* - METHOD_CONSTANT: Fee is set by the market to a constant value irrespective of any liquidity provider's nominated fee.
* @default METHOD_UNSPECIFIED
* @enum {string}
*/
readonly vegaLiquidityFeeSettingsMethod:
| 'METHOD_UNSPECIFIED'
| 'METHOD_MARGINAL_COST'
| 'METHOD_WEIGHTED_AVERAGE'
| 'METHOD_CONSTANT';
/** LiquidityMonitoringParameters contains settings used for liquidity monitoring */
readonly vegaLiquidityMonitoringParameters: {
/**
@@ -1674,6 +1908,13 @@ export interface components {
readonly vegaMatrixValue: {
readonly value?: readonly components['schemas']['vegaVectorValue'][];
};
/** @description Generic structure holding a key/value pair. */
readonly vegaMetadata: {
/** @description Key of the metadata. */
readonly key?: string;
/** @description Value of the metadata. */
readonly value?: string;
};
/** Represents a network parameter on Vega */
readonly vegaNetworkParameter: {
/** @description Unique key of the network parameter. */
@@ -1708,6 +1949,10 @@ export interface components {
readonly instrument?: components['schemas']['vegaInstrumentConfiguration'];
/** @description Linear slippage factor is used to cap the slippage component of maintenance margin - it is applied to the slippage volume. */
readonly linearSlippageFactor?: string;
/** @description Liquidation strategy for this market. */
readonly liquidationStrategy?: components['schemas']['vegaLiquidationStrategy'];
/** @description Specifies how the liquidity fee for the market will be calculated. */
readonly liquidityFeeSettings?: components['schemas']['vegaLiquidityFeeSettings'];
/** @description Liquidity monitoring parameters. */
readonly liquidityMonitoringParameters?: components['schemas']['vegaLiquidityMonitoringParameters'];
/** Liquidity SLA parameters */
@@ -1720,6 +1965,8 @@ export interface components {
* price levels over which automated liquidity provisions will be deployed.
*/
readonly lpPriceRange?: string;
/** @description Mark price configuration. */
readonly markPriceConfiguration?: components['schemas']['vegaCompositePriceConfiguration'];
/** @description Optional new futures market metadata, tags. */
readonly metadata?: readonly string[];
/**
@@ -1750,6 +1997,8 @@ export interface components {
readonly decimalPlaces?: string;
/** @description New spot market instrument configuration. */
readonly instrument?: components['schemas']['vegaInstrumentConfiguration'];
/** @description Specifies how the liquidity fee for the market will be calculated. */
readonly liquidityFeeSettings?: components['schemas']['vegaLiquidityFeeSettings'];
/** @description Log normal risk model parameters, valid only if MODEL_LOG_NORMAL is selected. */
readonly logNormal?: components['schemas']['vegaLogNormalRiskModel'];
/** @description Optional new spot market metadata, tags. */
@@ -1865,6 +2114,14 @@ export interface components {
readonly dataSourceSpecForSettlementData?: components['schemas']['vegaDataSourceDefinition'];
/** @description Data source spec describing the data source for settlement schedule. */
readonly dataSourceSpecForSettlementSchedule?: components['schemas']['vegaDataSourceDefinition'];
/** @description Lower bound for the funding-rate such that the funding-rate will never be lower than this value. */
readonly fundingRateLowerBound?: string;
/** @description Factor applied to funding-rates. This scales the impact that spot price deviations have on funding payments. */
readonly fundingRateScalingFactor?: string;
/** @description Upper bound for the funding-rate such that the funding-rate will never be higher than this value. */
readonly fundingRateUpperBound?: string;
/** @description Composite price configuration to drive the calculation of the index price used for funding payments. If undefined the default mark price of the market is used. */
readonly indexPriceConfiguration?: components['schemas']['vegaCompositePriceConfiguration'];
/** @description Continuously compounded interest rate used in funding rate calculation, in the range [-1, 1]. */
readonly interestRate?: string;
/** @description Controls how much the upcoming funding payment liability contributes to party's margin, in the range [0, 1]. */
@@ -2042,6 +2299,14 @@ export interface components {
*/
readonly probabilityOfTrading?: number;
};
/**
* @description Describes which property of the data source data is to be
* used for price source.
*/
readonly vegaSpecBindingForCompositePrice: {
/** @description The property name of price. */
readonly priceSourceProperty?: string;
};
/** Spot product configuration */
readonly vegaSpotProduct: {
/** @description Base asset ID. */
@@ -2172,6 +2437,8 @@ export interface components {
readonly code?: string;
/** @description Future. */
readonly future?: components['schemas']['vegaUpdateFutureProduct'];
/** Instrument name */
readonly name?: string;
/** @description Perpetual. */
readonly perpetual?: components['schemas']['vegaUpdatePerpetualProduct'];
};
@@ -2188,6 +2455,10 @@ export interface components {
readonly instrument?: components['schemas']['vegaUpdateInstrumentConfiguration'];
/** @description Linear slippage factor is used to cap the slippage component of maintenance margin - it is applied to the slippage volume. */
readonly linearSlippageFactor?: string;
/** Liquidation strategy parameters */
readonly liquidationStrategy?: components['schemas']['vegaLiquidationStrategy'];
/** @description Specifies how the liquidity fee for the market will be calculated. */
readonly liquidityFeeSettings?: components['schemas']['vegaLiquidityFeeSettings'];
/** @description Liquidity monitoring parameters. */
readonly liquidityMonitoringParameters?: components['schemas']['vegaLiquidityMonitoringParameters'];
/** Liquidity SLA parameters */
@@ -2200,6 +2471,8 @@ export interface components {
* price levels over which automated liquidity provisions will be deployed.
*/
readonly lpPriceRange?: string;
/** @description Mark price configuration. */
readonly markPriceConfiguration?: components['schemas']['vegaCompositePriceConfiguration'];
/** @description Optional futures market metadata, tags. */
readonly metadata?: readonly string[];
/** @description Price monitoring parameters. */
@@ -2238,6 +2511,14 @@ export interface components {
readonly dataSourceSpecForSettlementData?: components['schemas']['vegaDataSourceDefinition'];
/** @description Data source spec describing the data source for settlement schedule. */
readonly dataSourceSpecForSettlementSchedule?: components['schemas']['vegaDataSourceDefinition'];
/** @description Lower bound for the funding-rate such that the funding-rate will never be lower than this value. */
readonly fundingRateLowerBound?: string;
/** @description Factor applied to funding-rates. This scales the impact that spot price deviations have on funding payments. */
readonly fundingRateScalingFactor?: string;
/** @description Upper bound for the funding-rate such that the funding-rate will never be higher than this value. */
readonly fundingRateUpperBound?: string;
/** @description Configuration for the index price used in funding payment calculation. */
readonly indexPriceConfiguration?: components['schemas']['vegaCompositePriceConfiguration'];
/** @description Continuously compounded interest rate used in funding rate calculation, in the range [-1, 1]. */
readonly interestRate?: string;
/** @description Controls how much the upcoming funding payment liability contributes to party's margin, in the range [0, 1]. */
@@ -2258,6 +2539,8 @@ export interface components {
};
/** Configuration to update a spot market on Vega */
readonly vegaUpdateSpotMarketConfiguration: {
/** @description Specifies how the liquidity fee for the market will be calculated. */
readonly liquidityFeeSettings?: components['schemas']['vegaLiquidityFeeSettings'];
/** @description Log normal risk model parameters, valid only if MODEL_LOG_NORMAL is selected. */
readonly logNormal?: components['schemas']['vegaLogNormalRiskModel'];
/** @description Optional spot market metadata, tags. */
@@ -2356,19 +2639,14 @@ export interface operations {
};
/**
* List transactions
* @description List transactions from the Vega blockchain
* @description List transactions from the Vega blockchain from the newest to the oldest transactions.
*/
BlockExplorer_ListTransactions: {
parameters: {
query?: {
/**
* @description Number of transactions to be returned from the blockchain.
* This is deprecated, use first and last instead.
*/
limit?: number;
/** @description Optional cursor to paginate the request. */
/** @description Cursor to paginate the request. It can be used in conjunction with the `after` cursor. */
before?: string;
/** @description Optional cursor to paginate the request. */
/** @description Cursor to paginate the request. It can be used in conjunction with the `before` cursor. */
after?: string;
/** @description Transaction command types filter, for listing transactions with specified command types. */
cmdTypes?: readonly string[];
@@ -2377,13 +2655,19 @@ export interface operations {
/** @description Party IDs filter, can be sender or receiver. */
parties?: readonly string[];
/**
* @description Number of transactions to be returned from the blockchain. Use in conjunction with the `after` cursor to paginate forwards.
* On its own, this will return the first `first` transactions.
* @description Number of transactions to be returned from the blockchain.
* Use in conjunction with the `after` cursor to paginate forwards. Paginating forwards means toward the most recent
* transactions.
* It cannot be used in conjunction with the `before` cursor.
* On its own, this will return the `first` most recent transactions.
*/
first?: number;
/**
* @description Number of transactions to be returned from the blockchain. Use in conjunction with the `before` cursor to paginate backwards.
* On its own, this will return the last `last` transactions.
* @description Number of transactions to be returned from the blockchain.
* Use in conjunction with the `before` cursor to paginate backwards. Paginating forwards means toward the least recent
* transactions.
* It cannot be used in conjunction with the `after` cursor.
* On its own, this will return the `last` oldest transactions.
*/
last?: number;
};
@@ -215,7 +215,7 @@ context(
});
// 3003-PMAN-001
it(
it.skip(
'Able to submit valid new market proposal',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
@@ -266,7 +266,6 @@ export const ProposalMarketData = ({
/>
</>
))}
<h2 className={marketDataHeaderStyles}>
{t('Liquidity monitoring parameters')}
</h2>
@@ -31,7 +31,7 @@ export const CompetitionsCreateTeam = () => {
<div className="mx-auto md:w-2/3 max-w-xl">
<Box className="flex flex-col gap-4">
<h1 className="calt text-2xl lg:text-3xl xl:text-4xl">
{t('Create a team')}
{isSolo ? t('Create solo team') : t('Create a team')}
</h1>
{pubKey && !isReadOnly ? (
<CreateTeamFormContainer isSolo={isSolo} />
@@ -125,7 +125,7 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
onSubmit={onSubmit}
status={status}
err={err}
isSolo={isSolo}
isCreatingSoloTeam={isSolo}
/>
);
};
@@ -31,10 +31,7 @@ export const CompetitionsHome = () => {
currentEpoch,
});
const { data: teamsData, loading: teamsLoading } = useTeams({
sortByField: ['totalQuantumRewards'],
order: 'desc',
});
const { data: teamsData, loading: teamsLoading } = useTeams();
return (
<ErrorBoundary>
@@ -38,12 +38,11 @@ export const CompetitionsTeam = () => {
const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
const t = useT();
const { pubKey } = useVegaWallet();
const { team, partyTeam, stats, members, games, loading, refetch } = useTeam(
teamId,
pubKey || undefined
);
const { data, team, partyTeam, stats, members, games, loading, refetch } =
useTeam(teamId, pubKey || undefined);
if (loading) {
// only show spinner on first load so when users join teams its smoother
if (!data && loading) {
return (
<Splash>
<Loader />
@@ -100,8 +99,10 @@ const TeamPage = ({
>
{team.name}
</h1>
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
<div className="flex gap-2">
<JoinTeam team={team} partyTeam={partyTeam} refetch={refetch} />
<UpdateTeamButton team={team} />
</div>
</div>
</header>
<TeamStats stats={stats} members={members} games={games} />
@@ -184,7 +185,10 @@ const Members = ({ members }: { members?: Member[] }) => {
const data = orderBy(
members.map((m) => ({
referee: <RefereeLink pubkey={m.referee} />,
referee: <RefereeLink pubkey={m.referee} isCreator={m.isCreator} />,
rewards: formatNumber(m.totalQuantumRewards),
volume: formatNumber(m.totalQuantumVolume),
gamesPlayed: formatNumber(m.totalGamesPlayed),
joinedAt: getDateTimeFormat().format(new Date(m.joinedAt)),
joinedAtEpoch: Number(m.joinedAtEpoch),
})),
@@ -195,7 +199,10 @@ const Members = ({ members }: { members?: Member[] }) => {
return (
<Table
columns={[
{ name: 'referee', displayName: t('Referee') },
{ name: 'referee', displayName: t('Member ID') },
{ name: 'rewards', displayName: t('Rewards earned') },
{ name: 'volume', displayName: t('Total volume') },
{ name: 'gamesPlayed', displayName: t('Games played') },
{
name: 'joinedAt',
displayName: t('Joined at'),
@@ -211,14 +218,24 @@ const Members = ({ members }: { members?: Member[] }) => {
);
};
const RefereeLink = ({ pubkey }: { pubkey: string }) => {
const RefereeLink = ({
pubkey,
isCreator,
}: {
pubkey: string;
isCreator: boolean;
}) => {
const t = useT();
const linkCreator = useLinks(DApp.Explorer);
const link = linkCreator(EXPLORER_PARTIES.replace(':id', pubkey));
return (
<Link to={link} target="_blank" className="underline underline-offset-4">
{truncateMiddle(pubkey)}
</Link>
<>
<Link to={link} target="_blank" className="underline underline-offset-4">
{truncateMiddle(pubkey)}
</Link>{' '}
<span className="text-muted text-xs">{isCreator ? t('Owner') : ''}</span>
</>
);
};
@@ -17,10 +17,7 @@ export const CompetitionsTeams = () => {
usePageTitle([t('Competitions'), t('Teams')]);
const { data: teamsData, loading: teamsLoading } = useTeams({
sortByField: ['totalQuantumRewards'],
order: 'desc',
});
const { data: teamsData, loading: teamsLoading } = useTeams();
const inputRef = useRef<HTMLInputElement>(null);
const [filter, setFilter] = useState<string | null | undefined>(undefined);
@@ -98,7 +98,7 @@ const UpdateTeamFormContainer = ({
type={TransactionType.UpdateReferralSet}
status={status}
err={err}
isSolo={team.closed}
isCreatingSoloTeam={team.closed}
onSubmit={onSubmit}
defaultValues={defaultValues}
/>
@@ -6,11 +6,7 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import {
useSimpleTransaction,
useVegaWallet,
type Status,
} from '@vegaprotocol/wallet';
import { useSimpleTransaction, useVegaWallet } from '@vegaprotocol/wallet';
import { useT } from '../../lib/use-t';
import { type Team } from '../../lib/hooks/use-team';
import { useState } from 'react';
@@ -27,19 +23,8 @@ export const JoinTeam = ({
refetch: () => void;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { send, status } = useSimpleTransaction({
onSuccess: refetch,
});
const [confirmDialog, setConfirmDialog] = useState<JoinType>();
const joinTeam = () => {
send({
joinTeam: {
id: team.teamId,
},
});
};
return (
<>
<JoinButton
@@ -56,11 +41,10 @@ export const JoinTeam = ({
{confirmDialog !== undefined && (
<DialogContent
type={confirmDialog}
status={status}
team={team}
partyTeam={partyTeam}
onConfirm={joinTeam}
onCancel={() => setConfirmDialog(undefined)}
refetch={refetch}
/>
)}
</Dialog>
@@ -110,7 +94,7 @@ export const JoinButton = ({
// Not creator of the team, but still can't switch because
// creators cannot leave their own team
return (
<Tooltip description="As a team creator, you cannot switch teams">
<Tooltip description={t('As a team creator, you cannot switch teams')}>
<Button intent={Intent.Primary} disabled={true}>
{t('Switch team')}{' '}
</Button>
@@ -149,21 +133,39 @@ export const JoinButton = ({
const DialogContent = ({
type,
status,
team,
partyTeam,
onConfirm,
onCancel,
refetch,
}: {
type: JoinType;
status: Status;
team: Team;
partyTeam?: Team;
onConfirm: () => void;
onCancel: () => void;
refetch: () => void;
}) => {
const t = useT();
const { send, status, error } = useSimpleTransaction({
onSuccess: refetch,
});
const joinTeam = () => {
send({
joinTeam: {
id: team.teamId,
},
});
};
if (error) {
return (
<p className="text-vega-red break-words first-letter:capitalize">
{error}
</p>
);
}
if (status === 'requested') {
return <p>{t('Confirm in wallet...')}</p>;
}
@@ -213,7 +215,7 @@ const DialogContent = ({
</>
)}
<div className="flex justify-between gap-2">
<Button onClick={onConfirm} intent={Intent.Success}>
<Button onClick={joinTeam} intent={Intent.Success}>
{t('Confirm')}
</Button>
<Button onClick={onCancel} intent={Intent.Danger}>
@@ -6,6 +6,8 @@ import {
TextArea,
TradingButton,
Intent,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { URL_REGEX, isValidVegaPublicKey } from '@vegaprotocol/utils';
@@ -17,6 +19,8 @@ import type {
UpdateReferralSet,
Status,
} from '@vegaprotocol/wallet';
import classNames from 'classnames';
import { useLayoutEffect, useState } from 'react';
export type FormFields = {
id: string;
@@ -28,8 +32,8 @@ export type FormFields = {
};
export enum TransactionType {
CreateReferralSet,
UpdateReferralSet,
CreateReferralSet = 'CreateReferralSet',
UpdateReferralSet = 'UpdateReferralSet',
}
const prepareTransaction = (
@@ -75,14 +79,14 @@ export const TeamForm = ({
type,
status,
err,
isSolo,
isCreatingSoloTeam,
onSubmit,
defaultValues,
}: {
type: TransactionType;
status: ReturnType<typeof useReferralSetTransaction>['status'];
err: ReturnType<typeof useReferralSetTransaction>['err'];
isSolo: boolean;
isCreatingSoloTeam: boolean;
onSubmit: ReturnType<typeof useReferralSetTransaction>['onSubmit'];
defaultValues?: FormFields;
}) => {
@@ -96,7 +100,7 @@ export const TeamForm = ({
formState: { errors },
} = useForm<FormFields>({
defaultValues: {
private: isSolo,
private: isCreatingSoloTeam,
...defaultValues,
},
});
@@ -109,12 +113,7 @@ export const TeamForm = ({
return (
<form onSubmit={handleSubmit(sendTransaction)}>
<input
type="hidden"
{...register('id', {
disabled: true,
})}
/>
<input type="hidden" {...register('id')} />
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput {...register('name', { required: t('Required') })} />
{errors.name?.message && (
@@ -160,59 +159,70 @@ export const TeamForm = ({
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup
label={t('Make team private')}
labelFor="private"
hideLabel={true}
>
<Controller
name="private"
control={control}
render={({ field }) => {
return (
<TradingCheckbox
label={t('Make team private')}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
{
// allow changing to private/public if editing, but don't show these options if making a solo team
(type === TransactionType.UpdateReferralSet || !isCreatingSoloTeam) && (
<>
<TradingFormGroup
label={t('Make team private')}
labelFor="private"
hideLabel={true}
>
<Controller
name="private"
control={control}
render={({ field }) => {
return (
<TradingCheckbox
label={t('Make team private')}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
}}
/>
);
}}
disabled={isSolo}
/>
);
}}
/>
</TradingFormGroup>
{isPrivate && (
<TradingFormGroup
label={t('Public key allow list')}
labelFor="allowList"
labelDescription={t(
'Use a comma separated list to allow only specific public keys to join the team'
)}
>
<TextArea
{...register('allowList', {
required: t('Required'),
disabled: isSolo,
validate: {
allowList: (value) => {
const publicKeys = parseAllowListText(value);
if (publicKeys.every((pk) => isValidVegaPublicKey(pk))) {
return true;
}
return t('Invalid public key found in allow list');
},
},
})}
/>
{errors.allowList?.message && (
<TradingInputError forInput="avatarUrl">
{errors.allowList.message}
</TradingInputError>
)}
</TradingFormGroup>
</TradingFormGroup>
{isPrivate && (
<TradingFormGroup
label={t('Public key allow list')}
labelFor="allowList"
labelDescription={t(
'Use a comma separated list to allow only specific public keys to join the team'
)}
>
<TextArea
{...register('allowList', {
required: t('Required'),
validate: {
allowList: (value) => {
const publicKeys = parseAllowListText(value);
if (
publicKeys.every((pk) => isValidVegaPublicKey(pk))
) {
return true;
}
return t('Invalid public key found in allow list');
},
},
})}
/>
{errors.allowList?.message && (
<TradingInputError forInput="avatarUrl">
{errors.allowList.message}
</TradingInputError>
)}
</TradingFormGroup>
)}
</>
)
}
{err && (
<p className="text-danger text-xs mb-4 first-letter:capitalize">
{err}
</p>
)}
{err && <p className="text-danger text-xs mb-4 capitalize">{err}</p>}
<SubmitButton type={type} status={status} />
</form>
);
@@ -233,20 +243,56 @@ const SubmitButton = ({
text = t('Update');
}
let confirmedText = t('Created');
if (type === TransactionType.UpdateReferralSet) {
confirmedText = t('Updated');
}
if (status === 'requested') {
text = t('Confirm in wallet...');
} else if (status === 'pending') {
text = t('Confirming transaction...');
}
const [showConfirmed, setShowConfirmed] = useState<boolean>(false);
useLayoutEffect(() => {
let to: ReturnType<typeof setTimeout>;
if (status === 'confirmed' && !showConfirmed) {
to = setTimeout(() => {
setShowConfirmed(true);
}, 100);
}
return () => {
clearTimeout(to);
};
}, [showConfirmed, status]);
const confirmed = (
<span
className={classNames('text-sm transition-opacity opacity-0', {
'opacity-100': showConfirmed,
})}
>
<VegaIcon
name={VegaIconNames.TICK}
size={18}
className="text-vega-green-500"
/>{' '}
{confirmedText}
</span>
);
return (
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}>
{text}
</TradingButton>
<div className="flex gap-2 items-baseline">
<TradingButton type="submit" intent={Intent.Info} disabled={disabled}>
{text}
</TradingButton>
{status === 'confirmed' && confirmed}
</div>
);
};
const parseAllowListText = (str: string) => {
const parseAllowListText = (str: string = '') => {
return str
.split(',')
.map((v) => v.trim())
@@ -2,8 +2,10 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import { type Team } from '../../lib/hooks/use-team';
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t';
export const UpdateTeamButton = ({ team }: { team: Team }) => {
const t = useT();
const { pubKey, isReadOnly } = useVegaWallet();
if (pubKey && !isReadOnly && pubKey === team.referrer) {
@@ -12,7 +14,9 @@ export const UpdateTeamButton = ({ team }: { team: Team }) => {
data-testid="update-team-button"
href={Links.COMPETITIONS_UPDATE_TEAM(team.teamId)}
intent={Intent.Info}
/>
>
{t('Update team')}
</TradingAnchorButton>
);
}
@@ -105,7 +105,7 @@ describe('ActiveRewards', () => {
expect(
screen.getByText(/Liquidity provision fees received/i)
).toBeInTheDocument();
expect(screen.getByText('Entity scope')).toBeInTheDocument();
expect(screen.getByText('Individual scope')).toBeInTheDocument();
expect(screen.getByText('Average position')).toBeInTheDocument();
expect(screen.getByText('Ends in')).toBeInTheDocument();
expect(screen.getByText('115431 epochs')).toBeInTheDocument();
@@ -1,6 +1,17 @@
import { render, screen } from '@testing-library/react';
import { ActivityStreak } from './activity-streaks';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
useNetworkParams: jest.fn(() => ({
params: {
rewards_activityStreak_inactivityLimit: '3',
},
loading: false,
error: null,
})),
}));
describe('ActivityStreak', () => {
it('renders null when streak is not active', () => {
const tiers: {
@@ -3,6 +3,10 @@ import { useT } from '../../../lib/use-t';
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import type { PartyActivityStreak } from '@vegaprotocol/types';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
export const safeProgress = (
i: number,
@@ -69,7 +73,12 @@ export const ActivityStreak = ({
}) => {
const t = useT();
const userTierIndex = useGetUserTier(tiers, streak?.activeFor);
const { params } = useNetworkParams([
NetworkParams.rewards_activityStreak_inactivityLimit,
]);
const remaining = new BigNumber(params.rewards_activityStreak_inactivityLimit)
.minus(streak?.inactiveFor || 0)
.toNumber();
if (!tiers || tiers.length === 0) return null;
const progressBarHeight = 'h-10';
@@ -203,12 +212,35 @@ export const ActivityStreak = ({
<VegaIcon name={VegaIconNames.STREAK} />
<span className="flex flex-col">
{streak?.isActive && (
{streak && (
<span data-testid="epoch-streak">
{t('userActive', '{{active}} trader: {{count}} epochs so far', {
active: streak?.isActive ? 'Active' : 'Inactive',
count: streak?.activeFor || 0,
})}{' '}
{streak.isActive
? t(
'userActive',
'{{active}} trader: {{count}} epochs so far',
{
active: 'Active',
count: streak.activeFor || 0,
}
)
: remaining > 0
? t(
'userInactive',
'{{active}} trader: {{count}} epochs so far, you will lose your streak in {{remaining}} epochs!',
{
active: 'Inactive',
count: streak.inactiveFor || 0,
remaining,
}
)
: t(
'userActive',
'{{active}} trader: {{count}} epochs so far',
{
active: 'Inactive',
count: streak.inactiveFor || 0,
}
)}{' '}
{userTierIndex > 0 &&
new BigNumber(
tiers[0].minimum_activity_streak
+2 -2
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.0-preview.7
LOCAL_SERVER=true
VEGA_VERSION=v0.74.0-preview.8
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.74.0-preview.2
VEGA_VERSION=v0.74.0-preview.6
LOCAL_SERVER=false
+2 -2
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "certifi"
@@ -1161,7 +1161,7 @@ profile = ["pytest-profiling", "snakeviz"]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "HEAD"
resolved_reference = "4440abbb6ce0d3e80beba5cd01f20cd21983cbf8"
resolved_reference = "026976549c21e59f6f9c48f06ab15a210c5a5bf3"
[[package]]
name = "websocket-client"
@@ -38,7 +38,7 @@ def test_switch_cross_isolated_margin(
expect(page.get_by_test_id("toast-content")).to_have_text(
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 1.0x")
expect(page.locator(margin_row).nth(1)
).to_have_text("11,109.99996Isolated1.0x")
).to_have_text("22,109.99996Isolated1.0x")
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_test_id(tab_positions).get_by_text("Isolated").hover()
@@ -11,27 +11,34 @@ place_order = "place-order"
deal_ticket_warning_margin = "deal-ticket-warning-margin"
deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("200000")
page.get_by_test_id(order_price).fill("20")
# 7002-SORD-060
expect(page.get_by_test_id(deal_ticket_warning_margin)).to_have_text("You may not have enough margin available to open this position.")
expect(page.get_by_test_id(deal_ticket_warning_margin)).to_have_text(
"You may not have enough margin available to open this position.")
page.get_by_test_id(deal_ticket_warning_margin).hover()
expect(page.get_by_test_id("tooltip-content").nth(0)).to_have_text("1,661,896.6317 tDAI is currently required.You have only 1,000,000.00.Deposit tDAI")
expect(page.get_by_test_id("tooltip-content").nth(0)).to_have_text(
"1,661,896.6317 tDAI is currently required.You have only 1,000,000.00.Deposit tDAI")
page.get_by_test_id(deal_ticket_deposit_dialog_button).nth(0).click()
expect(page.get_by_test_id("sidebar-content")).to_contain_text("DepositFrom")
expect(page.get_by_test_id("sidebar-content")
).to_contain_text("DepositFrom")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -42,5 +49,6 @@ def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: V
# 7002-SORD-060
expect(page.get_by_test_id(place_order)).to_be_enabled()
# 7002-SORD-003
expect(page.get_by_test_id("deal-ticket-error-message-zero-balance")).to_have_text("You need tDAI in your wallet to trade in this market.Make a deposit")
expect(page.get_by_test_id(deal_ticket_deposit_dialog_button)).to_be_visible()
expect(page.get_by_test_id("deal-ticket-error-message-zero-balance")
).to_have_text("You need tDAI in your wallet to trade in this market.Make a deposit")
expect(page.get_by_test_id(deal_ticket_deposit_dialog_button)).to_be_visible()
@@ -10,15 +10,18 @@ import logging
logger = logging.getLogger()
@pytest.fixture(scope="class")
def vega():
with init_vega() as vega:
yield vega
@pytest.fixture(scope="class")
def simple_market(vega: VegaServiceNull):
return setup_simple_market(vega)
class TestGetStarted:
def test_get_started_interactive(self, vega: VegaServiceNull, page: Page):
page.goto("/")
@@ -30,7 +33,8 @@ class TestGetStarted:
expect(page.locator(".list-none")).to_contain_text(
"1.Connect2.Deposit funds3.Open a position"
)
DEFAULT_WALLET_NAME = "MarketSim" # This is the default wallet name within VegaServiceNull and CANNOT be changed
# This is the default wallet name within VegaServiceNull and CANNOT be changed
DEFAULT_WALLET_NAME = "MarketSim"
# Calling get_keypairs will internally call _load_tokens for the given wallet
keypairs = vega.wallet.get_keypairs(DEFAULT_WALLET_NAME)
@@ -137,7 +141,8 @@ class TestGetStarted:
def test_get_started_seen_already(self, simple_market, page: Page):
page.goto(f"/#/markets/{simple_market}")
get_started_locator = page.get_by_test_id("connect-vega-wallet")
page.wait_for_selector('[data-testid="connect-vega-wallet"]', state="attached")
page.wait_for_selector(
'[data-testid="connect-vega-wallet"]', state="attached")
expect(get_started_locator).to_be_enabled
expect(get_started_locator).to_be_visible
# 0007-FUGS-015
@@ -36,16 +36,19 @@ def validate_info_section(page: Page, fields: [[str, str]]):
for rowNumber, field in enumerate(fields):
name, value = field
expect(
page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dt")
page.get_by_test_id(
"key-value-table-row").nth(rowNumber).locator("dt")
).to_contain_text(name)
expect(
page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dd")
page.get_by_test_id(
"key-value-table-row").nth(rowNumber).locator("dd")
).to_contain_text(value)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_current_fees(page: Page):
# 6002-MDET-101
page.get_by_test_id(market_title_test_id).get_by_text("Current fees").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Current fees").click()
fields = [
["Maker Fee", "10%"],
["Infrastructure Fee", "0.05%"],
@@ -54,10 +57,11 @@ def test_market_info_current_fees(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_price(page: Page):
# 6002-MDET-102
page.get_by_test_id(market_title_test_id).get_by_text("Market price").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Market price").click()
fields = [
["Mark Price", "107.50"],
["Best Bid Price", "101.50"],
@@ -66,10 +70,11 @@ def test_market_info_market_price(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_volume(page: Page):
# 6002-MDET-103
page.get_by_test_id(market_title_test_id).get_by_text("Market volume").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Market volume").click()
fields = [
["24 Hour Volume", "-"],
["Open Interest", "1"],
@@ -80,17 +85,32 @@ def test_market_info_market_volume(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_insurance_pool(page: Page):
# 6002-MDET-104
page.get_by_test_id(market_title_test_id).get_by_text("Insurance pool").click()
fields = [["Balance", "0.00 tDAI"]]
def test_market_info_liquidation_strategy(page: Page):
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidation strategy").click()
fields = [
["Disposal Fraction", "1"],
["Disposal Time Step", "1"],
["Full Disposal Size", "1,000,000,000"],
["Max Fraction Consumed", "0.5"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidation(page: Page):
# 6002-MDET-104
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidations").click()
fields = [["Insurance Pool Balance", "0.00 tDAI"]]
validate_info_section(page, fields)
@pytest.mark.skip("core issue #5681")
def test_market_info_key_details(page: Page, vega: VegaServiceNull):
# 6002-MDET-201
page.get_by_test_id(market_title_test_id).get_by_text("Key details").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Key details").click()
market_id = vega.find_market_id("BTC:DAI_2023")
short_market_id = market_id[:6] + "" + market_id[-4:]
fields = [
@@ -106,7 +126,7 @@ def test_market_info_key_details(page: Page, vega: VegaServiceNull):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_instrument(page: Page):
# 6002-MDET-202
page.get_by_test_id(market_title_test_id).get_by_text("Instrument").click()
@@ -121,7 +141,7 @@ def test_market_info_instrument(page: Page):
# @pytest.mark.skip("oracle test to be fixed")
@pytest.mark.skip("tbd-market-sim")
def test_market_info_oracle(page: Page):
# 6002-MDET-203
page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click()
@@ -135,10 +155,11 @@ def test_market_info_oracle(page: Page):
# "href", re.compile(rf'(\/oracles\/{vega.find_market_id("BTC:DAI_2023")})')
# )
@pytest.mark.skip("tbd-market-sim")
def test_market_info_settlement_asset(page: Page, vega: VegaServiceNull):
# 6002-MDET-206
page.get_by_test_id(market_title_test_id).get_by_text("Settlement asset").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Settlement asset").click()
tdai_id = vega.find_asset_id("tDAI")
tdai_id_short = tdai_id[:6] + "" + tdai_id[-4:]
fields = [
@@ -155,7 +176,7 @@ def test_market_info_settlement_asset(page: Page, vega: VegaServiceNull):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_metadata(page: Page):
# 6002-MDET-207
page.get_by_test_id(market_title_test_id).get_by_text("Metadata").click()
@@ -164,7 +185,7 @@ def test_market_info_metadata(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_model(page: Page):
# 6002-MDET-208
page.get_by_test_id(market_title_test_id).get_by_text("Risk model").click()
@@ -175,7 +196,7 @@ def test_market_info_risk_model(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_margin_scaling_factors(page: Page):
# 6002-MDET-209
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -183,17 +204,17 @@ def test_market_info_margin_scaling_factors(page: Page):
).click()
fields = [
["Linear Slippage Factor", "0.001"],
["Quadratic Slippage Factor", "0"],
["Search Level", "1.1"],
["Initial Margin", "1.5"],
["Collateral Release", "1.7"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_factors(page: Page):
# 6002-MDET-210
page.get_by_test_id(market_title_test_id).get_by_text("Risk factors").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Risk factors").click()
fields = [
["Long", "0.05153"],
["Short", "0.05422"],
@@ -204,7 +225,7 @@ def test_market_info_risk_factors(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_price_monitoring_bounds(page: Page):
# 6002-MDET-211
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -213,27 +234,27 @@ def test_market_info_price_monitoring_bounds(page: Page):
expect(page.locator("p.col-span-1").nth(0)).to_contain_text(
"99.9999% probability price bounds"
)
expect(page.locator("p.col-span-1").nth(1)).to_contain_text("Within 86,400 seconds")
expect(page.locator("p.col-span-1").nth(1)
).to_contain_text("Within 86,400 seconds")
fields = [
["Highest Price", "138.66685 BTC"],
["Lowest Price", "83.11038 BTC"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_monitoring_parameters(page: Page):
# 6002-MDET-212
page.get_by_test_id(market_title_test_id).get_by_text(
"Liquidity monitoring parameters"
).click()
fields = [
["Triggering Ratio", "0.7"],
["Time Window", "3,600"],
["Scaling Factor", "1"],
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
# Liquidity resolves to 3 results
def test_market_info_liquidit(page: Page):
# 6002-MDET-213
@@ -246,7 +267,7 @@ def test_market_info_liquidit(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_price_range(page: Page):
# 6002-MDET-214
page.get_by_test_id(market_title_test_id).get_by_text(
@@ -259,19 +280,22 @@ def test_market_info_liquidity_price_range(page: Page):
]
validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_proposal(page: Page, vega: VegaServiceNull):
# 6002-MDET-301
page.get_by_test_id(market_title_test_id).get_by_text("Proposal").click()
first_link = (
page.get_by_test_id("accordion-content").get_by_test_id("external-link").first
page.get_by_test_id(
"accordion-content").get_by_test_id("external-link").first
)
second_link = (
page.get_by_test_id("accordion-content").get_by_test_id("external-link").nth(1)
page.get_by_test_id(
"accordion-content").get_by_test_id("external-link").nth(1)
)
expect(first_link).to_have_text("View governance proposal")
expect(first_link).to_have_attribute(
"href", re.compile(rf'(\/proposals\/{vega.find_market_id("BTC:DAI_2023")})')
"href", re.compile(
rf'(\/proposals\/{vega.find_market_id("BTC:DAI_2023")})')
)
expect(second_link).to_have_text("Propose a change to market")
@@ -280,13 +304,14 @@ def test_market_info_proposal(page: Page, vega: VegaServiceNull):
"href", re.compile(r"(\/proposals\/propose\/update-market)")
)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_succession_line(page: Page, vega: VegaServiceNull):
page.get_by_test_id(market_title_test_id).get_by_text("Succession line").click()
page.get_by_test_id(market_title_test_id).get_by_text(
"Succession line").click()
market_id = vega.find_market_id("BTC:DAI_2023")
succession_line = page.get_by_test_id("succession-line-item")
expect(succession_line.get_by_test_id("external-link")).to_have_text("BTC:DAI_2023")
expect(succession_line.get_by_test_id(
"external-link")).to_have_text("BTC:DAI_2023")
expect(succession_line.get_by_test_id("external-link")).to_have_attribute(
"href", re.compile(rf"(\/proposals\/{market_id})")
)
@@ -128,10 +128,11 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
position["leverage"]
)
liquidation = table.locator("[col-id='liquidationPrice']")
expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
position["liquidation"]
)
# need to ne check why it is not visible
# liquidation = table.locator("[col-id='liquidationPrice']")
# expect(liquidation.get_by_test_id("liquidation-price")).to_have_text(
# position["liquidation"]
# )
realisedPNL = table.locator("[col-id='realisedPNL']")
expect(realisedPNL).to_have_text(position["realised_pnl"])
@@ -46,7 +46,9 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
side="SIDE_BUY",
volume=1,
)
next_epoch(vega=vega)
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto("/#/rewards")
vega.update_market_state(
market_id=continuous_market,
@@ -55,8 +57,9 @@ def test_filtered_cards(continuous_market, vega: VegaServiceNull, page: Page):
forward_time_to_enactment=True,
)
next_epoch(vega=vega)
page.goto("/#/rewards")
expect(page.locator(".from-vega-cdark-400")).to_be_visible()
page.reload()
expect(page.locator(".from-vega-cdark-400")).to_be_visible(timeout=15000)
governance.submit_oracle_data(
wallet=vega.wallet,
payload={"trading.terminated": "true"},
@@ -8,7 +8,7 @@ from actions.utils import next_epoch
market_banner = "market-banner"
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("risk_accepted")
def test_succession_line(vega: VegaServiceNull, page: Page):
parent_market_id = setup_continuous_market(vega)
@@ -20,12 +20,14 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
successor_name = "successor market name"
successor_id = propose_successor(vega, parent_market_id, tdai_id, successor_name)
successor_id = propose_successor(
vega, parent_market_id, tdai_id, successor_name)
# Check that the banner notifying about the successor proposal is shown
banner = page.get_by_test_id(market_banner)
expect(banner).to_be_attached()
expect(banner.get_by_text("A successor to this market has been proposed")).to_be_visible()
expect(banner.get_by_text(
"A successor to this market has been proposed")).to_be_visible()
next_epoch(vega)
@@ -45,7 +47,6 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
# the succession line
page.reload()
#tbd issue - 5546
page.get_by_test_id("Info").click()
page.get_by_role("button", name="Succession line").click()
@@ -78,6 +79,7 @@ def test_succession_line(vega: VegaServiceNull, page: Page):
page.wait_for_selector('[data-testid="market-banner"]', state="attached")
expect(banner.get_by_text("This market has been succeeded")).to_be_visible()
@pytest.mark.usefixtures("risk_accepted")
def test_banners(vega: VegaServiceNull, page: Page):
@@ -91,9 +93,9 @@ def test_banners(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
vega.submit_termination_and_settlement_data(
settlement_key=GOVERNANCE_WALLET.name,
settlement_price=100,
market_id=parent_market_id,
settlement_key=GOVERNANCE_WALLET.name,
settlement_price=100,
market_id=parent_market_id,
)
successor_name = "successor market name"
@@ -108,7 +110,7 @@ def test_banners(vega: VegaServiceNull, page: Page):
# Check that the banner notifying about the successor proposal and market has been settled are shown still after reload
page.reload()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
expect(banner.get_by_text("1/2")).to_be_visible()
expect(banner.get_by_text("1/2")).to_be_visible()
# Check that the banner notifying about the successor proposal is not visible after close those banners
banner.get_by_test_id("icon-cross").click()
expect(banner.get_by_text("This market has been settled")).to_be_visible()
@@ -119,7 +121,8 @@ def test_banners(vega: VegaServiceNull, page: Page):
expect(page.get_by_test_id(market_banner)).not_to_be_attached()
page.reload()
expect(banner).to_be_attached()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
expect(banner.get_by_text(banner_successor_text)).to_be_visible()
def propose_successor(
vega: VegaServiceNull, parent_market_id, tdai_id, market_name
@@ -137,6 +140,7 @@ def propose_successor(
)
return market_id
def provide_successor_liquidity(
vega: VegaServiceNull, market_id
):
+3 -2
View File
@@ -137,6 +137,7 @@ def create_team(vega: VegaServiceNull):
return team_name
def test_team_page_games_table(team_page: Page):
team_page.pause()
team_page.get_by_test_id("games-toggle").click()
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)")
expect(team_page.get_by_test_id("rank-0")).to_have_text("1")
@@ -152,7 +153,7 @@ def test_team_page_games_table(team_page: Page):
def test_team_page_members_table(team_page: Page):
team_page.get_by_test_id("members-toggle").click()
expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (3)")
expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (4)")
expect(team_page.get_by_test_id("referee-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAt-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("8")
@@ -161,7 +162,7 @@ def test_team_page_headline(team_page: Page, setup_teams_and_games
):
team_name = setup_teams_and_games["team_name"]
expect(team_page.get_by_test_id("team-name")).to_have_text(team_name)
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("3")
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4")
expect(team_page.get_by_test_id("total-games-stat")).to_have_text(
"1"
+18 -1
View File
@@ -17,7 +17,7 @@ fragment TeamStatsFields on TeamStatistics {
totalGamesPlayed
quantumRewards {
epoch
total_quantum_rewards
totalQuantumRewards
}
gamesPlayed
}
@@ -51,6 +51,13 @@ fragment TeamGameFields on Game {
}
}
fragment TeamMemberStatsFields on TeamMemberStatistics {
partyId
totalQuantumVolume
totalQuantumRewards
totalGamesPlayed
}
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) {
edges {
@@ -87,4 +94,14 @@ query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
}
}
}
teamMembersStatistics(
teamId: $teamId
aggregationEpochs: $aggregationEpochs
) {
edges {
node {
...TeamMemberStatsFields
}
}
}
}
+22 -4
View File
@@ -5,7 +5,7 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type TeamFieldsFragment = { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> };
export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, total_quantum_rewards: string }> };
export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> };
export type TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number };
@@ -13,6 +13,8 @@ export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number,
export type TeamGameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> };
export type TeamMemberStatsFieldsFragment = { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number };
export type TeamQueryVariables = Types.Exact<{
teamId: Types.Scalars['ID'];
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
@@ -20,7 +22,7 @@ export type TeamQueryVariables = Types.Exact<{
}>;
export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, total_quantum_rewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null } };
export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null }, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null };
export const TeamFieldsFragmentDoc = gql`
fragment TeamFields on Team {
@@ -43,7 +45,7 @@ export const TeamStatsFieldsFragmentDoc = gql`
totalGamesPlayed
quantumRewards {
epoch
total_quantum_rewards
totalQuantumRewards
}
gamesPlayed
}
@@ -80,6 +82,14 @@ export const TeamGameFieldsFragmentDoc = gql`
}
}
${TeamEntityFragmentDoc}`;
export const TeamMemberStatsFieldsFragmentDoc = gql`
fragment TeamMemberStatsFields on TeamMemberStatistics {
partyId
totalQuantumVolume
totalQuantumRewards
totalGamesPlayed
}
`;
export const TeamDocument = gql`
query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
teams(teamId: $teamId) {
@@ -117,11 +127,19 @@ export const TeamDocument = gql`
}
}
}
teamMembersStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
edges {
node {
...TeamMemberStatsFields
}
}
}
}
${TeamFieldsFragmentDoc}
${TeamStatsFieldsFragmentDoc}
${TeamRefereeFieldsFragmentDoc}
${TeamGameFieldsFragmentDoc}`;
${TeamGameFieldsFragmentDoc}
${TeamMemberStatsFieldsFragmentDoc}`;
/**
* __useTeamQuery__
+14 -3
View File
@@ -1,12 +1,23 @@
import compact from 'lodash/compact';
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../components/rewards-container/active-rewards';
import { EntityScope, type TransferNode } from '@vegaprotocol/types';
import {
EntityScope,
IndividualScope,
type TransferNode,
} from '@vegaprotocol/types';
const isScopedToTeams = (node: TransferNode) =>
node.transfer.kind.__typename === 'RecurringTransfer' &&
node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_TEAMS;
// scoped to teams
(node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_TEAMS ||
// or to individuals
(node.transfer.kind.dispatchStrategy?.entityScope ===
EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
// but they have to be in a team
node.transfer.kind.dispatchStrategy.individualScope ===
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM));
export const useGames = ({
currentEpoch,
+49 -10
View File
@@ -6,17 +6,24 @@ import {
type TeamStatsFieldsFragment,
type TeamRefereeFieldsFragment,
type TeamEntityFragment,
type TeamMemberStatsFieldsFragment,
} from './__generated__/Team';
import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams';
export type Team = TeamFieldsFragment;
export type TeamStats = TeamStatsFieldsFragment;
export type Member = TeamRefereeFieldsFragment;
export type Member = TeamRefereeFieldsFragment & {
isCreator: boolean;
totalGamesPlayed: number;
totalQuantumVolume: string;
totalQuantumRewards: string;
};
export type TeamEntity = TeamEntityFragment;
export type TeamGame = ReturnType<typeof useTeam>['games'][number];
export type MemberStats = TeamMemberStatsFieldsFragment;
export const useTeam = (teamId?: string, partyId?: string) => {
const { data, loading, error, refetch } = useTeamQuery({
const queryResult = useTeamQuery({
variables: {
teamId: teamId || '',
partyId,
@@ -26,7 +33,11 @@ export const useTeam = (teamId?: string, partyId?: string) => {
fetchPolicy: 'cache-and-network',
});
const { data } = queryResult;
const teamEdge = data?.teams?.edges.find((e) => e.node.teamId === teamId);
const team = teamEdge?.node;
const partyTeam = data?.partyTeams?.edges?.length
? data.partyTeams.edges[0].node
: undefined;
@@ -34,9 +45,40 @@ export const useTeam = (teamId?: string, partyId?: string) => {
const teamStatsEdge = data?.teamsStatistics?.edges.find(
(e) => e.node.teamId === teamId
);
const members = data?.teamReferees?.edges
.filter((e) => e.node.teamId === teamId)
.map((e) => e.node);
const memberStats = data?.teamMembersStatistics?.edges.length
? data.teamMembersStatistics.edges.map((e) => e.node)
: [];
const members: Member[] = data?.teamReferees?.edges.length
? data.teamReferees.edges
.filter((e) => e.node.teamId === teamId)
.map((e) => {
const member = e.node;
const stats = memberStats.find((m) => m.partyId === member.referee);
return {
...member,
isCreator: false,
totalQuantumVolume: stats ? stats.totalQuantumVolume : '0',
totalQuantumRewards: stats ? stats.totalQuantumRewards : '0',
totalGamesPlayed: stats ? stats.totalGamesPlayed : 0,
};
})
: [];
if (team) {
const ownerStats = memberStats.find((m) => m.partyId === team.referrer);
members.unshift({
teamId: team.teamId,
referee: team.referrer,
joinedAt: team?.createdAt,
joinedAtEpoch: team?.createdAtEpoch,
isCreator: true,
totalQuantumVolume: ownerStats ? ownerStats.totalQuantumVolume : '0',
totalQuantumRewards: ownerStats ? ownerStats.totalQuantumRewards : '0',
totalGamesPlayed: ownerStats ? ownerStats.totalGamesPlayed : 0,
});
}
// Find games where the current team participated in
const gamesWithTeam = compact(data?.games.edges).map((edge) => {
@@ -60,12 +102,9 @@ export const useTeam = (teamId?: string, partyId?: string) => {
const games = orderBy(compact(gamesWithTeam), 'epoch', 'desc');
return {
data,
loading,
error,
refetch,
...queryResult,
stats: teamStatsEdge?.node,
team: teamEdge?.node,
team,
members,
games,
partyTeam,
+8 -33
View File
@@ -1,34 +1,13 @@
import orderBy from 'lodash/orderBy';
import { useMemo } from 'react';
import { type TeamsQuery, useTeamsQuery } from './__generated__/Teams';
import {
type TeamsStatisticsQuery,
useTeamsStatisticsQuery,
} from './__generated__/TeamsStatistics';
import { useTeamsQuery } from './__generated__/Teams';
import { useTeamsStatisticsQuery } from './__generated__/TeamsStatistics';
import compact from 'lodash/compact';
import sortBy from 'lodash/sortBy';
import { type ArrayElement } from 'type-fest/source/internal';
type SortableField = keyof Omit<
ArrayElement<NonNullable<TeamsQuery['teams']>['edges']>['node'] &
ArrayElement<
NonNullable<TeamsStatisticsQuery['teamsStatistics']>['edges']
>['node'],
'__typename'
>;
// 192
export const DEFAULT_AGGREGATION_EPOCHS = 192;
type UseTeamsArgs = {
aggregationEpochs?: number;
sortByField?: SortableField[];
order?: 'asc' | 'desc';
};
export const DEFAULT_AGGREGATION_EPOCHS = 10;
export const useTeams = ({
aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS,
sortByField = ['createdAtEpoch'],
order = 'asc',
}: UseTeamsArgs) => {
export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
const {
data: teamsData,
loading: teamsLoading,
@@ -57,12 +36,8 @@ export const useTeams = ({
...stats.find((s) => s.teamId === t.teamId),
}));
const sorted = sortBy(data, sortByField);
if (order === 'desc') {
return sorted.reverse();
}
return sorted;
}, [teams, sortByField, order, stats]);
return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc');
}, [teams, stats]);
return {
data,
+1 -1
View File
@@ -38,7 +38,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
{
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.code',
width: 90,
maxWidth: 150,
pinned: true,
sort: 'desc',
cellRenderer: ({
@@ -1,15 +0,0 @@
import type { Account } from './accounts-data-provider';
import * as Schema from '@vegaprotocol/types';
interface Props {
accounts: Account[] | null;
marketId: string;
}
export const getMarketAccount = ({ accounts, marketId }: Props) =>
accounts?.find((account) => {
return (
account.market?.id === marketId &&
account.type === Schema.AccountType.ACCOUNT_TYPE_MARGIN
);
}) || null;
+1 -2
View File
@@ -6,8 +6,7 @@ export * from './accounts-manager';
export * from './breakdown-table';
export * from './use-account-balance';
export * from './get-settlement-account';
export * from './use-market-account-balance';
export * from './use-margin-account-balance';
export * from './__generated__/Margins';
export { MarginHealthChart } from './margin-health-chart';
export * from './margin-data-provider';
export * from './transfer-container';
@@ -83,3 +83,25 @@ export const marketMarginDataProvider = makeDerivedDataProvider<
(margin) => margin.market.id === marketId
) || null
);
export type MarginModeData = Pick<
MarginFieldsFragment,
'marginMode' | 'marginFactor'
>;
export const marginModeDataProvider = makeDerivedDataProvider<
MarginModeData,
never,
MarginsQueryVariables & { marketId: string }
>([marketMarginDataProvider], ([data], variables, previousData) =>
produce(previousData, (draft) => {
if (!data) {
return data;
}
const newData = {
marginMode: (data as MarginFieldsFragment).marginMode,
marginFactor: (data as MarginFieldsFragment).marginFactor,
};
return draft ? Object.assign(draft, newData) : newData;
})
);
@@ -1,253 +0,0 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { Tooltip, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketMarginDataProvider } from './margin-data-provider';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useT, ns } from './use-t';
import { useAccountBalance } from './use-account-balance';
import { useMarketAccountBalance } from './use-market-account-balance';
import { Trans } from 'react-i18next';
const MarginHealthChartTooltipRow = ({
label,
value,
decimals,
href,
}: {
label: string;
value: string;
decimals: number;
href?: string;
}) => (
<>
<div
className="float-left clear-left"
key="label"
data-testid="margin-health-tooltip-label"
>
{href ? (
<ExternalLink href={href} target="_blank">
{label}
</ExternalLink>
) : (
label
)}
</div>
<div
className="float-right"
key="value"
data-testid="margin-health-tooltip-value"
>
{addDecimalsFormatNumber(value, decimals)}
</div>
</>
);
export const MarginHealthChartTooltip = ({
maintenanceLevel,
searchLevel,
initialLevel,
collateralReleaseLevel,
decimals,
marginAccountBalance,
}: {
maintenanceLevel: string;
searchLevel: string;
initialLevel: string;
collateralReleaseLevel: string;
decimals: number;
marginAccountBalance?: string;
}) => {
const t = useT();
const tooltipContent = [
<MarginHealthChartTooltipRow
key={'maintenance'}
label={t('maintenance level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance"
value={maintenanceLevel}
decimals={decimals}
/>,
<MarginHealthChartTooltipRow
key={'search'}
label={t('search level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-searching-for-collateral"
value={searchLevel}
decimals={decimals}
/>,
<MarginHealthChartTooltipRow
key={'initial'}
label={t('initial level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-initial"
value={initialLevel}
decimals={decimals}
/>,
<MarginHealthChartTooltipRow
key={'release'}
label={t('release level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-releasing-collateral"
value={collateralReleaseLevel}
decimals={decimals}
/>,
];
if (marginAccountBalance) {
const balance = (
<MarginHealthChartTooltipRow
key={'balance'}
label={t('balance')}
value={marginAccountBalance}
decimals={decimals}
/>
);
if (BigInt(marginAccountBalance) < BigInt(searchLevel)) {
tooltipContent.splice(1, 0, balance);
} else if (BigInt(marginAccountBalance) < BigInt(initialLevel)) {
tooltipContent.splice(2, 0, balance);
} else if (BigInt(marginAccountBalance) < BigInt(collateralReleaseLevel)) {
tooltipContent.splice(3, 0, balance);
} else {
tooltipContent.push(balance);
}
}
return (
<div className="overflow-hidden" data-testid="margin-health-tooltip">
{tooltipContent}
</div>
);
};
export const MarginHealthChart = ({
marketId,
assetId,
}: {
marketId: string;
assetId: string;
}) => {
const { data: assetsMap } = useAssetsMapProvider();
const { pubKey: partyId } = useVegaWallet();
const { data } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: { marketId, partyId: partyId ?? '' },
skip: !partyId,
});
const { accountBalance: rawGeneralAccountBalance } =
useAccountBalance(assetId);
const { accountBalance: rawMarginAccountBalance } =
useMarketAccountBalance(marketId);
const asset = assetsMap && assetsMap[assetId];
if (!data || !asset) {
return null;
}
const { decimals } = asset;
const collateralReleaseLevel = Number(data.collateralReleaseLevel);
const initialLevel = Number(data.initialLevel);
const maintenanceLevel = Number(data.maintenanceLevel);
const searchLevel = Number(data.searchLevel);
const marginAccountBalance = Number(rawMarginAccountBalance);
const generalAccountBalance = Number(rawGeneralAccountBalance);
const max = Math.max(
marginAccountBalance + generalAccountBalance,
collateralReleaseLevel
);
const red = maintenanceLevel / max;
const orange = (searchLevel - maintenanceLevel) / max;
const yellow = ((searchLevel + initialLevel) / 2 - searchLevel) / max;
const green = (collateralReleaseLevel - initialLevel) / max + yellow;
const balanceMarker = marginAccountBalance / max;
const tooltip = (
<MarginHealthChartTooltip
maintenanceLevel={data.maintenanceLevel}
searchLevel={data.searchLevel}
initialLevel={data.initialLevel}
collateralReleaseLevel={data.collateralReleaseLevel}
marginAccountBalance={rawMarginAccountBalance}
decimals={decimals}
/>
);
return (
<div data-testid="margin-health-chart">
<Trans
defaults="{{balance}} above <0>maintenance level</0>"
components={[
<ExternalLink href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance">
maintenance level
</ExternalLink>,
]}
values={{
balance: addDecimalsFormatNumber(
(
BigInt(marginAccountBalance) - BigInt(maintenanceLevel)
).toString(),
decimals
),
}}
ns={ns}
/>
<Tooltip description={tooltip}>
<div
data-testid="margin-health-chart-track"
className="relative bg-vega-green-650"
style={{
height: '6px',
marginBottom: '1px',
display: 'flex',
}}
>
<div
data-testid="margin-health-chart-red"
className="bg-vega-red-550"
style={{
height: '100%',
width: `${red * 100}%`,
}}
></div>
<div
data-testid="margin-health-chart-orange"
className="bg-vega-orange"
style={{
height: '100%',
width: `${orange * 100}%`,
}}
></div>
<div
data-testid="margin-health-chart-yellow"
className="bg-vega-yellow"
style={{
height: '100%',
width: `${yellow * 100}%`,
}}
></div>
<div
data-testid="margin-health-chart-green"
className="bg-vega-green-600"
style={{
height: '100%',
width: `${green * 100}%`,
}}
></div>
{balanceMarker > 0 && balanceMarker < 100 && (
<div
data-testid="margin-health-chart-balance"
className="absolute bg-vega-blue"
style={{
height: '8px',
width: '8px',
top: '-1px',
transform: 'translate(-4px, 0px)',
borderRadius: '50%',
border: '1px solid white',
backgroundColor: 'blue',
left: `${balanceMarker * 100}%`,
}}
></div>
)}
</div>
</Tooltip>
</div>
);
};
@@ -1,158 +0,0 @@
import {
MarginHealthChart,
MarginHealthChartTooltip,
} from './margin-health-chart';
import { act, render, screen } from '@testing-library/react';
import type { MarginFieldsFragment } from './__generated__/Margins';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { MarginMode } from '@vegaprotocol/types';
const asset: AssetFieldsFragment = {
id: 'assetId',
decimals: 2,
} as AssetFieldsFragment;
const margins: MarginFieldsFragment = {
asset: {
id: 'assetId',
},
collateralReleaseLevel: '1000',
initialLevel: '800',
searchLevel: '600',
maintenanceLevel: '400',
marginFactor: '',
marginMode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
orderMarginLevel: '',
market: {
id: 'marketId',
},
};
const mockGetMargins = jest.fn(() => margins);
const mockGetBalance = jest.fn(() => '0');
jest.mock('./margin-data-provider', () => ({}));
jest.mock('@vegaprotocol/assets', () => ({
useAssetsMapProvider: () => {
return {
data: {
assetId: asset,
},
};
},
}));
jest.mock('@vegaprotocol/wallet', () => ({
useVegaWallet: () => {
return {
pubKey: 'partyId',
};
},
}));
jest.mock('@vegaprotocol/data-provider', () => ({
useDataProvider: () => {
return {
data: mockGetMargins(),
};
},
}));
jest.mock('./use-account-balance', () => ({
useAccountBalance: () => {
return {
accountBalance: mockGetBalance(),
};
},
}));
jest.mock('./use-market-account-balance', () => ({
useMarketAccountBalance: () => {
return {
accountBalance: '700',
};
},
}));
describe('MarginHealthChart', () => {
it('should render correct values', async () => {
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
const chart = screen.getByTestId('margin-health-chart');
expect(chart).toHaveTextContent('3.00 above maintenance level');
const red = screen.getByTestId('margin-health-chart-red');
const orange = screen.getByTestId('margin-health-chart-orange');
const yellow = screen.getByTestId('margin-health-chart-yellow');
const green = screen.getByTestId('margin-health-chart-green');
const balance = screen.getByTestId('margin-health-chart-balance');
expect(parseInt(red.style.width)).toBe(40);
expect(parseInt(orange.style.width)).toBe(20);
expect(parseInt(yellow.style.width)).toBe(10);
expect(parseInt(green.style.width)).toBe(30);
expect(parseInt(balance.style.left)).toBe(70);
});
it('should use correct scale', async () => {
mockGetBalance.mockReturnValueOnce('1300');
await act(async () => {
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
});
await screen.findByTestId('margin-health-chart');
const red = screen.getByTestId('margin-health-chart-red');
expect(parseInt(red.style.width)).toBe(20);
});
});
describe('MarginHealthChartTooltip', () => {
it('renders correct values and labels', async () => {
await act(async () => {
render(
<MarginHealthChartTooltip
{...margins}
decimals={asset.decimals}
marginAccountBalance="500"
/>
);
});
const labels = await screen.findAllByTestId('margin-health-tooltip-label');
const expectedLabels = [
'maintenance level',
'balance',
'search level',
'initial level',
'release level',
];
labels.forEach((value, i) => {
expect(value).toHaveTextContent(expectedLabels[i]);
});
const values = await screen.findAllByTestId('margin-health-tooltip-value');
const expectedValues = ['4.00', '5.00', '6.00', '8.00', '10.00'];
values.forEach((value, i) => {
expect(value).toHaveTextContent(expectedValues[i]);
});
});
it('renders balance in correct place', async () => {
const { rerender } = render(
<MarginHealthChartTooltip
{...margins}
decimals={asset.decimals}
marginAccountBalance="700"
/>
);
let values = await screen.findAllByTestId('margin-health-tooltip-value');
expect(values[2]).toHaveTextContent('7.00');
rerender(
<MarginHealthChartTooltip
{...margins}
decimals={asset.decimals}
marginAccountBalance="900"
/>
);
values = await screen.findAllByTestId('margin-health-tooltip-value');
expect(values.length).toBe(5);
expect(values[3]).toHaveTextContent('9.00');
});
});
@@ -0,0 +1,68 @@
import { useCallback, useMemo, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { accountsDataProvider } from './accounts-data-provider';
import type { Account } from './accounts-data-provider';
import { AccountType } from '@vegaprotocol/types';
export const useMarginAccountBalance = (marketId: string) => {
const { pubKey } = useVegaWallet();
const [marginAccountBalance, setMarginAccountBalance] = useState<string>('');
const [orderMarginAccountBalance, setOrderMarginAccountBalance] =
useState<string>('');
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
const update = useCallback(
({ data }: { data: Account[] | null }) => {
const marginAccount = data?.find((account) => {
return (
account.market?.id === marketId &&
account.type === AccountType.ACCOUNT_TYPE_MARGIN
);
});
const orderMarginAccount = data?.find((account) => {
return (
account.market?.id === marketId &&
account.type === AccountType.ACCOUNT_TYPE_ORDER_MARGIN
);
});
if (marginAccount?.balance) {
setMarginAccountBalance(marginAccount?.balance || '');
}
if (orderMarginAccount?.balance) {
setOrderMarginAccountBalance(orderMarginAccount?.balance || '');
}
const decimals =
orderMarginAccount?.asset.decimals || marginAccount?.asset.decimals;
if (decimals) {
setAccountDecimals(decimals);
}
return true;
},
[marketId]
);
const { loading, error } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey || !marketId,
update,
});
return useMemo(
() => ({
marginAccountBalance: pubKey ? marginAccountBalance : '',
orderMarginAccountBalance: pubKey ? orderMarginAccountBalance : '',
accountDecimals: pubKey ? accountDecimals : null,
loading,
error,
}),
[
marginAccountBalance,
orderMarginAccountBalance,
accountDecimals,
pubKey,
loading,
error,
]
);
};
@@ -1,41 +0,0 @@
import { useCallback, useMemo, useState } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { accountsDataProvider } from './accounts-data-provider';
import type { Account } from './accounts-data-provider';
import { getMarketAccount } from './get-market-account';
export const useMarketAccountBalance = (marketId: string) => {
const { pubKey } = useVegaWallet();
const [accountBalance, setAccountBalance] = useState<string>('');
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
const update = useCallback(
({ data }: { data: Account[] | null }) => {
const account = getMarketAccount({ accounts: data, marketId });
if (account?.balance) {
setAccountBalance(account?.balance || '');
}
if (account?.asset.decimals) {
setAccountDecimals(account?.asset.decimals || null);
}
return true;
},
[marketId]
);
const { loading, error } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey || !marketId,
update,
});
return useMemo(
() => ({
accountBalance: pubKey ? accountBalance : '',
accountDecimals: pubKey ? accountDecimals : null,
loading,
error,
}),
[accountBalance, accountDecimals, pubKey, loading, error]
);
};
@@ -7,6 +7,7 @@ import { AssetsDocument, type AssetsQuery } from './__generated__/Assets';
import { AssetStatus } from '@vegaprotocol/types';
import { type Asset } from './asset-data-provider';
import { DENY_LIST } from './constants';
import { type AssetFieldsFragment } from './__generated__/Asset';
export interface BuiltinAssetSource {
__typename: 'BuiltinAsset';
@@ -29,6 +30,7 @@ export const assetsProvider = makeDataProvider<
>({
query: AssetsDocument,
getData,
errorPolicy: 'all',
});
export const assetsMapProvider = makeDerivedDataProvider<
@@ -88,3 +90,24 @@ export const useEnabledAssets = () => {
variables: undefined,
});
};
/** Wrapped ETH symbol */
const WETH = 'WETH';
type WETHDetails = Pick<AssetFieldsFragment, 'symbol' | 'decimals' | 'quantum'>;
/**
* Tries to find WETH asset configuration on Vega in order to provide its
* details, otherwise it returns hardcoded values.
*/
export const useWETH = (): WETHDetails => {
const { data } = useAssetsDataProvider();
if (data) {
const weth = data.find((a) => a.symbol.toUpperCase() === WETH);
if (weth) return weth;
}
return {
symbol: WETH,
decimals: 18,
quantum: '500000000000000', // 1 WETH ~= 2000 qUSD
};
};
@@ -20,6 +20,9 @@ query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
code
}
}
marketTimestamps {
open
}
candlesConnection(
interval: $interval
since: $since
+4 -1
View File
@@ -12,7 +12,7 @@ export type CandlesQueryVariables = Types.Exact<{
}>;
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
export type CandlesQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string } }, marketTimestamps: { __typename?: 'MarketTimestamps', open: any }, candlesConnection?: { __typename?: 'CandleDataConnection', edges?: Array<{ __typename?: 'CandleEdge', node: { __typename?: 'Candle', periodStart: any, lastUpdateInPeriod: any, high: string, low: string, open: string, close: string, volume: string } } | null> | null } | null } | null };
export type CandlesEventsSubscriptionVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
@@ -46,6 +46,9 @@ export const CandlesDocument = gql`
code
}
}
marketTimestamps {
open
}
candlesConnection(interval: $interval, since: $since, pagination: {last: 5000}) {
edges {
node {
@@ -15,6 +15,10 @@ export const candlesQuery = (
id: 'market-0',
decimalPlaces: 5,
positionDecimalPlaces: 0,
marketTimestamps: {
__typename: 'MarketTimestamps',
open: '2022-04-06T09:15:00Z',
},
tradableInstrument: {
instrument: {
id: '',
@@ -13,6 +13,9 @@ const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
market: {
decimalPlaces: 1,
positionDecimalPlaces: 1,
marketTimestamps: {
open: '2022-05-10T11:00:00Z',
},
candlesConnection: {
edges: nodes.map((node) => ({ node })),
},
+23
View File
@@ -172,13 +172,30 @@ export class VegaDataSource implements DataSource {
},
fetchPolicy: 'no-cache',
});
if (data?.market?.candlesConnection?.edges) {
const decimalPlaces = data.market.decimalPlaces;
const positionDecimalPlaces = data.market.positionDecimalPlaces;
const openSince =
typeof data.market.marketTimestamps.open === 'string' &&
data.market.marketTimestamps.open.length > 0
? new Date(data.market.marketTimestamps.open)
: // this should never happen, but just in case let's have it as
// Date(0) if the market data is incomplete for some reason
new Date(0);
if (this.from < openSince) {
// overwrite `from` if requested value is before the market's open date
this.from = openSince;
}
const candles = data.market.candlesConnection.edges
.map((edge) => edge?.node)
.filter((node): node is CandleFieldsFragment => !!node)
.filter(
(node) => sinceMarketOpen(node, openSince) && !emptyCandle(node)
)
.map((node) =>
parseCandle(node, decimalPlaces, positionDecimalPlaces)
)
@@ -326,3 +343,9 @@ function parseCandle(
volume: Number(addDecimal(candle.volume, positionDecimalPlaces)),
};
}
const sinceMarketOpen = (candle: CandleFieldsFragment, openSince: Date) =>
new Date(candle.periodStart) >= openSince;
const emptyCandle = (candle: CandleFieldsFragment) =>
candle.high === '' && candle.low === '';
@@ -22,11 +22,9 @@ import {
type QueryOptions,
type ApolloClient,
} from '@apollo/client';
import { ApolloError } from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import { type ApolloError } from '@apollo/client';
import { GraphQLError } from 'graphql';
import { type Subscription, type Observable } from 'zen-observable-ts';
import { waitFor } from '@testing-library/react';
type Item = {
cursor: string;
@@ -117,24 +115,6 @@ const paginatedSubscribe = makeDataProvider<
},
});
const mockErrorPolicyGuard: (errors: GraphQLErrors) => boolean = jest
.fn()
.mockImplementation(() => true);
const errorGuardedSubscribe = makeDataProvider<
QueryData,
Data,
SubscriptionData,
Delta,
Variables
>({
query,
subscriptionQuery,
update,
getData,
getDelta,
errorPolicyGuard: mockErrorPolicyGuard,
});
const derivedSubscribe = makeDerivedDataProvider(
[paginatedSubscribe, subscribe],
combineData,
@@ -404,34 +384,6 @@ describe('data provider', () => {
subscription.unsubscribe();
});
it('should retry with ignore error policy if errorPolicyGuard returns true', async () => {
const subscription = errorGuardedSubscribe(callback, client, variables);
const graphQLError = new GraphQLError(
'',
undefined,
undefined,
undefined,
['market', 'data'],
undefined,
{
type: 'Internal',
}
);
const graphQLErrors = [graphQLError];
const error = new ApolloError({ graphQLErrors });
await rejectQuery(error);
const data = generateData(0, 5);
await resolveQuery({
data,
});
expect(mockErrorPolicyGuard).toHaveBeenNthCalledWith(1, graphQLErrors);
await waitFor(() =>
expect(getData).toHaveBeenCalledWith({ data }, variables)
);
subscription.unsubscribe();
});
});
describe('derived data provider', () => {
+12 -21
View File
@@ -9,7 +9,6 @@ import type {
ApolloQueryResult,
QueryOptions,
} from '@apollo/client';
import type { GraphQLErrors } from '@apollo/client/errors';
import type { Subscription } from 'zen-observable-ts';
import isEqualWith from 'lodash/isEqualWith';
import { isNotFoundGraphQLError } from './helpers';
@@ -161,7 +160,7 @@ interface DataProviderParams<
resetDelay?: number;
pollInterval?: number;
additionalContext?: Record<string, unknown>;
errorPolicyGuard?: (graphqlErrors: GraphQLErrors) => boolean;
errorPolicy?: ErrorPolicy;
getQueryVariables?: (variables: Variables) => QueryVariables;
getSubscriptionVariables?: (
variables: Variables
@@ -176,7 +175,7 @@ interface DataProviderParams<
* @param fetchPolicy
* @param resetDelay
* @param additionalContext add property to the context of the query, ie. 'isEnlargedTimeout'
* @param errorPolicyGuard indicate which gql errors can be tolerate
* @param errorPolicy Apollos error policy, will be used when querying
* @returns subscribe function
*/
function makeDataProviderInternal<
@@ -197,7 +196,7 @@ function makeDataProviderInternal<
fetchPolicy,
resetDelay,
additionalContext,
errorPolicyGuard,
errorPolicy = 'none',
getQueryVariables,
getSubscriptionVariables,
pollInterval,
@@ -331,20 +330,10 @@ function makeDataProviderInternal<
const callQuery = (
pagination?: Pagination,
policy?: ErrorPolicy
): Promise<ApolloQueryResult<QueryData>> =>
client
.query<QueryData>(getQueryOptions(pagination, policy))
.catch((err) => {
if (
err.graphQLErrors &&
errorPolicyGuard &&
errorPolicyGuard(err.graphQLErrors)
) {
return callQuery(pagination, 'ignore');
} else {
throw err;
}
});
): Promise<ApolloQueryResult<QueryData>> => {
const options = getQueryOptions(pagination, policy);
return client.query<QueryData>(options);
};
const load = async () => {
if (!pagination) {
@@ -364,7 +353,7 @@ function makeDataProviderInternal<
}
}
const res = await callQuery(paginationVariables);
const res = await callQuery(paginationVariables, errorPolicy);
const insertionData = getData(res.data, variables);
const insertionPageInfo = pagination.getPageInfo(res.data);
@@ -417,12 +406,14 @@ function makeDataProviderInternal<
const paginationVariables = pagination
? { first: pagination.first }
: undefined;
if (pollInterval) {
callWatchQuery();
callWatchQuery(paginationVariables, errorPolicy);
return;
}
try {
onNext(await callQuery(paginationVariables));
onNext(await callQuery(paginationVariables, errorPolicy));
} catch (e) {
onError(e as Error);
} finally {
-7
View File
@@ -27,10 +27,3 @@ const hasNotFoundGraphQLErrors = (errors: GraphQLErrors, path?: string[]) => {
(!path || path.every((item, i) => item === e?.path?.[i]))
);
};
export const marketDataErrorPolicyGuard = (errors: GraphQLErrors) =>
errors.every(
(e) =>
e.message.match(/no market data for market:/i) ||
e.message.match(/Conditions list is empty/)
);
@@ -1,47 +1,14 @@
import { useCallback, useState } from 'react';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import { getAsset } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Market } from '@vegaprotocol/markets';
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
import {
formatNumberPercentage,
formatRange,
formatValue,
} from '@vegaprotocol/utils';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import * as Schema from '@vegaprotocol/types';
import {
MARGIN_DIFF_TOOLTIP_TEXT,
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
TOTAL_MARGIN_AVAILABLE,
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
} from '../../constants';
import { formatNumberPercentage, formatValue } from '@vegaprotocol/utils';
import { useEstimateFees } from '../../hooks/use-estimate-fees';
import { KeyValue } from './key-value';
import {
Accordion,
AccordionChevron,
AccordionPanel,
Intent,
ExternalLink,
Pill,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { Intent, Pill } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js';
import { FeesBreakdown } from '../fees-breakdown';
import { getTotalDiscountFactor, getDiscountedFee } from '../discounts';
import { useT, ns } from '../../use-t';
import { Trans } from 'react-i18next';
import { useT } from '../../use-t';
export const emptyValue = '-';
@@ -119,337 +86,3 @@ export const DealTicketFeeDetails = ({
/>
);
};
export interface DealTicketMarginDetailsProps {
generalAccountBalance?: string;
marginAccountBalance?: string;
market: Market;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
assetSymbol: string;
positionEstimate: EstimatePositionQuery['estimatePosition'];
side: Schema.Side;
}
export const DealTicketMarginDetails = ({
marginAccountBalance,
generalAccountBalance,
assetSymbol,
market,
onMarketClick,
positionEstimate,
side,
}: DealTicketMarginDetailsProps) => {
const t = useT();
const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet();
const { data: currentMargins } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: { marketId: market.id, partyId: partyId || '' },
skip: !partyId,
});
const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin;
const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined;
if (marginEstimate) {
if (currentMargins) {
marginRequiredBestCase = (
BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(currentMargins.initialLevel)
).toString();
if (marginRequiredBestCase.startsWith('-')) {
marginRequiredBestCase = '0';
}
marginRequiredWorstCase = (
BigInt(marginEstimate.worstCase.initialLevel) -
BigInt(currentMargins.initialLevel)
).toString();
if (marginRequiredWorstCase.startsWith('-')) {
marginRequiredWorstCase = '0';
}
} else {
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
}
}
const totalMarginAvailable = (
currentMargins
? totalBalance - BigInt(currentMargins.maintenanceLevel)
: totalBalance
).toString();
let deductionFromCollateral = null;
let projectedMargin = null;
if (marginAccountBalance) {
const deductionFromCollateralBestCase =
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
const deductionFromCollateralWorstCase =
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
deductionFromCollateral = (
<KeyValue
indent
label={t('Deduction from collateral')}
value={formatRange(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals
)}
formattedValue={formatValue(
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT',
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
{ assetSymbol }
)}
/>
);
projectedMargin = (
<KeyValue
label={t('Projected margin')}
value={formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals
)}
formattedValue={formatValue(
marginEstimate?.worstCase.initialLevel,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'EST_TOTAL_MARGIN_TOOLTIP_TEXT',
EST_TOTAL_MARGIN_TOOLTIP_TEXT
)}
/>
);
}
let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateRange = emptyValue;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCaseIncludingSellOrders = BigInt(
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCase =
side === Schema.Side.SIDE_BUY
? liquidationEstimateBestCaseIncludingBuyOrders
: liquidationEstimateBestCaseIncludingSellOrders;
const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt(
liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCaseIncludingSellOrders = BigInt(
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCase =
side === Schema.Side.SIDE_BUY
? liquidationEstimateWorstCaseIncludingBuyOrders
: liquidationEstimateWorstCaseIncludingSellOrders;
liquidationPriceEstimate = formatValue(
liquidationEstimateWorstCase.toString(),
market.decimalPlaces,
undefined,
market.decimalPlaces
);
liquidationPriceEstimateRange = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
(liquidationEstimateBestCase > liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
market.decimalPlaces,
undefined,
market.decimalPlaces
);
}
const onAccountBreakdownDialogClose = useCallback(
() => setBreakdownDialog(false),
[]
);
const quoteName = getQuoteName(market);
return (
<div className="flex flex-col w-full gap-2 pt-2">
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}
formattedValue={liquidationPriceEstimate}
symbol={quoteName}
labelDescription={
<>
<span>
{t(
'LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT',
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT
)}
</span>{' '}
<span>
<Trans
defaults="For full details please see <0>liquidation price estimate documentation</0>."
components={[
<ExternalLink
href={
'https://github.com/vegaprotocol/specs/blob/master/non-protocol-specs/0012-NP-LIPE-liquidation-price-estimate.md'
}
>
liquidation price estimate documentation
</ExternalLink>,
]}
ns={ns}
/>
</span>
</>
}
/>
{partyId && (
<AccountBreakdownDialog
assetId={breakdownDialog ? asset.id : undefined}
partyId={partyId}
onMarketClick={onMarketClick}
onClose={onAccountBreakdownDialogClose}
/>
)}
</div>
);
};
@@ -26,12 +26,25 @@ import {
import classNames from 'classnames';
import { useT, ns } from '../../use-t';
import { Trans } from 'react-i18next';
import type { DealTicketMarginDetailsProps } from './deal-ticket-fee-details';
import type { Market } from '@vegaprotocol/markets';
import { emptyValue } from './deal-ticket-fee-details';
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
export interface DealTicketMarginDetailsProps {
generalAccountBalance?: string;
marginAccountBalance?: string;
orderMarginAccountBalance?: string;
market: Market;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
assetSymbol: string;
positionEstimate: EstimatePositionQuery['estimatePosition'];
side: Schema.Side;
}
export const DealTicketMarginDetails = ({
marginAccountBalance,
generalAccountBalance,
orderMarginAccountBalance,
assetSymbol,
market,
onMarketClick,
@@ -46,33 +59,56 @@ export const DealTicketMarginDetails = ({
variables: { marketId: market.id, partyId: partyId || '' },
skip: !partyId,
});
const isInIsolatedMode =
positionEstimate?.margin.bestCase.marginMode ===
Schema.MarginMode.MARGIN_MODE_ISOLATED_MARGIN;
const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin;
const totalMarginAccountBalance =
BigInt(marginAccountBalance || '0') +
BigInt(orderMarginAccountBalance || '0');
const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
BigInt(generalAccountBalance || '0') + totalMarginAccountBalance;
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined;
if (marginEstimate) {
const collateralIncreaseEstimateBestCase = BigInt(
positionEstimate?.collateralIncreaseEstimate.bestCase ?? '0'
);
const collateralIncreaseEstimateWorstCase = BigInt(
positionEstimate?.collateralIncreaseEstimate.worstCase ?? '0'
);
const marginEstimateBestCase = isInIsolatedMode
? totalMarginAccountBalance + collateralIncreaseEstimateBestCase
: BigInt(marginEstimate?.bestCase.initialLevel ?? 0);
const marginEstimateWorstCase = isInIsolatedMode
? totalMarginAccountBalance + collateralIncreaseEstimateWorstCase
: BigInt(marginEstimate?.worstCase.initialLevel ?? 0);
if (isInIsolatedMode) {
marginRequiredBestCase = collateralIncreaseEstimateBestCase.toString();
marginRequiredWorstCase = collateralIncreaseEstimateWorstCase.toString();
} else if (marginEstimate) {
if (currentMargins) {
const currentMargin = BigInt(currentMargins.initialLevel);
marginRequiredBestCase = (
BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(currentMargins.initialLevel)
marginEstimateBestCase - currentMargin
).toString();
if (marginRequiredBestCase.startsWith('-')) {
marginRequiredBestCase = '0';
}
marginRequiredWorstCase = (
BigInt(marginEstimate.worstCase.initialLevel) -
BigInt(currentMargins.initialLevel)
marginEstimateWorstCase - currentMargin
).toString();
if (marginRequiredWorstCase.startsWith('-')) {
marginRequiredWorstCase = '0';
}
} else {
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
marginRequiredBestCase = marginEstimateBestCase.toString();
marginRequiredWorstCase = marginEstimateWorstCase.toString();
}
}
@@ -84,14 +120,12 @@ export const DealTicketMarginDetails = ({
let deductionFromCollateral = null;
let projectedMargin = null;
if (marginAccountBalance) {
if (totalMarginAccountBalance) {
const deductionFromCollateralBestCase =
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
marginEstimateBestCase - totalMarginAccountBalance;
const deductionFromCollateralWorstCase =
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
marginEstimateWorstCase - totalMarginAccountBalance;
deductionFromCollateral = (
<KeyValue
@@ -125,12 +159,12 @@ export const DealTicketMarginDetails = ({
<KeyValue
label={t('Projected margin')}
value={formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
marginEstimateBestCase.toString(),
marginEstimateWorstCase.toString(),
assetDecimals
)}
formattedValue={formatValue(
marginEstimate?.worstCase.initialLevel,
marginEstimateWorstCase.toString(),
assetDecimals,
quantum
)}
@@ -276,6 +310,11 @@ export const DealTicketMarginDetails = ({
assetDecimals,
quantum
),
orderMarginAccountBalance: formatValue(
orderMarginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
@@ -294,14 +333,17 @@ export const DealTicketMarginDetails = ({
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
value={formatValue(
totalMarginAccountBalance.toString(),
assetDecimals
)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
totalMarginAccountBalance.toString(),
assetDecimals,
quantum
)}
@@ -58,8 +58,9 @@ import type {
} from '@vegaprotocol/markets';
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import {
useMarketAccountBalance,
useMarginAccountBalance,
useAccountBalance,
marginModeDataProvider,
} from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { type OrderFormValues } from '../../hooks';
@@ -166,9 +167,10 @@ export const DealTicket = ({
const asset = getAsset(market);
const {
accountBalance: marginAccountBalance,
orderMarginAccountBalance,
marginAccountBalance,
loading: loadingMarginAccountBalance,
} = useMarketAccountBalance(market.id);
} = useMarginAccountBalance(market.id);
const {
accountBalance: generalAccountBalance,
@@ -176,7 +178,9 @@ export const DealTicket = ({
} = useAccountBalance(asset.id);
const balance = (
BigInt(marginAccountBalance) + BigInt(generalAccountBalance)
BigInt(marginAccountBalance) +
BigInt(generalAccountBalance) +
BigInt(orderMarginAccountBalance)
).toString();
const { marketState, marketTradingMode } = marketData;
@@ -241,7 +245,19 @@ export const DealTicket = ({
variables: { partyId: pubKey || '', marketId: market.id },
skip: !pubKey,
});
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
const { data: margin } = useDataProvider({
dataProvider: marginModeDataProvider,
variables: { partyId: pubKey || '', marketId: market.id },
skip: !pubKey,
});
const { openVolume, averageEntryPrice } = useOpenVolume(
pubKey,
market.id
) || {
openVolume: '0',
averageEntryPrice: '0',
};
const orders = activeOrders
? activeOrders.map<Schema.OrderInfo>((order) => ({
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
@@ -259,21 +275,25 @@ export const DealTicket = ({
});
}
const positionEstimate = usePositionEstimate({
marketId: market.id,
openVolume,
orders,
marginAccountBalance: marginAccountBalance,
generalAccountBalance: generalAccountBalance,
orderMarginAccountBalance: '0', // TODO: Get real balance
marginMode: Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN, // TODO: unhardcode this and get users margin mode for the market
averageEntryPrice: marketPrice || '0', // TODO: This assumes the order will be entirely filled at the current market price
skip:
!normalizedOrder ||
const positionEstimate = usePositionEstimate(
{
marketId: market.id,
openVolume,
averageEntryPrice,
orders,
marginAccountBalance: marginAccountBalance || '0',
generalAccountBalance: generalAccountBalance || '0',
orderMarginAccountBalance: orderMarginAccountBalance || '0',
marginFactor: margin?.marginFactor || '1',
marginMode:
margin?.marginMode || Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
includeCollateralIncreaseInAvailableCollateral: true,
},
!normalizedOrder ||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
(!normalizedOrder.price || normalizedOrder.price === '0')) ||
normalizedOrder.size === '0',
});
normalizedOrder.size === '0'
);
const assetSymbol = getAsset(market).symbol;
@@ -319,7 +339,9 @@ export const DealTicket = ({
}
const hasNoBalance =
!BigInt(generalAccountBalance) && !BigInt(marginAccountBalance);
!BigInt(generalAccountBalance) &&
!BigInt(marginAccountBalance) &&
!BigInt(orderMarginAccountBalance);
if (
hasNoBalance &&
!(loadingMarginAccountBalance || loadingGeneralAccountBalance)
@@ -349,6 +371,7 @@ export const DealTicket = ({
marketTradingMode,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
loadingMarginAccountBalance,
loadingGeneralAccountBalance,
pubKey,
@@ -707,10 +730,16 @@ export const DealTicket = ({
asset={asset}
marketTradingMode={marketData.marketTradingMode}
balance={balance}
margin={
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
'0'
}
margin={(
BigInt(
positionEstimate?.estimatePosition?.margin.bestCase.initialLevel ||
'0'
) +
BigInt(
positionEstimate?.estimatePosition?.margin.bestCase
.orderMarginLevel || '0'
)
).toString()}
isReadOnly={isReadOnly}
pubKey={pubKey}
onDeposit={onDeposit}
@@ -743,6 +772,7 @@ export const DealTicket = ({
onMarketClick={onMarketClick}
assetSymbol={asset.symbol}
marginAccountBalance={marginAccountBalance}
orderMarginAccountBalance={orderMarginAccountBalance}
generalAccountBalance={generalAccountBalance}
positionEstimate={positionEstimate?.estimatePosition}
market={market}
@@ -768,8 +798,20 @@ interface SummaryMessageProps {
export const NoWalletWarning = ({
isReadOnly,
}: Pick<SummaryMessageProps, 'isReadOnly'>) => {
noWalletConnected,
}: Pick<SummaryMessageProps, 'isReadOnly'> & {
noWalletConnected?: boolean;
}) => {
const t = useT();
if (noWalletConnected) {
return (
<div className="mb-2">
<InputError testId="deal-ticket-error-message-summary">
{t('You need a Vega wallet to start trading on this market')}
</InputError>
</div>
);
}
if (isReadOnly) {
return (
<div className="mb-2">
@@ -1,9 +1,12 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as Schema from '@vegaprotocol/types';
import {
TradingButton as Button,
TradingInput as Input,
FormGroup,
LeverageSlider,
Notification,
Intent,
} from '@vegaprotocol/ui-toolkit';
import { MarginMode, useVegaWallet } from '@vegaprotocol/wallet';
import * as Types from '@vegaprotocol/types';
@@ -15,15 +18,151 @@ import { Dialog } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { useT } from '../../use-t';
import classnames from 'classnames';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useMaxLeverage } from '@vegaprotocol/positions';
import {
marginModeDataProvider,
useAccountBalance,
useMarginAccountBalance,
} from '@vegaprotocol/accounts';
import { useMaxLeverage, useOpenVolume } from '@vegaprotocol/positions';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { usePositionEstimate } from '../../hooks/use-position-estimate';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { getAsset, useMarket } from '@vegaprotocol/markets';
import { NoWalletWarning } from './deal-ticket';
const defaultLeverage = 10;
export const MarginChange = ({
partyId,
marketId,
marginMode,
marginFactor,
}: {
partyId: string | null;
marketId: string;
marginMode: Types.MarginMode;
marginFactor: string;
}) => {
const t = useT();
const { data: market } = useMarket(marketId);
const asset = market && getAsset(market);
const {
marginAccountBalance,
orderMarginAccountBalance,
loading: marginAccountBalanceLoading,
} = useMarginAccountBalance(marketId);
const {
accountBalance: generalAccountBalance,
loading: generalAccountBalanceLoading,
} = useAccountBalance(asset?.id);
const { openVolume, averageEntryPrice } = useOpenVolume(
partyId,
marketId
) || {
openVolume: '0',
averageEntryPrice: '0',
};
const { data: activeOrders } = useDataProvider({
dataProvider: activeOrdersProvider,
variables: { partyId: partyId || '', marketId },
});
const orders = activeOrders
? activeOrders.map<Schema.OrderInfo>((order) => ({
isMarketOrder: order.type === Schema.OrderType.TYPE_MARKET,
price: order.price,
remaining: order.remaining,
side: order.side,
}))
: [];
const skip =
(!orders?.length && openVolume === '0') ||
marginAccountBalanceLoading ||
generalAccountBalanceLoading;
const estimateMargin = usePositionEstimate(
{
generalAccountBalance: generalAccountBalance || '0',
marginAccountBalance: marginAccountBalance || '0',
marginFactor,
marginMode,
averageEntryPrice,
openVolume,
marketId,
orderMarginAccountBalance: orderMarginAccountBalance || '0',
includeCollateralIncreaseInAvailableCollateral: true,
orders,
},
skip
);
if (
!asset ||
!estimateMargin?.estimatePosition?.collateralIncreaseEstimate.worstCase ||
estimateMargin.estimatePosition.collateralIncreaseEstimate.worstCase === '0'
) {
return null;
}
const collateralIncreaseEstimate = BigInt(
estimateMargin.estimatePosition.collateralIncreaseEstimate.worstCase
);
if (!collateralIncreaseEstimate) {
return null;
}
let positionWarning = '';
if (orders?.length && openVolume !== '0') {
positionWarning = t(
'youHaveOpenPositionAndOrders',
'You have an existing position and open orders on this market.',
{
count: orders.length,
}
);
} else if (!orders?.length) {
positionWarning = t('You have an existing position on this market.');
} else {
positionWarning = t(
'youHaveOpenOrders',
'You have open orders on this market.',
{
count: orders.length,
}
);
}
let marginChangeWarning = '';
const amount = addDecimalsFormatNumber(
collateralIncreaseEstimate.toString(),
asset?.decimals
);
const { symbol } = asset;
const interpolation = { amount, symbol };
if (marginMode === Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN) {
marginChangeWarning = t(
'Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.',
interpolation
);
} else {
marginChangeWarning = t(
'Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.',
interpolation
);
}
return (
<div className="mb-2">
<Notification
intent={Intent.Warning}
message={
<>
<p>{positionWarning}</p>
<p>{marginChangeWarning}</p>
</>
}
/>
</div>
);
};
interface MarginDialogProps {
open: boolean;
onClose: () => void;
marketId: string;
partyId: string;
create: VegaTransactionStore['create'];
}
@@ -33,6 +172,7 @@ const CrossMarginModeDialog = ({
marketId,
create,
}: MarginDialogProps) => {
const { pubKey: partyId, isReadOnly } = useVegaWallet();
const t = useT();
return (
<Dialog
@@ -60,15 +200,24 @@ const CrossMarginModeDialog = ({
)}
</p>
</div>
<MarginChange
marketId={marketId}
partyId={partyId}
marginMode={Types.MarginMode.MARGIN_MODE_CROSS_MARGIN}
marginFactor="1"
/>
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
<Button
className="w-full"
onClick={() => {
create({
updateMarginMode: {
market_id: marketId,
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
},
});
partyId &&
!isReadOnly &&
create({
updateMarginMode: {
marketId,
mode: MarginMode.MARGIN_MODE_CROSS_MARGIN,
},
});
onClose();
}}
>
@@ -82,10 +231,10 @@ const IsolatedMarginModeDialog = ({
open,
onClose,
marketId,
partyId,
marginFactor,
create,
}: MarginDialogProps & { marginFactor: string }) => {
const { pubKey: partyId, isReadOnly } = useVegaWallet();
const [leverage, setLeverage] = useState(
Number((1 / Number(marginFactor)).toFixed(1))
);
@@ -129,13 +278,15 @@ const IsolatedMarginModeDialog = ({
</div>
<form
onSubmit={() => {
create({
updateMarginMode: {
market_id: marketId,
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: `${1 / leverage}`,
},
});
partyId &&
!isReadOnly &&
create({
updateMarginMode: {
marketId,
mode: MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
marginFactor: `${1 / leverage}`,
},
});
onClose();
}}
>
@@ -144,7 +295,7 @@ const IsolatedMarginModeDialog = ({
<LeverageSlider
max={max}
step={0.1}
value={[leverage]}
value={[leverage || 1]}
onValueChange={([value]) => setLeverage(value)}
/>
</div>
@@ -154,10 +305,17 @@ const IsolatedMarginModeDialog = ({
min={1}
max={max}
step={0.1}
value={leverage}
value={leverage || ''}
onChange={(e) => setLeverage(Number(e.target.value))}
/>
</FormGroup>
<MarginChange
marketId={marketId}
partyId={partyId}
marginMode={Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN}
marginFactor={`${1 / leverage}`}
/>
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
<Button className="w-full" type="submit">
{t('Confirm')}
</Button>
@@ -169,27 +327,21 @@ const IsolatedMarginModeDialog = ({
export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
const t = useT();
const [dialog, setDialog] = useState<'cross' | 'isolated' | ''>();
const { pubKey: partyId, isReadOnly } = useVegaWallet();
const { pubKey: partyId } = useVegaWallet();
const { data: margin } = useDataProvider({
dataProvider: marketMarginDataProvider,
dataProvider: marginModeDataProvider,
variables: {
partyId: partyId || '',
marketId,
},
skip: !partyId,
});
useEffect(() => {
if (!partyId) {
setDialog('');
}
}, [partyId]);
const create = useVegaTransactionStore((state) => state.create);
const marginMode = margin?.marginMode;
const marginFactor =
margin?.marginFactor && margin?.marginFactor !== '0'
? margin?.marginFactor
: undefined;
const disabled = isReadOnly;
const onClose = () => setDialog(undefined);
const enabledModeClassName = 'bg-vega-clight-500 dark:bg-vega-cdark-500';
@@ -197,8 +349,8 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
<>
<div className="mb-4 grid h-8 leading-8 font-alpha text-xs grid-cols-2">
<button
disabled={disabled}
onClick={() => partyId && setDialog('cross')}
type="button"
onClick={() => setDialog('cross')}
className={classnames('rounded', {
[enabledModeClassName]:
!marginMode ||
@@ -208,8 +360,8 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
{t('Cross')}
</button>
<button
disabled={disabled}
onClick={() => partyId && setDialog('isolated')}
type="button"
onClick={() => setDialog('isolated')}
className={classnames('rounded', {
[enabledModeClassName]:
marginMode === Types.MarginMode.MARGIN_MODE_ISOLATED_MARGIN,
@@ -222,25 +374,23 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
})}
</button>
</div>
{partyId && (
{
<CrossMarginModeDialog
partyId={partyId}
open={dialog === 'cross'}
onClose={onClose}
marketId={marketId}
create={create}
/>
)}
{partyId && (
}
{
<IsolatedMarginModeDialog
partyId={partyId}
open={dialog === 'isolated'}
onClose={onClose}
marketId={marketId}
create={create}
marginFactor={marginFactor || `${1 / defaultLeverage}`}
/>
)}
}
</>
);
};
+1 -1
View File
@@ -8,7 +8,7 @@ export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT =
'To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.';
export const TOTAL_MARGIN_AVAILABLE =
'Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).';
'Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) + order margin balance ({{orderMarginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).';
export const CONTRACTS_MARGIN_TOOLTIP_TEXT =
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.';
@@ -5,37 +5,15 @@ import {
import { useEstimatePositionQuery } from '@vegaprotocol/positions';
import { useEffect, useState } from 'react';
interface PositionEstimateProps extends EstimatePositionQueryVariables {
skip: boolean;
}
export const usePositionEstimate = ({
marketId,
openVolume,
orders,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
skip,
}: PositionEstimateProps) => {
export const usePositionEstimate = (
variables: EstimatePositionQueryVariables,
skip: boolean
) => {
const [estimates, setEstimates] = useState<EstimatePositionQuery | undefined>(
undefined
);
const { data } = useEstimatePositionQuery({
variables: {
marketId,
openVolume,
orders,
generalAccountBalance,
marginAccountBalance,
orderMarginAccountBalance,
averageEntryPrice,
marginMode,
marginFactor,
},
variables,
skip,
fetchPolicy: 'no-cache',
});
+12 -1
View File
@@ -13,6 +13,8 @@
"Any orders placed now will not trade until the auction ends": "Any orders placed now will not trade until the auction ends",
"below": "below",
"Cancel": "Cancel",
"Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.": "Changing the margin mode will move {{amount}} {{symbol}} from your general account to fund the position.",
"Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.": "Changing the margin mode and leverage will move {{amount}} {{symbol}} from your general account to fund the position.",
"Closed": "Closed",
"Closing on {{time}}": "Closing on {{time}}",
"Confirm": "Confirm",
@@ -67,6 +69,13 @@
"One cancels another": "One cancels another",
"Only limit orders are permitted when market is in auction": "Only limit orders are permitted when market is in auction",
"Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.": "Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.",
"You have an existing position on this market.": "You have an existing position on this market.",
"youHaveOpenOrders_one": "You have an open order on this market.",
"youHaveOpenOrders_other": "You have open orders on this market.",
"youHaveOpenOrders": "You have open orders on this market.",
"youHaveOpenPositionAndOrders_one": "You have an existing position and and open order on this market.",
"youHaveOpenPositionAndOrders_other": "You have an existing position and open orders on this market.",
"youHaveOpenPositionAndOrders": "You have an existing position and open orders on this market.",
"Peak size": "Peak size",
"Peak size cannot be greater than the size ({{size}})": "Peak size cannot be greater than the size ({{size}})",
"Peak size cannot be lower than {{stepSize}}": "Peak size cannot be lower than {{stepSize}}",
@@ -124,7 +133,7 @@
"Total": "Total",
"Total fees": "Total fees",
"Total margin available": "Total margin available",
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) + order margin balance ({{orderMarginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
"No trading": "No trading",
"Trailing percent offset cannot be higher than 99.9": "Trailing percent offset cannot be higher than 99.9",
"Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}": "Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}",
@@ -140,8 +149,10 @@
"You are setting this market to cross-margin mode.": "You are setting this market to cross-margin mode.",
"You are setting this market to isolated margin mode.": "You are setting this market to isolated margin mode.",
"You have only {{amount}}.": "You have only {{amount}}.",
"You have an existing position and open orders on this market": "You have an existing position and open orders on this market",
"You may not have enough margin available to open this position.": "You may not have enough margin available to open this position.",
"You need {{symbol}} in your wallet to trade in this market.": "You need {{symbol}} in your wallet to trade in this market.",
"You need a Vega wallet to start trading on this market": "You need a Vega wallet to start trading on this market",
"You need provide a expiry time/date": "You need provide a expiry time/date",
"You need provide a price": "You need provide a price",
"You need provide a trailing percent offset": "You need provide a trailing percent offset",

Some files were not shown because too many files have changed in this diff Show More