Compare commits

..
Author SHA1 Message Date
Edd b25c1ea996 fix(explorer): format els as percentage in network param view 2023-03-10 15:32: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
169 changed files with 3840 additions and 2482 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
@@ -22,7 +22,6 @@
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"governance.proposal.updateAsset.requiredParticipation",
"governance.proposal.updateMarket.minProposerEquityLikeShare",
"market.fee.factors.infrastructureFee",
"market.fee.factors.makerFee",
"market.liquidity.bondPenaltyParameter",
@@ -77,6 +76,7 @@
"governance.proposal.updateMarket.requiredParticipationLP",
"governance.proposal.updateNetParam.requiredMajority",
"governance.proposal.updateNetParam.requiredParticipation",
"governance.proposal.updateMarket.minProposerEquityLikeShare",
"validators.vote.required"
],
"duration": [
@@ -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,
});
@@ -31,6 +31,7 @@ const PERCENTAGE_PARAMS = [
'governance.proposal.updateMarket.requiredParticipationLP',
'governance.proposal.updateNetParam.requiredMajority',
'governance.proposal.updateNetParam.requiredParticipation',
'governance.proposal.updateMarket.minProposerEquityLikeShare',
'validators.vote.required',
];
@@ -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');
@@ -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: '',
@@ -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>
);
@@ -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',
}}
/>
);
};
@@ -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
);
@@ -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 }) => (
@@ -21,8 +21,7 @@ import {
Intent,
Notification,
} from '@vegaprotocol/ui-toolkit';
import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
import {
validateExpiration,
validateMarketState,
@@ -32,11 +31,16 @@ 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 {
useMarketAccountBalance,
useAccountBalance,
} from '@vegaprotocol/accounts';
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import { useOrderForm } from '../../hooks/use-order-form';
import type { OrderObj } from '@vegaprotocol/orders';
export interface DealTicketProps {
market: Market;
@@ -54,10 +58,12 @@ export const DealTicket = ({
const { pubKey, isReadOnly } = useVegaWallet();
// store last used tif for market so that when changing OrderType the previous TIF
// selection for that type is used when switching back
const [lastTIF, setLastTIF] = useState({
[OrderType.TYPE_MARKET]: OrderTimeInForce.TIME_IN_FORCE_IOC,
[OrderType.TYPE_LIMIT]: OrderTimeInForce.TIME_IN_FORCE_GTC,
});
const {
control,
errors,
@@ -67,20 +73,41 @@ export const DealTicket = ({
update,
handleSubmit,
} = useOrderForm(market.id);
const marketStateError = validateMarketState(marketData.marketState);
const hasNoBalance = useHasNoBalance(
market.tradableInstrument.instrument.product.settlementAsset.id
);
const marketTradingModeError = validateMarketTradingMode(
marketData.marketTradingMode
const asset = market.tradableInstrument.instrument.product.settlementAsset;
const { accountBalance: marginAccountBalance } = useMarketAccountBalance(
market.id
);
const checkForErrors = useCallback(() => {
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
);
const { margin, totalMargin } = useInitialMargin(market.id, normalizedOrder);
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,
@@ -89,6 +116,7 @@ export const DealTicket = ({
return;
}
const hasNoBalance = generalAccountBalance === '0';
if (hasNoBalance) {
setError('summary', {
message: SummaryValidationType.NoCollateral,
@@ -97,6 +125,7 @@ export const DealTicket = ({
return;
}
const marketTradingModeError = validateMarketTradingMode(marketTradingMode);
if (marketTradingModeError !== true) {
setError('summary', {
message: marketTradingModeError,
@@ -104,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: OrderSubmission) => {
checkForErrors();
submit(
normalizeOrderSubmission(
order,
@@ -145,11 +154,11 @@ 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) return null;
if (!order || !normalizedOrder) return null;
return (
<form
@@ -175,7 +184,9 @@ export const DealTicket = ({
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}
@@ -223,7 +234,12 @@ export const DealTicket = ({
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 }));
setLastTIF((curr) => ({
...curr,
[order.type]: timeInForce,
expiresAt: undefined,
}));
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
@@ -254,9 +270,10 @@ export const DealTicket = ({
)}
<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}
@@ -266,9 +283,12 @@ export const DealTicket = ({
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>
);
@@ -280,9 +300,10 @@ export const DealTicket = ({
*/
interface SummaryMessageProps {
errorMessage?: string;
market: Market;
marketData: MarketData;
order: OrderObj;
asset: { id: string; symbol: string; name: string; decimals: number };
marketTradingMode: MarketData['marketTradingMode'];
balance: string;
margin: string;
isReadOnly: boolean;
pubKey: string | null;
onClickCollateral?: () => void;
@@ -290,22 +311,17 @@ interface SummaryMessageProps {
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
);
@@ -349,7 +365,7 @@ const SummaryMessage = memo(
return (
<div className="mb-2">
<ZeroBalanceError
asset={market.tradableInstrument.instrument.product.settlementAsset}
asset={asset}
onClickCollateral={onClickCollateral}
/>
</div>
@@ -370,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">
@@ -55,6 +55,7 @@ export const MarketSelector = ({ market, setMarket, ItemRenderer }: Props) => {
const { data, loading, error } = useDataProvider({
dataProvider: marketsProvider,
variables: undefined,
skipUpdates: true,
});
+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]);
};
@@ -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);
}
@@ -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);
};
-18
View File
@@ -1,18 +0,0 @@
import { act } from 'react-dom/test-utils';
const zu = jest.requireActual('zustand'); // if using jest
// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set();
// when creating a store, we get its initial state, create a reset function and add it in the set
export const create = (createState) => {
const store = zu.create(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()));
});
+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()));
});
@@ -34,7 +34,7 @@ export type EnvStore = Env & Actions;
export const STORAGE_KEY = 'vega_url';
const SUBSCRIPTION_TIMEOUT = 3000;
export const useEnvironment = create<EnvStore>((set, get) => ({
export const useEnvironment = create<EnvStore>()((set, get) => ({
...compileEnvVars(),
nodes: [],
status: 'default',
+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"]
}
+20 -6
View File
@@ -13,6 +13,7 @@ import type { PageInfo, Edge } from '@vegaprotocol/utils';
import { FillsDocument, FillsEventDocument } from './__generated__/Fills';
import type {
FillsQuery,
FillsQueryVariables,
FillFieldsFragment,
FillEdgeFragment,
FillsEventSubscription,
@@ -56,19 +57,28 @@ const update = (
});
};
export type Trade = Omit<FillFieldsFragment, 'market'> & { market?: Market };
export type Trade = Omit<FillFieldsFragment, 'market'> & {
market?: Market;
isLastPlaceholder?: boolean;
};
export type TradeEdge = Edge<Trade>;
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
responseData?.party?.tradesConnection?.edges || [];
const getPageInfo = (responseData: FillsQuery): PageInfo | null =>
responseData.party?.tradesConnection?.pageInfo || null;
const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
responseData?.party?.tradesConnection?.pageInfo || null;
const getDelta = (subscriptionData: FillsEventSubscription) =>
subscriptionData.trades || [];
export const fillsProvider = makeDataProvider({
export const fillsProvider = makeDataProvider<
Parameters<typeof getData>['0'],
ReturnType<typeof getData>,
Parameters<typeof getDelta>['0'],
ReturnType<typeof getDelta>,
FillsQueryVariables
>({
query: FillsDocument,
subscriptionQuery: FillsEventDocument,
update,
@@ -83,9 +93,13 @@ export const fillsProvider = makeDataProvider({
export const fillsWithMarketProvider = makeDerivedDataProvider<
(TradeEdge | null)[],
Trade[]
Trade[],
FillsQueryVariables
>(
[fillsProvider, marketsProvider],
[
fillsProvider,
(callback, client) => marketsProvider(callback, client, undefined),
],
(partsData): (TradeEdge | null)[] =>
(partsData[0] as ReturnType<typeof getData>)?.map(
(edge) =>
+42 -8
View File
@@ -1,10 +1,12 @@
import type { AgGridReact } from 'ag-grid-react';
import { useRef } from 'react';
import { useCallback, useRef } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import { useFillsList } from './use-fills-list';
import type { Trade } from './fills-data-provider';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
interface FillsManagerProps {
partyId: string;
@@ -19,22 +21,51 @@ export const FillsManager = ({
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
const { data, error, loading, addNewRows, getRows, reload } = useFillsList({
const {
data,
error,
loading,
addNewRows,
getRows,
reload,
makeBottomPlaceholders,
} = useFillsList({
partyId,
marketId,
gridRef,
scrolledToTop,
});
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
const checkBottomPlaceholder = useCallback(() => {
const rowCont = gridRef.current?.api?.getModel().getRowCount() ?? 0;
const lastRowIndex = gridRef.current?.api?.getLastDisplayedRow();
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
const lastrow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
lastrow?.setRowHeight(50);
makeBottomPlaceholders(lastrow?.data);
gridRef.current?.api.onRowHeightChanged();
gridRef.current?.api.refreshInfiniteCache();
}
};
}, [makeBottomPlaceholders]);
const onBodyScroll = (event: BodyScrollEvent) => {
const onBodyScrollEnd = useCallback(
(event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
checkBottomPlaceholder();
},
[addNewRows, checkBottomPlaceholder]
);
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
};
}, []);
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules } =
useBottomPlaceholder<Trade>({
gridRef,
});
return (
<div className="h-full relative">
@@ -48,6 +79,9 @@ export const FillsManager = ({
onMarketClick={onMarketClick}
suppressLoadingOverlay
suppressNoRowsOverlay
isFullWidthRow={isFullWidthRow}
fullWidthCellRenderer={fullWidthCellRenderer}
rowClassRules={rowClassRules}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+27 -9
View File
@@ -1,6 +1,6 @@
import type { RefObject } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useMemo, useRef } from 'react';
import { useCallback, useRef } from 'react';
import { makeInfiniteScrollGetRows } from '@vegaprotocol/utils';
import { useDataProvider, updateGridData } from '@vegaprotocol/react-helpers';
import type { Trade, TradeEdge } from './fills-data-provider';
@@ -22,6 +22,21 @@ export const useFillsList = ({
const dataRef = useRef<(TradeEdge | null)[] | null>(null);
const totalCountRef = useRef<number | undefined>(undefined);
const newRows = useRef(0);
const placeholderAdded = useRef(-1);
const makeBottomPlaceholders = useCallback((trade?: Trade) => {
if (!trade) {
if (placeholderAdded.current >= 0) {
dataRef.current?.splice(placeholderAdded.current, 1);
}
placeholderAdded.current = -1;
} else if (placeholderAdded.current === -1) {
dataRef.current?.push({
node: { ...trade, id: `${trade?.id}-1`, isLastPlaceholder: true },
});
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
}
}, []);
const addNewRows = useCallback(() => {
if (newRows.current === 0) {
@@ -73,16 +88,11 @@ export const useFillsList = ({
[gridRef]
);
const variables = useMemo(() => ({ partyId, marketId }), [partyId, marketId]);
const { data, error, loading, load, totalCount, reload } = useDataProvider<
(TradeEdge | null)[],
Trade[]
>({
const { data, error, loading, load, totalCount, reload } = useDataProvider({
dataProvider: fillsWithMarketProvider,
update,
insert,
variables,
variables: { partyId, marketId: marketId || '' },
});
totalCountRef.current = totalCount;
@@ -92,5 +102,13 @@ export const useFillsList = ({
load,
newRows
);
return { data, error, loading, addNewRows, getRows, reload };
return {
data,
error,
loading,
addNewRows,
getRows,
reload,
makeBottomPlaceholders,
};
};
@@ -42,7 +42,7 @@ export const update = (
data: ReturnType<typeof getData> | null,
delta: ReturnType<typeof getData>,
reload: () => void,
variables?: LedgerEntriesQueryVariables
variables: LedgerEntriesQueryVariables
) => {
if (!data) {
return data;
@@ -110,8 +110,8 @@ export const ledgerEntriesProvider = makeDerivedDataProvider<
>(
[
ledgerEntriesOnlyProvider,
(callback, client) => assetsProvider(callback, client),
marketsProvider,
(callback, client) => assetsProvider(callback, client, undefined),
(callback, client) => marketsProvider(callback, client, undefined),
],
([entries, assets, markets]) => {
return entries.map((edge: AggregatedLedgerEntriesEdge) => {
@@ -14,11 +14,14 @@ import {
import type {
MarketLpQuery,
MarketLpQueryVariables,
LiquidityProviderFeeShareFieldsFragment,
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareQueryVariables,
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProvisionFieldsFragment,
LiquidityProvisionsQuery,
LiquidityProvisionsQueryVariables,
LiquidityProvisionsUpdateSubscription,
} from './__generated__/MarketLiquidity';
import type { IterableElement } from 'type-fest';
@@ -27,7 +30,8 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
LiquidityProvisionsQuery,
LiquidityProvisionFieldsFragment[],
LiquidityProvisionsUpdateSubscription,
LiquidityProvisionsUpdateSubscription['liquidityProvisions']
LiquidityProvisionsUpdateSubscription['liquidityProvisions'],
LiquidityProvisionsQueryVariables
>({
query: LiquidityProvisionsDocument,
subscriptionQuery: LiquidityProvisionsUpdateDocument,
@@ -99,7 +103,8 @@ export const marketLiquidityDataProvider = makeDataProvider<
MarketLpQuery,
MarketLpQuery,
never,
never
never,
MarketLpQueryVariables
>({
query: MarketLpDocument,
getData: (responseData: MarketLpQuery | null) => {
@@ -111,7 +116,8 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
LiquidityProviderFeeShareQuery,
LiquidityProviderFeeShareFieldsFragment[],
LiquidityProviderFeeShareUpdateSubscription,
LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare']
LiquidityProviderFeeShareUpdateSubscription['marketsData'][0]['liquidityProviderFeeShare'],
LiquidityProviderFeeShareQueryVariables
>({
query: LiquidityProviderFeeShareDocument,
subscriptionQuery: LiquidityProviderFeeShareUpdateDocument,
@@ -147,7 +153,11 @@ export const liquidityFeeShareDataProvider = makeDataProvider<
},
});
export const lpAggregatedDataProvider = makeDerivedDataProvider(
export const lpAggregatedDataProvider = makeDerivedDataProvider<
ReturnType<typeof getLiquidityProvision>,
never,
MarketLpQueryVariables
>(
[
liquidityProvisionsDataProvider,
marketLiquidityDataProvider,
@@ -5,12 +5,10 @@ import { useDataProvider, useYesterday } from '@vegaprotocol/react-helpers';
import type {
MarketCandles,
MarketMaybeWithDataAndCandles,
MarketsCandlesQueryVariables,
} from '@vegaprotocol/market-list';
import {
marketsCandlesProvider,
marketListProvider,
} from '@vegaprotocol/market-list';
import { marketListProvider } from '@vegaprotocol/market-list';
import type { LiquidityProvisionMarketsQuery } from './__generated__/MarketsLiquidity';
import { LiquidityProvisionMarketsDocument } from './__generated__/MarketsLiquidity';
@@ -97,15 +95,18 @@ export const liquidityMarketsProvider = makeDataProvider<
getData,
});
const liquidityProvisionProvider = makeDerivedDataProvider<Market[], never>(
const liquidityProvisionProvider = makeDerivedDataProvider<
Market[],
never,
Exclude<MarketsCandlesQueryVariables, 'interval'>
>(
[
marketListProvider,
(callback, client, variables) =>
marketsCandlesProvider(callback, client, {
...variables,
marketListProvider(callback, client, {
since: variables.since,
interval: Schema.Interval.INTERVAL_I1D,
}),
liquidityMarketsProvider,
(callback, client) => liquidityMarketsProvider(callback, client, undefined),
],
(parts) => {
return addData(

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