Compare commits

..
176 changed files with 3033 additions and 4237 deletions
+3 -3
View File
@@ -10,8 +10,8 @@ on:
jobs:
run-tests:
name: run-tests
runs-on: 8-cores
timeout-minutes: 20
runs-on: ubuntu-22.04
timeout-minutes: 25
steps:
#----------------------------------------------
# check-out frontend-monorepo
@@ -125,7 +125,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -s --numprocesses auto
run: poetry run pytest --numprocesses auto
- name: Check files
run: |
ls -al .
@@ -23,7 +23,7 @@ export const Footer = () => {
);
return (
<footer className="grid grid-cols-[1fr_auto] items-center text-xs md:text-md lg:flex md:col-span-2 px-4 pt-2 pb-3 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<footer className="grid grid-rows-2 lg:grid-cols-[1fr_auto] text-xs md:text-md lg:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
@@ -43,12 +43,9 @@ export const Footer = () => {
</div>
)}
<div className="content-center flex pr-4 md:border-r border-neutral-700 dark:border-neutral-300">
<span className="pr-2">{VEGA_URL && <NodeUrl url={VEGA_URL} />}</span>
<Link
className="ml-2 underline-offset-4"
onClick={() => setNodeSwitcherOpen(true)}
>
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
{t('Change')}
</Link>
</div>
@@ -62,10 +59,7 @@ export const Footer = () => {
) : null}
</div>
<div className="pl-2 align-center lg:align-right lg:flex lg:justify-end gap-2 align-middle lg:max-w-xs lg:ml-auto">
<RouteLink
to={`/${Routes.DISCLAIMER}`}
className="underline underline-offset-4"
>
<RouteLink to={`/${Routes.DISCLAIMER}`} className="underline">
Disclaimer
</RouteLink>
</div>
@@ -1,9 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel,
} from '@vegaprotocol/markets';
import { PriceMonitoringBoundsInfoPanel } from '@vegaprotocol/markets';
import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
@@ -106,8 +103,6 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<OracleInfoPanel market={market} type="settlementData" />
</>
)}
<h2 className={`${headerClassName} mb-4`}>{t('Succession line')}</h2>
<SuccessionLineInfoPanel market={market} />
</div>
);
};
@@ -10,8 +10,6 @@ import Hash from '../../links/hash';
import { t } from '@vegaprotocol/i18n';
import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new';
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
import { MarketLink } from '../../links';
import { formatNumber } from '@vegaprotocol/utils';
export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
@@ -55,11 +53,7 @@ export function proposalTypeLabel(terms?: ProposalTerms): string {
} else if (has(terms, 'updateAsset')) {
return t('Update asset proposal');
} else if (has(terms, 'newMarket')) {
if (terms?.newMarket?.changes?.successor) {
return t('Successor market proposal');
} else {
return t('New market proposal');
}
return t('New market proposal');
} else if (has(terms, 'updateMarket')) {
return t('Update market proposal');
} else if (has(terms, 'updateNetworkParameter')) {
@@ -91,13 +85,6 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
}
const tx = proposal.terms?.newAsset || proposal.terms?.updateAsset;
const isSuccessorMarketProposal =
proposal?.terms?.newMarket?.changes?.successor;
const parentMarketId =
isSuccessorMarketProposal &&
proposal?.terms.newMarket?.changes?.successor?.parentMarketId;
const insurancePoolFraction =
proposal?.terms?.newMarket?.changes?.successor?.insurancePoolFraction;
// This component is not rendered if no bundle is required
const SignatureBundleComponent = proposal.terms?.newAsset
@@ -124,30 +111,6 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
<Hash text={deterministicId} />
</TableCell>
</TableRow>
{isSuccessorMarketProposal ? (
<>
{parentMarketId ? (
<TableRow modifier="bordered">
<TableCell>{t('Previous market')}</TableCell>
<TableCell>
<MarketLink
id={
proposal.terms?.newMarket.changes.successor.parentMarketId
}
/>
</TableCell>
</TableRow>
) : null}
{insurancePoolFraction ? (
<TableRow modifier="bordered">
<TableCell>{t('Insurance pool fraction')}</TableCell>
<TableCell>
{formatNumber(Number(insurancePoolFraction) * 100, 0)}%
</TableCell>
</TableRow>
) : null}
</>
) : null}
</TableWithTbody>
<ProposalSummary
id={deterministicId}
@@ -78,11 +78,7 @@ export function getLabelForProposal(
} else if (proposal.terms?.updateAsset) {
return t('Proposal: Update asset');
} else if (proposal.terms?.newMarket) {
if (proposal.terms?.newMarket.changes?.successor) {
return t('Proposal: Successor market');
} else {
return t('Proposal: New market');
}
return t('Proposal: New market');
} else if (proposal.terms?.updateMarket) {
return t('Proposal: Update market');
} else if (proposal.terms?.updateNetworkParameter) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

