Compare commits

...
Author SHA1 Message Date
Edd e3d3ee81dd fix(explorer,trading): asset details should show quantum in asset dps 2023-03-10 15:27:18 +00:00
b68136ba3f chore(deposits): update faucet feedback i18n (#3132)
Co-authored-by: m.ray <16125548+MadalinaRaicu@users.noreply.github.com>
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
2023-03-10 14:43:32 +00:00
daro-maj f5ad2de5a5 test(trading): skip send report to artifact if tests are passed (#3154) 2023-03-10 15:37:49 +01:00
Joe Tsang b3bfb90804 test(governance): new and update market proposal forms (#3131) 2023-03-10 14:03:36 +00:00
m.ray 327ebf3496 fix(trading): deal ticket expiry not wiped out after changing TIF from GTT (#3149) 2023-03-10 15:02:01 +01:00
Matthew Russell 1098accb84 fix(trading,explorer,governance): consistent zustand usage (#3133) 2023-03-10 14:01:51 +00:00
Edd 52183555a9 feat(explorer): improve display of multi cancellations (#3151) 2023-03-10 14:01:26 +00:00
dexturr 51187029aa chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-10 12:09:30 +00:00
dexturr f7f7be59d3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-10 06:10:07 +00:00
Bartłomiej Głownia 49e3baf094 feat(deal-ticket): simplify summary validation (#3144) 2023-03-09 16:12:59 -08:00
Maciek a575b4c502 chore(trading): tweaks and improvements of floating bottom buttons (#3138) 2023-03-09 15:52:38 -08:00
dexturr af2e52d59c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-09 18:09:03 +00:00
daro-maj 2725bef159 test(trading): increase coverage for 0002 wcon (#3137) 2023-03-09 14:11:46 +01:00
dexturr 070f1905e2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-09 12:10:33 +00:00
Bartłomiej Głowniaandmattrussell36 6705eb4398 feat(trading): calculate required margin base on open volume, active … (#2957)
Co-authored-by: mattrussell36 <mattrussell36@users.noreply.github.com>
2023-03-09 10:03:50 +00:00
dexturr 9d3fc04597 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-09 06:09:54 +00:00
dexturr d3929b8d4a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-09 00:17:17 +00:00
dexturr 8a5579b1cc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-08 18:08:10 +00:00
Maciek 197f2e8097 chore(trading): 2825 buttons floating over table rows (#3084) 2023-03-08 15:58:40 +01:00
Matthew RussellandMadalina Raicu 8f5a2276de fix(trading): order store connection to deal ticket (#3100)
Co-authored-by: Madalina Raicu <madalina@raygroup.uk>
2023-03-08 12:14:56 +00:00
dexturr 46513685c8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-08 12:10:32 +00:00
m.ray e66e96f12d fix(trading): add loss socialisation docs links (#3109) 2023-03-08 09:42:25 +00:00
dexturr 31c8365812 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-08 06:07:09 +00:00
dexturr 460f534c7a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-08 00:17:38 +00:00
dexturr 24cd080dd4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-07 18:08:33 +00:00
Bartłomiej Głownia f10c33748f fix(orders): orders table flickering (#3105) 2023-03-07 17:13:34 +01:00
188 changed files with 4556 additions and 2792 deletions
+1 -1
View File
@@ -87,7 +87,7 @@ jobs:
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
- uses: actions/upload-artifact@v3
if: ${{ always() }}
if: ${{ failure() }}
with:
name: logs-${{ matrix.project }}
path: /home/runner/.vegacapsule/testnet/logs
@@ -4,9 +4,8 @@ import {
getMarketExpiryDateFormatted,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoNoCandlesQuery } from '@vegaprotocol/market-info';
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info';
import pick from 'lodash/pick';
import {
MarketStateMapping,
MarketTradingModeMapping,
@@ -17,11 +16,7 @@ import BigNumber from 'bignumber.js';
import { useMemo } from 'react';
import { Link } from 'react-router-dom';
export const MarketDetails = ({
market,
}: {
market: MarketInfoNoCandlesQuery['market'];
}) => {
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
@@ -32,7 +27,9 @@ export const MarketDetails = ({
if (!market) return null;
const keyDetails = {
...pick(market, 'decimalPlaces', 'positionDecimalPlaces', 'tradingMode'),
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
tradingMode: market.tradingMode,
state: MarketStateMapping[market.state],
};
const assetDecimals =
+1 -1
View File
@@ -21,7 +21,7 @@ type NavStore = {
hide: () => void;
};
export const useNavStore = create<NavStore>((set, get) => ({
export const useNavStore = create<NavStore>()((set, get) => ({
open: false,
toggle: () => set({ open: !get().open }),
hide: () => set({ open: false }),
@@ -0,0 +1,26 @@
import { t } from '@vegaprotocol/i18n';
interface CancelSummaryProps {
orderId?: string;
marketId?: string;
}
/**
* Simple component for rendering a reasonable string from an order cancellation
*/
export const CancelSummary = ({ orderId, marketId }: CancelSummaryProps) => {
return <span className="font-bold">{getLabel(orderId, marketId)}</span>;
};
export function getLabel(
orderId: string | undefined,
marketId: string | undefined
): string {
if (!orderId && !marketId) {
return t('All orders');
} else if (marketId && !orderId) {
return t('All in market');
}
return '-';
}
@@ -2,6 +2,7 @@ import type { BatchCancellationInstruction } from '../../../../routes/types/bloc
import { TxOrderType } from '../../tx-order-type';
import { MarketLink } from '../../../links';
import OrderSummary from '../../../order-summary/order-summary';
import { CancelSummary } from '../../../order-summary/order-cancellation';
interface BatchCancelProps {
index: number;
@@ -19,7 +20,14 @@ export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
<TxOrderType orderType={'OrderCancellation'} />
</td>
<td>
<OrderSummary id={submission.orderId} modifier="cancelled" />
{submission.orderId ? (
<OrderSummary id={submission.orderId} modifier="cancelled" />
) : (
<CancelSummary
orderId={submission.orderId}
marketId={submission.marketId}
/>
)}
</td>
<td>
<MarketLink id={submission.marketId} />
@@ -5,6 +5,8 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
import { CancelSummary } from '../../order-summary/order-cancellation';
import Hash from '../../links/hash';
interface TxDetailsOrderCancelProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -24,8 +26,8 @@ export const TxDetailsOrderCancel = ({
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const marketId: string = txData.command.orderCancellation.marketId || '-';
const orderId: string = txData.command.orderCancellation.orderId || '-';
const marketId: string = txData.command.orderCancellation.marketId;
const orderId: string = txData.command.orderCancellation.orderId;
return (
<>
@@ -38,18 +40,24 @@ export const TxDetailsOrderCancel = ({
<TableRow modifier="bordered">
<TableCell>{t('Order')}</TableCell>
<TableCell>
<code>{orderId}</code>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
{orderId ? (
<Hash text={orderId} />
) : (
<CancelSummary orderId={orderId} marketId={marketId} />
)}
</TableCell>
</TableRow>
{marketId ? (
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
{orderId !== '-' ? <DeterministicOrderDetails id={orderId} /> : null}
{orderId ? <DeterministicOrderDetails id={orderId} /> : null}
</>
);
};
@@ -12,6 +12,7 @@ export const Proposals = () => {
const { data, loading, error } = useDataProvider({
dataProvider: proposalsDataProvider,
variables: {},
});
useDocumentTitle([t('Governance Proposals')]);
@@ -1,14 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { MarketDetails } from '../../components/markets/market-details';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import compact from 'lodash/compact';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { marketInfoNoCandlesDataProvider } from '@vegaprotocol/market-info';
import { marketInfoProvider } from '@vegaprotocol/market-info';
import { PageTitle } from '../../components/page-helpers/page-title';
export const MarketPage = () => {
@@ -16,24 +16,17 @@ export const MarketPage = () => {
const { marketId } = useParams<{ marketId: string }>();
const variables = useMemo(
() => ({
marketId,
}),
[marketId]
);
const { data, loading, error } = useDataProvider({
dataProvider: marketInfoNoCandlesDataProvider,
dataProvider: marketInfoProvider,
skipUpdates: true,
variables,
variables: {
marketId: marketId || '',
skip: !marketId,
},
});
useDocumentTitle(
compact([
'Market details',
data?.market?.tradableInstrument.instrument.name,
])
compact(['Market details', data?.tradableInstrument.instrument.name])
);
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
@@ -43,10 +36,10 @@ export const MarketPage = () => {
<section className="relative">
<PageTitle
data-testid="markets-heading"
title={data?.market?.tradableInstrument.instrument.name || ''}
title={data?.tradableInstrument.instrument.name || ''}
actions={
<Button
disabled={!data?.market}
disabled={!data}
size="xs"
onClick={() => setDialogOpen(true)}
>
@@ -60,14 +53,14 @@ export const MarketPage = () => {
loading={loading}
error={error}
>
<MarketDetails market={data?.market} />
{data && <MarketDetails market={data} />}
</AsyncRenderer>
</section>
<JsonViewerDialog
open={dialogOpen}
onChange={(isOpen) => setDialogOpen(isOpen)}
title={data?.market?.tradableInstrument.instrument.name || ''}
content={data?.market}
title={data?.tradableInstrument.instrument.name || ''}
content={data}
/>
</>
);
@@ -13,6 +13,7 @@ export const MarketsPage = () => {
const { data, loading, error } = useDataProvider({
dataProvider: marketsProvider,
variables: undefined,
skipUpdates: true,
});
@@ -22,7 +22,7 @@
"filters": [
{
"key": {
"name": "prices.ETH.value",
"name": "prices.BTC.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
@@ -38,30 +38,34 @@
}
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
"external": {
"oracle": {
"signers": [
{
"operator": "OPERATOR_EQUALS",
"value": "true"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
]
}
]
}
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.BTC.value",
"settlementDataProperty": "prices.BTC.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
@@ -0,0 +1,86 @@
{
"lpPriceRange": "11",
"instrument": {
"code": "Token.24h",
"future": {
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForTradingTermination": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
]
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "prices.BTC.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
}
}
@@ -1,62 +1,76 @@
{
"lpPriceRange": "10",
"instrument": {
"code": "TEST.24h",
"future": {
"quoteName": "fUSDC",
"settlementDataDecimals": 5,
"dataSourceSpecForSettlementData": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER"
},
"conditions": [
"external": {
"oracle": {
"signers": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
]
}
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
"external": {
"oracle": {
"signers": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
]
}
]
}
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.ETH.value",
"settlementDataProperty": "prices.ETH.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"metadata": [
"sector:energy",
"sector:food",
"source:docs.vega.xyz",
"test:update"
],
"priceMonitoringParameters": {
"triggers": [
{
@@ -66,6 +80,14 @@
}
]
},
"liquidityMonitoringParameters": {
"targetStakeParameters": {
"timeWindow": "3600",
"scalingFactor": 10
},
"triggeringRatio": "0.7",
"auctionExtension": "1"
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
@@ -26,6 +26,10 @@ const enactmentDeadlineError =
'[data-testid="enactment-before-voting-deadline"]';
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
const feedbackError = '[data-testid="Error"]';
const viewProposalBtn = 'view-proposal-btn';
const liquidityVoteStatus = 'liquidity-votes-status';
const tokenVoteStatus = 'token-votes-status';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
@@ -45,6 +49,7 @@ context(
{ tags: '@slow' },
function () {
before('connect wallets and set approval limit', function () {
cy.createMarket();
cy.visit('/');
cy.vega_wallet_set_specified_approval_amount('1000');
});
@@ -65,7 +70,7 @@ context(
// 3002-PROP-007
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalParameterSelect).find('option').should('have.length', 116);
cy.get(proposalParameterSelect).find('option').should('have.length', 117);
cy.get(proposalParameterSelect).select(
// 3007-PNEC-002
'governance_proposal_asset_minEnact'
@@ -175,9 +180,8 @@ context(
cy.get(enactmentDeadlineError).should('not.exist');
});
// Skipping because unclear what the required json is yet for new market proposal, will update once docs have been updated
// 3003-todo-PMAN-001
it.skip('Able to submit valid new market proposal', function () {
// 3003-PMAN-001
it('Able to submit valid new market proposal', function () {
cy.go_to_make_new_proposal(governanceProposalType.NEW_MARKET);
cy.get(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
@@ -199,6 +203,7 @@ context(
cy.get(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
newMarketProposal.invalid = 'I am an invalid field';
let newMarketPayload = JSON.stringify(newMarketProposal);
cy.get(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false,
@@ -209,11 +214,66 @@ context(
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should(
'have.text',
'Invalid params: the transaction is malformed'
'Invalid params: the transaction is not a valid Vega command: unknown field "invalid" in vega.NewMarket'
);
});
it.skip('Able to submit update market proposal', function () {
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
// 3002-PROP-022
it('Unable to submit update market proposal without equity-like share in the market', function () {
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal - rejected');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
cy.getByTestId('dialog-content')
.find('p')
.should('have.text', 'PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
cy.ensure_specified_unstaked_tokens_are_associated('1');
});
// 3002-PROP-020
it('Unable to submit update market proposal without minimum amount of tokens', function () {
cy.vega_wallet_teardown();
cy.vega_wallet_faucet_assets_without_check(
'fUSDC',
'1000000',
vegaWalletPublicKey
);
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal - rejected');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should(
'have.text',
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)'
);
});
// 3001-VOTE-092
it('Able to submit update market proposal and vote for proposal', function () {
cy.vega_wallet_faucet_assets_without_check(
'fUSDC',
'1000000',
vegaWalletPublicKey
);
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
@@ -222,6 +282,10 @@ context(
cy.get('dd').eq(0).should('have.text', 'Test market 1');
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
cy.get('dd').eq(2).should('not.be.empty');
cy.get('dd').eq(2).invoke('text').as('EnactedMarketId');
});
cy.get('@EnactedMarketId').then((marketId) => {
cy.VegaWalletSubmitLiquidityProvision(marketId, '1');
});
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
@@ -232,6 +296,34 @@ context(
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.wait_for_proposal_submitted();
cy.navigate_to('proposals');
cy.get('@EnactedMarketId').then((marketId) => {
cy.contains(marketId)
.parentsUntil(proposalListItem)
.within(() => {
cy.getByTestId(viewProposalBtn).click();
});
});
cy.getByTestId(liquidityVoteStatus).should(
'contain.text',
'Currently expected to fail'
);
cy.getByTestId(tokenVoteStatus).should(
'contain.text',
'Currently expected to fail'
);
cy.vote_for_proposal('for');
cy.getByTestId(liquidityVoteStatus).should(
'contain.text',
'Currently expected to pass'
);
cy.getByTestId(tokenVoteStatus).should(
'contain.text',
'Currently expected to pass'
);
cy.get_proposal_information_from_table('Expected to pass')
.contains('👍 by Token vote')
.should('be.visible');
});
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
@@ -298,7 +390,7 @@ context(
.parentsUntil(proposalListItem)
.within(() => {
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
cy.getByTestId('view-proposal-btn').click();
cy.getByTestId(viewProposalBtn).click();
});
});
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
@@ -82,9 +82,10 @@ Cypress.Commands.add(
Cypress.Commands.add(
'get_submitted_proposal_from_proposal_list',
(proposalTitle) => {
cy.get_proposal_id_from_list(proposalTitle);
cy.get('@proposalIdText').then((proposalId) => {
return cy.get(`#${proposalId}`);
cy.get_proposal_id_from_list(proposalTitle).then(() => {
cy.get('@proposalIdText').then((proposalId) => {
return cy.get(`#${proposalId}`);
});
});
}
);
@@ -9,26 +9,28 @@ export type PendingTxsStore = {
resetPendingTxs: () => void;
};
export const usePendingBalancesStore = create<PendingTxsStore>((set, get) => ({
pendingBalances: [],
addPendingTxs: (event: Event[]) => {
set({
pendingBalances: uniqBy(
[...get().pendingBalances, ...event],
'transactionHash'
),
});
},
removePendingTx: (event: Event) => {
set({
pendingBalances: [
...get().pendingBalances.filter(
({ transactionHash }) => transactionHash !== event.transactionHash
export const usePendingBalancesStore = create<PendingTxsStore>()(
(set, get) => ({
pendingBalances: [],
addPendingTxs: (event: Event[]) => {
set({
pendingBalances: uniqBy(
[...get().pendingBalances, ...event],
'transactionHash'
),
],
});
},
resetPendingTxs: () => {
set({ pendingBalances: [] });
},
}));
});
},
removePendingTx: (event: Event) => {
set({
pendingBalances: [
...get().pendingBalances.filter(
({ transactionHash }) => transactionHash !== event.transactionHash
),
],
});
},
resetPendingTxs: () => {
set({ pendingBalances: [] });
},
})
);
@@ -38,7 +38,7 @@ export interface RefreshBalances {
vestingAssociatedBalance: BigNumber;
}
export const useBalances = create<BalancesStore>((set) => ({
export const useBalances = create<BalancesStore>()((set) => ({
associationBreakdown: {
stakingAssociations: {},
vestingAssociations: {},
@@ -1,7 +1,7 @@
import { toBigNum } from '@vegaprotocol/utils';
import type { TrancheServiceResponse } from '@vegaprotocol/smart-contracts';
import type BigNumber from 'bignumber.js';
import create from 'zustand';
import { create } from 'zustand';
import { ENV } from '../../config';
export interface Tranche {
@@ -36,7 +36,7 @@ export type TranchesStore = {
const secondsToDate = (seconds: number) => new Date(seconds * 1000);
export const useTranches = create<TranchesStore>((set) => ({
export const useTranches = create<TranchesStore>()((set) => ({
tranches: null,
loading: false,
error: null,
@@ -56,7 +56,7 @@ export const VoteDetails = ({
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
<section>
<SubHeading title={t('liquidityVotes')} />
<p>
<p data-testid="liquidity-votes-status">
<span>
<CurrentProposalStatus proposal={proposal} />
</span>
@@ -105,7 +105,7 @@ export const VoteDetails = ({
)}
<section data-testid="votes-table">
<SubHeading title={t('tokenVotes')} />
<p>
<p data-testid="token-votes-status">
<span>
<CurrentProposalStatus proposal={proposal} />
</span>
+22 -25
View File
@@ -1,5 +1,4 @@
import type ethers from 'ethers';
import type { GetState, SetState } from 'zustand';
import { create } from 'zustand';
export interface TxData {
@@ -16,27 +15,25 @@ interface TransactionStore {
remove: (tx: TxData) => void;
}
export const useTransactionStore = create(
(set: SetState<TransactionStore>, get: GetState<TransactionStore>) => ({
transactions: [],
add: (tx) => {
const { transactions } = get();
set({ transactions: [...transactions, tx] });
},
update: (tx) => {
const { transactions } = get();
set({
transactions: [
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
tx,
],
});
},
remove: (tx) => {
const { transactions } = get();
set({
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
});
},
})
);
export const useTransactionStore = create<TransactionStore>()((set, get) => ({
transactions: [],
add: (tx) => {
const { transactions } = get();
set({ transactions: [...transactions, tx] });
},
update: (tx) => {
const { transactions } = get();
set({
transactions: [
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
tx,
],
});
},
remove: (tx) => {
const { transactions } = get();
set({
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
});
},
}));
@@ -1,5 +1,4 @@
import { useParams } from 'react-router-dom';
import { useMemo } from 'react';
import { makeDerivedDataProvider } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
@@ -43,7 +42,7 @@ const useMarketDetails = (marketId: string | undefined) => {
const { data, loading, error } = useDataProvider({
dataProvider: lpDataProvider,
skipUpdates: true,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId: marketId || '' },
});
const liquidityProviders = data?.liquidityProviders || [];
@@ -39,14 +39,11 @@ export const Last24hVolume = ({
[marketId, yTimestamp]
);
const variables24hAgo = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1D,
since: yTimestamp,
}),
[marketId, yTimestamp]
);
const variables24hAgo = {
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1D,
since: yTimestamp,
};
const throttledSetCandles = useRef(
throttle((data: Candle[]) => {
@@ -64,7 +61,7 @@ export const Last24hVolume = ({
[throttledSetCandles]
);
const { data, error } = useDataProvider<Candle[], Candle>({
const { data, error } = useDataProvider({
dataProvider: marketCandlesProvider,
variables: variables,
update,
@@ -88,7 +85,7 @@ export const Last24hVolume = ({
[throttledSetVolumeChange]
);
useDataProvider<Candle[], Candle>({
useDataProvider({
dataProvider: marketCandlesProvider,
update: updateCandle24hAgo,
variables: variables24hAgo,
File diff suppressed because it is too large Load Diff
@@ -26,20 +26,20 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('market price', () => {
cy.getByTestId(marketTitle).contains('Market price').click();
validateMarketDataRow(0, 'Mark Price', '0.05749');
validateMarketDataRow(1, 'Best Bid Price', '6.81765 ');
validateMarketDataRow(2, 'Best Offer Price', '6.81769 ');
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
validateMarketDataRow(2, 'Best Offer Price', '48,126.90058 ');
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
it('market volume displayed', () => {
cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(0, '24 Hour Volume', '-');
validateMarketDataRow(0, '24 Hour Volume', '1');
validateMarketDataRow(1, 'Open Interest', '0');
validateMarketDataRow(2, 'Best Bid Volume', '5');
validateMarketDataRow(3, 'Best Offer Volume', '1');
validateMarketDataRow(4, 'Best Static Bid Volume', '5');
validateMarketDataRow(5, 'Best Static Offer Volume', '1');
validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3');
validateMarketDataRow(4, 'Best Static Bid Volume', '2');
validateMarketDataRow(5, 'Best Static Offer Volume', '4');
});
it('insurance pool displayed', () => {
@@ -149,9 +149,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
.contains(/Liquidity(?! m)/)
.click();
validateMarketDataRow(0, 'Target Stake', '0.56789 tBTC');
validateMarketDataRow(1, 'Supplied Stake', '0.56767 tBTC');
validateMarketDataRow(2, 'Market Value Proxy', '6.77678 tBTC');
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
cy.getByTestId('view-liquidity-link').should(
'have.text',
@@ -163,8 +163,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
validateMarketDataRow(1, 'Lowest Price', '0.05634 BTC');
validateMarketDataRow(2, 'Highest Price', '0.05864 BTC');
validateMarketDataRow(1, 'Lowest Price', '45,204.362 BTC');
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
});
it('oracle displayed', () => {
@@ -38,7 +38,7 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="deposited"]')
.should('have.text', '1,001.00');
.should('have.text', '100,001.01');
});
describe('sorting by ag-grid columns should work well', () => {
it('sorting by asset', () => {
@@ -58,24 +58,24 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'1,001.00',
'1,000.01',
'100,001.01',
'1,000.01',
'1,000.00',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00',
'1,000.00001',
'1,000.00002',
'1,000.01',
'1,000.01',
'1,001.00',
'100,001.01',
];
const marketsSortedDesc = [
'1,001.00',
'1,000.01',
'100,001.01',
'1,000.01',
'1,000.00002',
'1,000.00001',
'1,000.00',
];
checkSorting(
'deposited',
@@ -87,9 +87,9 @@ describe('accounts', { tags: '@smoke' }, () => {
it('sorting by used', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = ['0.00', '1.00', '0.01', '0.01', '0.00'];
const marketsSortedAsc = ['0.00', '0.00', '0.01', '0.01', '1.00'];
const marketsSortedDesc = ['1.00', '0.01', '0.01', '0.00', '0.00'];
const marketsSortedDefault = ['0.00', '1.01', '0.01', '0.00', '0.00'];
const marketsSortedAsc = ['0.00', '0.00', '0.00', '0.01', '1.01'];
const marketsSortedDesc = ['1.01', '0.01', '0.00', '0.00', '0.00'];
checkSorting(
'used',
marketsSortedDefault,
@@ -102,24 +102,24 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'1,000.00',
'100,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
'1,000.00002',
'100,000.00',
];
const marketsSortedDesc = [
'100,000.00',
'1,000.00002',
'1,000.00001',
'1,000.00',
'1,000.00',
'1,000.00',
];
checkSorting(
@@ -2,7 +2,11 @@ import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery, mockConnectWallet } from '@vegaprotocol/cypress';
import { testOrderSubmission } from '../support/order-validation';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import { accountsQuery, estimateOrderQuery } from '@vegaprotocol/mock';
import {
accountsQuery,
estimateOrderQuery,
amendGeneralAccountBalance,
} from '@vegaprotocol/mock';
import { createOrder } from '../support/create-order';
const orderSizeField = 'order-size';
@@ -583,6 +587,10 @@ describe('suspended market validation', { tags: '@regression' }, () => {
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
);
const accounts = accountsQuery();
cy.mockGQL((req) => {
aliasGQLQuery(req, 'Accounts', accounts);
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
@@ -632,30 +640,10 @@ describe('account validation', { tags: '@regression' }, () => {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
const accounts = accountsQuery();
amendGeneralAccountBalance(accounts, 'market-0', '0');
cy.mockGQL((req) => {
aliasGQLQuery(
req,
'Accounts',
accountsQuery({
party: {
accountsConnection: {
edges: [
{
node: {
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '0',
market: null,
asset: {
__typename: 'Asset',
id: 'asset-0',
},
},
},
],
},
},
})
);
aliasGQLQuery(req, 'Accounts', accounts);
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -669,7 +657,7 @@ describe('account validation', { tags: '@regression' }, () => {
'have.text',
'You need ' +
'tDAI' +
' in your wallet to trade in this market. See all your collateral.Make a deposit'
' in your wallet to trade in this market.See all your collateral.Make a deposit'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
});
@@ -679,19 +667,13 @@ describe('account validation', { tags: '@regression' }, () => {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
const accounts = accountsQuery();
amendGeneralAccountBalance(accounts, 'market-0', '100000000');
cy.mockGQL((req) => {
aliasGQLQuery(
req,
'EstimateOrder',
estimateOrderQuery({
estimateOrder: {
marginLevels: {
__typename: 'MarginLevels',
initialLevel: '1000000000',
},
},
})
);
aliasGQLQuery(req, 'Accounts', accounts);
});
cy.mockGQL((req) => {
aliasGQLQuery(req, 'EstimateOrder', estimateOrderQuery());
});
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -707,7 +689,7 @@ describe('account validation', { tags: '@regression' }, () => {
);
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'9,999.99 tDAI is currently required. You have only 1,000.00 tDAI available.Deposit tDAI'
'You may not have enough margin available to open this position. 2,354.72283 tDAI is currently required. You have only 1,000.01 tDAI available.'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content')
@@ -31,7 +31,7 @@ describe('orders list', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
});
cy.wait('@Markets');
});
@@ -136,7 +136,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
});
});
const orderId = '1234567890';
@@ -354,7 +354,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click();
cy.wait('@Orders').then(() => {
expect(subscriptionMocks.OrdersUpdate).to.be.calledTwice;
expect(subscriptionMocks.OrdersUpdate).to.be.calledThrice;
});
cy.mockVegaWalletTransaction();
});
@@ -164,10 +164,10 @@ describe('positions', { tags: '@smoke' }, () => {
cy.get('[col-id="liquidationPrice"]').should('contain.text', '0'); // liquidation price
cy.get('[col-id="currentLeverage"]').should('contain.text', '138.446.1');
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
cy.get('[col-id="marginAccountBalance"]') // margin allocated
.should('contain.text', '1,000');
.should('contain.text', '0.01');
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
@@ -17,6 +17,13 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
});
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: {
@@ -41,6 +48,9 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
},
});
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');
@@ -51,9 +61,13 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
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: {
@@ -99,6 +113,10 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
});
it('can connect', () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
mockConnectWallet();
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
@@ -110,16 +128,40 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
});
it('can change selected public key and disconnect', () => {
// 0002-WCON-022
// 0002-WCON-023
// 0002-WCON-025
// 0002-WCON-026
// 0002-WCON-021
// 0002-WCON-027
// 0002-WCON-030
// 0002-WCON-029
// 0002-WCON-008
// 0002-WCON-035
// 0002-WCON-014
// 0002-WCON-010
mockConnectWallet();
const key2 = Cypress.env('VEGA_PUBLIC_KEY2');
const truncatedKey2 = Cypress.env('TRUNCATED_VEGA_PUBLIC_KEY2');
cy.connectVegaWallet();
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('keypair-list').should('exist');
cy.getByTestId(`key-${key2}`).should('contain.text', truncatedKey2);
cy.getByTestId(`key-${key2}`)
.find('[data-testid="copy-vega-public-key"]')
.should('be.visible');
cy.get(`[data-testid="key-${key2}"] > .mr-2`).click();
cy.getByTestId('keypair-list')
.find('[data-state="checked"]')
.should('be.visible');
cy.getByTestId('disconnect').click();
cy.getByTestId('connect-vega-wallet').should('exist');
cy.getByTestId('manage-vega-wallet').should('not.exist');
cy.getByTestId('connect-vega-wallet').click();
cy.contains(
'Choose wallet app to connect, or to change port or server URL enter a custom wallet location first'
);
});
});
@@ -76,6 +76,9 @@ describe(
cy.getByTestId(closeDialog).click();
cy.getByTestId('Trading').first().click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).should('not.exist');
cy.getByTestId('Portfolio').eq(0).click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).click();
cy.getByTestId(dialogTransferText).should(
'contain.text',
@@ -7,13 +7,13 @@ import type { onMessage } from '@vegaprotocol/cypress';
import type { PartialDeep } from 'type-fest';
import { orderUpdateSubscription } from '@vegaprotocol/mock';
let sendOrderUpdate: (data: OrdersUpdateSubscription) => void;
const sendOrderUpdate: ((data: OrdersUpdateSubscription) => void)[] = [];
const getOnOrderUpdate = () => {
const onOrderUpdate: onMessage<
OrdersUpdateSubscription,
OrdersUpdateSubscriptionVariables
> = (send) => {
sendOrderUpdate = send;
sendOrderUpdate.push(send);
};
return onOrderUpdate;
};
@@ -31,5 +31,5 @@ export function updateOrder(
if (!sendOrderUpdate) {
throw new Error('OrderSub not called');
}
sendOrderUpdate(update);
sendOrderUpdate.forEach((send) => send(update));
}
+1 -18
View File
@@ -28,7 +28,6 @@ import {
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
import type { MarketInfoQuery } from '@vegaprotocol/market-info';
type MarketPageMockData = {
state: Schema.MarketState;
@@ -69,18 +68,6 @@ const marketsDataOverride = (
},
});
const marketInfoOverride = (
data: MarketPageMockData
): PartialDeep<MarketInfoQuery> => ({
market: {
state: data.state,
tradingMode: data.tradingMode,
data: {
trigger: data.trigger,
},
},
});
const mockTradingPage = (
req: CyHttpMessages.IncomingHttpRequest,
state: Schema.MarketState = Schema.MarketState.STATE_ACTIVE,
@@ -109,11 +96,7 @@ const mockTradingPage = (
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
aliasGQLQuery(req, 'Asset', assetQuery());
aliasGQLQuery(
req,
'MarketInfo',
marketInfoQuery(marketInfoOverride({ state, tradingMode, trigger }))
);
aliasGQLQuery(req, 'MarketInfo', marketInfoQuery());
aliasGQLQuery(req, 'Trades', tradesQuery());
aliasGQLQuery(req, 'Chart', chartQuery());
aliasGQLQuery(req, 'Candles', candlesQuery());
+1
View File
@@ -12,6 +12,7 @@ export const Home = () => {
// should be the oldest market that is currently trading in us mode(i.e. not in auction).
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithDataProvider,
variables: undefined,
});
const update = useGlobalStore((store) => store.update);
const marketId = useGlobalStore((store) => store.marketId);
@@ -47,7 +47,8 @@ export const Liquidity = () => {
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId: marketId || '' },
skip: !marketId,
});
useEffect(() => {
const interval = setInterval(reload, 10000);
@@ -77,7 +78,8 @@ export const LiquidityContainer = ({
const { data, loading, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId: marketId || '' },
skip: !marketId,
});
const assetDecimalPlaces =
@@ -161,7 +163,8 @@ export const LiquidityViewContainer = ({
} = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId: marketId || '' },
skip: !marketId,
});
const targetStake = marketData?.targetStake;
+2 -9
View File
@@ -7,10 +7,6 @@ import {
useThrottledDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
import { useGlobalStore, usePageTitleStore } from '../../stores';
@@ -35,13 +31,10 @@ const TitleUpdater = ({
}) => {
const pageTitle = usePageTitleStore((store) => store.pageTitle);
const updateTitle = usePageTitleStore((store) => store.updateTitle);
const { data: marketData } = useThrottledDataProvider<
MarketData,
MarketDataUpdateFieldsFragment
>(
const { data: marketData } = useThrottledDataProvider(
{
dataProvider: marketDataProvider,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId: marketId || '' },
skip: !marketId,
},
1000
@@ -179,7 +179,10 @@ const MainGrid = ({
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral pinnedAsset={pinnedAsset} />
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
hideButtons
/>
</VegaWalletContainer>
</Tab>
</Tabs>
@@ -2,10 +2,16 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import {
useDataProvider,
useBottomPlaceholder,
} from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
export const DepositsContainer = () => {
const gridRef = useRef<AgGridReact | null>(null);
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
dataProvider: depositsProvider,
@@ -13,13 +19,15 @@ export const DepositsContainer = () => {
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
return (
<div className="h-full grid grid-rows-[1fr,min-content]">
<div className="h-full">
<div className="h-full relative">
<DepositsTable
rowData={data || []}
noRowsOverlayComponent={() => null}
ref={gridRef}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -33,8 +41,9 @@ export const DepositsContainer = () => {
</div>
</div>
{!isReadOnly && (
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => openDepositDialog()}
data-testid="deposit-button"
@@ -49,7 +49,10 @@ export const Portfolio = () => {
</Tab>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<PositionsContainer onMarketClick={onMarketClick} />
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
@@ -20,36 +20,35 @@ export const WithdrawalsContainer = () => {
return (
<VegaWalletContainer>
<div className="h-full relative grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
noRowsOverlayComponent={() => null}
<div className="h-full relative">
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="w-full dark:bg-black bg-white absolute bottom-0 h-auto flex justify-end px-[11px] py-2">
<Button
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
onClick={() => openWithdrawDialog()}
data-testid="withdraw-dialog-button"
>
{t('Make withdrawal')}
</Button>
</div>
)}
</VegaWalletContainer>
);
};
@@ -11,8 +11,10 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -36,27 +38,30 @@ export const AccountsContainer = ({
}
return (
<div className="h-full relative grid grid-rows-[1fr,min-content]">
<div>
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
/>
</div>
{!isReadOnly && (
<div className="flex gap-2 justify-end p-2 px-[11px]">
<div className="h-full relative">
<AccountManager
partyId={pubKey}
onClickAsset={onClickAsset}
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
<Button
variant="primary"
size="sm"
data-testid="open-transfer-dialog"
onClick={() => openTransferDialog()}
>
{t('Transfer')}
</Button>
<Button size="sm" onClick={() => openDepositDialog()}>
<Button
variant="primary"
size="sm"
onClick={() => openDepositDialog()}
>
{t('Deposit')}
</Button>
</div>
+1 -1
View File
@@ -14,7 +14,7 @@ export const Footer = () => {
const { blockDiff, datanodeBlockHeight } = useNodeHealth();
return (
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300">
<footer className="px-4 py-1 text-xs border-t border-default text-vega-light-300 dark:text-vega-dark-300 lg:fixed bottom-0 left-0 border-r bg-white dark:bg-black">
{/* Pull left to align with top nav, due to button padding */}
<div className="-ml-2">
{VEGA_URL && (
@@ -1,5 +1,4 @@
import type { RefObject } from 'react';
import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { isNumeric } from '@vegaprotocol/utils';
import {
@@ -9,7 +8,6 @@ import {
import { PriceChangeCell } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import type { CandleClose } from '@vegaprotocol/types';
import type { Candle } from '@vegaprotocol/market-list';
import { marketCandlesProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
@@ -30,19 +28,14 @@ export const Last24hPriceChange = ({
}: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const yesterday = useYesterday();
const variables = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
}),
[marketId, yesterday]
);
const { data, error } = useThrottledDataProvider<Candle[], Candle>(
const { data, error } = useThrottledDataProvider(
{
dataProvider: marketCandlesProvider,
variables,
variables: {
marketId: marketId || '',
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
},
skip: !marketId || !inView,
},
THROTTLE_UPDATE_TIME
@@ -10,8 +10,6 @@ import {
useYesterday,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { useMemo } from 'react';
import type { Candle } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
interface Props {
@@ -32,19 +30,14 @@ export const Last24hVolume = ({
const yesterday = useYesterday();
const [ref, inView] = useInView({ root: inViewRoot?.current });
const variables = useMemo(
() => ({
marketId: marketId,
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
}),
[marketId, yesterday]
);
const { data } = useThrottledDataProvider<Candle[], Candle>(
const { data } = useThrottledDataProvider(
{
dataProvider: marketCandlesProvider,
variables,
variables: {
marketId: marketId || '',
interval: Schema.Interval.INTERVAL_I1H,
since: new Date(yesterday).toISOString(),
},
skip: !(inView && marketId),
},
THROTTLE_UPDATE_TIME
@@ -4,10 +4,7 @@ import {
useDataProvider,
useNetworkParams,
} from '@vegaprotocol/react-helpers';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import {
@@ -70,7 +67,7 @@ export const MarketLiquiditySupplied = ({
[noUpdate]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
useDataProvider({
dataProvider: marketDataProvider,
update,
variables,
@@ -1,13 +1,8 @@
import type { RefObject } from 'react';
import { useMemo } from 'react';
import { useInView } from 'react-intersection-observer';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import { useThrottledDataProvider } from '@vegaprotocol/react-helpers';
import { PriceCell } from '@vegaprotocol/datagrid';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list';
import { THROTTLE_UPDATE_TIME } from '../constants';
@@ -27,15 +22,10 @@ export const MarketMarkPrice = ({
asPriceCell,
}: Props) => {
const [ref, inView] = useInView({ root: inViewRoot?.current });
const variables = useMemo(() => ({ marketId }), [marketId]);
const { data } = useThrottledDataProvider<
MarketData,
MarketDataUpdateFieldsFragment
>(
const { data } = useThrottledDataProvider(
{
dataProvider: marketDataProvider,
variables,
variables: { marketId: marketId || '' },
skip: !inView,
},
THROTTLE_UPDATE_TIME
@@ -1,15 +1,11 @@
import throttle from 'lodash/throttle';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
Market,
} from '@vegaprotocol/market-list';
import type { MarketData, Market } from '@vegaprotocol/market-list';
import { marketDataProvider } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import { HeaderStat } from '../header';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import * as constants from '../constants';
export const MarketState = ({ market }: { market: Market | null }) => {
@@ -33,14 +29,10 @@ export const MarketState = ({ market }: { market: Market | null }) => {
[throttledSetMarketState]
);
const variables = useMemo(
() => ({ marketId: market?.id || '' }),
[market?.id]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
useDataProvider({
dataProvider: marketDataProvider,
update,
variables,
variables: { marketId: market?.id || '' },
skip: !market?.id,
});
@@ -1,24 +1,16 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import throttle from 'lodash/throttle';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type {
MarketData,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const MarketVolume = ({ marketId }: { marketId: string }) => {
const [marketVolume, setMarketVolume] = useState<string>('-');
const variables = useMemo(
() => ({
marketId: marketId,
}),
[marketId]
);
const variables = { marketId };
const { data } = useDataProvider({
dataProvider: marketProvider,
variables,
@@ -46,7 +38,7 @@ export const MarketVolume = ({ marketId }: { marketId: string }) => {
[data?.positionDecimalPlaces, throttledSetMarketVolume]
);
useDataProvider<MarketData, MarketDataUpdateFieldsFragment>({
useDataProvider({
dataProvider: marketDataProvider,
update,
variables,
@@ -106,14 +106,13 @@ export const SelectMarketPopover = ({
loading: marketsLoading,
reload: marketListReload,
} = useMarketList();
const variables = useMemo(() => ({ partyId: pubKey }), [pubKey]);
const {
data: positions,
loading: positionsLoading,
reload,
} = useDataProvider({
dataProvider: positionsDataProvider,
variables,
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const onSelectMarket = useCallback(
@@ -20,6 +20,7 @@ export const WelcomeDialog = () => {
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
const { data } = useDataProvider({
dataProvider: activeMarketsProvider,
variables: undefined,
});
const { update, shouldDisplayWelcomeDialog } = useGlobalStore((store) => ({
+1 -1
View File
@@ -72,7 +72,7 @@ function AppBody({ Component }: AppProps) {
const gridClasses = classNames(
'h-full relative z-0 grid',
'grid-rows-[repeat(3,min-content),1fr,min-content]'
'grid-rows-[repeat(3,min-content),1fr]'
);
return (
+1 -1
View File
@@ -15,7 +15,7 @@ interface PageTitleStore {
updateTitle: (title: string) => void;
}
export const useGlobalStore = create<GlobalStore>((set) => ({
export const useGlobalStore = create<GlobalStore>()((set) => ({
nodeSwitcherDialog: false,
marketId: LocalStorage.getItem('marketId') || null,
shouldDisplayWelcomeDialog: false,
@@ -18,6 +18,7 @@ import type {
AccountFieldsFragment,
AccountsQuery,
AccountEventsSubscription,
AccountsQueryVariables,
} from './__generated__/Accounts';
import type { Market } from '@vegaprotocol/market-list';
import type { Asset } from '@vegaprotocol/assets';
@@ -85,7 +86,8 @@ export const accountsOnlyDataProvider = makeDataProvider<
AccountsQuery,
AccountFieldsFragment[],
AccountEventsSubscription,
AccountEventsSubscription['accounts']
AccountEventsSubscription['accounts'],
AccountsQueryVariables
>({
query: AccountsDocument,
subscriptionQuery: AccountEventsDocument,
@@ -159,8 +161,16 @@ const getAssetAccountAggregation = (
return { ...balanceAccount, breakdown };
};
export const accountsDataProvider = makeDerivedDataProvider<Account[], never>(
[accountsOnlyDataProvider, marketsProvider, assetsProvider],
export const accountsDataProvider = makeDerivedDataProvider<
Account[],
never,
AccountsQueryVariables
>(
[
accountsOnlyDataProvider,
(callback, client) => marketsProvider(callback, client, undefined),
(callback, client) => assetsProvider(callback, client, undefined),
],
([accounts, markets, assets]): Account[] | null => {
return accounts
? accounts
@@ -194,7 +204,8 @@ export const accountsDataProvider = makeDerivedDataProvider<Account[], never>(
export const aggregatedAccountsDataProvider = makeDerivedDataProvider<
AccountFields[],
never
never,
AccountsQueryVariables
>(
[accountsDataProvider],
(parts) => parts[0] && getAccountData(parts[0] as Account[])
+25 -7
View File
@@ -1,12 +1,16 @@
import { useRef, useMemo, memo, useCallback } from 'react';
import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import {
useDataProvider,
useBottomPlaceholder,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { useRef, useMemo, memo } from 'react';
import type { AccountFields } from './accounts-data-provider';
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
import type { PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
import type { RowHeightParams } from 'ag-grid-community';
interface AccountManagerProps {
partyId: string;
@@ -27,14 +31,26 @@ export const AccountManager = ({
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const { data, loading, error, reload } = useDataProvider<
AccountFields[],
never
>({
const { data, loading, error, reload } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables,
});
const setId = useCallback(
(data: AccountFields) => ({
...data,
asset: { ...data.asset, id: `${data.asset.id}-1` },
}),
[]
);
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
gridRef,
setId,
});
const getRowHeight = useCallback(
(params: RowHeightParams) => (params.node.rowPinned ? 32 : 22),
[]
);
return (
<div className="relative h-full">
<AccountTable
@@ -46,6 +62,8 @@ export const AccountManager = ({
isReadOnly={isReadOnly}
noRowsOverlayComponent={() => null}
pinnedAsset={pinnedAsset}
getRowHeight={getRowHeight}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+157 -89
View File
@@ -1,13 +1,21 @@
import { forwardRef, useMemo, useState } from 'react';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import {
addDecimalsFormatNumber,
isNumeric,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
VegaValueGetterParams,
} from '@vegaprotocol/datagrid';
import { ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
import { Button, ButtonLink, Dialog } from '@vegaprotocol/ui-toolkit';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import {
AgGridDynamic as AgGrid,
CenteredGridCellWrapper,
} from '@vegaprotocol/datagrid';
import { AgGridColumn } from 'ag-grid-react';
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
@@ -86,18 +94,23 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
cellRenderer={({
value,
data,
node,
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
return value ? (
<ButtonLink
data-testid="asset"
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
}}
<CenteredGridCellWrapper
className={node.rowPinned ? 'h-[30px]' : undefined}
>
{value}
</ButtonLink>
<ButtonLink
data-testid="asset"
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
}}
>
{value}
</ButtonLink>
</CenteredGridCellWrapper>
) : null;
}}
maxWidth={300}
@@ -109,16 +122,31 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
headerTooltip={t(
'This is the total amount of collateral used plus the amount available in your general account.'
)}
valueFormatter={({
value,
valueGetter={({
data,
}: VegaValueFormatterParams<AccountFields, 'deposited'>) =>
data &&
data.asset &&
isNumeric(value) &&
addDecimalsFormatNumber(value, data.asset.decimals)
}
}: VegaValueGetterParams<AccountFields, 'deposited'>) => {
return !data?.deposited
? undefined
: toBigNum(data.deposited, data.asset.decimals).toNumber();
}}
maxWidth={300}
cellRenderer={({
data,
node,
}: VegaICellRendererParams<AccountFields, 'deposited'>) => {
const valueFormatted =
data &&
data.asset &&
isNumeric(data.deposited) &&
addDecimalsFormatNumber(data.deposited, data.asset.decimals);
return node.rowPinned ? (
<CenteredGridCellWrapper className="h-[30px] justify-end">
{valueFormatted}
</CenteredGridCellWrapper>
) : (
valueFormatted
);
}}
/>
<AgGridColumn
headerName={t('Used')}
@@ -127,16 +155,31 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
headerTooltip={t(
'This is the amount of collateral used from your general account.'
)}
valueFormatter={({
value,
valueGetter={({
data,
}: VegaValueFormatterParams<AccountFields, 'used'>) =>
data &&
data.asset &&
isNumeric(value) &&
addDecimalsFormatNumber(value, data.asset.decimals)
}
}: VegaValueGetterParams<AccountFields, 'used'>) => {
return !data?.used
? undefined
: toBigNum(data.used, data.asset.decimals).toNumber();
}}
maxWidth={300}
cellRenderer={({
data,
node,
}: VegaICellRendererParams<AccountFields, 'used'>) => {
const valueFormatted =
data &&
data.asset &&
isNumeric(data.used) &&
addDecimalsFormatNumber(data.used, data.asset.decimals);
return node.rowPinned ? (
<CenteredGridCellWrapper className="h-[30px] justify-end">
{valueFormatted}
</CenteredGridCellWrapper>
) : (
valueFormatted
);
}}
/>
<AgGridColumn
headerName={t('Available')}
@@ -145,83 +188,108 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
headerTooltip={t(
'This is the amount of collateral available in your general account.'
)}
valueGetter={({
data,
}: VegaValueGetterParams<AccountFields, 'available'>) => {
return !data?.available
? undefined
: toBigNum(data.available, data.asset.decimals).toNumber();
}}
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<AccountFields, 'available'>) =>
data &&
data.asset &&
isNumeric(value) &&
addDecimalsFormatNumber(value, data.asset.decimals)
isNumeric(data.available) &&
addDecimalsFormatNumber(data.available, data.asset.decimals)
}
maxWidth={300}
cellRenderer={({
data,
node,
}: VegaICellRendererParams<AccountFields, 'available'>) => {
const valueFormatted =
data &&
data.asset &&
isNumeric(data.available) &&
addDecimalsFormatNumber(data.available, data.asset.decimals);
return node.rowPinned ? (
<CenteredGridCellWrapper className="h-[30px] justify-end">
{valueFormatted}
</CenteredGridCellWrapper>
) : (
valueFormatted
);
}}
/>
{
<AgGridColumn
colId="breakdown"
headerName=""
sortable={false}
minWidth={200}
type="rightAligned"
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
if (!data) return null;
else {
if (
data.asset.id === pinnedAssetId &&
new BigNumber(data.deposited).isLessThanOrEqualTo(0)
) {
return (
<ButtonLink
<AgGridColumn
colId="breakdown"
headerName=""
sortable={false}
minWidth={200}
type="rightAligned"
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
if (!data) return null;
else {
if (
data.asset.id === pinnedAssetId &&
new BigNumber(data.deposited).isLessThanOrEqualTo(0)
) {
return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
<Button
size="xs"
variant="primary"
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit to trade')}
</ButtonLink>
);
}
return (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
)}
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
)}
</>
</Button>
</CenteredGridCellWrapper>
);
}
}}
/>
}
return (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
)}
<span className="mx-1" />
{!props.isReadOnly && (
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
)}
</>
);
}
}}
/>
</AgGrid>
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
<div className="h-[35vh] w-full m-auto flex flex-col">
+24 -6
View File
@@ -43,10 +43,6 @@ export const accountFields: AccountFieldsFragment[] = [
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
market: {
id: 'market-0',
__typename: 'Market',
},
asset: {
__typename: 'Asset',
id: 'asset-id-2',
@@ -75,7 +71,7 @@ export const accountFields: AccountFieldsFragment[] = [
},
asset: {
__typename: 'Asset',
id: 'asset-id-2',
id: 'asset-0',
},
},
{
@@ -94,7 +90,7 @@ export const accountFields: AccountFieldsFragment[] = [
{
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
balance: '10000000000',
market: null,
asset: {
__typename: 'Asset',
@@ -141,3 +137,25 @@ export const accountEventsSubscription = (
};
return merge(defaultResult, override);
};
export const amendGeneralAccountBalance = (
accounts: AccountsQuery,
marketId: string,
balance: string
) => {
if (accounts.party?.accountsConnection?.edges) {
const marginAccount = accounts.party.accountsConnection.edges.find(
(edge) => edge?.node.market?.id === marketId
);
if (marginAccount) {
const generalAccount = accounts.party.accountsConnection.edges.find(
(edge) =>
edge?.node.asset.id === marginAccount.node.asset.id &&
!edge?.node.market
);
if (generalAccount) {
generalAccount.node.balance = balance;
}
}
}
};
+1 -1
View File
@@ -20,7 +20,7 @@ export const TransferContainer = () => {
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
const { data } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey },
variables: { partyId: pubKey || '' },
skip: !pubKey,
});
const create = useVegaTransactionStore((store) => store.create);
+1 -1
View File
@@ -11,7 +11,7 @@ interface Actions {
open: (open?: boolean) => void;
}
export const useTransferDialog = create<State & Actions>((set) => ({
export const useTransferDialog = create<State & Actions>()((set) => ({
isOpen: false,
open: (open = true) => {
set(() => ({ isOpen: open }));
+1 -1
View File
@@ -9,4 +9,4 @@ type HeaderStore = {
[url: string]: HeaderEntry | undefined;
};
export const useHeaderStore = create<HeaderStore>(() => ({}));
export const useHeaderStore = create<HeaderStore>()(() => ({}));
+13 -10
View File
@@ -1,8 +1,11 @@
import { makeDataProvider } from '@vegaprotocol/utils';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
import type { AssetQuery, AssetFieldsFragment } from './__generated__/Asset';
import type {
AssetQuery,
AssetFieldsFragment,
AssetQueryVariables,
} from './__generated__/Asset';
import { AssetDocument } from './__generated__/Asset';
export type Asset = AssetFieldsFragment;
@@ -15,21 +18,21 @@ export const getData = (responseData: AssetQuery | null | undefined) => {
return null;
};
export const assetProvider = makeDataProvider<AssetQuery, Asset, never, never>({
export const assetProvider = makeDataProvider<
AssetQuery,
Asset,
never,
never,
AssetQueryVariables
>({
query: AssetDocument,
getData,
});
export const useAssetDataProvider = (assetId: string) => {
const variables = useMemo(
() => ({
assetId,
}),
[assetId]
);
return useDataProvider({
dataProvider: assetProvider,
variables,
variables: { assetId: assetId || '' },
skip: !assetId,
});
};
+1 -1
View File
@@ -20,7 +20,7 @@ export type AssetDetailsDialogStore = {
open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void;
};
export const useAssetDetailsDialogStore = create<AssetDetailsDialogStore>(
export const useAssetDetailsDialogStore = create<AssetDetailsDialogStore>()(
(set) => ({
isOpen: false,
id: '',
+1 -1
View File
@@ -86,7 +86,7 @@ export const rows: Rows = [
key: AssetDetail.QUANTUM,
label: t('Quantum'),
tooltip: t('The minimum economically meaningful amount of the asset'),
value: (asset) => asset.quantum,
value: (asset) => num(asset, asset.quantum),
},
{
key: AssetDetail.STATUS,
@@ -40,4 +40,5 @@ export const enabledAssetsProvider = makeDerivedDataProvider<
export const useAssetsDataProvider = () =>
useDataProvider({
dataProvider: assetsProvider,
variables: undefined,
});
+2
View File
@@ -19,6 +19,7 @@ import { addCreateMarket } from './lib/commands/create-market';
import { addConnectPublicKey } from './lib/commands/add-connect-public-key';
import { addVegaWalletSubmitProposal } from './lib/commands/vega-wallet-submit-proposal';
import { addGetNodes } from './lib/commands/get-nodes';
import { addVegaWalletSubmitLiquidityProvision } from './lib/commands/vega-wallet-submit-liquidity-provision';
addGetTestIdcommand();
addSlackCommand();
@@ -39,6 +40,7 @@ addMockTransactionResponse();
addCreateMarket();
addConnectPublicKey();
addVegaWalletSubmitProposal();
addVegaWalletSubmitLiquidityProvision();
export { mockConnectWallet } from './lib/commands/vega-wallet-connect';
export type { onMessage } from './lib/mock-ws';
@@ -0,0 +1,67 @@
import { PeggedReference } from '@vegaprotocol/types';
import type { LiquidityProvisionSubmission } from '@vegaprotocol/wallet';
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
VegaWalletSubmitLiquidityProvision(
marketId: string,
amount: string
): void;
}
}
}
export const addVegaWalletSubmitLiquidityProvision = () => {
Cypress.Commands.add(
'VegaWalletSubmitLiquidityProvision',
(marketId, amount) => {
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
const liquidityProvisionTx: LiquidityProvisionSubmission = {
liquidityProvisionSubmission: {
marketId: marketId,
commitmentAmount: amount,
fee: '0.001',
buys: [
{
offset: '10',
proportion: '1',
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '12',
proportion: '2',
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
sells: [
{
offset: '10',
proportion: '2',
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '12',
proportion: '2',
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
},
pubKey: vegaPubKey,
propagate: true,
};
createWalletClient(vegaWalletUrl, token);
cy.highlight('Submitting liquidity provision');
sendVegaTx(vegaPubKey, liquidityProvisionTx);
}
);
};
+1
View File
@@ -8,6 +8,7 @@ export * from './lib/cells/price-cell';
export * from './lib/cells/price-change-cell';
export * from './lib/cells/price-flash-cell';
export * from './lib/cells/vol-cell';
export * from './lib/cells/centered-grid-cell';
export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter';
@@ -22,13 +22,15 @@ const agGridDarkVariables = `
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
.ag-theme-balham-dark .ag-row.no-hover, .ag-theme-balham-dark .ag-row.no-hover:hover {
background: black;
}
.ag-theme-balham-dark .ag-react-container {
overflow: hidden;
text-overflow: ellipsis;
}
.ag-theme-balham-dark .ag-cell, .ag-theme-balham-dark .ag-full-width-row .ag-cell-wrapper.ag-row-group {
.ag-theme-balham-dark .ag-cell, .ag-theme-balham-dark .ag-full-width-row .ag-cell-wrapper.ag-row-group {
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
`;
@@ -22,13 +22,15 @@ const agGridLightVariables = `
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
.ag-theme-balham .ag-row.no-hover, .ag-theme-balham .ag-row.no-hover:hover {
background: white;
}
.ag-theme-balham .ag-react-container {
overflow: hidden;
text-overflow: ellipsis;
}
.ag-theme-balham .ag-cell, .ag-theme-balham .ag-full-width-row .ag-cell-wrapper.ag-row-group {
.ag-theme-balham .ag-cell, .ag-theme-balham .ag-full-width-row .ag-cell-wrapper.ag-row-group {
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
}
`;
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';
import classNames from 'classnames';
export const CenteredGridCellWrapper = ({
children,
className,
}: {
children: ReactNode;
className?: string;
}) => (
<div
className={classNames('flex h-[20px] p-0 justify-items-center', className)}
>
<div className="self-center">{children}</div>
</div>
);
+21
View File
@@ -0,0 +1,21 @@
import type { StateCreator } from 'zustand';
import { act } from 'react-dom/test-utils';
const { create: actualCreate } = jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set<() => void>();
// when creating a store, we get its initial state, create a reset function and add it in the set
export const create =
() =>
<S>(createState: StateCreator<S>) => {
const store = actualCreate(createState);
const initialState = store.getState();
storeResetFns.add(() => store.setState(initialState, true));
return store;
};
// Reset all stores after each test run
beforeEach(() => {
act(() => storeResetFns.forEach((resetFn) => resetFn()));
});
@@ -1,7 +1,7 @@
import { formatNumber } from '@vegaprotocol/utils';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Notification, Intent } from '@vegaprotocol/ui-toolkit';
import { DepositDialog, useDepositDialog } from '@vegaprotocol/deposits';
import { useDepositDialog } from '@vegaprotocol/deposits';
interface Props {
margin: string;
@@ -16,25 +16,22 @@ interface Props {
export const MarginWarning = ({ margin, balance, asset }: Props) => {
const openDepositDialog = useDepositDialog((state) => state.open);
return (
<>
<Notification
intent={Intent.Warning}
testId="dealticket-warning-margin"
message={`You may not have enough margin available to open this position. ${formatNumber(
margin,
asset.decimals
)} ${asset.symbol} ${t(
'is currently required. You have only'
)} ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t(
'available.'
)}`}
buttonProps={{
text: t(`Deposit ${asset.symbol}`),
action: () => openDepositDialog(asset.id),
dataTestId: 'deal-ticket-deposit-dialog-button',
}}
/>
<DepositDialog />
</>
<Notification
intent={Intent.Warning}
testId="dealticket-warning-margin"
message={`You may not have enough margin available to open this position. ${addDecimalsFormatNumber(
margin,
asset.decimals
)} ${asset.symbol} ${t(
'is currently required. You have only'
)} ${addDecimalsFormatNumber(balance, asset.decimals)} ${
asset.symbol
} ${t('available.')}`}
buttonProps={{
text: t(`Deposit ${asset.symbol}`),
action: () => openDepositDialog(asset.id),
dataTestId: 'deal-ticket-deposit-dialog-button',
}}
/>
);
};
@@ -7,7 +7,7 @@ interface ZeroBalanceErrorProps {
id: string;
symbol: string;
};
onClickCollateral: () => void;
onClickCollateral?: () => void;
}
export const ZeroBalanceError = ({
@@ -21,8 +21,12 @@ export const ZeroBalanceError = ({
testId="dealticket-error-message-zero-balance"
message={
<>
You need {asset.symbol} in your wallet to trade in this market. See
all your <Link onClick={onClickCollateral}>collateral</Link>.
You need {asset.symbol} in your wallet to trade in this market.
{onClickCollateral && (
<>
See all your <Link onClick={onClickCollateral}>collateral</Link>.
</>
)}
</>
}
buttonProps={{
@@ -1,17 +1,21 @@
import type { UseFormRegister } from 'react-hook-form';
import type { Control } from 'react-hook-form';
import type { Market, MarketData } from '@vegaprotocol/market-list';
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
import * as Schema from '@vegaprotocol/types';
import type { DealTicketFormFields } from './deal-ticket';
import type { OrderObj } from '@vegaprotocol/orders';
import type { OrderFormFields } from '../../hooks/use-order-form';
export interface DealTicketAmountProps {
control: Control<OrderFormFields>;
orderType: Schema.OrderType;
marketData: MarketData;
market: Market;
register: UseFormRegister<DealTicketFormFields>;
sizeError?: string;
priceError?: string;
update: (obj: Partial<OrderObj>) => void;
size: string;
price?: string;
}
export const DealTicketAmount = ({
@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useThrottledDataProvider } from '@vegaprotocol/react-helpers';
@@ -29,7 +28,7 @@ export const DealTicketContainer = ({
} = useThrottledDataProvider(
{
dataProvider: marketDataProvider,
variables: useMemo(() => ({ marketId }), [marketId]),
variables: { marketId },
},
1000
);
@@ -47,7 +46,7 @@ export const DealTicketContainer = ({
market={market}
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
onClickCollateral={onClickCollateral || (() => null)}
onClickCollateral={onClickCollateral}
/>
) : (
<Splash>
@@ -1,6 +1,5 @@
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { Market, MarketData } from '@vegaprotocol/market-list';
import {
@@ -12,22 +11,51 @@ interface DealTicketFeeDetailsProps {
order: OrderSubmissionBody['orderSubmission'];
market: Market;
marketData: MarketData;
margin: string;
totalMargin: string;
balance: string;
}
export interface DealTicketFeeDetails {
export interface DealTicketFeeDetailProps {
label: string;
value?: string | number | null;
labelDescription?: string | ReactNode;
symbol?: string;
}
export const DealTicketFeeDetail = ({
label,
value,
labelDescription,
symbol,
}: DealTicketFeeDetailProps) => (
<div className="text-xs mt-2 flex justify-between items-center gap-4 flex-wrap">
<div>
<Tooltip description={labelDescription}>
<div>{label}</div>
</Tooltip>
</div>
<div className="text-neutral-500 dark:text-neutral-300">{`${value ?? '-'} ${
symbol || ''
}`}</div>
</div>
);
export const DealTicketFeeDetails = ({
order,
market,
marketData,
margin,
totalMargin,
balance,
}: DealTicketFeeDetailsProps) => {
const feeDetails = useFeeDealTicketDetails(order, market, marketData);
const details = useMemo(() => getFeeDetailsValues(feeDetails), [feeDetails]);
const details = getFeeDetailsValues({
...feeDetails,
margin,
totalMargin,
balance,
});
return (
<div>
{details.map(({ label, value, labelDescription, symbol }) => (
@@ -2,6 +2,7 @@ import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { Controller } from 'react-hook-form';
export type DealTicketLimitAmountProps = Omit<
Omit<DealTicketAmountProps, 'marketData'>,
@@ -9,10 +10,13 @@ export type DealTicketLimitAmountProps = Omit<
>;
export const DealTicketLimitAmount = ({
register,
control,
market,
sizeError,
priceError,
update,
price,
size,
}: DealTicketLimitAmountProps) => {
const priceStep = toDecimal(market?.decimalPlaces);
const sizeStep = toDecimal(market?.positionDecimalPlaces);
@@ -47,22 +51,30 @@ export const DealTicketLimitAmount = ({
labelFor="input-order-size-limit"
className="!mb-1"
>
<Input
id="input-order-size-limit"
className="w-full"
type="number"
step={sizeStep}
min={sizeStep}
data-testid="order-size"
onWheel={(e) => e.currentTarget.blur()}
{...register('size', {
<Controller
name="size"
control={control}
rules={{
required: t('You need to provide a size'),
min: {
value: sizeStep,
message: t('Size cannot be lower than ' + sizeStep),
},
validate: validateAmount(sizeStep, 'Size'),
})}
}}
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()}
/>
)}
/>
</FormGroup>
</div>
@@ -77,14 +89,10 @@ export const DealTicketLimitAmount = ({
labelAlign="right"
className="!mb-1"
>
<Input
id="input-price-quote"
className="w-full"
type="number"
step={priceStep}
data-testid="order-price"
onWheel={(e) => e.currentTarget.blur()}
{...register('price', {
<Controller
name="price"
control={control}
rules={{
required: t('You need provide a price'),
min: {
value: priceStep,
@@ -92,7 +100,19 @@ export const DealTicketLimitAmount = ({
},
// @ts-ignore this fulfills the interface but still errors
validate: validateAmount(priceStep, 'Price'),
})}
}}
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()}
/>
)}
/>
</FormGroup>
</div>
@@ -8,6 +8,7 @@ import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
import { isMarketInAuction } from '../../utils';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { getMarketPrice } from '../../utils/get-price';
import { Controller } from 'react-hook-form';
export type DealTicketMarketAmountProps = Omit<
DealTicketAmountProps,
@@ -15,10 +16,12 @@ export type DealTicketMarketAmountProps = Omit<
>;
export const DealTicketMarketAmount = ({
register,
control,
market,
marketData,
sizeError,
update,
size,
}: DealTicketMarketAmountProps) => {
const quoteName = market.tradableInstrument.instrument.product.quoteName;
const sizeStep = toDecimal(market?.positionDecimalPlaces);
@@ -47,22 +50,30 @@ export const DealTicketMarketAmount = ({
</div>
<div className="flex items-center gap-4">
<div className="flex-1">
<Input
id="input-order-size-market"
className="w-full"
type="number"
step={sizeStep}
min={sizeStep}
onWheel={(e) => e.currentTarget.blur()}
data-testid="order-size"
{...register('size', {
<Controller
name="size"
control={control}
rules={{
required: t('You need to provide a size'),
min: {
value: sizeStep,
message: t('Size cannot be lower than ' + sizeStep),
},
validate: validateAmount(sizeStep, 'Size'),
})}
}}
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"
/>
)}
/>
</div>
<div>@</div>
@@ -1,38 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { fireEvent, render, screen, act } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { generateMarket, generateMarketData } from '../../test-helpers';
import { DealTicket } from './deal-ticket';
import * as Schema from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { ChainIdQuery } from '@vegaprotocol/react-helpers';
import { ChainIdDocument } from '@vegaprotocol/react-helpers';
import { addDecimal } from '@vegaprotocol/utils';
import { useOrderStore } from '@vegaprotocol/orders';
jest.mock('zustand');
jest.mock('./deal-ticket-fee-details', () => ({
DealTicketFeeDetails: () => <div data-testid="deal-ticket-fee-details" />,
}));
const pubKey = 'pubKey';
const market = generateMarket();
const marketData = generateMarketData();
const submit = jest.fn();
const mockChainId = 'chain-id';
function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
const chainIdMock: MockedResponse<ChainIdQuery> = {
request: {
query: ChainIdDocument,
},
result: {
data: {
statistics: {
chainId: mockChainId,
},
},
},
};
function generateJsx() {
return (
<MockedProvider mocks={[chainIdMock]}>
<VegaWalletContext.Provider value={{ pubKey: mockChainId } as any}>
<MockedProvider>
<VegaWalletContext.Provider value={{ pubKey, isReadOnly: false } as any}>
<DealTicket market={market} marketData={marketData} submit={submit} />
</VegaWalletContext.Provider>
</MockedProvider>
@@ -41,10 +31,11 @@ function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
describe('DealTicket', () => {
beforeEach(() => {
window.localStorage.clear();
localStorage.clear();
});
afterEach(() => {
window.localStorage.clear();
localStorage.clear();
jest.clearAllMocks();
});
@@ -61,9 +52,7 @@ describe('DealTicket', () => {
expect(
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
String(1 / Math.pow(10, market.positionDecimalPlaces))
);
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
@@ -76,7 +65,49 @@ describe('DealTicket', () => {
);
});
it('handles TIF select box dependent on order type', () => {
it('should use local storage state for initial values', () => {
const expectedOrder = {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_SELL,
size: '0.1',
price: '300.22',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
persist: true,
};
useOrderStore.setState({
orders: {
[expectedOrder.marketId]: expectedOrder,
},
});
render(generateJsx());
// Assert correct defaults are used from store
expect(
screen
.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
).toBeChecked();
expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
expectedOrder.size
);
expect(screen.getByTestId('order-tif')).toHaveValue(
expectedOrder.timeInForce
);
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
expectedOrder.price
);
});
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
// Only FOK and IOC should be present by default (type market order)
@@ -86,50 +117,72 @@ describe('DealTicket', () => {
)
).toEqual(['Fill or Kill (FOK)', 'Immediate or Cancel (IOC)']);
// IOC should be default
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
'Immediate or Cancel (IOC)'
);
// Select FOK - FOK should be selected
await userEvent.selectOptions(
screen.getByTestId('order-tif'),
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
);
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
'Fill or Kill (FOK)'
);
// Switch to type limit order -> all TIF options should be shown
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
await userEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
expect(screen.getByTestId('order-tif').children).toHaveLength(
Object.keys(Schema.OrderTimeInForce).length
);
// Select GTC -> GTC should be selected
fireEvent.change(screen.getByTestId('order-tif'), {
target: { value: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC },
});
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
);
// Switch to type market order -> IOC should be selected (default)
fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
// Select IOC -> IOC should be selected
fireEvent.change(screen.getByTestId('order-tif'), {
target: { value: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC },
});
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
// Switch to type limit order -> GTC should be selected
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
// expect GTC as LIMIT default
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
);
// Select GTT -> GTT should be selected
fireEvent.change(screen.getByTestId('order-tif'), {
target: { value: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT },
});
await userEvent.selectOptions(
screen.getByTestId('order-tif'),
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
);
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
);
// Switch to type market order -> IOC should be selected
fireEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
// Switch back to type market order -> FOK should be preserved from previous selection
await userEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
);
// Select IOC -> IOC should be selected
await userEvent.selectOptions(
screen.getByTestId('order-tif'),
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
// Switch back type limit order -> GTT should be preserved
await userEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
);
// Select GFN -> GFN should be selected
await userEvent.selectOptions(
screen.getByTestId('order-tif'),
Schema.OrderTimeInForce.TIME_IN_FORCE_GFN
);
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GFN
);
// Switch to type market order -> IOC should be preserved
await userEvent.click(screen.getByTestId('order-type-TYPE_MARKET'));
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
@@ -143,23 +196,20 @@ describe('DealTicket', () => {
screen.getByTestId('order-side-SIDE_BUY')?.querySelector('input')
).toBeChecked();
await act(async () => {
fireEvent.change(screen.getByTestId('order-size'), {
target: { value: '200' },
});
});
await userEvent.type(screen.getByTestId('order-size'), '200');
expect(screen.getByTestId('order-size')).toHaveDisplayValue('200');
fireEvent.change(screen.getByTestId('order-tif'), {
target: { value: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC },
});
await userEvent.selectOptions(
screen.getByTestId('order-tif'),
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
// Switch to limit order
fireEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
await userEvent.click(screen.getByTestId('order-type-TYPE_LIMIT'));
// Check all TIF options shown
expect(screen.getByTestId('order-tif').children).toHaveLength(
@@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { memo, useCallback, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { memo, useCallback, useEffect, useState } from 'react';
import { Controller } from 'react-hook-form';
import { DealTicketAmount } from './deal-ticket-amount';
import { DealTicketButton } from './deal-ticket-button';
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
@@ -9,20 +9,21 @@ import { ExpirySelector } from './expiry-selector';
import { SideSelector } from './side-selector';
import { TimeInForceSelector } from './time-in-force-selector';
import { TypeSelector } from './type-selector';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import {
normalizeOrderSubmission,
useVegaWallet,
useVegaWalletDialogStore,
} from '@vegaprotocol/wallet';
import {
ExternalLink,
InputError,
Intent,
Notification,
} from '@vegaprotocol/ui-toolkit';
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import {
getDefaultOrder,
validateExpiration,
validateMarketState,
validateMarketTradingMode,
validateTimeInForce,
@@ -30,29 +31,24 @@ import {
} from '../../utils';
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
import { SummaryValidationType } from '../../constants';
import { useHasNoBalance } from '../../hooks/use-has-no-balance';
import { useInitialMargin } from '../../hooks/use-initial-margin';
import type { Market, MarketData } from '@vegaprotocol/market-list';
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import {
usePersistedOrderStore,
usePersistedOrderStoreSubscription,
} from '@vegaprotocol/orders';
import { OrderType } from '@vegaprotocol/types';
useMarketAccountBalance,
useAccountBalance,
} from '@vegaprotocol/accounts';
export type TransactionStatus = 'default' | 'pending';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import { useOrderForm } from '../../hooks/use-order-form';
export interface DealTicketProps {
market: Market;
marketData: MarketData;
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
submit: (order: OrderSubmission) => void;
onClickCollateral?: () => void;
}
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
// This is not a field used in the form but allows us to set a
// summary error message
summary: string;
};
export const DealTicket = ({
market,
marketData,
@@ -60,58 +56,58 @@ export const DealTicket = ({
onClickCollateral,
}: DealTicketProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
(store) => ({
getPersistedOrder: store.getOrder,
setPersistedOrder: store.setOrder,
})
);
// 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 {
register,
control,
handleSubmit,
watch,
errors,
order,
setError,
clearErrors,
formState: { errors },
setValue,
} = useForm<DealTicketFormFields>({
defaultValues: getPersistedOrder(market.id) || getDefaultOrder(market),
});
update,
handleSubmit,
} = useOrderForm(market.id);
const order = watch();
const asset = market.tradableInstrument.instrument.product.settlementAsset;
watch((orderData) => {
const persistable = !(
orderData.type === OrderType.TYPE_LIMIT && orderData.price === ''
const { accountBalance: marginAccountBalance } = useMarketAccountBalance(
market.id
);
const { accountBalance: generalAccountBalance } = useAccountBalance(asset.id);
const balance = (
BigInt(marginAccountBalance) + BigInt(generalAccountBalance)
).toString();
const { marketState, marketTradingMode } = marketData;
const normalizedOrder =
order &&
normalizeOrderSubmission(
order,
market.decimalPlaces,
market.positionDecimalPlaces
);
if (persistable) {
setPersistedOrder(orderData as DealTicketFormFields);
}
});
usePersistedOrderStoreSubscription(market.id, (storedOrder) => {
if (order.price !== storedOrder.price) {
clearErrors('price');
setValue('price', storedOrder.price);
}
});
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder);
const marketStateError = validateMarketState(marketData.marketState);
const hasNoBalance = useHasNoBalance(
market.tradableInstrument.instrument.product.settlementAsset.id
);
const marketTradingModeError = validateMarketTradingMode(
marketData.marketTradingMode
);
const checkForErrors = useCallback(() => {
useEffect(() => {
if (!pubKey) {
setError('summary', { message: t('No public key selected') });
setError('summary', {
message: t('No public key selected'),
type: SummaryValidationType.NoPubKey,
});
return;
}
const marketStateError = validateMarketState(marketState);
if (marketStateError !== true) {
setError('summary', {
message: marketStateError,
@@ -120,6 +116,7 @@ export const DealTicket = ({
return;
}
const hasNoBalance = generalAccountBalance === '0';
if (hasNoBalance) {
setError('summary', {
message: SummaryValidationType.NoCollateral,
@@ -128,6 +125,7 @@ export const DealTicket = ({
return;
}
const marketTradingModeError = validateMarketTradingMode(marketTradingMode);
if (marketTradingModeError !== true) {
setError('summary', {
message: marketTradingModeError,
@@ -135,39 +133,19 @@ export const DealTicket = ({
});
return;
}
clearErrors('summary');
}, [
hasNoBalance,
marketStateError,
marketTradingModeError,
marketState,
marketTradingMode,
generalAccountBalance,
pubKey,
setError,
]);
useEffect(() => {
if (
(!hasNoBalance &&
errors.summary?.type === SummaryValidationType.NoCollateral) ||
(marketStateError === true &&
errors.summary?.type === SummaryValidationType.MarketState) ||
(marketTradingModeError === true &&
errors.summary?.type === SummaryValidationType.TradingMode)
) {
clearErrors('summary');
}
checkForErrors();
}, [
hasNoBalance,
marketStateError,
marketTradingModeError,
clearErrors,
errors.summary?.message,
errors.summary?.type,
checkForErrors,
errors.summary,
]);
const onSubmit = useCallback(
(order: OrderSubmissionBody['orderSubmission']) => {
checkForErrors();
(order: OrderSubmission) => {
submit(
normalizeOrderSubmission(
order,
@@ -176,12 +154,15 @@ export const DealTicket = ({
)
);
},
[checkForErrors, submit, market.decimalPlaces, market.positionDecimalPlaces]
[submit, market.decimalPlaces, market.positionDecimalPlaces]
);
// if an order doesn't exist one will be created by the store immediately
if (!order || !normalizedOrder) return null;
return (
<form
onSubmit={isReadOnly ? () => null : handleSubmit(onSubmit)}
onSubmit={isReadOnly ? undefined : handleSubmit(onSubmit)}
className="p-4"
noValidate
>
@@ -194,10 +175,19 @@ export const DealTicket = ({
marketData.trigger
),
}}
render={({ field }) => (
render={() => (
<TypeSelector
value={field.value}
onSelect={field.onChange}
value={order.type}
onSelect={(type) => {
if (type === OrderType.TYPE_NETWORK) return;
update({
type,
// when changing type also update the tif to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
expiresAt: undefined,
});
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.type?.message}
@@ -207,17 +197,25 @@ export const DealTicket = ({
<Controller
name="side"
control={control}
render={({ field }) => (
<SideSelector value={field.value} onSelect={field.onChange} />
render={() => (
<SideSelector
value={order.side}
onSelect={(side) => {
update({ side });
}}
/>
)}
/>
<DealTicketAmount
control={control}
orderType={order.type}
market={market}
marketData={marketData}
register={register}
sizeError={errors.size?.message}
priceError={errors.price?.message}
update={update}
size={order.size}
price={order.price}
/>
<Controller
name="timeInForce"
@@ -228,11 +226,21 @@ export const DealTicket = ({
marketData.trigger
),
}}
render={({ field }) => (
render={() => (
<TimeInForceSelector
value={field.value}
value={order.timeInForce}
orderType={order.type}
onSelect={field.onChange}
onSelect={(timeInForce) => {
update({ timeInForce });
// 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}
@@ -244,33 +252,43 @@ export const DealTicket = ({
<Controller
name="expiresAt"
control={control}
render={({ field }) => (
rules={{
validate: validateExpiration,
}}
render={() => (
<ExpirySelector
value={field.value}
onSelect={field.onChange}
value={order.expiresAt}
onSelect={(expiresAt) =>
update({
expiresAt: expiresAt || undefined,
})
}
errorMessage={errors.expiresAt?.message}
register={register}
/>
)}
/>
)}
<SummaryMessage
errorMessage={errors.summary?.message}
market={market}
marketData={marketData}
order={order}
asset={asset}
marketTradingMode={marketData.marketTradingMode}
balance={balance}
margin={totalMargin}
isReadOnly={isReadOnly}
pubKey={pubKey}
onClickCollateral={onClickCollateral || (() => null)}
onClickCollateral={onClickCollateral}
/>
<DealTicketButton
disabled={Object.keys(errors).length >= 1 || isReadOnly}
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
/>
<DealTicketFeeDetails
order={order}
order={normalizedOrder}
market={market}
marketData={marketData}
margin={margin}
totalMargin={totalMargin}
balance={marginAccountBalance}
/>
</form>
);
@@ -282,32 +300,28 @@ export const DealTicket = ({
*/
interface SummaryMessageProps {
errorMessage?: string;
market: Market;
marketData: MarketData;
order: OrderSubmissionBody['orderSubmission'];
asset: { id: string; symbol: string; name: string; decimals: number };
marketTradingMode: MarketData['marketTradingMode'];
balance: string;
margin: string;
isReadOnly: boolean;
pubKey: string | null;
onClickCollateral: () => void;
onClickCollateral?: () => void;
}
const SummaryMessage = memo(
({
errorMessage,
market,
marketData,
order,
asset,
marketTradingMode,
balance,
margin,
isReadOnly,
pubKey,
onClickCollateral,
}: SummaryMessageProps) => {
// Specific error UI for if balance is so we can
// render a deposit dialog
const asset = market.tradableInstrument.instrument.product.settlementAsset;
const assetSymbol = asset.symbol;
const { balanceError, balance, margin } = useOrderMarginValidation({
market,
marketData,
order,
});
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
@@ -351,7 +365,7 @@ const SummaryMessage = memo(
return (
<div className="mb-2">
<ZeroBalanceError
asset={market.tradableInstrument.instrument.product.settlementAsset}
asset={asset}
onClickCollateral={onClickCollateral}
/>
</div>
@@ -372,21 +386,16 @@ const SummaryMessage = memo(
// If there is no blocking error but user doesn't have enough
// balance render the margin warning, but still allow submission
if (balanceError) {
return (
<div className="mb-2">
<MarginWarning balance={balance} margin={margin} asset={asset} />
</div>
);
if (BigInt(balance) < BigInt(margin)) {
return <MarginWarning balance={balance} margin={margin} asset={asset} />;
}
// Show auction mode warning
if (
[
Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
].includes(marketData.marketTradingMode)
].includes(marketTradingMode)
) {
return (
<div className="mb-2">
@@ -1,22 +1,17 @@
import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { UseFormRegister } from 'react-hook-form';
import { validateExpiration } from '../../utils/validate-expiration';
import type { DealTicketFormFields } from '.';
interface ExpirySelectorProps {
value?: string;
onSelect: (expiration: string | null) => void;
errorMessage?: string;
register?: UseFormRegister<DealTicketFormFields>;
}
export const ExpirySelector = ({
value,
onSelect,
errorMessage,
register,
}: ExpirySelectorProps) => {
const date = value ? new Date(value) : new Date();
const dateFormatted = formatForInput(date);
@@ -34,9 +29,6 @@ export const ExpirySelector = ({
value={dateFormatted}
onChange={(e) => onSelect(e.target.value)}
min={minDate}
{...register?.('expiresAt', {
validate: validateExpiration,
})}
/>
{errorMessage && (
<InputError testId="dealticket-error-message-expiry">
@@ -55,6 +55,7 @@ export const MarketSelector = ({ market, setMarket, ItemRenderer }: Props) => {
const { data, loading, error } = useDataProvider({
dataProvider: marketsProvider,
variables: undefined,
skipUpdates: true,
});
@@ -1,4 +1,3 @@
import { useEffect, useState } from 'react';
import {
FormGroup,
InputError,
@@ -22,15 +21,6 @@ interface TimeInForceSelectorProps {
errorMessage?: string;
}
type OrderType = Schema.OrderType.TYPE_MARKET | Schema.OrderType.TYPE_LIMIT;
type PreviousTimeInForce = {
[key in OrderType]: Schema.OrderTimeInForce;
};
const DEFAULT_TIME_IN_FORCE: PreviousTimeInForce = {
[Schema.OrderType.TYPE_MARKET]: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
[Schema.OrderType.TYPE_LIMIT]: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
};
export const TimeInForceSelector = ({
value,
orderType,
@@ -47,28 +37,6 @@ export const TimeInForceSelector = ({
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_FOK ||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
const [previousOrderType, setPreviousOrderType] = useState(
Schema.OrderType.TYPE_MARKET
);
const [previousTimeInForce, setPreviousTimeInForce] =
useState<PreviousTimeInForce>({
...DEFAULT_TIME_IN_FORCE,
[orderType]: value,
});
useEffect(() => {
if (previousOrderType !== orderType) {
setPreviousOrderType(orderType);
const prev = previousTimeInForce[orderType as OrderType];
onSelect(prev);
}
}, [
onSelect,
orderType,
previousTimeInForce,
previousOrderType,
setPreviousOrderType,
]);
const renderError = (errorType: string) => {
if (errorType === MarketModeValidationType.Auction) {
@@ -128,10 +96,16 @@ export const TimeInForceSelector = ({
id="select-time-in-force"
value={value}
onChange={(e) => {
setPreviousTimeInForce({
...previousTimeInForce,
[orderType]: e.target.value,
});
// setPreviousTimeInForce({
// ...previousTimeInForce,
// [orderType]: e.target.value,
// });
// if (previousOrderType !== orderType) {
// setPreviousOrderType(orderType);
// const prev = previousTimeInForce[orderType as OrderType];
// onSelect(prev);
// }
onSelect(e.target.value as Schema.OrderTimeInForce);
}}
className="w-full"
+10
View File
@@ -7,6 +7,15 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
For example, for a notional size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.`,
[settlementAsset]
);
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
'Estimated total margin that will cover open position, active orders and this order.'
);
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance');
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
[settlementAsset]
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
@@ -41,6 +50,7 @@ export enum MarketModeValidationType {
}
export enum SummaryValidationType {
NoPubKey = 'NoPubKey',
NoCollateral = 'NoCollateral',
TradingMode = 'MarketTradingMode',
MarketState = 'MarketState',
-2
View File
@@ -4,5 +4,3 @@ export * from './use-fee-deal-ticket-details';
export * from './use-market-positions';
export * from './use-maximum-position-size';
export * from './use-order-closeout';
export * from './use-order-margin';
export * from './use-order-margin-validation';
@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import { marketDepthProvider } from '@vegaprotocol/market-depth';
import * as Schema from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/market-list';
@@ -13,11 +12,10 @@ interface Props {
}
export const useCalculateSlippage = ({ market, order }: Props) => {
const variables = useMemo(() => ({ marketId: market.id }), [market.id]);
const { data } = useThrottledDataProvider(
{
dataProvider: marketDepthProvider,
variables,
variables: { marketId: market.id },
},
1000
);
@@ -3,24 +3,24 @@ import {
addDecimal,
addDecimalsFormatNumber,
formatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { useVegaWallet } from '@vegaprotocol/wallet';
import BigNumber from 'bignumber.js';
import { useMemo } from 'react';
import type { Market, MarketData } from '@vegaprotocol/market-list';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import {
EST_CLOSEOUT_TOOLTIP_TEXT,
EST_MARGIN_TOOLTIP_TEXT,
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
NOTIONAL_SIZE_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
MARGIN_DIFF_TOOLTIP_TEXT,
} from '../constants';
import { useCalculateSlippage } from './use-calculate-slippage';
import { useOrderCloseOut } from './use-order-closeout';
import { useOrderMargin } from './use-order-margin';
import type { OrderMargin } from './use-order-margin';
import { useMarketAccountBalance } from '@vegaprotocol/accounts';
import { getDerivedPrice } from '../utils/get-price';
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
import type { EstimateOrderQuery } from './__generated__/EstimateOrder';
export const useFeeDealTicketDetails = (
order: OrderSubmissionBody['orderSubmission'],
@@ -28,33 +28,23 @@ export const useFeeDealTicketDetails = (
marketData: MarketData
) => {
const { pubKey } = useVegaWallet();
const slippage = useCalculateSlippage({ market, order });
const { accountBalance } = useMarketAccountBalance(market.id);
const derivedPrice = useMemo(() => {
return getDerivedPrice(order, market, marketData);
}, [order, market, marketData]);
const price = useMemo(() => {
return getDerivedPrice(order, marketData);
}, [order, marketData]);
// Note this isn't currently used anywhere
const slippageAdjustedPrice = useMemo(() => {
if (derivedPrice) {
if (slippage && parseFloat(slippage) !== 0) {
const isLong = order.side === Schema.Side.SIDE_BUY;
const multiplier = new BigNumber(1)[isLong ? 'plus' : 'minus'](
parseFloat(slippage) / 100
);
return new BigNumber(derivedPrice).multipliedBy(multiplier).toNumber();
}
return derivedPrice;
}
return null;
}, [derivedPrice, order.side, slippage]);
const estMargin = useOrderMargin({
order,
market,
marketData,
partyId: pubKey || '',
derivedPrice,
const { data: estMargin } = useEstimateOrderQuery({
variables: {
marketId: market.id,
partyId: pubKey || '',
price,
size: order.size,
side: order.side,
timeInForce: order.timeInForce,
type: order.type,
},
skip: !pubKey || !market || !order.size || !price,
});
const estCloseOut = useOrderCloseOut({
@@ -64,13 +54,13 @@ export const useFeeDealTicketDetails = (
});
const notionalSize = useMemo(() => {
if (derivedPrice && order.size) {
return new BigNumber(order.size)
.multipliedBy(addDecimal(derivedPrice, market.decimalPlaces))
if (price && order.size) {
return toBigNum(order.size, market.positionDecimalPlaces)
.multipliedBy(addDecimal(price, market.decimalPlaces))
.toString();
}
return null;
}, [derivedPrice, order.size, market.decimalPlaces]);
}, [price, order.size, market.decimalPlaces, market.positionDecimalPlaces]);
const assetSymbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
@@ -80,41 +70,41 @@ export const useFeeDealTicketDetails = (
market,
assetSymbol,
notionalSize,
estMargin,
accountBalance,
estimateOrder: estMargin?.estimateOrder,
estCloseOut,
slippage,
slippageAdjustedPrice,
};
}, [
market,
assetSymbol,
notionalSize,
accountBalance,
estMargin,
estCloseOut,
slippage,
slippageAdjustedPrice,
]);
};
export interface FeeDetails {
balance: string;
market: Market;
assetSymbol: string;
notionalSize: string | null;
estMargin: OrderMargin | null;
estCloseOut: string | null;
slippage: string | null;
estimateOrder: EstimateOrderQuery['estimateOrder'] | undefined;
margin: string;
totalMargin: string;
}
export const getFeeDetailsValues = ({
balance,
assetSymbol,
notionalSize,
estMargin,
estCloseOut,
estimateOrder,
market,
notionalSize,
totalMargin,
}: FeeDetails) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quoteName = market.tradableInstrument.instrument.product.quoteName;
const formatValueWithMarketDp = (
value: string | number | null | undefined
): string => {
@@ -129,7 +119,12 @@ export const getFeeDetailsValues = ({
? addDecimalsFormatNumber(value, assetDecimals)
: '-';
};
return [
const details: {
label: string;
value?: string | null;
symbol: string;
labelDescription: React.ReactNode;
}[] = [
{
label: t('Notional'),
value: formatValueWithMarketDp(notionalSize),
@@ -139,8 +134,8 @@ export const getFeeDetailsValues = ({
{
label: t('Fees'),
value:
estMargin?.totalFees &&
`~${formatValueWithAssetDp(estMargin?.totalFees)}`,
estimateOrder?.totalFeeAmount &&
`~${formatValueWithAssetDp(estimateOrder?.totalFeeAmount)}`,
labelDescription: (
<>
<span>
@@ -149,7 +144,7 @@ export const getFeeDetailsValues = ({
)}
</span>
<FeesBreakdown
fees={estMargin?.fees}
fees={estimateOrder?.fee}
feeFactors={market.fees.factors}
symbol={assetSymbol}
decimals={assetDecimals}
@@ -158,18 +153,40 @@ export const getFeeDetailsValues = ({
),
symbol: assetSymbol,
},
/*
{
label: t('Margin'),
value:
estMargin?.margin && `~${formatValueWithAssetDp(estMargin?.margin)}`,
label: t('Initial margin'),
value: margin && `~${formatValueWithAssetDp(margin)}`,
symbol: assetSymbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol),
},
*/
{
label: t('Liquidation'),
value: estCloseOut && `~${formatValueWithMarketDp(estCloseOut)}`,
symbol: market.tradableInstrument.instrument.product.quoteName,
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT(quoteName),
label: t('Margin required'),
value: `~${formatValueWithAssetDp(
balance
? (BigInt(totalMargin) - BigInt(balance)).toString()
: totalMargin
)}`,
symbol: assetSymbol,
labelDescription: MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol),
},
];
if (balance) {
details.push({
label: t('Projected margin'),
value: `~${formatValueWithAssetDp(totalMargin)}`,
symbol: assetSymbol,
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
});
}
details.push({
label: t('Current margin allocation'),
value: balance
? `~${formatValueWithAssetDp(balance)}`
: `${formatValueWithAssetDp(balance)}`,
symbol: assetSymbol,
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
});
return details;
};
@@ -1,11 +0,0 @@
import { useAccountBalance } from '@vegaprotocol/accounts';
import { toBigNum } from '@vegaprotocol/utils';
export const useHasNoBalance = (assetId: string) => {
const { accountBalance, accountDecimals } = useAccountBalance(assetId);
const balance =
accountBalance && accountDecimals !== null
? toBigNum(accountBalance, accountDecimals)
: toBigNum('0', 0);
return balance.isZero();
};
@@ -0,0 +1,69 @@
import { useMemo } from 'react';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { marketDataProvider } from '@vegaprotocol/market-list';
import {
calculateMargins,
// getDerivedPrice,
volumeAndMarginProvider,
} from '@vegaprotocol/positions';
import { Side } from '@vegaprotocol/types';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { marketInfoProvider } from '@vegaprotocol/market-info';
export const useInitialMargin = (
marketId: OrderSubmissionBody['orderSubmission']['marketId'],
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey: partyId } = useVegaWallet();
const commonVariables = { marketId, partyId: partyId || '' };
const { data: marketData } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId },
});
const { data: activeVolumeAndMargin } = useDataProvider({
dataProvider: volumeAndMarginProvider,
variables: commonVariables,
skip: !partyId,
});
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: commonVariables,
});
let totalMargin = '0';
let margin = '0';
if (marketInfo?.riskFactors && marketData && order) {
const {
positionDecimalPlaces,
decimalPlaces,
tradableInstrument,
riskFactors,
} = marketInfo;
const { marginCalculator, instrument } = tradableInstrument;
const { decimals } = instrument.product.settlementAsset;
margin = totalMargin = calculateMargins({
side: order.side,
size: order.size,
price: marketData.markPrice, // getDerivedPrice(order, marketData), same in positions-data-providers
positionDecimalPlaces,
decimalPlaces,
decimals,
scalingFactors: marginCalculator?.scalingFactors,
riskFactors,
}).initialMargin;
}
if (activeVolumeAndMargin) {
let sellMargin = BigInt(activeVolumeAndMargin.sellInitialMargin);
let buyMargin = BigInt(activeVolumeAndMargin.buyInitialMargin);
if (order?.side === Side.SIDE_SELL) {
sellMargin += BigInt(totalMargin);
} else {
buyMargin += BigInt(totalMargin);
}
totalMargin =
sellMargin > buyMargin ? sellMargin.toString() : buyMargin.toString();
}
return useMemo(() => ({ totalMargin, margin }), [totalMargin, margin]);
};
@@ -0,0 +1,66 @@
import omit from 'lodash/omit';
import { act, renderHook } from '@testing-library/react';
import { getDefaultOrder, useOrderStore } from '@vegaprotocol/orders';
import { useOrderForm } from './use-order-form';
jest.mock('zustand');
describe('useOrderForm', () => {
const marketId = 'market-id';
const setup = (marketId: string) => {
return renderHook(() => useOrderForm(marketId));
};
it('updates form fields when the order changes', async () => {
const order = getDefaultOrder(marketId);
const { result } = setup(marketId);
// expect default values
expect(result.current.order).toEqual(order);
expect(result.current.getValues()).toEqual(order);
const priceUpdate = {
...order,
price: '100',
size: '22',
};
await act(async () => {
useOrderStore.setState({
orders: {
[marketId]: priceUpdate,
},
});
});
// check order store has updated fields
expect(result.current.order).toEqual(priceUpdate);
// check react-hook-form has updated fields
expect(result.current.getValues()).toEqual(priceUpdate);
});
it('removes persist key on submit', async () => {
const order = {
...getDefaultOrder(marketId),
price: '99',
size: '22',
};
const onSubmit = jest.fn();
const { result } = setup(marketId);
await act(async () => {
useOrderStore.setState({
orders: {
[marketId]: order,
},
});
});
await act(async () => {
result.current.handleSubmit(onSubmit)();
});
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0][0]).toEqual(omit(order, 'persist'));
expect(onSubmit.mock.calls[0][0].persist).toBeUndefined();
});
});
@@ -0,0 +1,74 @@
import omit from 'lodash/omit';
import type { OrderObj } from '@vegaprotocol/orders';
import { getDefaultOrder, useOrder } from '@vegaprotocol/orders';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import type { Exact } from 'type-fest';
export type OrderFormFields = OrderObj & {
summary: string;
};
/**
* Connects the order store to a react-hook-form instance. Any time a field
* changes in the store the form will be updated so that validation rules
* for those fields are applied
*/
export const useOrderForm = (marketId: string) => {
const [order, update] = useOrder(marketId);
const {
control,
formState: { errors, isSubmitted },
handleSubmit,
setError,
setValue,
clearErrors,
getValues,
} = useForm<OrderFormFields>({
// order can be undefined if there is nothing in the store, it
// will be created but the form still needs some default values
defaultValues: order || getDefaultOrder(marketId),
});
// Keep form fields in sync with the store values,
// inputs are updating the store, fields need updating
// to ensure validation rules are applied
useEffect(() => {
if (!order) return;
const currOrder = getValues();
for (const k in order) {
const key = k as keyof typeof order;
const curr = currOrder[key];
const value = order[key];
if (value !== curr) {
setValue(key, value, {
shouldValidate: isSubmitted, // only apply validation after the form has been submitted and failed
shouldDirty: true,
shouldTouch: true,
});
}
}
}, [order, isSubmitted, getValues, setValue]);
const handleSubmitWrapper = (
cb: <T>(o: Exact<OrderSubmission, T>) => void
) => {
return handleSubmit(() => {
// remove the persist key from the order in the store, the wallet will reject
// an order that contains unrecognized additional keys
cb(omit(order, 'persist'));
});
};
return {
order,
update,
control,
errors,
setError,
clearErrors,
getValues, // returned for test purposes only
handleSubmit: handleSubmitWrapper,
};
};
@@ -1,48 +0,0 @@
import { useMemo } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { toBigNum } from '@vegaprotocol/utils';
import { useAccountBalance } from '@vegaprotocol/accounts';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useOrderMargin } from './use-order-margin';
import type { Market, MarketData } from '@vegaprotocol/market-list';
interface Props {
market: Market;
marketData: MarketData;
order: OrderSubmissionBody['orderSubmission'];
}
export const useOrderMarginValidation = ({
market,
marketData,
order,
}: Props) => {
const { pubKey } = useVegaWallet();
const estMargin = useOrderMargin({
order,
market,
marketData,
partyId: pubKey || '',
});
const { id: assetId, decimals: assetDecimals } =
market.tradableInstrument.instrument.product.settlementAsset;
const { accountBalance, accountDecimals } = useAccountBalance(assetId);
const balance =
accountBalance && accountDecimals !== null
? toBigNum(accountBalance, accountDecimals)
: toBigNum('0', assetDecimals);
const margin = toBigNum(estMargin?.margin || 0, assetDecimals);
// return only simple types (bool, string) for make memo sensible
const balanceError = balance.isGreaterThan(0) && balance.isLessThan(margin);
const balanceAsString = balance.toString();
const marginAsString = margin.toString();
return useMemo(() => {
return {
balance: balanceAsString,
margin: marginAsString,
balanceError,
};
}, [balanceAsString, marginAsString, balanceError]);
};
@@ -1,116 +0,0 @@
import { renderHook } from '@testing-library/react';
import { useQuery } from '@apollo/client';
import { BigNumber } from 'bignumber.js';
import type { PositionMargin } from './use-market-positions';
import type { Props } from './use-order-margin';
import { useOrderMargin } from './use-order-margin';
import * as Schema from '@vegaprotocol/types';
import type { Market, MarketData } from '@vegaprotocol/market-list';
let mockEstimateData = {
estimateOrder: {
fee: {
makerFee: '100000.000',
infrastructureFee: '100000.000',
liquidityFee: '100000.000',
},
marginLevels: {
initialLevel: '200000',
},
},
};
jest.mock('@apollo/client', () => ({
...jest.requireActual('@apollo/client'),
useQuery: jest.fn(() => ({ data: mockEstimateData })),
}));
let mockMarketPositions: PositionMargin = {
openVolume: '1',
balance: '100000',
};
jest.mock('./use-market-positions', () => ({
useMarketPositions: ({
marketId,
partyId,
}: {
marketId: string;
partyId: string;
}) => mockMarketPositions,
}));
describe('useOrderMargin', () => {
const marketId = 'marketId';
const args: Props = {
order: {
marketId,
size: '2',
side: Schema.Side.SIDE_BUY,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
type: Schema.OrderType.TYPE_MARKET,
},
market: {
id: marketId,
decimalPlaces: 2,
positionDecimalPlaces: 0,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
} as unknown as Market,
marketData: {
indicativePrice: '100',
markPrice: '200',
} as unknown as MarketData,
partyId: 'partyId',
};
afterEach(() => {
jest.clearAllMocks();
});
it('should calculate margin correctly', () => {
const { result } = renderHook(() => useOrderMargin(args));
expect(result.current?.margin).toEqual('100000');
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
args.order.size
);
});
it('should calculate fees correctly', () => {
const { result } = renderHook(() => useOrderMargin(args));
expect(result.current?.totalFees).toEqual('300000');
});
it('should not subtract initialMargin if there is no position', () => {
mockMarketPositions = null;
const { result } = renderHook(() => useOrderMargin(args));
expect(result.current?.margin).toEqual('200000');
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
args.order.size
);
});
it('should return empty value if API fails', () => {
mockEstimateData = {
estimateOrder: {
fee: {
makerFee: '100000.000',
infrastructureFee: '100000.000',
liquidityFee: '100000.000',
},
marginLevels: {
initialLevel: '',
},
},
};
const { result } = renderHook(() => useOrderMargin(args));
expect(result.current).toEqual(null);
const calledSize = new BigNumber(mockMarketPositions?.openVolume || 0)
.plus(args.order.size)
.toString();
expect((useQuery as jest.Mock).mock.calls[0][1].variables.size).toEqual(
calledSize
);
});
});
@@ -1,76 +0,0 @@
import { useMemo } from 'react';
import { BigNumber } from 'bignumber.js';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { removeDecimal } from '@vegaprotocol/utils';
import { useMarketPositions } from './use-market-positions';
import { useEstimateOrderQuery } from './__generated__/EstimateOrder';
import type { Market, MarketData } from '@vegaprotocol/market-list';
import { getDerivedPrice } from '../utils/get-price';
export interface Props {
order: OrderSubmissionBody['orderSubmission'];
market: Market;
marketData: MarketData;
partyId: string;
derivedPrice?: string;
}
export interface OrderMargin {
margin: string;
totalFees: string | null;
fees: {
makerFee: string;
liquidityFee: string;
infrastructureFee: string;
};
}
export const useOrderMargin = ({
order,
market,
marketData,
partyId,
derivedPrice,
}: Props): OrderMargin | null => {
const { balance } = useMarketPositions({ marketId: market.id }) || {};
const priceForEstimate =
derivedPrice || getDerivedPrice(order, market, marketData);
const { data } = useEstimateOrderQuery({
variables: {
marketId: market.id,
partyId,
price: priceForEstimate,
size: removeDecimal(order.size, market.positionDecimalPlaces),
side: order.side,
timeInForce: order.timeInForce,
type: order.type,
},
skip: !partyId || !market.id || !order.size || !priceForEstimate,
});
const { makerFee, liquidityFee, infrastructureFee } = data?.estimateOrder
.fee || { makerFee: '', liquidityFee: '', infrastructureFee: '' };
const { initialLevel } = data?.estimateOrder.marginLevels ?? {};
return useMemo(() => {
if (initialLevel) {
const margin = BigNumber.maximum(
0,
new BigNumber(initialLevel).minus(balance || 0)
).toString();
const fees = new BigNumber(makerFee)
.plus(liquidityFee)
.plus(infrastructureFee)
.toString();
return {
margin,
totalFees: fees,
fees: {
makerFee,
liquidityFee,
infrastructureFee,
},
};
}
return null;
}, [initialLevel, makerFee, liquidityFee, infrastructureFee, balance]);
};
+17 -10
View File
@@ -64,20 +64,27 @@ export function generateMarketData(
id: 'market-id',
__typename: 'Market',
},
auctionStart: '2022-06-21T17:18:43.484055236Z',
auctionEnd: '2022-06-21T17:18:43.484055236Z',
targetStake: '1000000',
suppliedStake: '1000',
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
marketState: Schema.MarketState.STATE_ACTIVE,
staticMidPrice: '0',
indicativePrice: '100',
bestStaticBidPrice: '0',
bestStaticOfferPrice: '0',
indicativeVolume: '10',
auctionStart: '2022-06-21T17:18:43.484055236Z',
bestBidPrice: '0',
bestBidVolume: '0',
bestOfferPrice: '0',
bestOfferVolume: '0',
bestStaticBidPrice: '0',
bestStaticBidVolume: '0',
bestStaticOfferPrice: '0',
bestStaticOfferVolume: '0',
indicativePrice: '100',
indicativeVolume: '10',
marketState: Schema.MarketState.STATE_ACTIVE,
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
marketValueProxy: '',
markPrice: '200',
midPrice: '0',
openInterest: '',
staticMidPrice: '0',
suppliedStake: '1000',
targetStake: '1000000',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_BATCH,
};
return merge(defaultMarketData, override);
+2 -4
View File
@@ -1,7 +1,6 @@
import { removeDecimal } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
import { isMarketInAuction } from './is-market-in-auction';
import type { MarketData, Market } from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
/**
* Get the market price based on market mode (auction or not auction)
@@ -34,7 +33,6 @@ export const getDerivedPrice = (
type: Schema.OrderType;
price?: string | undefined;
},
market: Market,
marketData: MarketData
) => {
// If order type is market we should use either the mark price
@@ -44,7 +42,7 @@ export const getDerivedPrice = (
// Use the market price if order is a market order
let price;
if (order.type === Schema.OrderType.TYPE_LIMIT && order.price) {
price = removeDecimal(order.price, market.decimalPlaces);
price = order.price;
} else {
price = getMarketPrice(marketData);
}
+2 -1
View File
@@ -17,7 +17,8 @@
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"jest.config.ts"
"jest.config.ts",
"__mocks__"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}
@@ -12,6 +12,7 @@ export const DepositContainer = ({ assetId }: { assetId?: string }) => {
const { VEGA_ENV } = useEnvironment();
const { data, loading, error } = useDataProvider({
dataProvider: enabledAssetsProvider,
variables: undefined,
});
return (
+1 -1
View File
@@ -15,7 +15,7 @@ interface Actions {
close: () => void;
}
export const useDepositDialog = create<State & Actions>((set) => ({
export const useDepositDialog = create<State & Actions>()((set) => ({
isOpen: false,
assetId: undefined,
open: (assetId) => set(() => ({ assetId, isOpen: true })),
+1 -1
View File
@@ -392,7 +392,7 @@ const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
data-testid="deposit-submit"
variant={isActive ? 'primary' : 'default'}
fill={true}
disabled={invalidChain || (selectedAsset && !approved)}
disabled={invalidChain}
>
{t('Deposit')}
</Button>
+2
View File
@@ -72,7 +72,9 @@ export const DepositsTable = forwardRef<
field="txHash"
cellRenderer={({
value,
data,
}: VegaICellRendererParams<DepositFieldsFragment, 'txHash'>) => {
if (!data) return null;
if (!value) return '-';
return (
<Link
+30 -15
View File
@@ -3,6 +3,7 @@ import { useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
import { getFaucetError } from './get-faucet-error';
interface FaucetNotificationProps {
isActive: boolean;
@@ -36,13 +37,13 @@ export const FaucetNotification = ({
}
if (tx.status === EthTxStatus.Error) {
const errorMessage = getFaucetError(tx.error, selectedAsset.symbol);
return (
<div className="mb-4">
<Notification
intent={Intent.Danger}
testId="faucet-error"
// @ts-ignore tx.error not typed correctly
message={t(`Faucet failed: ${tx.error?.reason}`)}
message={errorMessage}
/>
</div>
);
@@ -55,7 +56,7 @@ export const FaucetNotification = ({
intent={Intent.Warning}
testId="faucet-requested"
message={t(
`Go to your Ethereum wallet and approve the faucet transaction for ${selectedAsset?.symbol}`
`Confirm the transaction in your Ethereum wallet to use the ${selectedAsset?.symbol} faucet`
)}
/>
</div>
@@ -69,14 +70,21 @@ export const FaucetNotification = ({
intent={Intent.Primary}
testId="faucet-pending"
message={
<p>
{t('Faucet pending...')}{' '}
<>
<p className="mb-2">
{t(
'Your request for funds from the %s faucet is being confirmed by the Ethereum network',
selectedAsset.symbol
)}{' '}
</p>
{tx.txHash && (
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
{t('View on Etherscan')}
</ExternalLink>
<p>
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
{t('View on Etherscan')}
</ExternalLink>
</p>
)}
</p>
</>
}
/>
</div>
@@ -90,14 +98,21 @@ export const FaucetNotification = ({
intent={Intent.Success}
testId="faucet-confirmed"
message={
<p>
{t('Faucet successful')}{' '}
<>
<p className="mb-2">
{t(
'%s has been deposited in your Ethereum wallet',
selectedAsset.symbol
)}{' '}
</p>
{tx.txHash && (
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
{t('View on Etherscan')}
</ExternalLink>
<p>
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
{t('View on Etherscan')}
</ExternalLink>
</p>
)}
</p>
</>
}
/>
</div>
+24
View File
@@ -0,0 +1,24 @@
import { t } from '@vegaprotocol/i18n';
import type { TxError } from '@vegaprotocol/web3';
export const getFaucetError = (error: TxError | null, symbol: string) => {
const reasonMap: {
[reason: string]: string;
} = {
'faucet not enabled': t(
'The %s faucet is not available at this time',
symbol
),
'must wait faucetCallLimit between faucet calls': t(
'You have exceeded the maximum number of faucet attempts allowed'
),
'user rejected transaction': t(
'The faucet transaction was rejected by the connected Ethereum wallet'
),
};
// render a customized failure message from the map above or fallback
// to a non generic error message
return error && 'reason' in error && reasonMap[error.reason]
? reasonMap[error.reason]
: t('Faucet of %s failed', symbol);
};

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