-20
View File
@@ -1,20 +0,0 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
-3
View File
@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+10 -26
View File
@@ -1,39 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Explorer" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://explorer.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Explorer" />
<meta name="og:site_name" content="Vega Protocol - Explorer" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Vega Protocol - Explorer" />
<meta name="twitter:description" content="Vega Protocol - Explorer" />
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Explorer" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<link rel="manifest" href="assets/manifest.json" />
<title>Explorer</title>
<base href="/" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<title>VEGA Explorer</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<script src="./assets/env-config.js"></script>
</head>
<body class="dark:bg-black h-full w-full">
-1
View File
@@ -18,7 +18,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
NX_SUCCESSOR_MARKETS=true
#Test configuration variables
CYPRESS_FAIRGROUND=false
@@ -11,9 +11,7 @@ import {
getDateFormatForSpecifiedDays,
getProposalFromTitle,
getProposalInformationFromTable,
proposalChangeType,
submitUniqueRawProposal,
validateProposalDetailsDiff,
voteForProposal,
} from '../../../../governance-e2e/src/support/governance.functions';
import {
@@ -28,9 +26,7 @@ import {
} from '../../support/wallet-functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const proposalVoteProgressAgainstPercentage =
@@ -46,7 +42,6 @@ const viewProposalButton = 'view-proposal-btn';
const proposalDescriptionToggle = 'proposal-description-toggle';
const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle';
describe(
'Governance flow for proposal details',
@@ -359,98 +354,5 @@ describe(
switchVegaWalletPubKey();
stakingPageDisassociateAllTokens();
});
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(viewProposalButton).click();
});
});
getProposalInformationFromTable('ID')
.invoke('text')
.as('parentMarketId')
.then(() => {
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
});
navigateTo(navigation.proposals);
cy.reload();
getProposalFromTitle('Test successor market proposal details').within(
() => cy.getByTestId(viewProposalButton).click()
);
// #3003-PMAN-010
cy.getByTestId(proposalTermsToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
});
// 3003-PMAN-011 3003-PMAN-012
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-market-data').within(() => {
cy.contains('Key details').click();
validateProposalDetailsDiff(
'Name',
proposalChangeType.UPDATED,
'Token test market',
'Test market 1'
);
validateProposalDetailsDiff(
'Parent Market ID',
proposalChangeType.ADDED,
this.parentMarketId
);
validateProposalDetailsDiff(
'Insurance Pool Fraction',
proposalChangeType.ADDED,
'0.75'
);
validateProposalDetailsDiff(
'Trading Mode',
proposalChangeType.UPDATED,
'No trading',
'Opening auction'
);
cy.contains('Instrument').click();
validateProposalDetailsDiff(
'Market Name',
proposalChangeType.UPDATED,
'Token test market',
'Test market 1'
);
cy.contains('Metadata').click();
validateProposalDetailsDiff(
'Sector',
proposalChangeType.UPDATED,
'materials',
'tech'
);
});
// 3003-PMAN-011
cy.get('.underline').contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.get('.underline')
.contains('Insurance Pool Fraction')
.realMouseUp()
.realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
}
);
@@ -654,7 +654,44 @@ context(
.find('a')
.should('have.attr', 'href')
.and('contain', this.parentMarketId);
cy.getByTestId('view-proposal-btn').click();
});
// #3003-PMAN-010
cy.getByTestId(proposalJsonToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
});
cy.getByTestId('proposal-market-data').within(() => {
cy.getByTestId('proposal-market-data-toggle').click();
cy.contains('Key details').click();
// 3003-PMAN-009
getMarketProposalDetailsFromTable('Parent Market ID').should(
'have.text',
this.parentMarketId
);
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
'have.text',
'0.75'
);
getMarketProposalDetailsFromTable('Trading Mode').should(
'have.text',
'No trading'
);
});
// 3003-PMAN-011
cy.contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.contains('Insurance Pool Fraction').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
after('Disassociate from second wallet key if present', function () {
@@ -693,7 +730,6 @@ context(
.getByTestId('key-value-table-row')
.contains(heading)
.parent()
.parent()
.siblings();
}
}
@@ -25,9 +25,6 @@ import {
vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown,
} from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const stakeValidatorListTotalStake = 'total-stake';
const stakeValidatorListTotalShare = 'total-stake-share';
const stakeValidatorListStakePercentage = 'stake-percentage';
@@ -58,10 +55,6 @@ context(
function () {
// 1002-STKE-002, 1002-STKE-032
before('visit staking tab and connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
ethereumWalletConnect();
cy.connectVegaWallet();
@@ -72,10 +65,6 @@ context(
beforeEach(
'teardown wallet & drill into a specific validator',
function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.clearLocalStorage();
turnTelemetryOff();
// Go to homepage to allow wallet teardown without epoch timer refreshing page
@@ -6,8 +6,6 @@ import {
} from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { depositAsset } from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const withdraw = 'withdraw';
const withdrawalForm = 'withdraw-form';
@@ -44,20 +42,12 @@ context(
{ tags: '@slow' },
function () {
before('visit withdrawals and connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
ethereumWalletConnect();
depositAsset(usdcEthAddress, '1000', 5);
});
beforeEach('Navigate to withdrawal page', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.clearLocalStorage();
turnTelemetryOff();
cy.reload();
@@ -11,7 +11,6 @@ import {
waitForBeginningOfEpoch,
} from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const guideLink = 'staking-guide-link';
const validatorTitle = 'validator-node-title';
@@ -34,22 +33,11 @@ const overstakedPenaltyToolTip = 'overstaked-penalty-tooltip';
const multisigPenaltyToolTip = 'multisig-error-tooltip';
const epochCountDown = 'epoch-countdown';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
const txTimeout = Cypress.env('txTimeout');
context('Validators Page - verify elements on page', function () {
before('navigate to validators page', () => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
before('navigate to validators page', function () {
cy.visit('/validators');
});
beforeEach(() => {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
});
describe('with wallets disconnected', { tags: '@smoke' }, function () {
it('Should have validators tab highlighted', function () {
@@ -189,11 +177,6 @@ context('Validators Page - verify elements on page', function () {
{ tags: '@smoke' },
function () {
before('connect wallets and click on validator', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/validators');
cy.connectVegaWallet();
clickOnValidatorFromList(0);
});
@@ -280,7 +263,7 @@ context('Validators Page - verify elements on page', function () {
cy.getByTestId(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo, txTimeout).should('contain.text', 'Next epoch');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
});
});
}
@@ -4,8 +4,6 @@ import {
vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown,
} from '../../support/wallet-functions';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
@@ -16,6 +14,10 @@ const dialogHeader = 'dialog-title';
const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = 'connectors-list';
const dialogCloseBtn = 'dialog-close';
const restConnectorForm = 'rest-connector-form';
const restWallet = '#wallet';
const restPassphrase = '#passphrase';
const restConnectBtn = '[type="submit"]';
const accountNo = 'vega-account-truncated';
const currencyTitle = 'currency-title';
const currencyValue = 'currency-value';
@@ -67,7 +69,7 @@ context(
cy.get(dialog).within(() => {
cy.getByTestId(walletDialogHeader)
.should('be.visible')
.and('have.text', 'Get a Vega wallet');
.and('have.text', 'Connect');
});
});
@@ -75,7 +77,10 @@ context(
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-jsonRpc')
.should('be.visible')
.and('have.text', 'Use the Desktop App/CLI');
.and('have.text', 'Connect Vega wallet');
cy.getByTestId('connector-rest')
.should('be.visible')
.and('have.text', 'Hosted Fairground wallet');
});
});
@@ -86,14 +91,48 @@ context(
});
});
describe('when rest connector form opened', function () {
before('click hosted wallet app button', function () {
cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-rest').click();
});
});
// 0002-WCON-002
it('should have wallet field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restWallet).should('be.visible');
});
});
it('should have password field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restPassphrase).should('be.visible');
});
});
it('should have connect button visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectBtn)
.should('be.visible')
.and('have.text', 'Connect');
});
});
it('should have close button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogCloseBtn).should('be.visible');
});
});
after('close dialog', function () {
cy.getByTestId(dialogCloseBtn).click().should('not.exist');
});
});
describe('when vega wallet connected', function () {
before('connect vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
cy.visit('/');
cy.wait('@ChainId');
cy.connectVegaWallet();
vegaWalletTeardown();
});
@@ -276,10 +315,6 @@ context(
];
before('faucet assets to connected vega wallet', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
});
for (const { id, amount } of assets) {
vegaWalletFaucetAssetsWithoutCheck(id, amount, vegaWalletPublicKey);
}
@@ -232,23 +232,6 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
return filepath;
}
export function validateProposalDetailsDiff(
RowName: string,
changeType: proposalChangeType,
newValue: string,
oldValue?: string
) {
cy.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first()
.within(() => {
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
}
function getFormattedTime() {
const now = new Date();
const day = now.getDate().toString().padStart(2, '0');
@@ -269,8 +252,3 @@ export enum governanceProposalType {
FREEFORM = 'Freeform',
RAW = 'raw proposal',
}
export enum proposalChangeType {
UPDATED = 'Updated',
ADDED = 'Added',
}
@@ -211,143 +211,6 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
};
}
// Requires cy.createMarket() to be run to set up parent market
export function createSuccessorMarketProposalTxBody(
parentMarketId: string
): ProposalSubmissionBody {
const MIN_CLOSE_SEC = 10000;
const MIN_ENACT_SEC = 10000;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
return {
proposalSubmission: {
rationale: {
title: 'Test successor market proposal details',
description: 'E2E test for successor market',
},
terms: {
newMarket: {
changes: {
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
lpPriceRange: '10',
instrument: {
name: 'Token test market',
code: 'TEST.24h',
future: {
settlementAsset:
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
quoteName: 'fUSDC',
dataSourceSpecForSettlementData: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'prices.BTC.value',
type: 'TYPE_INTEGER' as const,
numberDecimalPlaces: '0',
},
conditions: [
{
operator: 'OPERATOR_GREATER_THAN' as const,
value: '0',
},
],
},
],
},
},
},
dataSourceSpecForTradingTermination: {
external: {
oracle: {
signers: [
{
pubKey: {
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
},
},
],
filters: [
{
key: {
name: 'trading.terminated.ETH5',
type: 'TYPE_BOOLEAN' as const,
},
conditions: [
{
operator: 'OPERATOR_EQUALS' as const,
value: 'true',
},
],
},
],
},
},
},
dataSourceSpecBinding: {
settlementDataProperty: 'prices.BTC.value',
tradingTerminationProperty: 'trading.terminated.ETH5',
},
},
},
metadata: [
'sector:food',
'sector:materials',
'source:docs.vega.xyz',
],
priceMonitoringParameters: {
triggers: [
{
horizon: '43200',
probability: '0.9999999',
auctionExtension: '600',
},
],
},
liquidityMonitoringParameters: {
targetStakeParameters: {
timeWindow: '3600',
scalingFactor: 10,
},
triggeringRatio: '0.7',
auctionExtension: '1',
},
logNormal: {
tau: 0.0001140771161,
riskAversionParameter: 0.01,
params: {
mu: 0,
r: 0.016,
sigma: 0.5,
},
},
successor: {
parentMarketId: parentMarketId,
insurancePoolFraction: '0.75',
},
},
},
closingTimestamp,
enactmentTimestamp,
},
},
};
}
export function mockNetworkUpgradeProposal() {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Nodes', nodeData);
+1 -4
View File
@@ -21,12 +21,9 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
#Test configuration variables
CYPRESS_FAIRGROUND=false
LC_ALL="en_US.UTF-8"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_SUCCESSOR_MARKETS=true
@@ -1,8 +1,4 @@
import {
VegaConnectDialog,
VegaManageDialog,
ViewAsDialog,
} from '@vegaprotocol/wallet';
import { VegaConnectDialog, VegaManageDialog } from '@vegaprotocol/wallet';
import {
AppStateActionType,
useAppState,
@@ -29,8 +25,6 @@ export const VegaWalletDialogs = () => {
})
}
/>
<ViewAsDialog connector={Connectors.view} />
</>
);
};
@@ -0,0 +1,17 @@
import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
export const DownloadWalletPrompt = () => {
const { t } = useTranslation();
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
<p>
<Link className="text-neutral-500" href={ExternalLinks.VEGA_WALLET_URL}>
{t('getWalletLink')}
</Link>
</p>
</>
);
};
@@ -1,26 +0,0 @@
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
import { useViewAsDialog } from '@vegaprotocol/wallet';
export const VegaWalletPrompt = () => {
const { t } = useTranslation();
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
<div className="flex flex-row gap-4">
<Link className="text-neutral-500" href={ExternalLinks.VEGA_WALLET_URL}>
{t('getWalletLink')}
</Link>
<ButtonLink
className="text-neutral-500"
data-testid="view-as-user"
onClick={() => setViewAsDialog(true)}
>
{t('viewAsParty')}
</ButtonLink>
</div>
</>
);
};
@@ -20,7 +20,7 @@ import {
WalletCardHeader,
WalletCardRow,
} from '../wallet-card';
import { VegaWalletPrompt } from './vega-wallet-prompt';
import { DownloadWalletPrompt } from './download-wallet-prompt';
import { usePollForDelegations } from './hooks';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
@@ -85,7 +85,7 @@ const VegaWalletNotConnected = () => {
>
{t('connectVegaWalletToUseAssociated')}
</Button>
<VegaWalletPrompt />
<DownloadWalletPrompt />
</>
);
};
@@ -855,6 +855,5 @@
"Estimated time to upgrade": "Estimated time to upgrade",
"Upgraded at": "Upgraded at",
"dataIsIdentical": "Data is identical",
"updatesToMarket": "Updates to market",
"viewAsParty": "View as party"
"updatesToMarket": "Updates to market"
}
+13 -20
View File
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en" class="dark bg-black w-full h-full">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="stylesheet" href="//static.vega.xyz/fonts.css" />
<link
rel="icon"
type="image/x-icon"
@@ -10,42 +10,35 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - VEGA Governance" />
<meta name="og:type" content="website" />
<meta name="og:url" content="https://governance.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Governance" />
<meta name="og:site_name" content="Vega Protocol - Governance" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Vega Protocol - Governance" />
<meta name="twitter:description" content="Vega Protocol - Governance" />
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
<meta name="description" content="Vega Protocol - Governance" />
<meta name="description" content="Vega Protocol - VEGA Token Vesting" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="assets/manifest.json" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<title>VEGA Governance</title>
<title>Vega Governance dApp</title>
<script src="./assets/env-config.js"></script>
</head>
<body class="h-full">
<noscript> You need to enable JavaScript to run this app. </noscript>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="h-full"></div>
<!-- This HTML file is a template.
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. -->
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
@@ -1,4 +1,5 @@
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
@@ -7,11 +8,13 @@ import {
const urlParams = new URLSearchParams(window.location.search);
export const injected = new InjectedConnector();
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const Connectors = {
injected,
rest,
jsonRpc,
view,
};
@@ -45,10 +45,8 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
export const ProposalMarketData = ({
marketData,
parentMarketData,
}: {
marketData: MarketInfoWithData;
parentMarketData?: MarketInfoWithData;
}) => {
const { t } = useTranslation();
const { isOpen, open, close } = useMarketDataDialogStore();
@@ -60,21 +58,8 @@ export const ProposalMarketData = ({
const settlementData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const parentSettlementData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForSettlementData?.data;
const terminationData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const parentTerminationData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForTradingTermination?.data;
const isParentSettlementDataEqual =
parentSettlementData !== undefined &&
isEqual(settlementData, parentSettlementData);
const isParentTerminationDataEqual =
parentTerminationData !== undefined &&
isEqual(terminationData, parentTerminationData);
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
@@ -112,22 +97,12 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="key-details"
title={t('Key details')}
content={
<KeyDetailsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<KeyDetailsInfoPanel market={marketData} />}
/>
<AccordionItem
itemId="instrument"
title={t('Instrument')}
content={
<InstrumentInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<InstrumentInfoPanel market={marketData} />}
/>
{isEqual(
getSigners(settlementData),
@@ -140,11 +115,6 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@@ -157,11 +127,6 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@@ -170,21 +135,11 @@ export const ProposalMarketData = ({
itemId="termination-oracle"
title={t('Termination Oracle')}
content={
<OracleInfoPanel
market={marketData}
type="termination"
parentMarket={
isParentTerminationDataEqual
? undefined
: parentMarketData
}
/>
<OracleInfoPanel market={marketData} type="termination" />
}
/>
</>
)}
{/*Note: successor markets will not differ in their settlement*/}
{/*assets, so no need to pass in parent market data for comparison.*/}
<AccordionItem
itemId="settlement-asset"
title={t('Settlement asset')}
@@ -193,42 +148,22 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="metadata"
title={t('Metadata')}
content={
<MetadataInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<MetadataInfoPanel market={marketData} />}
/>
<AccordionItem
itemId="risk-model"
title={t('Risk model')}
content={
<RiskModelInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<RiskModelInfoPanel market={marketData} />}
/>
<AccordionItem
itemId="risk-parameters"
title={t('Risk parameters')}
content={
<RiskParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<RiskParametersInfoPanel market={marketData} />}
/>
<AccordionItem
itemId="risk-factors"
title={t('Risk factors')}
content={
<RiskFactorsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<RiskFactorsInfoPanel market={marketData} />}
/>
{(
marketData.priceMonitoringSettings?.parameters?.triggers || []
@@ -239,7 +174,6 @@ export const ProposalMarketData = ({
content={
<PriceMonitoringBoundsInfoPanel
market={marketData}
parentMarket={parentMarketData}
triggerIndex={triggerIndex}
/>
}
@@ -249,21 +183,13 @@ export const ProposalMarketData = ({
itemId="liqudity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
content={
<LiquidityMonitoringParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
<LiquidityMonitoringParametersInfoPanel market={marketData} />
}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
content={<LiquidityPriceRangeInfoPanel market={marketData} />}
/>
</Accordion>
</div>
@@ -32,7 +32,6 @@ export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
newMarketData?: MarketInfoWithData | null;
parentMarketData?: MarketInfoWithData | null;
assetData?: AssetQuery | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
restData: any;
@@ -47,7 +46,6 @@ export const Proposal = ({
networkParams,
restData,
newMarketData,
parentMarketData,
assetData,
originalMarketProposalRestData,
mostRecentlyEnactedAssociatedMarketProposal,
@@ -159,10 +157,7 @@ export const Proposal = ({
{newMarketData && (
<div className="mb-4">
<ProposalMarketData
marketData={newMarketData}
parentMarketData={parentMarketData ? parentMarketData : undefined}
/>
<ProposalMarketData marketData={newMarketData} />
</div>
)}
@@ -14,9 +14,6 @@ import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { FLAGS } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
export const ProposalContainer = () => {
const [
@@ -57,10 +54,6 @@ export const ProposalContainer = () => {
skip: !params.proposalId,
});
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
const {
state: {
data: originalMarketProposalRestData,
@@ -103,36 +96,6 @@ export const ProposalContainer = () => {
},
});
const {
data: parentMarketId,
loading: parentMarketIdLoading,
error: parentMarketIdError,
} = useParentMarketIdQuery({
variables: {
marketId: newMarketData?.data?.market?.id || '',
},
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!isSuccessor ||
!newMarketData?.data?.market?.id,
});
const {
data: parentMarketData,
loading: parentMarketLoading,
error: parentMarketError,
} = useDataProvider({
dataProvider: marketInfoWithDataProvider,
skipUpdates: true,
variables: {
marketId: parentMarketId?.market?.parentMarketID || '',
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!isSuccessor ||
!parentMarketId?.market?.parentMarketID,
},
});
const {
data: assetData,
loading: assetLoading,
@@ -197,8 +160,6 @@ export const ProposalContainer = () => {
newMarketLoading ||
assetLoading ||
networkParamsLoading ||
parentMarketIdLoading ||
parentMarketLoading ||
(restLoading ? (restLoading as boolean) : false) ||
(originalMarketProposalRestLoading
? (originalMarketProposalRestLoading as boolean)
@@ -211,18 +172,15 @@ export const ProposalContainer = () => {
error ||
newMarketError ||
assetError ||
networkParamsError ||
parentMarketIdError ||
parentMarketError ||
restError ||
originalMarketProposalRestError ||
previouslyEnactedMarketProposalsRestError
previouslyEnactedMarketProposalsRestError ||
networkParamsError
}
data={{
...data,
...networkParams,
...(newMarketData ? { newMarketData } : {}),
...(parentMarketData ? { parentMarketData } : {}),
...(assetData ? { assetData } : {}),
...(restData ? { restData } : {}),
...(originalMarketProposalRestData
@@ -239,7 +197,6 @@ export const ProposalContainer = () => {
networkParams={networkParams}
restData={restData}
newMarketData={newMarketData}
parentMarketData={parentMarketData}
assetData={assetData}
originalMarketProposalRestData={originalMarketProposalRestData}
mostRecentlyEnactedAssociatedMarketProposal={
+1
View File
@@ -26,6 +26,7 @@ module.exports = defineConfig({
requestTimeout: 20000,
retries: 1,
testIsolation: false,
experimentalMemoryManagement: true,
},
env: {
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
@@ -104,11 +104,11 @@ describe('deposit actions', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-1');
});
it('Deposit to trade is visible', () => {
it('Deposit to trade is visble', () => {
cy.getByTestId('Collateral').click();
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
cy.contains('[data-testid="deposit"]', 'Deposit').click();
cy.contains('[data-testid="deposit"]', 'Deposit')
.should('be.visible')
.click();
cy.getByTestId('deposit-submit').should('be.visible');
});
});
+5 -4
View File
@@ -169,13 +169,14 @@ describe('home', { tags: '@regression' }, () => {
it('click get started button should open connect dialog', () => {
cy.getByTestId('welcome-dialog').should('be.visible');
cy.getByTestId('get-started-button').click();
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.window().then((window) => {
// @ts-ignore stub it out just for test case
window.vega = {};
cy.getByTestId('get-started-button').click();
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
'true'
);
});
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
});
});
@@ -63,7 +63,6 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
@@ -74,7 +73,6 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
describe('market order', () => {
before(() => {
cy.getByTestId(toggleMarket).click();
cy.getByTestId(placeOrderBtn).click();
});
it('must not see the price unit', function () {
@@ -48,7 +48,6 @@ describe('suspended market validation', { tags: '@regression' }, () => {
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-warning-auction').should(
'have.text',
'Any orders placed now will not trade until the auction ends'
@@ -61,7 +60,6 @@ describe('suspended market validation', { tags: '@regression' }, () => {
TIFlist.filter((item) => item.code === 'FOK')[0].value
);
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-tif').should(
'have.text',
'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
@@ -53,7 +53,7 @@ describe('fills', { tags: '@regression' }, () => {
cy.getByTestId(tabFills).contains('Market');
cy.getByTestId(tabFills)
.get(
'[role="gridcell"][col-id="market.tradableInstrument.instrument.code"]'
'[role="gridcell"][col-id="market.tradableInstrument.instrument.name"]'
)
.each(($marketSymbol) => {
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
@@ -142,22 +142,22 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
it('sorting by Market', () => {
visitAndClickPositions();
const marketsSortedDefault = [
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
'ACTIVE MARKET',
'Apple Monthly (30 Jun 2022)',
'ETHBTC Quarterly (30 Jun 2022)',
'SUSPENDED MARKET',
];
const marketsSortedAsc = [
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
'ACTIVE MARKET',
'Apple Monthly (30 Jun 2022)',
'ETHBTC Quarterly (30 Jun 2022)',
'SUSPENDED MARKET',
];
const marketsSortedDesc = [
'SOLUSD',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAPL.MF21',
'SUSPENDED MARKET',
'ETHBTC Quarterly (30 Jun 2022)',
'Apple Monthly (30 Jun 2022)',
'ACTIVE MARKET',
];
cy.getByTestId(positions).click();
// 7004-POSI-003
@@ -291,11 +291,9 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
it('View settlement asset', () => {
visitAndClickPositions();
cy.get('[col-id="asset"]')
.eq(3)
.within(() => {
cy.get('button[type="button"]').click();
});
cy.get('[col-id="asset"]').within(() => {
cy.get('button[type="button"]').first().click();
});
// 7004-POSI-008
cy.getByTestId(dialogContent).should('be.visible');
cy.getByTestId(dialogCloseX).click();
@@ -310,7 +308,7 @@ function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions')
.get('.ag-center-cols-container .ag-row')
.eq(multiKey ? 3 : 1)
.first()
.within(() => {
cy.get('[col-id="marketName"]')
.should('be.visible')
@@ -5,8 +5,118 @@ import {
const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
const dialogContent = 'dialog-content';
describe(
'connect hosted wallet',
{ tags: '@smoke', testIsolation: true },
() => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
});
it('can connect', () => {
// 0002-WCON-002
// 0002-WCON-003
// 0002-WCON-039
// 0002-WCON-017
// 0002-WCON-018
// 0002-WCON-019
// Mock authentication
cy.intercept(
'POST',
'https://wallet.testnet.vega.xyz/api/v1/auth/token',
{
body: {
token: 'test-token',
},
}
);
// Mock getting keys from wallet
cy.intercept('GET', 'https://wallet.testnet.vega.xyz/api/v1/keys', {
body: {
keys: [
{
algorithm: {
name: 'algo',
version: 1,
},
index: 0,
meta: [],
pub: 'HOSTED_PUBKEY',
tainted: false,
},
],
},
});
cy.getByTestId(connectVegaBtn).click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
cy.contains('Connect Vega wallet');
cy.contains('Hosted Fairground wallet');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('user');
cy.getByTestId(form).find('#passphrase').click().type('pass');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(manageVegaBtn).should('exist');
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
});
it('doesnt connect with invalid credentials', () => {
// 0002-WCON-020
// Mock incorrect username/password
cy.intercept(
'POST',
'https://wallet.testnet.vega.xyz/api/v1/auth/token',
{
body: {
error: 'No wallet',
},
statusCode: 403, // 403 forbidden invalid crednetials
}
);
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('invalid name');
cy.getByTestId(form).find('#passphrase').click().type('invalid password');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
});
it('doesnt connect with empty fields', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-rest"]')
.click();
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.getByTestId(form)
.find('#wallet')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
cy.getByTestId(form)
.find('#passphrase')
.next('[data-testid="input-error-text"]')
.should('have.text', 'Required');
});
}
);
describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
// Using portfolio page as it requires vega wallet connection
@@ -86,6 +196,8 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.getByTestId('connect-vega-wallet').should('exist');
cy.getByTestId('manage-vega-wallet').should('not.exist');
cy.getByTestId('connect-vega-wallet').click();
cy.contains('Enter a custom wallet location');
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
});
});
@@ -12,108 +12,100 @@ const walletTransfer = 'wallet-transfer';
const ASSET_SEPOLIA_TBTC = 2;
describe.skip(
'transfer fees',
{ tags: '@regression', testIsolation: true },
() => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/');
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.visit('/');
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.wait('@Assets');
cy.wait('@Accounts');
cy.wait('@Assets');
cy.wait('@Accounts');
cy.mockVegaWalletTransaction();
});
cy.mockVegaWalletTransaction();
});
it('transfer fees tooltips', () => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
// 1003-TRAN-018
// 1003-TRAN-019
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type(
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
it('transfer fees tooltips', () => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
// 1003-TRAN-018
// 1003-TRAN-019
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
/// Check Include Transfer Fee tooltip
cy.get('label[for="include-transfer-fee"] div').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
/// Check Include Transfer Fee tooltip
cy.get('label[for="include-transfer-fee"] div').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
});
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
});
it('transfer fees', () => {
// 1003-TRAN-020
// 1003-TRAN-021
// 1003-TRAN-022
// 1003-TRAN-023
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
it('transfer fees', () => {
// 1003-TRAN-020
// 1003-TRAN-021
// 1003-TRAN-022
// 1003-TRAN-023
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type(
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
cy.getByTestId(transferForm)
.find(toAddressField)
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '1.00');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.01');
cy.getByTestId(includeTransferFeeRadioBtn).click();
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '0.99');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.00');
});
}
);
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '1.00');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.01');
cy.getByTestId(includeTransferFeeRadioBtn).click();
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '0.99');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.00');
});
});
+2 -3
View File
@@ -1,3 +1,4 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
@@ -12,11 +13,9 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
# NX_PRODUCT_PERPETUALS
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

-1
View File
@@ -1 +0,0 @@
window._env_ = {};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

-20
View File
@@ -1,20 +0,0 @@
{
"short_name": "Mainnet Stats",
"name": "Vega Mainnet statistics",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
-3
View File
@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+7 -4
View File
@@ -10,6 +10,7 @@ import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { ViewType, useSidebar } from '../../components/sidebar';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
@@ -64,6 +65,8 @@ export const MarketPage = () => {
const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId);
const onSelect = useMarketClickHandler();
const { data, error, loading } = useMarket(marketId);
useEffect(() => {
@@ -84,6 +87,7 @@ export const MarketPage = () => {
return (
<TradeGrid
market={data}
onSelect={onSelect}
pinnedAsset={
data?.tradableInstrument.instrument.product.settlementAsset
}
@@ -93,12 +97,11 @@ export const MarketPage = () => {
return (
<TradePanels
market={data}
pinnedAsset={
data?.tradableInstrument.instrument.product.settlementAsset
}
onSelect={onSelect}
onClickCollateral={() => navigate('/portfolio')}
/>
);
}, [largeScreen, data]);
}, [largeScreen, data, onSelect, navigate]);
if (!data && marketId) {
return (
+40 -31
View File
@@ -9,6 +9,7 @@ import { OracleBanner } from '@vegaprotocol/markets';
import type { Market } from '@vegaprotocol/markets';
import { Filter } from '@vegaprotocol/orders';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import {
ResizableGrid,
ResizableGridPanel,
@@ -23,6 +24,7 @@ import { FLAGS } from '@vegaprotocol/environment';
interface TradeGridProps {
market: Market | null;
onSelect: (marketId: string, metaKey?: boolean) => void;
pinnedAsset?: PinnedAsset;
}
@@ -38,19 +40,20 @@ const MainGrid = memo(
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle-1',
});
const onMarketClick = useMarketClickHandler(true);
return (
<ResizableGrid vertical onChange={handleOnLayoutChange}>
<ResizableGridPanel
preferredSize={sizes[0]}
priority={LayoutPriority.High}
minSize={200}
>
<ResizableGrid onChange={handleOnMiddleLayoutChange}>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
<ResizableGrid
proportionalLayout={false}
minSize={200}
onChange={handleOnMiddleLayoutChange}
>
<ResizableGridPanel
priority={LayoutPriority.High}
minSize={200}
preferredSize={sizesMiddle[0] || '75%'}
preferredSize={sizesMiddle[1] || '50%'}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-left">
@@ -71,8 +74,8 @@ const MainGrid = memo(
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
preferredSize={sizesMiddle[2] || 300}
minSize={200}
preferredSize={sizesMiddle[1] || 300}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-main-right">
@@ -88,34 +91,37 @@ const MainGrid = memo(
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={sizes[1] || '25%'}
minSize={50}
priority={LayoutPriority.Low}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<TradingViews.positions.component />
<TradingViews.positions.component
onMarketClick={onMarketClick}
/>
</Tab>
<Tab
id="open-orders"
name={t('Open')}
menu={<TradingViews.activeOrders.menu marketId={marketId} />}
>
<TradingViews.orders.component filter={Filter.Open} />
<Tab id="open-orders" name={t('Open')}>
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
/>
</Tab>
<Tab id="closed-orders" name={t('Closed')}>
<TradingViews.orders.component filter={Filter.Closed} />
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
/>
</Tab>
<Tab id="rejected-orders" name={t('Rejected')}>
<TradingViews.orders.component filter={Filter.Rejected} />
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
/>
</Tab>
<Tab
id="orders"
name={t('All')}
menu={<TradingViews.orders.menu marketId={marketId} />}
>
<TradingViews.orders.component />
<Tab id="orders" name={t('All')}>
<TradingViews.orders.component marketId={marketId} />
</Tab>
{FLAGS.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}>
@@ -123,14 +129,17 @@ const MainGrid = memo(
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<TradingViews.fills.component marketId={marketId} />
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
/>
</Tab>
<Tab
id="accounts"
name={t('Collateral')}
menu={<TradingViews.collateral.menu />}
>
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
<Tab id="accounts" name={t('Collateral')}>
<TradingViews.collateral.component
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
/>
</Tab>
</Tabs>
</TradeGridChild>
@@ -1,9 +1,13 @@
import type { PinnedAsset } from '@vegaprotocol/accounts';
import type { Market } from '@vegaprotocol/markets';
import { OracleBanner } from '@vegaprotocol/markets';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import type { TradingView } from './trade-views';
import { TradingViews } from './trade-views';
import { useState } from 'react';
import { memo, useState } from 'react';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { NO_MARKET } from './constants';
import AutoSizer from 'react-virtualized-auto-sizer';
@@ -16,14 +20,33 @@ import { FLAGS } from '@vegaprotocol/environment';
interface TradePanelsProps {
market: Market | null;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onOrderTypeClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
export const TradePanels = ({
market,
onSelect,
onClickCollateral,
pinnedAsset,
}: TradePanelsProps) => {
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const [view, setView] = useState<TradingView>('candles');
const renderView = () => {
const Component = TradingViews[view].component;
const Component = memo<{
marketId: string;
onSelect: (marketId: string, metaKey?: boolean) => void;
onMarketClick?: (marketId: string) => void;
onOrderTypeClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}>(TradingViews[view].component);
if (!Component) {
throw new Error(`No component for view: ${view}`);
@@ -31,7 +54,16 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
if (!market) return <Splash>{NO_MARKET}</Splash>;
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
return (
<Component
marketId={market?.id}
onSelect={onSelect}
onClickCollateral={onClickCollateral}
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
/>
);
};
const renderMenu = () => {
@@ -39,10 +71,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
if ('menu' in viewCfg) {
const Menu = viewCfg.menu;
return (
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu marketId={market?.id || ''} />
<Menu />
</div>
);
}
@@ -6,7 +6,7 @@ import {
CandlesChartContainer,
CandlesMenu,
} from '@vegaprotocol/candles-chart';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { Filter } from '@vegaprotocol/orders';
import { NO_MARKET } from './constants';
import { OrderbookContainer } from '../../components/orderbook-container';
import { FillsContainer } from '../../components/fills-container';
@@ -16,7 +16,6 @@ import { LiquidityContainer } from '../../components/liquidity-container';
import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container';
import { StopOrdersContainer } from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -63,7 +62,6 @@ export const TradingViews = {
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Open} />
),
menu: OpenOrdersMenu,
},
closedOrders: {
label: 'Closed',
@@ -80,16 +78,11 @@ export const TradingViews = {
orders: {
label: 'All',
component: OrdersContainer,
menu: OpenOrdersMenu,
},
stopOrders: {
label: 'Stop',
component: StopOrdersContainer,
},
collateral: {
label: 'Collateral',
component: AccountsContainer,
menu: AccountsMenu,
},
collateral: { label: 'Collateral', component: AccountsContainer },
fills: { label: 'Fills', component: FillsContainer },
};
@@ -411,26 +411,24 @@ describe('Closed', () => {
},
},
};
await act(() => {
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
render(
<MemoryRouter>
<MockedProvider
mocks={[
mixedMarketsMock,
marketsDataMock,
oracleDataMock,
successorMarketsMock,
]}
>
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
>
<VegaWalletContext.Provider
value={{ pubKey } as VegaWalletContextShape}
>
<Closed />
</VegaWalletContext.Provider>
</MockedProvider>
</MemoryRouter>
);
});
<Closed />
</VegaWalletContext.Provider>
</MockedProvider>
</MemoryRouter>
);
await waitFor(() => {
expect(
@@ -0,0 +1,40 @@
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useSidebar, ViewType } from '../../components/sidebar';
export const DepositsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const setView = useSidebar((store) => store.setView);
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<div className="h-full">
<DepositsTable
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
{!isReadOnly && (
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
>
{t('Deposit')}
</Button>
</div>
)}
</div>
);
};
@@ -6,11 +6,12 @@ import { t } from '@vegaprotocol/i18n';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { AccountsContainer } from '../../components/accounts-container';
import { DepositsContainer } from '../../components/deposits-container';
import { DepositsContainer } from './deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { WithdrawalsContainer } from './withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container';
import { AccountHistoryContainer } from './account-history-container';
@@ -20,9 +21,6 @@ import {
usePaneLayout,
} from '../../components/resizable-grid';
import { ViewType, useSidebar } from '../../components/sidebar';
import { AccountsMenu } from '../../components/accounts-menu';
import { DepositsMenu } from '../../components/deposits-menu';
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
@@ -53,6 +51,7 @@ export const Portfolio = () => {
}
}, [init, view, setView]);
const onMarketClick = useMarketClickHandler(true);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
return (
@@ -65,13 +64,13 @@ export const Portfolio = () => {
<AccountHistoryContainer />
</Tab>
<Tab id="positions" name={t('Positions')}>
<PositionsContainer allKeys />
<PositionsContainer onMarketClick={onMarketClick} allKeys />
</Tab>
<Tab id="orders" name={t('Orders')}>
<OrdersContainer />
</Tab>
<Tab id="fills" name={t('Fills')}>
<FillsContainer />
<FillsContainer onMarketClick={onMarketClick} />
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
<LedgerContainer />
@@ -86,21 +85,16 @@ export const Portfolio = () => {
>
<PortfolioGridChild>
<Tabs storageKey="console-portfolio-bottom">
<Tab
id="collateral"
name={t('Collateral')}
menu={<AccountsMenu />}
>
<Tab id="collateral" name={t('Collateral')}>
<AccountsContainer />
</Tab>
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
<Tab id="deposits" name={t('Deposits')}>
<DepositsContainer />
</Tab>
<Tab
id="withdrawals"
name={t('Withdrawals')}
indicator={<WithdrawalsIndicator />}
menu={<WithdrawalsMenu />}
>
<WithdrawalsContainer />
</Tab>
@@ -0,0 +1,49 @@
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
WithdrawalsTable,
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { ViewType, useSidebar } from '../../components/sidebar';
export const WithdrawalsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const setView = useSidebar((store) => store.setView);
const { ready, delayed } = useIncompleteWithdrawals();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<>
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
ready={ready}
delayed={delayed}
/>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</>
);
};
@@ -1,4 +1,5 @@
import { useCallback } from 'react';
import { Button } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
@@ -11,14 +12,16 @@ import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { ViewType, useSidebar } from '../sidebar';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
onMarketClick,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const setView = useSidebar((store) => store.setView);
@@ -45,23 +48,44 @@ export const AccountsContainer = ({
}
return (
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setView({ type: ViewType.Withdraw, assetId });
}}
onClickDeposit={(assetId) => {
setView({ type: ViewType.Deposit, assetId });
}}
onClickTransfer={(assetId) => {
setView({ type: ViewType.Transfer, assetId });
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
/>
<div className="h-full relative">
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={(assetId) => {
setView({ type: ViewType.Withdraw, assetId });
}}
onClickDeposit={(assetId) => {
setView({ type: ViewType.Deposit, assetId });
}}
onClickTransfer={(assetId) => {
setView({ type: ViewType.Transfer, assetId });
}}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 absolute bottom-0 right-0 dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
>
{t('Transfer')}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => setView({ type: ViewType.Deposit })}
>
{t('Deposit')}
</Button>
</div>
)}
</div>
);
};
@@ -1,27 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const AccountsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<>
<TradingButton
intent={Intent.Primary}
size="extra-small"
data-testid="open-transfer"
onClick={() => setView({ type: ViewType.Transfer })}
>
{t('Transfer')}
</TradingButton>
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
>
{t('Deposit')}
</TradingButton>
</>
);
};
@@ -1 +0,0 @@
export * from './accounts-menu';
@@ -1,24 +0,0 @@
import { Splash } from '@vegaprotocol/ui-toolkit';
import { DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { pubKey } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<DepositsTable
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
);
};
@@ -1 +0,0 @@
export * from './deposits-container';
@@ -1,18 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const DepositsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Deposit })}
data-testid="deposit-button"
>
{t('Deposit')}
</TradingButton>
);
};
@@ -1 +0,0 @@
export * from './deposits-menu';
@@ -7,10 +7,14 @@ import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const FillsContainer = ({ marketId }: { marketId?: string }) => {
const onMarketClick = useMarketClickHandler(true);
export const FillsContainer = ({
marketId,
onMarketClick,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey } = useVegaWallet();
const gridStore = useFillsStore((store) => store.gridStore);
@@ -134,20 +134,6 @@ describe('MarketSelector', () => {
error: undefined,
});
it('Button "All" should be selected by default', () => {
const buttons = ['All', 'Futures', 'Spot', 'Perpetuals'];
render(
<MemoryRouter>
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
</MemoryRouter>
);
screen
.getAllByTestId(/^product-(All|Future|Spot|Perpetual)$/)
.forEach((elem, i) => {
expect(elem.textContent).toEqual(buttons[i]);
});
});
it('renders only active markets', () => {
render(
<MemoryRouter>
@@ -183,12 +169,6 @@ describe('MarketSelector', () => {
activeMarkets.length
);
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
await userEvent.click(screen.getByTestId('product-All'));
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(
activeMarkets.length
);
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
});
it('filters by search term', async () => {
@@ -39,7 +39,7 @@ export const MarketSelector = ({
}) => {
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
product: Product.All,
product: Product.Future,
sort: Sort.None,
assets: [],
});
@@ -6,7 +6,6 @@ import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
// Make sure these match the available __typename properties on product
export const Product = {
All: 'All',
Future: 'Future',
Spot: 'Spot',
Perpetual: 'Perpetual',
@@ -17,7 +16,6 @@ export type ProductType = keyof typeof Product;
const ProductTypeMapping: {
[key in ProductType]: string;
} = {
[Product.All]: 'All',
[Product.Future]: 'Futures',
[Product.Spot]: 'Spot',
[Product.Perpetual]: 'Perpetuals',
@@ -51,12 +49,8 @@ export const ProductSelector = ({
</button>
);
})}
<Link
to={Routes.MARKETS}
className="flex items-center gap-2 ml-auto"
title={t('See all markets')}
>
<span className="underline underline-offset-4">{t('Browse')}</span>
<Link to={Routes.MARKETS} className="flex items-center gap-2 ml-auto">
<span className="underline underline-offset-4">{t('All markets')}</span>
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
</Link>
</div>
@@ -120,13 +120,6 @@ describe('useMarketSelectorList', () => {
assets: [],
});
expect(result.current.markets).toEqual([markets[2]]);
rerender({
searchTerm: '',
product: Product.All,
sort: Sort.None,
assets: [],
});
expect(result.current.markets).toEqual(markets);
});
it('filters by asset', () => {
@@ -5,7 +5,6 @@ import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets';
import { priceChangePercentage } from '@vegaprotocol/utils';
import type { Filter } from '../../components/market-selector/market-selector';
import { Sort } from './sort-dropdown';
import { Product } from './product-selector';
// Used for sort order and filter
const MARKET_TEMPLATE = [
@@ -29,10 +28,7 @@ export const useMarketSelectorList = ({
.filter((m) => isMarketActive(m.state))
// only selected product type
.filter((m) => {
if (
product === Product.All ||
m.tradableInstrument.instrument.product.__typename === product
) {
if (m.tradableInstrument.instrument.product.__typename === product) {
return true;
}
return false;
@@ -1,15 +1,27 @@
import { OrderbookManager } from '@vegaprotocol/market-depth';
import { useCreateOrderStore } from '@vegaprotocol/orders';
import { ViewType, useSidebar } from '../sidebar';
import { useDealTicketFormValues } from '@vegaprotocol/deal-ticket';
import { useStopOrderFormValues } from '@vegaprotocol/deal-ticket';
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
const update = useDealTicketFormValues((state) => state.updateAll);
const useOrderStoreRef = useCreateOrderStore();
const updateOrder = useOrderStoreRef((store) => store.update);
const updateStoredFormValues = useStopOrderFormValues(
(state) => state.update
);
const setView = useSidebar((store) => store.setView);
return (
<OrderbookManager
marketId={marketId}
onClick={(values) => {
update(marketId, values);
onClick={({ price, size }) => {
if (price) {
updateOrder(marketId, { price });
updateStoredFormValues(marketId, { price });
}
if (size) {
updateOrder(marketId, { size });
updateStoredFormValues(marketId, { size });
}
setView({ type: ViewType.Order });
}}
/>
@@ -25,10 +25,11 @@ export const FilterStatusValue = {
};
export interface OrderContainerProps {
marketId?: string;
filter?: Filter;
}
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
@@ -44,6 +45,7 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
return (
<OrderListManager
partyId={pubKey}
marketId={marketId}
filter={filter}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
@@ -7,10 +7,14 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true);
export const PositionsContainer = ({
onMarketClick,
allKeys,
}: {
onMarketClick?: (marketId: string) => void;
allKeys?: boolean;
}) => {
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
const gridStore = usePositionsStore((store) => store.gridStore);
@@ -1,31 +0,0 @@
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { GetStarted } from './get-started';
import { render, screen } from '@testing-library/react';
describe('GetStarted', () => {
const renderComponent = (context: Partial<VegaWalletContextShape> = {}) => {
return render(
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
<GetStarted />
</VegaWalletContext.Provider>
);
};
it('renders full get started content if not connected and no browser wallet detected', () => {
renderComponent();
expect(screen.getByTestId('get-started-banner')).toBeInTheDocument();
});
it('renders connect prompt if no pubKey but wallet installed', () => {
globalThis.window.vega = {} as Vega;
renderComponent();
expect(screen.getByTestId('order-connect-wallet')).toBeInTheDocument();
globalThis.window.vega = undefined as unknown as Vega;
});
it('renders nothing if connected', () => {
const { container } = renderComponent({ pubKey: 'my-pubkey' });
expect(container).toBeEmptyDOMElement();
});
});
@@ -16,6 +16,7 @@ interface Props {
export const GetStarted = ({ lead }: Props) => {
const { pubKey } = useVegaWallet();
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
@@ -39,7 +40,7 @@ export const GetStarted = ({ lead }: Props) => {
{ 'mt-8': !lead }
);
if (!pubKey && !isBrowserWalletInstalled()) {
if (!isBrowserWalletInstalled()) {
return (
<div className={wrapperClasses} data-testid="get-started-banner">
{lead && <h2>{lead}</h2>}
@@ -1 +0,0 @@
export * from './withdrawals-container';
@@ -1,31 +0,0 @@
import { Splash } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
WithdrawalsTable,
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider';
export const WithdrawalsContainer = () => {
const { pubKey } = useVegaWallet();
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const { ready, delayed } = useIncompleteWithdrawals();
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
ready={ready}
delayed={delayed}
/>
);
};
@@ -1 +0,0 @@
export * from './withdrawals-menu';
@@ -1,18 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import { ViewType, useSidebar } from '../sidebar';
export const WithdrawalsMenu = () => {
const setView = useSidebar((store) => store.setView);
return (
<TradingButton
intent={Intent.Primary}
size="extra-small"
onClick={() => setView({ type: ViewType.Withdraw })}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</TradingButton>
);
};
+3
View File
@@ -1,9 +1,11 @@
import {
RestConnector,
JsonRpcConnector,
ViewConnector,
InjectedConnector,
} from '@vegaprotocol/wallet';
export const rest = new RestConnector();
export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
@@ -17,6 +19,7 @@ if (typeof window !== 'undefined') {
export const Connectors = {
injected,
rest,
jsonRpc,
view,
};
+1 -27
View File
@@ -1,4 +1,3 @@
import Head from 'next/head';
import { ClientRouter } from './client-router';
/**
@@ -7,30 +6,5 @@ import { ClientRouter } from './client-router';
* have to serve a static site via next export
*/
export default function Index() {
return (
<>
<Head>
<meta charSet="utf-8" />
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Vega Protocol - Console" />
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
<link rel="manifest" href="assets/manifest.json" />
<link
rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
<title>VEGA Console dApp</title>
</Head>
<ClientRouter />
</>
);
return <ClientRouter />;
}
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
'scope-enum': async (ctx) => [
2,
'always',
['ci', 'docs', 'specs', ...(await utils.getProjects(ctx))],
['ci', 'docs', ...(await utils.getProjects(ctx))],
],
},
};
@@ -23,7 +23,6 @@ const singleRow = {
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
code: 'BTCUSD.MF21',
},
},
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
@@ -58,7 +57,7 @@ describe('BreakdownTable', () => {
});
const cells = await screen.findAllByRole('gridcell');
const expectedValues = [
'BTCUSD.MF21',
'BTCUSD Monthly (30 Jun 2022)',
'Margin',
'1,256.00 (50%)',
'1,256.00',
@@ -119,7 +118,6 @@ describe('BreakdownTable', () => {
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
code: 'BTCUSD.MF21',
},
},
},
+2 -2
View File
@@ -31,12 +31,12 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
const defs: ColDef[] = [
{
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.code',
field: 'market.tradableInstrument.instrument.name',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
AccountFields,
'market.tradableInstrument.instrument.code'
'market.tradableInstrument.instrument.name'
>) => {
if (!value) return 'None';
return value;
+2 -2
View File
@@ -28,10 +28,10 @@ export const assetProvider = makeDataProvider<
getData,
});
export const useAssetDataProvider = (assetId: string, skip?: boolean) => {
export const useAssetDataProvider = (assetId: string) => {
return useDataProvider({
dataProvider: assetProvider,
variables: { assetId: assetId || '' },
skip: !assetId || skip,
skip: !assetId,
});
};
@@ -10,11 +10,14 @@ declare global {
export const addConnectPublicKey = () => {
Cypress.Commands.add('connectPublicKey', (publicKey) => {
const connectVegaWaletBtn = Cypress.$(`[data-testid="view-as-user"]`);
const connectVegaWaletBtn = Cypress.$(
`[data-testid="connect-vega-wallet"]`
);
if (connectVegaWaletBtn.length > 0) {
cy.get('aside button').contains('View as party').click();
cy.getByTestId('address').should('be.visible').focus();
cy.getByTestId('address').type(publicKey, { delay: 50 });
cy.get('aside [data-testid="connect-vega-wallet"]').click();
cy.getByTestId('connector-view').should('be.visible').click();
cy.getByTestId('address').click();
cy.getByTestId('address').type(publicKey);
cy.getByTestId('connect').click();
}
});
@@ -29,7 +29,7 @@ export const mockConnectWallet = () => {
export const mockConnectWalletWithUserError = () => {
cy.mockWallet((req) => {
aliasWalletConnectWithUserError(req, Cypress.env('VEGA_WALLET_API_TOKEN'));
aliasWalletConnectWithUserError(req);
});
};
+1 -34
View File
@@ -62,27 +62,10 @@ export const aliasWalletConnectQuery = (
},
});
}
if (hasMethod(req, 'client.get_chain_id')) {
req.reply({
statusCode: 200,
headers: {
'Access-Control-Expose-Headers': 'Authorization',
Authorization: `VWT ${token}`,
},
body: {
jsonrpc: '2.0',
result: {
chainID: 'test-id',
},
id: '1',
},
});
}
};
export const aliasWalletConnectWithUserError = (
req: CyHttpMessages.IncomingHttpRequest,
token: string
req: CyHttpMessages.IncomingHttpRequest
) => {
if (hasMethod(req, 'client.connect_wallet')) {
req.alias = 'client.connect_wallet';
@@ -99,20 +82,4 @@ export const aliasWalletConnectWithUserError = (
},
});
}
if (hasMethod(req, 'client.get_chain_id')) {
req.reply({
statusCode: 200,
headers: {
'Access-Control-Expose-Headers': 'Authorization',
Authorization: `VWT ${token}`,
},
body: {
jsonrpc: '2.0',
result: {
chainID: 'test-id',
},
id: '1',
},
});
}
};
@@ -1,4 +1,4 @@
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { Intent, Notification, Link } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
interface ZeroBalanceErrorProps {
@@ -6,11 +6,13 @@ interface ZeroBalanceErrorProps {
id: string;
symbol: string;
};
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
export const ZeroBalanceError = ({
asset,
onClickCollateral,
onDeposit,
}: ZeroBalanceErrorProps) => {
return (
@@ -23,6 +25,12 @@ export const ZeroBalanceError = ({
'You need %s in your wallet to trade in this market. ',
asset.symbol
)}
{onClickCollateral && (
<>
{t('See all your')}{' '}
<Link onClick={onClickCollateral}>collateral</Link>.
</>
)}
</>
}
buttonProps={{
@@ -3,25 +3,29 @@ import type { Market, StaticMarketData } from '@vegaprotocol/markets';
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
import * as Schema from '@vegaprotocol/types';
import type { OrderFormValues } from '../../hooks/use-form-values';
import type { OrderObj } from '@vegaprotocol/orders';
import type { OrderFormFields } from '../../hooks/use-order-form';
export interface DealTicketAmountProps {
control: Control<OrderFormValues>;
type: Schema.OrderType;
control: Control<OrderFormFields>;
orderType: Schema.OrderType;
marketData: StaticMarketData;
marketPrice?: string;
market: Market;
sizeError?: string;
priceError?: string;
update: (obj: Partial<OrderObj>) => void;
size: string;
price?: string;
}
export const DealTicketAmount = ({
type,
orderType,
marketData,
marketPrice,
...props
}: DealTicketAmountProps) => {
switch (type) {
switch (orderType) {
case Schema.OrderType.TYPE_MARKET:
return (
<DealTicketMarketAmount
@@ -33,7 +37,7 @@ export const DealTicketAmount = ({
case Schema.OrderType.TYPE_LIMIT:
return <DealTicketLimitAmount {...props} />;
default: {
throw new Error('Invalid ticket type ' + type);
throw new Error('Invalid ticket type');
}
}
};
@@ -1,8 +1,8 @@
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
import {
isStopOrderType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
DealTicketType,
useDealTicketTypeStore,
} from '../../hooks/use-type-store';
import { StopOrder } from './deal-ticket-stop-order';
import {
useStaticMarketData,
@@ -17,6 +17,7 @@ import { FLAGS } from '@vegaprotocol/environment';
interface DealTicketContainerProps {
marketId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
@@ -24,9 +25,7 @@ export const DealTicketContainer = ({
marketId,
...props
}: DealTicketContainerProps) => {
const showStopOrder = useDealTicketFormValues((state) =>
isStopOrderType(state.formValues[marketId]?.type)
);
const type = useDealTicketTypeStore((state) => state.type[marketId]);
const {
data: market,
error: marketError,
@@ -49,7 +48,9 @@ export const DealTicketContainer = ({
reload={reload}
>
{market && marketData ? (
FLAGS.STOP_ORDERS && showStopOrder ? (
FLAGS.STOP_ORDERS &&
(type === DealTicketType.StopLimit ||
type === DealTicketType.StopMarket) ? (
<StopOrder
market={market}
marketPrice={marketPrice}
@@ -5,8 +5,8 @@ import type { DealTicketAmountProps } from './deal-ticket-amount';
import { Controller } from 'react-hook-form';
export type DealTicketLimitAmountProps = Omit<
DealTicketAmountProps,
'marketData' | 'type'
Omit<DealTicketAmountProps, 'marketData'>,
'orderType'
>;
export const DealTicketLimitAmount = ({
@@ -14,6 +14,9 @@ export const DealTicketLimitAmount = ({
market,
sizeError,
priceError,
update,
price,
size,
}: DealTicketLimitAmountProps) => {
const priceStep = toDecimal(market?.decimalPlaces);
const sizeStep = toDecimal(market?.positionDecimalPlaces);
@@ -59,16 +62,17 @@ export const DealTicketLimitAmount = ({
},
validate: validateAmount(sizeStep, 'Size'),
}}
render={({ field }) => (
render={() => (
<Input
id="input-order-size-limit"
className="w-full"
type="number"
value={size}
onChange={(e) => update({ size: e.target.value })}
step={sizeStep}
min={sizeStep}
data-testid="order-size"
onWheel={(e) => e.currentTarget.blur()}
{...field}
/>
)}
/>
@@ -91,17 +95,19 @@ export const DealTicketLimitAmount = ({
value: priceStep,
message: t('Price cannot be lower than ' + priceStep),
},
// @ts-ignore this fulfills the interface but still errors
validate: validateAmount(priceStep, 'Price'),
}}
render={({ field }) => (
render={() => (
<Input
id="input-price-quote"
className="w-full"
type="number"
value={price}
onChange={(e) => update({ price: e.target.value })}
step={priceStep}
data-testid="order-price"
onWheel={(e) => e.currentTarget.blur()}
{...field}
/>
)}
/>
@@ -10,7 +10,10 @@ import type { DealTicketAmountProps } from './deal-ticket-amount';
import { Controller } from 'react-hook-form';
import classNames from 'classnames';
export type DealTicketMarketAmountProps = Omit<DealTicketAmountProps, 'type'>;
export type DealTicketMarketAmountProps = Omit<
DealTicketAmountProps,
'orderType'
>;
export const DealTicketMarketAmount = ({
control,
@@ -18,6 +21,8 @@ export const DealTicketMarketAmount = ({
marketData,
marketPrice,
sizeError,
update,
size,
}: DealTicketMarketAmountProps) => {
const quoteName = market.tradableInstrument.instrument.product.quoteName;
const sizeStep = toDecimal(market?.positionDecimalPlaces);
@@ -45,16 +50,17 @@ export const DealTicketMarketAmount = ({
},
validate: validateAmount(sizeStep, 'Size'),
}}
render={({ field }) => (
render={() => (
<Input
id="input-order-size-market"
className="w-full"
type="number"
value={size}
onChange={(e) => update({ size: e.target.value })}
step={sizeStep}
min={sizeStep}
onWheel={(e) => e.currentTarget.blur()}
data-testid="order-size"
{...field}
/>
)}
/>
@@ -1,6 +1,7 @@
import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import type { OrderObj } from '@vegaprotocol/orders';
import type { OrderFormFields } from '../../hooks/use-order-form';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
@@ -11,21 +12,25 @@ import {
} from '@vegaprotocol/ui-toolkit';
export interface DealTicketSizeIcebergProps {
control: Control<OrderFormValues>;
control: Control<OrderFormFields>;
market: Market;
peakSizeError?: string;
minimumVisibleSizeError?: string;
update: (obj: Partial<OrderObj>) => void;
peakSize: string;
minimumVisibleSize: string;
size: string;
peakSize?: string;
}
export const DealTicketSizeIceberg = ({
control,
market,
update,
peakSizeError,
minimumVisibleSizeError,
size,
peakSize,
minimumVisibleSize,
size,
}: DealTicketSizeIcebergProps) => {
const sizeStep = toDecimal(market?.positionDecimalPlaces);
@@ -75,7 +80,7 @@ export const DealTicketSizeIceberg = ({
className="!mb-1"
>
<Controller
name="peakSize"
name="icebergOpts.peakSize"
control={control}
rules={{
required: t('You need to provide a peak size'),
@@ -92,17 +97,25 @@ export const DealTicketSizeIceberg = ({
},
validate: validateAmount(sizeStep, 'peakSize'),
}}
render={({ field }) => (
render={() => (
<Input
id="input-order-peak-size"
className="w-full"
type="number"
value={peakSize}
onChange={(e) =>
update({
icebergOpts: {
peakSize: e.target.value,
minimumVisibleSize,
},
})
}
step={sizeStep}
min={sizeStep}
max={size}
data-testid="order-peak-size"
onWheel={(e) => e.currentTarget.blur()}
{...field}
/>
)}
/>
@@ -131,7 +144,7 @@ export const DealTicketSizeIceberg = ({
className="!mb-1"
>
<Controller
name="minimumVisibleSize"
name="icebergOpts.minimumVisibleSize"
control={control}
rules={{
required: t('You need to provide a minimum visible size'),
@@ -141,7 +154,7 @@ export const DealTicketSizeIceberg = ({
'Minimum visible size cannot be lower than ' + sizeStep
),
},
max: peakSize && {
max: {
value: peakSize,
message: t(
'Minimum visible size cannot be greater than the peak size (%s)',
@@ -150,17 +163,25 @@ export const DealTicketSizeIceberg = ({
},
validate: validateAmount(sizeStep, 'minimumVisibleSize'),
}}
render={({ field }) => (
render={() => (
<Input
id="input-order-minimum-size"
className="w-full"
type="number"
value={minimumVisibleSize}
onChange={(e) =>
update({
icebergOpts: {
peakSize,
minimumVisibleSize: e.target.value,
},
})
}
step={sizeStep}
min={sizeStep}
max={peakSize}
data-testid="order-minimum-size"
onWheel={(e) => e.currentTarget.blur()}
{...field}
/>
)}
/>
@@ -6,11 +6,8 @@ import { generateMarket } from '../../test-helpers';
import { StopOrder } from './deal-ticket-stop-order';
import * as Schema from '@vegaprotocol/types';
import { MockedProvider } from '@apollo/client/testing';
import type { StopOrderFormValues } from '../../hooks/use-form-values';
import {
DealTicketType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
import type { StopOrderFormValues } from '../../hooks/use-stop-order-form-values';
import { useStopOrderFormValues } from '../../hooks/use-stop-order-form-values';
import type { FeatureFlags } from '@vegaprotocol/environment';
jest.mock('zustand');
@@ -134,11 +131,9 @@ describe('StopOrder', () => {
expiresAt: '2023-07-27T16:43:27.000',
};
useDealTicketFormValues.setState({
useStopOrderFormValues.setState({
formValues: {
[market.id]: {
[DealTicketType.StopLimit]: values,
},
[market.id]: values,
},
});
@@ -212,13 +207,11 @@ describe('StopOrder', () => {
// switch to market order type error should disappear
await userEvent.click(screen.getByTestId(orderTypeTrigger));
await userEvent.click(screen.getByTestId(orderTypeMarket));
await userEvent.click(screen.getByTestId(submitButton));
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
// switch back to limit type
await userEvent.click(screen.getByTestId(orderTypeTrigger));
await userEvent.click(screen.getByTestId(orderTypeLimit));
await userEvent.click(screen.getByTestId(submitButton));
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
// to small value should be invalid
@@ -1,3 +1,4 @@
import type { FormEventHandler } from 'react';
import { useRef, useCallback, useEffect } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
@@ -7,7 +8,7 @@ import {
toDecimal,
validateAmount,
} from '@vegaprotocol/utils';
import { useForm, Controller, useController } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import * as Schema from '@vegaprotocol/types';
import {
Radio,
@@ -23,22 +24,22 @@ import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n';
import { ExpirySelector } from './expiry-selector';
import { SideSelector } from './side-selector';
import { timeInForceLabel } from '@vegaprotocol/orders';
import { timeInForceLabel, useOrder } from '@vegaprotocol/orders';
import {
NoWalletWarning,
REDUCE_ONLY_TOOLTIP,
stopSubmit,
getNotionalSize,
useNotionalSize,
} from './deal-ticket';
import { TypeToggle } from './type-selector';
import {
useDealTicketFormValues,
DealTicketType,
useStopOrderFormValues,
type StopOrderFormValues,
dealTicketTypeToOrderType,
isStopOrderType,
} from '../../hooks/use-form-values';
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
} from '../../hooks/use-stop-order-form-values';
import {
DealTicketType,
useDealTicketTypeStore,
} from '../../hooks/use-type-store';
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-stop-order-submission';
import { DealTicketButton } from './deal-ticket-button';
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
import { validateExpiration } from '../../utils';
@@ -49,36 +50,32 @@ export interface StopOrderProps {
submit: (order: StopOrdersSubmission) => void;
}
const getDefaultValues = (
type: Schema.OrderType,
storedValues?: Partial<StopOrderFormValues>
): StopOrderFormValues => ({
type,
const defaultValues: Partial<StopOrderFormValues> = {
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
triggerType: 'price',
triggerDirection:
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE,
expire: false,
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT,
size: '0',
...storedValues,
});
};
const stopSubmit: FormEventHandler = (e) => e.preventDefault();
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType);
const updateStoredFormValues = useDealTicketFormValues(
(state) => state.updateStopOrder
const setDealTicketType = useDealTicketTypeStore((state) => state.set);
const [, updateOrder] = useOrder(market.id);
const updateStoredFormValues = useStopOrderFormValues(
(state) => state.update
);
const storedFormValues = useDealTicketFormValues(
const storedFormValues = useStopOrderFormValues(
(state) => state.formValues[market.id]
);
const dealTicketType = storedFormValues?.type ?? DealTicketType.StopLimit;
const type = dealTicketTypeToOrderType(dealTicketType);
const { handleSubmit, setValue, watch, control, formState, reset } =
const { handleSubmit, setValue, watch, control, formState } =
useForm<StopOrderFormValues>({
defaultValues: getDefaultValues(type, storedFormValues?.[dealTicketType]),
defaultValues: { ...defaultValues, ...storedFormValues },
});
const { errors } = formState;
const lastSubmitTime = useRef(0);
@@ -105,22 +102,16 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
const triggerType = watch('triggerType');
const triggerPrice = watch('triggerPrice');
const timeInForce = watch('timeInForce');
const type = watch('type');
const rawPrice = watch('price');
const rawSize = watch('size');
useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size;
if (size && rawSize !== size) {
setValue('size', size);
}
}, [storedFormValues, dealTicketType, rawSize, setValue]);
useEffect(() => {
const price = storedFormValues?.[dealTicketType]?.price;
if (price && rawPrice !== price) {
setValue('price', price);
}
}, [storedFormValues, dealTicketType, rawPrice, setValue]);
if (storedFormValues?.size && rawSize !== storedFormValues?.size) {
setValue('size', storedFormValues.size);
}
if (storedFormValues?.price && rawPrice !== storedFormValues?.price) {
setValue('price', storedFormValues.price);
}
const isPriceTrigger = triggerType === 'price';
const size = removeDecimal(rawSize, market.positionDecimalPlaces);
@@ -136,7 +127,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
: marketPrice
);
const notionalSize = getNotionalSize(
const notionalSize = useNotionalSize(
price,
size,
market.decimalPlaces,
@@ -162,28 +153,47 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
? formatNumber(triggerPrice, market.decimalPlaces)
: undefined;
useController({
name: 'type',
control,
});
return (
<form
onSubmit={isReadOnly || !pubKey ? stopSubmit : handleSubmit(onSubmit)}
noValidate
>
<TypeToggle
value={dealTicketType}
onValueChange={(dealTicketType) => {
setType(market.id, dealTicketType);
if (isStopOrderType(dealTicketType)) {
reset(
getDefaultValues(
dealTicketTypeToOrderType(dealTicketType),
storedFormValues?.[dealTicketType]
)
);
}
<Controller
name="type"
control={control}
render={({ field }) => {
const { value } = field;
return (
<TypeToggle
value={
value === Schema.OrderType.TYPE_LIMIT
? DealTicketType.StopLimit
: DealTicketType.StopMarket
}
onValueChange={(value) => {
const type = value as DealTicketType;
setDealTicketType(market.id, type);
if (
type === DealTicketType.Limit ||
type === DealTicketType.Market
) {
updateOrder({
type:
type === DealTicketType.Limit
? Schema.OrderType.TYPE_LIMIT
: Schema.OrderType.TYPE_MARKET,
});
return;
}
setValue(
'type',
type === DealTicketType.StopLimit
? Schema.OrderType.TYPE_LIMIT
: Schema.OrderType.TYPE_MARKET
);
}}
/>
);
}}
/>
{errors.type && (
@@ -1,6 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { act, render, screen, waitFor } from '@testing-library/react';
import {
act,
render,
renderHook,
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { generateMarket, generateMarketData } from '../../test-helpers';
import { DealTicket } from './deal-ticket';
@@ -9,10 +15,7 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { addDecimal } from '@vegaprotocol/utils';
import type { OrdersQuery } from '@vegaprotocol/orders';
import {
DealTicketType,
useDealTicketFormValues,
} from '../../hooks/use-form-values';
import { useCreateOrderStore } from '@vegaprotocol/orders';
import * as positionsTools from '@vegaprotocol/positions';
import { OrdersDocument } from '@vegaprotocol/orders';
@@ -47,6 +50,9 @@ function generateJsx(mocks: MockedResponse[] = []) {
}
describe('DealTicket', () => {
const { result } = renderHook(() => useCreateOrderStore());
const useOrderStore = result.current;
beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
@@ -160,11 +166,9 @@ describe('DealTicket', () => {
persist: true,
};
useDealTicketFormValues.setState({
formValues: {
[expectedOrder.marketId]: {
[DealTicketType.Limit]: expectedOrder,
},
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
@@ -200,11 +204,9 @@ describe('DealTicket', () => {
reduceOnly: true,
postOnly: false,
};
useDealTicketFormValues.setState({
formValues: {
[expectedOrder.marketId]: {
[DealTicketType.Limit]: expectedOrder,
},
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
@@ -245,11 +247,9 @@ describe('DealTicket', () => {
postOnly: true,
};
useDealTicketFormValues.setState({
formValues: {
[expectedOrder.marketId]: {
[DealTicketType.Limit]: expectedOrder,
},
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
@@ -295,11 +295,9 @@ describe('DealTicket', () => {
},
};
useDealTicketFormValues.setState({
formValues: {
[expectedOrder.marketId]: {
[DealTicketType.Limit]: expectedOrder,
},
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
@@ -341,11 +339,9 @@ describe('DealTicket', () => {
reduceOnly: false,
postOnly: false,
};
useDealTicketFormValues.setState({
formValues: {
[expectedOrder.marketId]: {
[DealTicketType.Limit]: expectedOrder,
},
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
@@ -374,7 +370,6 @@ describe('DealTicket', () => {
expect(screen.getByTestId('iceberg')).not.toBeChecked();
});
// eslint-disable-next-line jest/no-disabled-tests
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
@@ -1,8 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import type { FormEventHandler } from 'react';
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
import { Controller, useController, useForm } from 'react-hook-form';
import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react';
import { Controller } from 'react-hook-form';
import { DealTicketAmount } from './deal-ticket-amount';
import { DealTicketButton } from './deal-ticket-button';
import {
@@ -14,8 +13,7 @@ import { SideSelector } from './side-selector';
import { TimeInForceSelector } from './time-in-force-selector';
import { TypeSelector } from './type-selector';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { mapFormValuesToOrderSubmission } from '../../utils/map-form-values-to-submission';
import { normalizeOrderSubmission, useVegaWallet } from '@vegaprotocol/wallet';
import {
Checkbox,
InputError,
@@ -53,15 +51,14 @@ import {
useAccountBalance,
} from '@vegaprotocol/accounts';
import { OrderType } from '@vegaprotocol/types';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import { useOrderForm } from '../../hooks/use-order-form';
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
DealTicketType,
dealTicketTypeToOrderType,
isStopOrderType,
} from '../../hooks/use-form-values';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { useDealTicketFormValues } from '../../hooks/use-form-values';
useDealTicketTypeStore,
} from '../../hooks/use-type-store';
import { useStopOrderFormValues } from '../../hooks/use-stop-order-form-values';
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
import noop from 'lodash/noop';
@@ -74,45 +71,27 @@ export interface DealTicketProps {
marketPrice?: string | null;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
submit: (order: OrderSubmission) => void;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
export const getNotionalSize = (
export const useNotionalSize = (
price: string | null | undefined,
size: string | undefined,
decimalPlaces: number,
positionDecimalPlaces: number
) => {
if (price && size) {
return removeDecimal(
toBigNum(size, positionDecimalPlaces).multipliedBy(
toBigNum(price, decimalPlaces)
),
decimalPlaces
);
}
return null;
};
export const stopSubmit: FormEventHandler = (e) => e.preventDefault();
const getDefaultValues = (
type: Schema.OrderType,
storedValues?: Partial<OrderFormValues>
): OrderFormValues => ({
type,
side: Schema.Side.SIDE_BUY,
timeInForce:
type === Schema.OrderType.TYPE_LIMIT
? Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
size: '0',
price: '0',
expiresAt: undefined,
postOnly: false,
reduceOnly: false,
...storedValues,
});
) =>
useMemo(() => {
if (price && size) {
return removeDecimal(
toBigNum(size, positionDecimalPlaces).multipliedBy(
toBigNum(price, decimalPlaces)
),
decimalPlaces
);
}
return null;
}, [price, size, decimalPlaces, positionDecimalPlaces]);
export const DealTicket = ({
market,
@@ -120,32 +99,36 @@ export const DealTicket = ({
marketData,
marketPrice,
submit,
onClickCollateral,
onDeposit,
}: DealTicketProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType);
const storedFormValues = useDealTicketFormValues(
(state) => state.formValues[market.id]
const setDealTicketType = useDealTicketTypeStore((state) => state.set);
const updateStopOrderFormValues = useStopOrderFormValues(
(state) => state.update
);
const updateStoredFormValues = useDealTicketFormValues(
(state) => state.updateOrder
);
const dealTicketType = storedFormValues?.type ?? DealTicketType.Limit;
const type = dealTicketTypeToOrderType(dealTicketType);
// store last used tif for market so that when changing OrderType the previous TIF
// selection for that type is used when switching back
const [lastTIF, setLastTIF] = useState({
[OrderType.TYPE_MARKET]: OrderTimeInForce.TIME_IN_FORCE_IOC,
[OrderType.TYPE_LIMIT]: OrderTimeInForce.TIME_IN_FORCE_GTC,
});
const {
control,
reset,
formState: { errors },
errors,
order,
setError,
clearErrors,
update,
handleSubmit,
setValue,
watch,
} = useForm<OrderFormValues>({
defaultValues: getDefaultValues(type, storedFormValues?.[dealTicketType]),
});
} = useOrderForm(market.id);
const lastSubmitTime = useRef(0);
const asset = market.tradableInstrument.instrument.product.settlementAsset;
const {
accountBalance: marginAccountBalance,
loading: loadingMarginAccountBalance,
@@ -161,54 +144,24 @@ export const DealTicket = ({
).toString();
const { marketState, marketTradingMode } = marketData;
const timeInForce = watch('timeInForce');
const side = watch('side');
const rawSize = watch('size');
const rawPrice = watch('price');
const iceberg = watch('iceberg');
const peakSize = watch('peakSize');
const normalizedOrder =
order &&
normalizeOrderSubmission(
order,
market.decimalPlaces,
market.positionDecimalPlaces
);
useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size;
if (size && rawSize !== size) {
setValue('size', size);
}
}, [storedFormValues, dealTicketType, rawSize, setValue]);
const price = useMemo(() => {
return (
normalizedOrder &&
marketPrice &&
getDerivedPrice(normalizedOrder, marketPrice)
);
}, [normalizedOrder, marketPrice]);
useEffect(() => {
const price = storedFormValues?.[dealTicketType]?.price;
if (price && rawPrice !== price) {
setValue('price', price);
}
}, [storedFormValues, dealTicketType, rawPrice, setValue]);
useEffect(() => {
const subscription = watch((value, { name, type }) => {
updateStoredFormValues(market.id, value);
});
return () => subscription.unsubscribe();
}, [watch, market.id, updateStoredFormValues]);
const normalizedOrder = mapFormValuesToOrderSubmission(
{
price: rawPrice || undefined,
side,
size: rawSize,
timeInForce,
type,
},
market.id,
market.decimalPlaces,
market.positionDecimalPlaces
);
const price =
normalizedOrder &&
marketPrice &&
getDerivedPrice(normalizedOrder, marketPrice);
const notionalSize = getNotionalSize(
const notionalSize = useNotionalSize(
price,
normalizedOrder?.size,
market.decimalPlaces,
@@ -252,20 +205,22 @@ export const DealTicket = ({
const assetSymbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
const summaryError = useMemo(() => {
useEffect(() => {
if (!pubKey) {
return {
setError('summary', {
message: t('No public key selected'),
type: SummaryValidationType.NoPubKey,
};
});
return;
}
const marketStateError = validateMarketState(marketState);
if (marketStateError !== true) {
return {
setError('summary', {
message: marketStateError,
type: SummaryValidationType.MarketState,
};
});
return;
}
const hasNoBalance =
@@ -274,21 +229,24 @@ export const DealTicket = ({
hasNoBalance &&
!(loadingMarginAccountBalance || loadingGeneralAccountBalance)
) {
return {
setError('summary', {
message: SummaryValidationType.NoCollateral,
type: SummaryValidationType.NoCollateral,
};
});
return;
}
const marketTradingModeError = validateMarketTradingMode(marketTradingMode);
if (marketTradingModeError !== true) {
return {
setError('summary', {
message: marketTradingModeError,
type: SummaryValidationType.TradingMode,
};
});
return;
}
return undefined;
// No error found above clear the error in case it was active on a previous render
clearErrors('summary');
}, [
marketState,
marketTradingMode,
@@ -297,83 +255,156 @@ export const DealTicket = ({
loadingMarginAccountBalance,
loadingGeneralAccountBalance,
pubKey,
setError,
clearErrors,
]);
const disablePostOnlyCheckbox = [
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(timeInForce);
const disablePostOnlyCheckbox = useMemo(() => {
const disabled = order
? [
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(order.timeInForce)
: true;
return disabled;
}, [order]);
const disableReduceOnlyCheckbox = !disablePostOnlyCheckbox;
const disableReduceOnlyCheckbox = useMemo(() => {
const disabled = order
? ![
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
].includes(order.timeInForce)
: true;
return disabled;
}, [order]);
const onSubmit = useCallback(
(formValues: OrderFormValues) => {
(order: OrderSubmission) => {
const now = new Date().getTime();
if (lastSubmitTime.current && now - lastSubmitTime.current < 1000) {
return;
}
submit(
mapFormValuesToOrderSubmission(
formValues,
market.id,
normalizeOrderSubmission(
order,
market.decimalPlaces,
market.positionDecimalPlaces
)
);
lastSubmitTime.current = now;
},
[submit, market.decimalPlaces, market.positionDecimalPlaces, market.id]
[submit, market.decimalPlaces, market.positionDecimalPlaces]
);
useController({
name: 'type',
control,
rules: {
validate: validateType(marketData.marketTradingMode, marketData.trigger),
},
});
// if an order doesn't exist one will be created by the store immediately
if (!order || !normalizedOrder) {
return null;
}
return (
<form
onSubmit={
isReadOnly || !pubKey
? stopSubmit
: handleSubmit(summaryError ? noop : onSubmit)
}
onSubmit={isReadOnly ? noop : handleSubmit(onSubmit)}
noValidate
data-testid="deal-ticket-form"
>
<TypeSelector
value={dealTicketType}
onValueChange={(dealTicketType) => {
setType(market.id, dealTicketType);
if (!isStopOrderType(dealTicketType)) {
reset(
getDefaultValues(
dealTicketTypeToOrderType(dealTicketType),
storedFormValues?.[dealTicketType]
)
);
}
<Controller
name="type"
control={control}
rules={{
validate: validateType(
marketData.marketTradingMode,
marketData.trigger
),
}}
market={market}
marketData={marketData}
errorMessage={errors.type?.message}
render={() => (
<TypeSelector
value={
order.type === OrderType.TYPE_LIMIT
? DealTicketType.Limit
: DealTicketType.Market
}
onValueChange={(dealTicketType) => {
setDealTicketType(market.id, dealTicketType);
if (
dealTicketType !== DealTicketType.Limit &&
dealTicketType !== DealTicketType.Market
) {
updateStopOrderFormValues(market.id, {
type:
dealTicketType === DealTicketType.StopLimit
? OrderType.TYPE_LIMIT
: OrderType.TYPE_MARKET,
});
return;
}
const type =
dealTicketType === DealTicketType.Limit
? OrderType.TYPE_LIMIT
: OrderType.TYPE_MARKET;
update({
type,
// when changing type also update the TIF to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
postOnly:
type === OrderType.TYPE_MARKET ? false : order.postOnly,
iceberg:
type === OrderType.TYPE_MARKET ||
[
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(lastTIF[type] || order.timeInForce)
? false
: order.iceberg,
icebergOpts:
type === OrderType.TYPE_MARKET ||
[
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(lastTIF[type] || order.timeInForce)
? undefined
: order.icebergOpts,
reduceOnly:
type === OrderType.TYPE_LIMIT &&
![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(lastTIF[type] || order.timeInForce)
? false
: order.postOnly,
expiresAt: undefined,
});
clearErrors(['expiresAt', 'price']);
}}
market={market}
marketData={marketData}
errorMessage={errors.type?.message}
/>
)}
/>
<Controller
name="side"
control={control}
render={({ field }) => (
<SideSelector value={field.value} onValueChange={field.onChange} />
render={() => (
<SideSelector
value={order.side}
onValueChange={(side) => {
update({ side });
}}
/>
)}
/>
<DealTicketAmount
type={type}
control={control}
orderType={order.type}
market={market}
marketData={marketData}
marketPrice={marketPrice || undefined}
sizeError={errors.size?.message}
priceError={errors.price?.message}
update={update}
size={order.size}
price={order.price}
/>
<Controller
name="timeInForce"
@@ -384,29 +415,58 @@ export const DealTicket = ({
marketData.trigger
),
}}
render={({ field }) => (
render={() => (
<TimeInForceSelector
value={field.value}
orderType={type}
onSelect={field.onChange}
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
// Reset post only and reduce only when changing TIF
update({
timeInForce,
postOnly: [
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.postOnly,
reduceOnly: ![
OrderTimeInForce.TIME_IN_FORCE_FOK,
OrderTimeInForce.TIME_IN_FORCE_IOC,
].includes(timeInForce)
? false
: order.reduceOnly,
});
// Set TIF value for the given order type, so that when switching
// types we know the last used TIF for the given order type
setLastTIF((curr) => ({
...curr,
[order.type]: timeInForce,
expiresAt: undefined,
}));
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.timeInForce?.message}
/>
)}
/>
{type === Schema.OrderType.TYPE_LIMIT &&
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
{order.type === Schema.OrderType.TYPE_LIMIT &&
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
<Controller
name="expiresAt"
control={control}
rules={{
validate: validateExpiration,
}}
render={({ field }) => (
render={() => (
<ExpirySelector
value={field.value}
onSelect={(expiresAt) => field.onChange(expiresAt)}
value={order.expiresAt}
onSelect={(expiresAt) =>
update({
expiresAt: expiresAt || undefined,
})
}
errorMessage={errors.expiresAt?.message}
/>
)}
@@ -416,14 +476,13 @@ export const DealTicket = ({
<Controller
name="postOnly"
control={control}
render={({ field }) => (
render={() => (
<Checkbox
name="post-only"
checked={!disablePostOnlyCheckbox && field.value}
checked={order.postOnly}
disabled={disablePostOnlyCheckbox}
onCheckedChange={(postOnly) => {
field.onChange(postOnly);
setValue('reduceOnly', false);
onCheckedChange={() => {
update({ postOnly: !order.postOnly, reduceOnly: false });
}}
label={
<Tooltip
@@ -448,14 +507,13 @@ export const DealTicket = ({
<Controller
name="reduceOnly"
control={control}
render={({ field }) => (
render={() => (
<Checkbox
name="reduce-only"
checked={!disableReduceOnlyCheckbox && field.value}
checked={order.reduceOnly}
disabled={disableReduceOnlyCheckbox}
onCheckedChange={(reduceOnly) => {
field.onChange(reduceOnly);
setValue('postOnly', false);
onCheckedChange={() => {
update({ postOnly: false, reduceOnly: !order.reduceOnly });
}}
label={
<Tooltip
@@ -476,49 +534,53 @@ export const DealTicket = ({
)}
/>
</div>
{type === Schema.OrderType.TYPE_LIMIT && (
<>
<div className="flex gap-2 pb-2 justify-between">
<Controller
name="iceberg"
control={control}
render={({ field }) => (
<Checkbox
name="iceberg"
checked={field.value}
onCheckedChange={field.onChange}
label={
<Tooltip
description={
<p>
{t(`Trade only a fraction of the order size at once.
<div className="flex gap-2 pb-2 justify-between">
{order.type === Schema.OrderType.TYPE_LIMIT && (
<Controller
name="iceberg"
control={control}
render={() => (
<Checkbox
name="iceberg"
checked={order.iceberg}
onCheckedChange={() => {
update({ iceberg: !order.iceberg, icebergOpts: undefined });
}}
label={
<Tooltip
description={
<p>
{t(`Trade only a fraction of the order size at once.
After the peak size of the order has traded, the size is reset. This is repeated until the order is cancelled, expires, or its full volume trades away.
For example, an iceberg order with a size of 1000 and a peak size of 100 will effectively be split into 10 orders with a size of 100 each.
Note that the full volume of the order is not hidden and is still reflected in the order book.`)}
</p>
}
>
<span className="text-xs">{t('Iceberg')}</span>
</Tooltip>
}
/>
)}
/>
</div>
{iceberg && (
<DealTicketSizeIceberg
market={market}
peakSizeError={errors.peakSize?.message}
minimumVisibleSizeError={errors.minimumVisibleSize?.message}
control={control}
size={rawSize}
peakSize={peakSize}
/>
)}
</>
</p>
}
>
<span className="text-xs">{t('Iceberg')}</span>
</Tooltip>
}
/>
)}
/>
)}
</div>
{order.iceberg && (
<DealTicketSizeIceberg
update={update}
market={market}
peakSizeError={errors.icebergOpts?.peakSize?.message}
minimumVisibleSizeError={
errors.icebergOpts?.minimumVisibleSize?.message
}
control={control}
size={order.size}
peakSize={order.icebergOpts?.peakSize || ''}
minimumVisibleSize={order.icebergOpts?.minimumVisibleSize || ''}
/>
)}
<SummaryMessage
error={summaryError}
errorMessage={errors.summary?.message}
asset={asset}
marketTradingMode={marketData.marketTradingMode}
balance={balance}
@@ -528,9 +590,10 @@ export const DealTicket = ({
}
isReadOnly={isReadOnly}
pubKey={pubKey}
onClickCollateral={onClickCollateral}
onDeposit={onDeposit}
/>
<DealTicketButton side={side} />
<DealTicketButton side={order.side} />
<DealTicketFeeDetails
order={
normalizedOrder && { ...normalizedOrder, price: price || undefined }
@@ -556,13 +619,14 @@ export const DealTicket = ({
* renders warnings about current state of the market
*/
interface SummaryMessageProps {
error?: { message: string; type: string };
errorMessage?: string;
asset: { id: string; symbol: string; name: string; decimals: number };
marketTradingMode: MarketData['marketTradingMode'];
balance: string;
margin: string;
isReadOnly: boolean;
pubKey: string | null;
onClickCollateral?: () => void;
onDeposit: (assetId: string) => void;
}
@@ -585,13 +649,14 @@ export const NoWalletWarning = ({
const SummaryMessage = memo(
({
error,
errorMessage,
asset,
marketTradingMode,
balance,
margin,
isReadOnly,
pubKey,
onClickCollateral,
onDeposit,
}: SummaryMessageProps) => {
// Specific error UI for if balance is so we can
@@ -600,21 +665,25 @@ const SummaryMessage = memo(
return <NoWalletWarning isReadOnly={isReadOnly} />;
}
if (error?.type === SummaryValidationType.NoCollateral) {
if (errorMessage === SummaryValidationType.NoCollateral) {
return (
<div className="mb-2">
<ZeroBalanceError asset={asset} onDeposit={onDeposit} />
<ZeroBalanceError
asset={asset}
onClickCollateral={onClickCollateral}
onDeposit={onDeposit}
/>
</div>
);
}
// If we have any other full error which prevents
// submission render that first
if (error?.message) {
if (errorMessage) {
return (
<div className="mb-2">
<InputError testId="deal-ticket-error-message-summary">
{error?.message}
{errorMessage}
</InputError>
</div>
);
@@ -21,13 +21,6 @@ interface TimeInForceSelectorProps {
errorMessage?: string;
}
const typeLimitOptions = Object.entries(Schema.OrderTimeInForce);
const typeMarketOptions = typeLimitOptions.filter(
([_, timeInForce]) =>
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_FOK ||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
export const TimeInForceSelector = ({
value,
orderType,
@@ -38,8 +31,12 @@ export const TimeInForceSelector = ({
}: TimeInForceSelectorProps) => {
const options =
orderType === Schema.OrderType.TYPE_LIMIT
? typeLimitOptions
: typeMarketOptions;
? Object.entries(Schema.OrderTimeInForce)
: Object.entries(Schema.OrderTimeInForce).filter(
([_, timeInForce]) =>
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_FOK ||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
const renderError = (errorType: string) => {
if (errorType === MarketModeValidationType.Auction) {
@@ -16,7 +16,7 @@ import { t } from '@vegaprotocol/i18n';
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
import { compileGridData } from '../trading-mode-tooltip';
import { MarketModeValidationType } from '../../constants';
import { DealTicketType } from '../../hooks/use-form-values';
import { DealTicketType } from '../../hooks/use-type-store';
import * as RadioGroup from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import { FLAGS } from '@vegaprotocol/environment';
+2 -1
View File
@@ -1,3 +1,4 @@
export * from './__generated__/EstimateOrder';
export * from './use-estimate-fees';
export * from './use-form-values';
export * from './use-type-store';
export * from './use-stop-order-form-values';
@@ -1,144 +0,0 @@
import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware';
import type { OrderTimeInForce, Side, OrderType } from '@vegaprotocol/types';
import * as Schema from '@vegaprotocol/types';
import { immer } from 'zustand/middleware/immer';
export enum DealTicketType {
Limit = 'Limit',
Market = 'Market',
StopLimit = 'StopLimit',
StopMarket = 'StopMarket',
}
export interface StopOrderFormValues {
side: Side;
triggerDirection: Schema.StopOrderTriggerDirection;
triggerType: 'price' | 'trailingPercentOffset';
triggerPrice?: string;
triggerTrailingPercentOffset?: string;
type: OrderType;
size: string;
timeInForce: OrderTimeInForce;
price?: string;
expire: boolean;
expiryStrategy?: Schema.StopOrderExpiryStrategy;
expiresAt?: string;
}
export type OrderFormValues = {
type: OrderType;
side: Side;
size: string;
timeInForce: OrderTimeInForce;
price?: string;
expiresAt?: string | undefined;
postOnly?: boolean;
reduceOnly?: boolean;
iceberg?: boolean;
peakSize?: string;
minimumVisibleSize?: string;
};
type UpdateOrder = (marketId: string, values: Partial<OrderFormValues>) => void;
type UpdateStopOrder = (
marketId: string,
values: Partial<StopOrderFormValues>
) => void;
type Store = {
updateOrder: UpdateOrder;
updateStopOrder: UpdateStopOrder;
setType: (marketId: string, value: DealTicketType) => void;
updateAll: (
marketId: string,
values: { size?: string; price?: string }
) => void;
formValues: Record<
string,
| {
[DealTicketType.Limit]?: Partial<OrderFormValues>;
[DealTicketType.Market]?: Partial<OrderFormValues>;
[DealTicketType.StopLimit]?: Partial<StopOrderFormValues>;
[DealTicketType.StopMarket]?: Partial<StopOrderFormValues>;
type?: DealTicketType;
}
| undefined
>;
};
export const dealTicketTypeToOrderType = (dealTicketType?: DealTicketType) =>
dealTicketType === DealTicketType.Limit ||
dealTicketType === DealTicketType.StopLimit
? Schema.OrderType.TYPE_LIMIT
: Schema.OrderType.TYPE_MARKET;
export const isStopOrderType = (dealTicketType?: DealTicketType) =>
dealTicketType === DealTicketType.StopLimit ||
dealTicketType === DealTicketType.StopMarket;
export const useDealTicketFormValues = create<Store>()(
immer(
persist(
subscribeWithSelector((set) => ({
formValues: {},
updateStopOrder: (marketId, formValues) => {
set((state) => {
const type =
formValues.type === Schema.OrderType.TYPE_LIMIT
? DealTicketType.StopLimit
: DealTicketType.StopMarket;
const market = state.formValues[marketId] || {};
if (!state.formValues[marketId]) {
state.formValues[marketId] = market;
}
market[type] = Object.assign(market[type] ?? {}, formValues);
});
},
updateOrder: (marketId, formValues) => {
set((state) => {
const type =
formValues.type === Schema.OrderType.TYPE_LIMIT
? DealTicketType.Limit
: DealTicketType.Market;
const market = state.formValues[marketId] || {};
if (!state.formValues[marketId]) {
state.formValues[marketId] = market;
}
market[type] = Object.assign(market[type] ?? {}, formValues);
});
},
updateAll: (
marketId: string,
formValues: { size?: string; price?: string }
) => {
set((state) => {
const market = state.formValues[marketId] || {};
if (!state.formValues[marketId]) {
state.formValues[marketId] = market;
}
for (const type of Object.values(DealTicketType)) {
market[type] = Object.assign(market[type] ?? {}, formValues);
}
});
},
setType: (marketId, type) => {
set((state) => {
state.formValues[marketId] = Object.assign(
state.formValues[marketId] ?? {},
{ type }
);
});
},
})),
{
name: 'vega_deal_ticket_store',
}
)
)
);

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